| # Copyright (c) 2014 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Model extensions common to both the server and client rdb modules. |
| """ |
| |
| |
| from django.core import exceptions as django_exceptions |
| from django.db import models as dbmodels |
| |
| |
| from autotest_lib.client.common_lib import host_protections |
| from autotest_lib.client.common_lib import host_states |
| from autotest_lib.frontend import settings |
| |
| |
| class ModelValidators(object): |
| """Convenience functions for model validation. |
| |
| This model is duplicated both on the client and server rdb. Any method |
| added to this class must only be capable of class level validation of model |
| fields, since anything else is meaningless on the client side. |
| """ |
| # TODO: at least some of these functions really belong in a custom |
| # Manager class. |
| |
| field_dict = None |
| # subclasses should override if they want to support smart_get() by name |
| name_field = None |
| |
| @classmethod |
| def get_field_dict(cls): |
| if cls.field_dict is None: |
| cls.field_dict = {} |
| for field in cls._meta.fields: |
| cls.field_dict[field.name] = field |
| return cls.field_dict |
| |
| |
| @classmethod |
| def clean_foreign_keys(cls, data): |
| """\ |
| -Convert foreign key fields in data from <field>_id to just |
| <field>. |
| -replace foreign key objects with their IDs |
| This method modifies data in-place. |
| """ |
| for field in cls._meta.fields: |
| if not field.rel: |
| continue |
| if (field.attname != field.name and |
| field.attname in data): |
| data[field.name] = data[field.attname] |
| del data[field.attname] |
| if field.name not in data: |
| continue |
| value = data[field.name] |
| if isinstance(value, dbmodels.Model): |
| data[field.name] = value._get_pk_val() |
| |
| |
| @classmethod |
| def _convert_booleans(cls, data): |
| """ |
| Ensure BooleanFields actually get bool values. The Django MySQL |
| backend returns ints for BooleanFields, which is almost always not |
| a problem, but it can be annoying in certain situations. |
| """ |
| for field in cls._meta.fields: |
| if type(field) == dbmodels.BooleanField and field.name in data: |
| data[field.name] = bool(data[field.name]) |
| |
| |
| # TODO(showard) - is there a way to not have to do this? |
| @classmethod |
| def provide_default_values(cls, data): |
| """\ |
| Provide default values for fields with default values which have |
| nothing passed in. |
| |
| For CharField and TextField fields with "blank=True", if nothing |
| is passed, we fill in an empty string value, even if there's no |
| :retab default set. |
| """ |
| new_data = dict(data) |
| field_dict = cls.get_field_dict() |
| for name, obj in field_dict.iteritems(): |
| if data.get(name) is not None: |
| continue |
| if obj.default is not dbmodels.fields.NOT_PROVIDED: |
| new_data[name] = obj.default |
| elif (isinstance(obj, dbmodels.CharField) or |
| isinstance(obj, dbmodels.TextField)): |
| new_data[name] = '' |
| return new_data |
| |
| |
| @classmethod |
| def validate_field_names(cls, data): |
| 'Checks for extraneous fields in data.' |
| errors = {} |
| field_dict = cls.get_field_dict() |
| for field_name in data: |
| if field_name not in field_dict: |
| errors[field_name] = 'No field of this name' |
| return errors |
| |
| |
| @classmethod |
| def prepare_data_args(cls, data): |
| 'Common preparation for add_object and update_object' |
| # must check for extraneous field names here, while we have the |
| # data in a dict |
| errors = cls.validate_field_names(data) |
| if errors: |
| raise django_exceptions.ValidationError(errors) |
| return data |
| |
| |
| @classmethod |
| def _get_required_field_names(cls): |
| """Get the fields without which we cannot create a host. |
| |
| @return: A list of field names that cannot be blank on host creation. |
| """ |
| return [field.name for field in cls._meta.fields if not field.blank] |
| |
| |
| @classmethod |
| def get_basic_field_names(cls): |
| """Get all basic fields of the Model. |
| |
| This method returns the names of all fields that the client can provide |
| a value for during host creation. The fields not included in this list |
| are those that we can leave blank. Specifying non-null values for such |
| fields only makes sense as an update to the host. |
| |
| @return A list of basic fields. |
| Eg: set([hostname, locked, leased, status, invalid, |
| protection, lock_time, dirty]) |
| """ |
| return [field.name for field in cls._meta.fields |
| if field.has_default()] + cls._get_required_field_names() |
| |
| |
| @classmethod |
| def validate_model_fields(cls, data): |
| """Validate parameters needed to create a host. |
| |
| Check that all required fields are specified, that specified fields |
| are actual model values, and provide defaults for the unspecified |
| but unrequired fields. |
| |
| @param dict: A dictionary with the args to create the model. |
| |
| @raises dajngo_exceptions.ValidationError: If either an invalid field |
| is specified or a required field is missing. |
| """ |
| missing_fields = set(cls._get_required_field_names()) - set(data.keys()) |
| if missing_fields: |
| raise django_exceptions.ValidationError('%s required to create %s, ' |
| 'supplied %s ' % (missing_fields, cls.__name__, data)) |
| data = cls.prepare_data_args(data) |
| data = cls.provide_default_values(data) |
| return data |
| |
| |
| class AbstractHostModel(dbmodels.Model, ModelValidators): |
| """Abstract model specifying all fields one can use to create a host. |
| |
| This model enforces consistency between the host models of the rdb and |
| their representation on the client side. |
| |
| Internal fields: |
| status: string describing status of host |
| invalid: true if the host has been deleted |
| protection: indicates what can be done to this host during repair |
| lock_time: DateTime at which the host was locked |
| dirty: true if the host has been used without being rebooted |
| lock_reason: The reason for locking the host. |
| """ |
| Status = host_states.Status |
| hostname = dbmodels.CharField(max_length=255, unique=True) |
| locked = dbmodels.BooleanField(default=False) |
| leased = dbmodels.BooleanField(default=True) |
| # TODO(ayatane): This is needed until synch_id is removed from Host._fields |
| synch_id = dbmodels.IntegerField(blank=True, null=True, |
| editable=settings.FULL_ADMIN) |
| status = dbmodels.CharField(max_length=255, default=Status.READY, |
| choices=Status.choices(), |
| editable=settings.FULL_ADMIN) |
| invalid = dbmodels.BooleanField(default=False, |
| editable=settings.FULL_ADMIN) |
| protection = dbmodels.SmallIntegerField(null=False, blank=True, |
| choices=host_protections.choices, |
| default=host_protections.default) |
| lock_time = dbmodels.DateTimeField(null=True, blank=True, editable=False) |
| dirty = dbmodels.BooleanField(default=True, editable=settings.FULL_ADMIN) |
| lock_reason = dbmodels.CharField(null=True, max_length=255, blank=True, |
| default='') |
| |
| |
| class Meta: |
| abstract = True |