1*9c5db199SXin Li# Copyright (c) 2014 The Chromium OS Authors. All rights reserved. 2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 3*9c5db199SXin Li# found in the LICENSE file. 4*9c5db199SXin Li 5*9c5db199SXin Li"""Model extensions common to both the server and client rdb modules. 6*9c5db199SXin Li""" 7*9c5db199SXin Li 8*9c5db199SXin Liimport six 9*9c5db199SXin Lifrom autotest_lib.client.common_lib import host_protections, host_states 10*9c5db199SXin Lifrom autotest_lib.frontend import settings 11*9c5db199SXin Lifrom django.core import exceptions as django_exceptions 12*9c5db199SXin Lifrom django.db import models as dbmodels 13*9c5db199SXin Li 14*9c5db199SXin Li 15*9c5db199SXin Liclass ModelValidators(object): 16*9c5db199SXin Li """Convenience functions for model validation. 17*9c5db199SXin Li 18*9c5db199SXin Li This model is duplicated both on the client and server rdb. Any method 19*9c5db199SXin Li added to this class must only be capable of class level validation of model 20*9c5db199SXin Li fields, since anything else is meaningless on the client side. 21*9c5db199SXin Li """ 22*9c5db199SXin Li # TODO: at least some of these functions really belong in a custom 23*9c5db199SXin Li # Manager class. 24*9c5db199SXin Li 25*9c5db199SXin Li field_dict = None 26*9c5db199SXin Li # subclasses should override if they want to support smart_get() by name 27*9c5db199SXin Li name_field = None 28*9c5db199SXin Li 29*9c5db199SXin Li @classmethod 30*9c5db199SXin Li def get_field_dict(cls): 31*9c5db199SXin Li if cls.field_dict is None: 32*9c5db199SXin Li cls.field_dict = {} 33*9c5db199SXin Li for field in cls._meta.fields: 34*9c5db199SXin Li cls.field_dict[field.name] = field 35*9c5db199SXin Li return cls.field_dict 36*9c5db199SXin Li 37*9c5db199SXin Li 38*9c5db199SXin Li @classmethod 39*9c5db199SXin Li def clean_foreign_keys(cls, data): 40*9c5db199SXin Li """\ 41*9c5db199SXin Li -Convert foreign key fields in data from <field>_id to just 42*9c5db199SXin Li <field>. 43*9c5db199SXin Li -replace foreign key objects with their IDs 44*9c5db199SXin Li This method modifies data in-place. 45*9c5db199SXin Li """ 46*9c5db199SXin Li for field in cls._meta.fields: 47*9c5db199SXin Li if not field.rel: 48*9c5db199SXin Li continue 49*9c5db199SXin Li if (field.attname != field.name and 50*9c5db199SXin Li field.attname in data): 51*9c5db199SXin Li data[field.name] = data[field.attname] 52*9c5db199SXin Li del data[field.attname] 53*9c5db199SXin Li if field.name not in data: 54*9c5db199SXin Li continue 55*9c5db199SXin Li value = data[field.name] 56*9c5db199SXin Li if isinstance(value, dbmodels.Model): 57*9c5db199SXin Li data[field.name] = value._get_pk_val() 58*9c5db199SXin Li 59*9c5db199SXin Li 60*9c5db199SXin Li @classmethod 61*9c5db199SXin Li def _convert_booleans(cls, data): 62*9c5db199SXin Li """ 63*9c5db199SXin Li Ensure BooleanFields actually get bool values. The Django MySQL 64*9c5db199SXin Li backend returns ints for BooleanFields, which is almost always not 65*9c5db199SXin Li a problem, but it can be annoying in certain situations. 66*9c5db199SXin Li """ 67*9c5db199SXin Li for field in cls._meta.fields: 68*9c5db199SXin Li if type(field) == dbmodels.BooleanField and field.name in data: 69*9c5db199SXin Li data[field.name] = bool(data[field.name]) 70*9c5db199SXin Li 71*9c5db199SXin Li 72*9c5db199SXin Li # TODO(showard) - is there a way to not have to do this? 73*9c5db199SXin Li @classmethod 74*9c5db199SXin Li def provide_default_values(cls, data): 75*9c5db199SXin Li """\ 76*9c5db199SXin Li Provide default values for fields with default values which have 77*9c5db199SXin Li nothing passed in. 78*9c5db199SXin Li 79*9c5db199SXin Li For CharField and TextField fields with "blank=True", if nothing 80*9c5db199SXin Li is passed, we fill in an empty string value, even if there's no 81*9c5db199SXin Li :retab default set. 82*9c5db199SXin Li """ 83*9c5db199SXin Li new_data = dict(data) 84*9c5db199SXin Li field_dict = cls.get_field_dict() 85*9c5db199SXin Li for name, obj in six.iteritems(field_dict): 86*9c5db199SXin Li if data.get(name) is not None: 87*9c5db199SXin Li continue 88*9c5db199SXin Li if obj.default is not dbmodels.fields.NOT_PROVIDED: 89*9c5db199SXin Li new_data[name] = obj.default 90*9c5db199SXin Li elif (isinstance(obj, dbmodels.CharField) or 91*9c5db199SXin Li isinstance(obj, dbmodels.TextField)): 92*9c5db199SXin Li new_data[name] = '' 93*9c5db199SXin Li return new_data 94*9c5db199SXin Li 95*9c5db199SXin Li 96*9c5db199SXin Li @classmethod 97*9c5db199SXin Li def validate_field_names(cls, data): 98*9c5db199SXin Li 'Checks for extraneous fields in data.' 99*9c5db199SXin Li errors = {} 100*9c5db199SXin Li field_dict = cls.get_field_dict() 101*9c5db199SXin Li for field_name in data: 102*9c5db199SXin Li if field_name not in field_dict: 103*9c5db199SXin Li errors[field_name] = 'No field of this name' 104*9c5db199SXin Li return errors 105*9c5db199SXin Li 106*9c5db199SXin Li 107*9c5db199SXin Li @classmethod 108*9c5db199SXin Li def prepare_data_args(cls, data): 109*9c5db199SXin Li 'Common preparation for add_object and update_object' 110*9c5db199SXin Li # must check for extraneous field names here, while we have the 111*9c5db199SXin Li # data in a dict 112*9c5db199SXin Li errors = cls.validate_field_names(data) 113*9c5db199SXin Li if errors: 114*9c5db199SXin Li raise django_exceptions.ValidationError(errors) 115*9c5db199SXin Li return data 116*9c5db199SXin Li 117*9c5db199SXin Li 118*9c5db199SXin Li @classmethod 119*9c5db199SXin Li def _get_required_field_names(cls): 120*9c5db199SXin Li """Get the fields without which we cannot create a host. 121*9c5db199SXin Li 122*9c5db199SXin Li @return: A list of field names that cannot be blank on host creation. 123*9c5db199SXin Li """ 124*9c5db199SXin Li return [field.name for field in cls._meta.fields if not field.blank] 125*9c5db199SXin Li 126*9c5db199SXin Li 127*9c5db199SXin Li @classmethod 128*9c5db199SXin Li def get_basic_field_names(cls): 129*9c5db199SXin Li """Get all basic fields of the Model. 130*9c5db199SXin Li 131*9c5db199SXin Li This method returns the names of all fields that the client can provide 132*9c5db199SXin Li a value for during host creation. The fields not included in this list 133*9c5db199SXin Li are those that we can leave blank. Specifying non-null values for such 134*9c5db199SXin Li fields only makes sense as an update to the host. 135*9c5db199SXin Li 136*9c5db199SXin Li @return A list of basic fields. 137*9c5db199SXin Li Eg: set([hostname, locked, leased, status, invalid, 138*9c5db199SXin Li protection, lock_time, dirty]) 139*9c5db199SXin Li """ 140*9c5db199SXin Li return [field.name for field in cls._meta.fields 141*9c5db199SXin Li if field.has_default()] + cls._get_required_field_names() 142*9c5db199SXin Li 143*9c5db199SXin Li 144*9c5db199SXin Li @classmethod 145*9c5db199SXin Li def validate_model_fields(cls, data): 146*9c5db199SXin Li """Validate parameters needed to create a host. 147*9c5db199SXin Li 148*9c5db199SXin Li Check that all required fields are specified, that specified fields 149*9c5db199SXin Li are actual model values, and provide defaults for the unspecified 150*9c5db199SXin Li but unrequired fields. 151*9c5db199SXin Li 152*9c5db199SXin Li @param dict: A dictionary with the args to create the model. 153*9c5db199SXin Li 154*9c5db199SXin Li @raises dajngo_exceptions.ValidationError: If either an invalid field 155*9c5db199SXin Li is specified or a required field is missing. 156*9c5db199SXin Li """ 157*9c5db199SXin Li missing_fields = set(cls._get_required_field_names()) - set(data.keys()) 158*9c5db199SXin Li if missing_fields: 159*9c5db199SXin Li raise django_exceptions.ValidationError('%s required to create %s, ' 160*9c5db199SXin Li 'supplied %s ' % (missing_fields, cls.__name__, data)) 161*9c5db199SXin Li data = cls.prepare_data_args(data) 162*9c5db199SXin Li data = cls.provide_default_values(data) 163*9c5db199SXin Li return data 164*9c5db199SXin Li 165*9c5db199SXin Li 166*9c5db199SXin Liclass AbstractHostModel(dbmodels.Model, ModelValidators): 167*9c5db199SXin Li """Abstract model specifying all fields one can use to create a host. 168*9c5db199SXin Li 169*9c5db199SXin Li This model enforces consistency between the host models of the rdb and 170*9c5db199SXin Li their representation on the client side. 171*9c5db199SXin Li 172*9c5db199SXin Li Internal fields: 173*9c5db199SXin Li status: string describing status of host 174*9c5db199SXin Li invalid: true if the host has been deleted 175*9c5db199SXin Li protection: indicates what can be done to this host during repair 176*9c5db199SXin Li lock_time: DateTime at which the host was locked 177*9c5db199SXin Li dirty: true if the host has been used without being rebooted 178*9c5db199SXin Li lock_reason: The reason for locking the host. 179*9c5db199SXin Li """ 180*9c5db199SXin Li Status = host_states.Status 181*9c5db199SXin Li hostname = dbmodels.CharField(max_length=255, unique=True) 182*9c5db199SXin Li locked = dbmodels.BooleanField(default=False) 183*9c5db199SXin Li leased = dbmodels.BooleanField(default=True) 184*9c5db199SXin Li # TODO(ayatane): This is needed until synch_id is removed from Host._fields 185*9c5db199SXin Li synch_id = dbmodels.IntegerField(blank=True, null=True, 186*9c5db199SXin Li editable=settings.FULL_ADMIN) 187*9c5db199SXin Li status = dbmodels.CharField(max_length=255, default=Status.READY, 188*9c5db199SXin Li choices=Status.choices(), 189*9c5db199SXin Li editable=settings.FULL_ADMIN) 190*9c5db199SXin Li invalid = dbmodels.BooleanField(default=False, 191*9c5db199SXin Li editable=settings.FULL_ADMIN) 192*9c5db199SXin Li protection = dbmodels.SmallIntegerField(null=False, blank=True, 193*9c5db199SXin Li choices=host_protections.choices, 194*9c5db199SXin Li default=host_protections.default) 195*9c5db199SXin Li lock_time = dbmodels.DateTimeField(null=True, blank=True, editable=False) 196*9c5db199SXin Li dirty = dbmodels.BooleanField(default=True, editable=settings.FULL_ADMIN) 197*9c5db199SXin Li lock_reason = dbmodels.CharField(null=True, max_length=255, blank=True, 198*9c5db199SXin Li default='') 199*9c5db199SXin Li 200*9c5db199SXin Li 201*9c5db199SXin Li class Meta: 202*9c5db199SXin Li """Extends dbmodels.Model.Meta""" 203*9c5db199SXin Li abstract = True 204