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