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