17c7852819d0611b4d0e8e69b3011b79e4016a770showard"""
27c7852819d0611b4d0e8e69b3011b79e4016a770showardExtensions to Django's model logic.
37c7852819d0611b4d0e8e69b3011b79e4016a770showard"""
47c7852819d0611b4d0e8e69b3011b79e4016a770showard
5a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshowardimport django.core.exceptions
675be1d3f881ef4f4f9cffe0c38fc3139338d8f84Prashanth Balasubramanianfrom django.db import connection
775be1d3f881ef4f4f9cffe0c38fc3139338d8f84Prashanth Balasubramanianfrom django.db import connections
875be1d3f881ef4f4f9cffe0c38fc3139338d8f84Prashanth Balasubramanianfrom django.db import models as dbmodels
975be1d3f881ef4f4f9cffe0c38fc3139338d8f84Prashanth Balasubramanianfrom django.db import transaction
10a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshowardfrom django.db.models.sql import query
117e67b433965702c0ffd8205ac08f5e801d9f98a6showardimport django.db.models.sql.where
1214cac44542bc6a403cb7a3585eb3ebae00227992Aviv Keshet# TODO(akeshet): Replace with monarch stats once we know how to instrument rpc
1314cac44542bc6a403cb7a3585eb3ebae00227992Aviv Keshet# handling with ts_mon.
145cfd96aca6204b6fd193ab2e15a24808756e6198MK Ryufrom autotest_lib.client.common_lib.cros.graphite import autotest_stats
15489b91d72cd225e902081dbd3f9e47448fe867f6Prashanth Bfrom autotest_lib.frontend.afe import rdb_model_extensions
167c7852819d0611b4d0e8e69b3011b79e4016a770showard
17489b91d72cd225e902081dbd3f9e47448fe867f6Prashanth B
18489b91d72cd225e902081dbd3f9e47448fe867f6Prashanth Bclass ValidationError(django.core.exceptions.ValidationError):
190afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    """\
20a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward    Data validation error in adding or updating an object. The associated
210afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    value is a dictionary mapping field names to error strings.
220afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    """
237c7852819d0611b4d0e8e69b3011b79e4016a770showard
24a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshowarddef _quote_name(name):
25a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward    """Shorthand for connection.ops.quote_name()."""
26a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward    return connection.ops.quote_name(name)
27a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward
28a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward
29cc9fc70587d37775673e47b3dcb4d6ded0c6dcb4beepsclass LeasedHostManager(dbmodels.Manager):
30cc9fc70587d37775673e47b3dcb4d6ded0c6dcb4beeps    """Query manager for unleased, unlocked hosts.
31cc9fc70587d37775673e47b3dcb4d6ded0c6dcb4beeps    """
32cc9fc70587d37775673e47b3dcb4d6ded0c6dcb4beeps    def get_query_set(self):
33cc9fc70587d37775673e47b3dcb4d6ded0c6dcb4beeps        return (super(LeasedHostManager, self).get_query_set().filter(
34cc9fc70587d37775673e47b3dcb4d6ded0c6dcb4beeps                leased=0, locked=0))
35cc9fc70587d37775673e47b3dcb4d6ded0c6dcb4beeps
36cc9fc70587d37775673e47b3dcb4d6ded0c6dcb4beeps
377c7852819d0611b4d0e8e69b3011b79e4016a770showardclass ExtendedManager(dbmodels.Manager):
380afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    """\
390afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    Extended manager supporting subquery filtering.
400afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    """
410afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
42f828c77299496a78d8e7f5afe608f7e73851fbd0showard    class CustomQuery(query.Query):
437e67b433965702c0ffd8205ac08f5e801d9f98a6showard        def __init__(self, *args, **kwargs):
44f828c77299496a78d8e7f5afe608f7e73851fbd0showard            super(ExtendedManager.CustomQuery, self).__init__(*args, **kwargs)
457e67b433965702c0ffd8205ac08f5e801d9f98a6showard            self._custom_joins = []
460afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
470afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
487e67b433965702c0ffd8205ac08f5e801d9f98a6showard        def clone(self, klass=None, **kwargs):
49f828c77299496a78d8e7f5afe608f7e73851fbd0showard            obj = super(ExtendedManager.CustomQuery, self).clone(klass)
507e67b433965702c0ffd8205ac08f5e801d9f98a6showard            obj._custom_joins = list(self._custom_joins)
51a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward            return obj
520afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
530afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
547e67b433965702c0ffd8205ac08f5e801d9f98a6showard        def combine(self, rhs, connector):
55f828c77299496a78d8e7f5afe608f7e73851fbd0showard            super(ExtendedManager.CustomQuery, self).combine(rhs, connector)
567e67b433965702c0ffd8205ac08f5e801d9f98a6showard            if hasattr(rhs, '_custom_joins'):
577e67b433965702c0ffd8205ac08f5e801d9f98a6showard                self._custom_joins.extend(rhs._custom_joins)
580afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
5908f981bddc5f1a199d789d177cbf583dee9f6c17showard
607e67b433965702c0ffd8205ac08f5e801d9f98a6showard        def add_custom_join(self, table, condition, join_type,
617e67b433965702c0ffd8205ac08f5e801d9f98a6showard                            condition_values=(), alias=None):
627e67b433965702c0ffd8205ac08f5e801d9f98a6showard            if alias is None:
637e67b433965702c0ffd8205ac08f5e801d9f98a6showard                alias = table
647e67b433965702c0ffd8205ac08f5e801d9f98a6showard            join_dict = dict(table=table,
657e67b433965702c0ffd8205ac08f5e801d9f98a6showard                             condition=condition,
667e67b433965702c0ffd8205ac08f5e801d9f98a6showard                             condition_values=condition_values,
677e67b433965702c0ffd8205ac08f5e801d9f98a6showard                             join_type=join_type,
687e67b433965702c0ffd8205ac08f5e801d9f98a6showard                             alias=alias)
697e67b433965702c0ffd8205ac08f5e801d9f98a6showard            self._custom_joins.append(join_dict)
7008f981bddc5f1a199d789d177cbf583dee9f6c17showard
7143a3d26e49662d06e145ed94a0c2dfb2b455126fshoward
727e67b433965702c0ffd8205ac08f5e801d9f98a6showard        @classmethod
737e67b433965702c0ffd8205ac08f5e801d9f98a6showard        def convert_query(self, query_set):
747e67b433965702c0ffd8205ac08f5e801d9f98a6showard            """
75f828c77299496a78d8e7f5afe608f7e73851fbd0showard            Convert the query set's "query" attribute to a CustomQuery.
767e67b433965702c0ffd8205ac08f5e801d9f98a6showard            """
777e67b433965702c0ffd8205ac08f5e801d9f98a6showard            # Make a copy of the query set
787e67b433965702c0ffd8205ac08f5e801d9f98a6showard            query_set = query_set.all()
797e67b433965702c0ffd8205ac08f5e801d9f98a6showard            query_set.query = query_set.query.clone(
80f828c77299496a78d8e7f5afe608f7e73851fbd0showard                    klass=ExtendedManager.CustomQuery,
817e67b433965702c0ffd8205ac08f5e801d9f98a6showard                    _custom_joins=[])
827e67b433965702c0ffd8205ac08f5e801d9f98a6showard            return query_set
8343a3d26e49662d06e145ed94a0c2dfb2b455126fshoward
84a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward
857e67b433965702c0ffd8205ac08f5e801d9f98a6showard    class _WhereClause(object):
867e67b433965702c0ffd8205ac08f5e801d9f98a6showard        """Object allowing us to inject arbitrary SQL into Django queries.
87a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward
887e67b433965702c0ffd8205ac08f5e801d9f98a6showard        By using this instead of extra(where=...), we can still freely combine
897e67b433965702c0ffd8205ac08f5e801d9f98a6showard        queries with & and |.
90a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward        """
917e67b433965702c0ffd8205ac08f5e801d9f98a6showard        def __init__(self, clause, values=()):
927e67b433965702c0ffd8205ac08f5e801d9f98a6showard            self._clause = clause
937e67b433965702c0ffd8205ac08f5e801d9f98a6showard            self._values = values
94a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward
957e67b433965702c0ffd8205ac08f5e801d9f98a6showard
9674a314b490ff542c4dd2ae4aa0d11c6394d92960Dale Curtis        def as_sql(self, qn=None, connection=None):
977e67b433965702c0ffd8205ac08f5e801d9f98a6showard            return self._clause, self._values
987e67b433965702c0ffd8205ac08f5e801d9f98a6showard
997e67b433965702c0ffd8205ac08f5e801d9f98a6showard
1007e67b433965702c0ffd8205ac08f5e801d9f98a6showard        def relabel_aliases(self, change_map):
1017e67b433965702c0ffd8205ac08f5e801d9f98a6showard            return
10243a3d26e49662d06e145ed94a0c2dfb2b455126fshoward
10343a3d26e49662d06e145ed94a0c2dfb2b455126fshoward
1048b0ea2285c1327a686ff0b6ab245915e7fd20094showard    def add_join(self, query_set, join_table, join_key, join_condition='',
1057e67b433965702c0ffd8205ac08f5e801d9f98a6showard                 join_condition_values=(), join_from_key=None, alias=None,
1067e67b433965702c0ffd8205ac08f5e801d9f98a6showard                 suffix='', exclude=False, force_left_join=False):
1077e67b433965702c0ffd8205ac08f5e801d9f98a6showard        """Add a join to query_set.
1087e67b433965702c0ffd8205ac08f5e801d9f98a6showard
1097e67b433965702c0ffd8205ac08f5e801d9f98a6showard        Join looks like this:
1107e67b433965702c0ffd8205ac08f5e801d9f98a6showard                (INNER|LEFT) JOIN <join_table> AS <alias>
1117e67b433965702c0ffd8205ac08f5e801d9f98a6showard                    ON (<this table>.<join_from_key> = <join_table>.<join_key>
1127e67b433965702c0ffd8205ac08f5e801d9f98a6showard                        and <join_condition>)
1137e67b433965702c0ffd8205ac08f5e801d9f98a6showard
1140957a848a17b726836aaf9b5532bbfac691d983dshoward        @param join_table table to join to
1150957a848a17b726836aaf9b5532bbfac691d983dshoward        @param join_key field referencing back to this model to use for the join
1160957a848a17b726836aaf9b5532bbfac691d983dshoward        @param join_condition extra condition for the ON clause of the join
1177e67b433965702c0ffd8205ac08f5e801d9f98a6showard        @param join_condition_values values to substitute into join_condition
1187e67b433965702c0ffd8205ac08f5e801d9f98a6showard        @param join_from_key column on this model to join from.
1198b0ea2285c1327a686ff0b6ab245915e7fd20094showard        @param alias alias to use for for join
1208b0ea2285c1327a686ff0b6ab245915e7fd20094showard        @param suffix suffix to add to join_table for the join alias, if no
1218b0ea2285c1327a686ff0b6ab245915e7fd20094showard                alias is provided
1220957a848a17b726836aaf9b5532bbfac691d983dshoward        @param exclude if true, exclude rows that match this join (will use a
123a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward        LEFT OUTER JOIN and an appropriate WHERE condition)
124c47804079f4154e9c4084584386a8cbdebbde925showard        @param force_left_join - if true, a LEFT OUTER JOIN will be used
125c47804079f4154e9c4084584386a8cbdebbde925showard        instead of an INNER JOIN regardless of other options
1260957a848a17b726836aaf9b5532bbfac691d983dshoward        """
1277e67b433965702c0ffd8205ac08f5e801d9f98a6showard        join_from_table = query_set.model._meta.db_table
1287e67b433965702c0ffd8205ac08f5e801d9f98a6showard        if join_from_key is None:
1297e67b433965702c0ffd8205ac08f5e801d9f98a6showard            join_from_key = self.model._meta.pk.name
1307e67b433965702c0ffd8205ac08f5e801d9f98a6showard        if alias is None:
1317e67b433965702c0ffd8205ac08f5e801d9f98a6showard            alias = join_table + suffix
1327e67b433965702c0ffd8205ac08f5e801d9f98a6showard        full_join_key = _quote_name(alias) + '.' + _quote_name(join_key)
1337e67b433965702c0ffd8205ac08f5e801d9f98a6showard        full_join_condition = '%s = %s.%s' % (full_join_key,
1347e67b433965702c0ffd8205ac08f5e801d9f98a6showard                                              _quote_name(join_from_table),
1357e67b433965702c0ffd8205ac08f5e801d9f98a6showard                                              _quote_name(join_from_key))
13643a3d26e49662d06e145ed94a0c2dfb2b455126fshoward        if join_condition:
13743a3d26e49662d06e145ed94a0c2dfb2b455126fshoward            full_join_condition += ' AND (' + join_condition + ')'
13843a3d26e49662d06e145ed94a0c2dfb2b455126fshoward        if exclude or force_left_join:
139a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward            join_type = query_set.query.LOUTER
14043a3d26e49662d06e145ed94a0c2dfb2b455126fshoward        else:
141a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward            join_type = query_set.query.INNER
14243a3d26e49662d06e145ed94a0c2dfb2b455126fshoward
143f828c77299496a78d8e7f5afe608f7e73851fbd0showard        query_set = self.CustomQuery.convert_query(query_set)
1447e67b433965702c0ffd8205ac08f5e801d9f98a6showard        query_set.query.add_custom_join(join_table,
1457e67b433965702c0ffd8205ac08f5e801d9f98a6showard                                        full_join_condition,
1467e67b433965702c0ffd8205ac08f5e801d9f98a6showard                                        join_type,
1477e67b433965702c0ffd8205ac08f5e801d9f98a6showard                                        condition_values=join_condition_values,
1487e67b433965702c0ffd8205ac08f5e801d9f98a6showard                                        alias=alias)
1497e67b433965702c0ffd8205ac08f5e801d9f98a6showard
15043a3d26e49662d06e145ed94a0c2dfb2b455126fshoward        if exclude:
1517e67b433965702c0ffd8205ac08f5e801d9f98a6showard            query_set = query_set.extra(where=[full_join_key + ' IS NULL'])
15243a3d26e49662d06e145ed94a0c2dfb2b455126fshoward
1537e67b433965702c0ffd8205ac08f5e801d9f98a6showard        return query_set
1547e67b433965702c0ffd8205ac08f5e801d9f98a6showard
1557e67b433965702c0ffd8205ac08f5e801d9f98a6showard
1567e67b433965702c0ffd8205ac08f5e801d9f98a6showard    def _info_for_many_to_one_join(self, field, join_to_query, alias):
1577e67b433965702c0ffd8205ac08f5e801d9f98a6showard        """
1587e67b433965702c0ffd8205ac08f5e801d9f98a6showard        @param field: the ForeignKey field on the related model
1597e67b433965702c0ffd8205ac08f5e801d9f98a6showard        @param join_to_query: the query over the related model that we're
1607e67b433965702c0ffd8205ac08f5e801d9f98a6showard                joining to
1617e67b433965702c0ffd8205ac08f5e801d9f98a6showard        @param alias: alias of joined table
1627e67b433965702c0ffd8205ac08f5e801d9f98a6showard        """
1637e67b433965702c0ffd8205ac08f5e801d9f98a6showard        info = {}
1647e67b433965702c0ffd8205ac08f5e801d9f98a6showard        rhs_table = join_to_query.model._meta.db_table
1657e67b433965702c0ffd8205ac08f5e801d9f98a6showard        info['rhs_table'] = rhs_table
1667e67b433965702c0ffd8205ac08f5e801d9f98a6showard        info['rhs_column'] = field.column
1677e67b433965702c0ffd8205ac08f5e801d9f98a6showard        info['lhs_column'] = field.rel.get_related_field().column
1687e67b433965702c0ffd8205ac08f5e801d9f98a6showard        rhs_where = join_to_query.query.where
1697e67b433965702c0ffd8205ac08f5e801d9f98a6showard        rhs_where.relabel_aliases({rhs_table: alias})
17074a314b490ff542c4dd2ae4aa0d11c6394d92960Dale Curtis        compiler = join_to_query.query.get_compiler(using=join_to_query.db)
17174a314b490ff542c4dd2ae4aa0d11c6394d92960Dale Curtis        initial_clause, values = compiler.as_sql()
172583215128a773aad0fedf3030e5fdf91f1e0f380Dan Shi        # initial_clause is compiled from `join_to_query`, which is a SELECT
173583215128a773aad0fedf3030e5fdf91f1e0f380Dan Shi        # query returns at most one record. For it to be used in WHERE clause,
174583215128a773aad0fedf3030e5fdf91f1e0f380Dan Shi        # it must be converted to a boolean value using EXISTS.
175583215128a773aad0fedf3030e5fdf91f1e0f380Dan Shi        all_clauses = ('EXISTS (%s)' % initial_clause,)
17674a314b490ff542c4dd2ae4aa0d11c6394d92960Dale Curtis        if hasattr(join_to_query.query, 'extra_where'):
17774a314b490ff542c4dd2ae4aa0d11c6394d92960Dale Curtis            all_clauses += join_to_query.query.extra_where
17874a314b490ff542c4dd2ae4aa0d11c6394d92960Dale Curtis        info['where_clause'] = (
17974a314b490ff542c4dd2ae4aa0d11c6394d92960Dale Curtis                    ' AND '.join('(%s)' % clause for clause in all_clauses))
1807e67b433965702c0ffd8205ac08f5e801d9f98a6showard        info['values'] = values
1817e67b433965702c0ffd8205ac08f5e801d9f98a6showard        return info
1827e67b433965702c0ffd8205ac08f5e801d9f98a6showard
1837e67b433965702c0ffd8205ac08f5e801d9f98a6showard
1847e67b433965702c0ffd8205ac08f5e801d9f98a6showard    def _info_for_many_to_many_join(self, m2m_field, join_to_query, alias,
1857e67b433965702c0ffd8205ac08f5e801d9f98a6showard                                    m2m_is_on_this_model):
1867e67b433965702c0ffd8205ac08f5e801d9f98a6showard        """
1877e67b433965702c0ffd8205ac08f5e801d9f98a6showard        @param m2m_field: a Django field representing the M2M relationship.
1887e67b433965702c0ffd8205ac08f5e801d9f98a6showard                It uses a pivot table with the following structure:
1897e67b433965702c0ffd8205ac08f5e801d9f98a6showard                this model table <---> M2M pivot table <---> joined model table
1907e67b433965702c0ffd8205ac08f5e801d9f98a6showard        @param join_to_query: the query over the related model that we're
1917e67b433965702c0ffd8205ac08f5e801d9f98a6showard                joining to.
1927e67b433965702c0ffd8205ac08f5e801d9f98a6showard        @param alias: alias of joined table
1937e67b433965702c0ffd8205ac08f5e801d9f98a6showard        """
1947e67b433965702c0ffd8205ac08f5e801d9f98a6showard        if m2m_is_on_this_model:
1957e67b433965702c0ffd8205ac08f5e801d9f98a6showard            # referenced field on this model
1967e67b433965702c0ffd8205ac08f5e801d9f98a6showard            lhs_id_field = self.model._meta.pk
1977e67b433965702c0ffd8205ac08f5e801d9f98a6showard            # foreign key on the pivot table referencing lhs_id_field
1987e67b433965702c0ffd8205ac08f5e801d9f98a6showard            m2m_lhs_column = m2m_field.m2m_column_name()
1997e67b433965702c0ffd8205ac08f5e801d9f98a6showard            # foreign key on the pivot table referencing rhd_id_field
2007e67b433965702c0ffd8205ac08f5e801d9f98a6showard            m2m_rhs_column = m2m_field.m2m_reverse_name()
2017e67b433965702c0ffd8205ac08f5e801d9f98a6showard            # referenced field on related model
2027e67b433965702c0ffd8205ac08f5e801d9f98a6showard            rhs_id_field = m2m_field.rel.get_related_field()
2037e67b433965702c0ffd8205ac08f5e801d9f98a6showard        else:
2047e67b433965702c0ffd8205ac08f5e801d9f98a6showard            lhs_id_field = m2m_field.rel.get_related_field()
2057e67b433965702c0ffd8205ac08f5e801d9f98a6showard            m2m_lhs_column = m2m_field.m2m_reverse_name()
2067e67b433965702c0ffd8205ac08f5e801d9f98a6showard            m2m_rhs_column = m2m_field.m2m_column_name()
2077e67b433965702c0ffd8205ac08f5e801d9f98a6showard            rhs_id_field = join_to_query.model._meta.pk
2087e67b433965702c0ffd8205ac08f5e801d9f98a6showard
2097e67b433965702c0ffd8205ac08f5e801d9f98a6showard        info = {}
2107e67b433965702c0ffd8205ac08f5e801d9f98a6showard        info['rhs_table'] = m2m_field.m2m_db_table()
2117e67b433965702c0ffd8205ac08f5e801d9f98a6showard        info['rhs_column'] = m2m_lhs_column
2127e67b433965702c0ffd8205ac08f5e801d9f98a6showard        info['lhs_column'] = lhs_id_field.column
2137e67b433965702c0ffd8205ac08f5e801d9f98a6showard
2147e67b433965702c0ffd8205ac08f5e801d9f98a6showard        # select the ID of related models relevant to this join.  we can only do
2157e67b433965702c0ffd8205ac08f5e801d9f98a6showard        # a single join, so we need to gather this information up front and
2167e67b433965702c0ffd8205ac08f5e801d9f98a6showard        # include it in the join condition.
2177e67b433965702c0ffd8205ac08f5e801d9f98a6showard        rhs_ids = join_to_query.values_list(rhs_id_field.attname, flat=True)
2187e67b433965702c0ffd8205ac08f5e801d9f98a6showard        assert len(rhs_ids) == 1, ('Many-to-many custom field joins can only '
2197e67b433965702c0ffd8205ac08f5e801d9f98a6showard                                   'match a single related object.')
2207e67b433965702c0ffd8205ac08f5e801d9f98a6showard        rhs_id = rhs_ids[0]
2217e67b433965702c0ffd8205ac08f5e801d9f98a6showard
2227e67b433965702c0ffd8205ac08f5e801d9f98a6showard        info['where_clause'] = '%s.%s = %s' % (_quote_name(alias),
2237e67b433965702c0ffd8205ac08f5e801d9f98a6showard                                               _quote_name(m2m_rhs_column),
2247e67b433965702c0ffd8205ac08f5e801d9f98a6showard                                               rhs_id)
2257e67b433965702c0ffd8205ac08f5e801d9f98a6showard        info['values'] = ()
2267e67b433965702c0ffd8205ac08f5e801d9f98a6showard        return info
2277e67b433965702c0ffd8205ac08f5e801d9f98a6showard
2287e67b433965702c0ffd8205ac08f5e801d9f98a6showard
2297e67b433965702c0ffd8205ac08f5e801d9f98a6showard    def join_custom_field(self, query_set, join_to_query, alias,
2307e67b433965702c0ffd8205ac08f5e801d9f98a6showard                          left_join=True):
2317e67b433965702c0ffd8205ac08f5e801d9f98a6showard        """Join to a related model to create a custom field in the given query.
2327e67b433965702c0ffd8205ac08f5e801d9f98a6showard
2337e67b433965702c0ffd8205ac08f5e801d9f98a6showard        This method is used to construct a custom field on the given query based
2347e67b433965702c0ffd8205ac08f5e801d9f98a6showard        on a many-valued relationsip.  join_to_query should be a simple query
2357e67b433965702c0ffd8205ac08f5e801d9f98a6showard        (no joins) on the related model which returns at most one related row
2367e67b433965702c0ffd8205ac08f5e801d9f98a6showard        per instance of this model.
2377e67b433965702c0ffd8205ac08f5e801d9f98a6showard
2387e67b433965702c0ffd8205ac08f5e801d9f98a6showard        For many-to-one relationships, the joined table contains the matching
2397e67b433965702c0ffd8205ac08f5e801d9f98a6showard        row from the related model it one is related, NULL otherwise.
2407e67b433965702c0ffd8205ac08f5e801d9f98a6showard
2417e67b433965702c0ffd8205ac08f5e801d9f98a6showard        For many-to-many relationships, the joined table contains the matching
2427e67b433965702c0ffd8205ac08f5e801d9f98a6showard        row if it's related, NULL otherwise.
2437e67b433965702c0ffd8205ac08f5e801d9f98a6showard        """
2447e67b433965702c0ffd8205ac08f5e801d9f98a6showard        relationship_type, field = self.determine_relationship(
2457e67b433965702c0ffd8205ac08f5e801d9f98a6showard                join_to_query.model)
2467e67b433965702c0ffd8205ac08f5e801d9f98a6showard
2477e67b433965702c0ffd8205ac08f5e801d9f98a6showard        if relationship_type == self.MANY_TO_ONE:
2487e67b433965702c0ffd8205ac08f5e801d9f98a6showard            info = self._info_for_many_to_one_join(field, join_to_query, alias)
2497e67b433965702c0ffd8205ac08f5e801d9f98a6showard        elif relationship_type == self.M2M_ON_RELATED_MODEL:
2507e67b433965702c0ffd8205ac08f5e801d9f98a6showard            info = self._info_for_many_to_many_join(
2517e67b433965702c0ffd8205ac08f5e801d9f98a6showard                    m2m_field=field, join_to_query=join_to_query, alias=alias,
2527e67b433965702c0ffd8205ac08f5e801d9f98a6showard                    m2m_is_on_this_model=False)
2537e67b433965702c0ffd8205ac08f5e801d9f98a6showard        elif relationship_type ==self.M2M_ON_THIS_MODEL:
2547e67b433965702c0ffd8205ac08f5e801d9f98a6showard            info = self._info_for_many_to_many_join(
2557e67b433965702c0ffd8205ac08f5e801d9f98a6showard                    m2m_field=field, join_to_query=join_to_query, alias=alias,
2567e67b433965702c0ffd8205ac08f5e801d9f98a6showard                    m2m_is_on_this_model=True)
2577e67b433965702c0ffd8205ac08f5e801d9f98a6showard
2587e67b433965702c0ffd8205ac08f5e801d9f98a6showard        return self.add_join(query_set, info['rhs_table'], info['rhs_column'],
2597e67b433965702c0ffd8205ac08f5e801d9f98a6showard                             join_from_key=info['lhs_column'],
2607e67b433965702c0ffd8205ac08f5e801d9f98a6showard                             join_condition=info['where_clause'],
2617e67b433965702c0ffd8205ac08f5e801d9f98a6showard                             join_condition_values=info['values'],
2627e67b433965702c0ffd8205ac08f5e801d9f98a6showard                             alias=alias,
2637e67b433965702c0ffd8205ac08f5e801d9f98a6showard                             force_left_join=left_join)
2647e67b433965702c0ffd8205ac08f5e801d9f98a6showard
2657e67b433965702c0ffd8205ac08f5e801d9f98a6showard
2667e67b433965702c0ffd8205ac08f5e801d9f98a6showard    def add_where(self, query_set, where, values=()):
2677e67b433965702c0ffd8205ac08f5e801d9f98a6showard        query_set = query_set.all()
2687e67b433965702c0ffd8205ac08f5e801d9f98a6showard        query_set.query.where.add(self._WhereClause(where, values),
2697e67b433965702c0ffd8205ac08f5e801d9f98a6showard                                  django.db.models.sql.where.AND)
270c47804079f4154e9c4084584386a8cbdebbde925showard        return query_set
2717c7852819d0611b4d0e8e69b3011b79e4016a770showard
2727c7852819d0611b4d0e8e69b3011b79e4016a770showard
273eaccf8fd578288cb20941dad6c621182db29e9aeshoward    def _get_quoted_field(self, table, field):
274a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward        return _quote_name(table) + '.' + _quote_name(field)
2755ef36e9a24e3688893cf2a64824c29e55c391543showard
2765ef36e9a24e3688893cf2a64824c29e55c391543showard
2777c199df48dbc7a8200c8a80a163be4613c405cb8showard    def get_key_on_this_table(self, key_field=None):
2785ef36e9a24e3688893cf2a64824c29e55c391543showard        if key_field is None:
2795ef36e9a24e3688893cf2a64824c29e55c391543showard            # default to primary key
2805ef36e9a24e3688893cf2a64824c29e55c391543showard            key_field = self.model._meta.pk.column
2815ef36e9a24e3688893cf2a64824c29e55c391543showard        return self._get_quoted_field(self.model._meta.db_table, key_field)
2825ef36e9a24e3688893cf2a64824c29e55c391543showard
2835ef36e9a24e3688893cf2a64824c29e55c391543showard
284eaccf8fd578288cb20941dad6c621182db29e9aeshoward    def escape_user_sql(self, sql):
285eaccf8fd578288cb20941dad6c621182db29e9aeshoward        return sql.replace('%', '%%')
286eaccf8fd578288cb20941dad6c621182db29e9aeshoward
2875ef36e9a24e3688893cf2a64824c29e55c391543showard
2880957a848a17b726836aaf9b5532bbfac691d983dshoward    def _custom_select_query(self, query_set, selects):
2897bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich        """Execute a custom select query.
2907bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich
2917bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich        @param query_set: query set as returned by query_objects.
2927bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich        @param selects: Tables/Columns to select, e.g. tko_test_labels_list.id.
2937bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich
2947bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich        @returns: Result of the query as returned by cursor.fetchall().
2957bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich        """
29674a314b490ff542c4dd2ae4aa0d11c6394d92960Dale Curtis        compiler = query_set.query.get_compiler(using=query_set.db)
29774a314b490ff542c4dd2ae4aa0d11c6394d92960Dale Curtis        sql, params = compiler.as_sql()
298a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward        from_ = sql[sql.find(' FROM'):]
299a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward
300a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward        if query_set.query.distinct:
3010957a848a17b726836aaf9b5532bbfac691d983dshoward            distinct = 'DISTINCT '
3020957a848a17b726836aaf9b5532bbfac691d983dshoward        else:
3030957a848a17b726836aaf9b5532bbfac691d983dshoward            distinct = ''
304a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward
305a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward        sql_query = ('SELECT ' + distinct + ','.join(selects) + from_)
3067bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich        # Chose the connection that's responsible for this type of object
3077bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich        cursor = connections[query_set.db].cursor()
3080957a848a17b726836aaf9b5532bbfac691d983dshoward        cursor.execute(sql_query, params)
3090957a848a17b726836aaf9b5532bbfac691d983dshoward        return cursor.fetchall()
3100957a848a17b726836aaf9b5532bbfac691d983dshoward
3110957a848a17b726836aaf9b5532bbfac691d983dshoward
31268693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward    def _is_relation_to(self, field, model_class):
31368693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        return field.rel and field.rel.to is model_class
3140957a848a17b726836aaf9b5532bbfac691d983dshoward
3150957a848a17b726836aaf9b5532bbfac691d983dshoward
3167e67b433965702c0ffd8205ac08f5e801d9f98a6showard    MANY_TO_ONE = object()
3177e67b433965702c0ffd8205ac08f5e801d9f98a6showard    M2M_ON_RELATED_MODEL = object()
3187e67b433965702c0ffd8205ac08f5e801d9f98a6showard    M2M_ON_THIS_MODEL = object()
3197e67b433965702c0ffd8205ac08f5e801d9f98a6showard
3207e67b433965702c0ffd8205ac08f5e801d9f98a6showard    def determine_relationship(self, related_model):
3210957a848a17b726836aaf9b5532bbfac691d983dshoward        """
3227e67b433965702c0ffd8205ac08f5e801d9f98a6showard        Determine the relationship between this model and related_model.
3237e67b433965702c0ffd8205ac08f5e801d9f98a6showard
3247e67b433965702c0ffd8205ac08f5e801d9f98a6showard        related_model must have some sort of many-valued relationship to this
3257e67b433965702c0ffd8205ac08f5e801d9f98a6showard        manager's model.
3267e67b433965702c0ffd8205ac08f5e801d9f98a6showard        @returns (relationship_type, field), where relationship_type is one of
3277e67b433965702c0ffd8205ac08f5e801d9f98a6showard                MANY_TO_ONE, M2M_ON_RELATED_MODEL, M2M_ON_THIS_MODEL, and field
3287e67b433965702c0ffd8205ac08f5e801d9f98a6showard                is the Django field object for the relationship.
3290957a848a17b726836aaf9b5532bbfac691d983dshoward        """
3307e67b433965702c0ffd8205ac08f5e801d9f98a6showard        # look for a foreign key field on related_model relating to this model
33168693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        for field in related_model._meta.fields:
3320957a848a17b726836aaf9b5532bbfac691d983dshoward            if self._is_relation_to(field, self.model):
3337e67b433965702c0ffd8205ac08f5e801d9f98a6showard                return self.MANY_TO_ONE, field
3340957a848a17b726836aaf9b5532bbfac691d983dshoward
3357e67b433965702c0ffd8205ac08f5e801d9f98a6showard        # look for an M2M field on related_model relating to this model
33668693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        for field in related_model._meta.many_to_many:
3370957a848a17b726836aaf9b5532bbfac691d983dshoward            if self._is_relation_to(field, self.model):
3387e67b433965702c0ffd8205ac08f5e801d9f98a6showard                return self.M2M_ON_RELATED_MODEL, field
3390957a848a17b726836aaf9b5532bbfac691d983dshoward
3400957a848a17b726836aaf9b5532bbfac691d983dshoward        # maybe this model has the many-to-many field
3410957a848a17b726836aaf9b5532bbfac691d983dshoward        for field in self.model._meta.many_to_many:
34268693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward            if self._is_relation_to(field, related_model):
3437e67b433965702c0ffd8205ac08f5e801d9f98a6showard                return self.M2M_ON_THIS_MODEL, field
3440957a848a17b726836aaf9b5532bbfac691d983dshoward
3450957a848a17b726836aaf9b5532bbfac691d983dshoward        raise ValueError('%s has no relation to %s' %
34668693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward                         (related_model, self.model))
3470957a848a17b726836aaf9b5532bbfac691d983dshoward
3480957a848a17b726836aaf9b5532bbfac691d983dshoward
3497e67b433965702c0ffd8205ac08f5e801d9f98a6showard    def _get_pivot_iterator(self, base_objects_by_id, related_model):
3507e67b433965702c0ffd8205ac08f5e801d9f98a6showard        """
3517e67b433965702c0ffd8205ac08f5e801d9f98a6showard        Determine the relationship between this model and related_model, and
3527e67b433965702c0ffd8205ac08f5e801d9f98a6showard        return a pivot iterator.
3537e67b433965702c0ffd8205ac08f5e801d9f98a6showard        @param base_objects_by_id: dict of instances of this model indexed by
3547e67b433965702c0ffd8205ac08f5e801d9f98a6showard        their IDs
3557e67b433965702c0ffd8205ac08f5e801d9f98a6showard        @returns a pivot iterator, which yields a tuple (base_object,
3567e67b433965702c0ffd8205ac08f5e801d9f98a6showard        related_object) for each relationship between a base object and a
3577e67b433965702c0ffd8205ac08f5e801d9f98a6showard        related object.  all base_object instances come from base_objects_by_id.
3587e67b433965702c0ffd8205ac08f5e801d9f98a6showard        Note -- this depends on Django model internals.
3597e67b433965702c0ffd8205ac08f5e801d9f98a6showard        """
3607e67b433965702c0ffd8205ac08f5e801d9f98a6showard        relationship_type, field = self.determine_relationship(related_model)
3617e67b433965702c0ffd8205ac08f5e801d9f98a6showard        if relationship_type == self.MANY_TO_ONE:
3627e67b433965702c0ffd8205ac08f5e801d9f98a6showard            return self._many_to_one_pivot(base_objects_by_id,
3637e67b433965702c0ffd8205ac08f5e801d9f98a6showard                                           related_model, field)
3647e67b433965702c0ffd8205ac08f5e801d9f98a6showard        elif relationship_type == self.M2M_ON_RELATED_MODEL:
3657e67b433965702c0ffd8205ac08f5e801d9f98a6showard            return self._many_to_many_pivot(
3667e67b433965702c0ffd8205ac08f5e801d9f98a6showard                    base_objects_by_id, related_model, field.m2m_db_table(),
3677e67b433965702c0ffd8205ac08f5e801d9f98a6showard                    field.m2m_reverse_name(), field.m2m_column_name())
3687e67b433965702c0ffd8205ac08f5e801d9f98a6showard        else:
3697e67b433965702c0ffd8205ac08f5e801d9f98a6showard            assert relationship_type == self.M2M_ON_THIS_MODEL
3707e67b433965702c0ffd8205ac08f5e801d9f98a6showard            return self._many_to_many_pivot(
3717e67b433965702c0ffd8205ac08f5e801d9f98a6showard                    base_objects_by_id, related_model, field.m2m_db_table(),
3727e67b433965702c0ffd8205ac08f5e801d9f98a6showard                    field.m2m_column_name(), field.m2m_reverse_name())
3737e67b433965702c0ffd8205ac08f5e801d9f98a6showard
3747e67b433965702c0ffd8205ac08f5e801d9f98a6showard
37568693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward    def _many_to_one_pivot(self, base_objects_by_id, related_model,
37668693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward                           foreign_key_field):
37768693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        """
37868693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        @returns a pivot iterator - see _get_pivot_iterator()
37968693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        """
38068693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        filter_data = {foreign_key_field.name + '__pk__in':
38168693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward                       base_objects_by_id.keys()}
38268693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        for related_object in related_model.objects.filter(**filter_data):
383a5a72c9d41d83f244533892d409c10b58398c1d5showard            # lookup base object in the dict, rather than grabbing it from the
384a5a72c9d41d83f244533892d409c10b58398c1d5showard            # related object.  we need to return instances from the dict, not
385a5a72c9d41d83f244533892d409c10b58398c1d5showard            # fresh instances of the same models (and grabbing model instances
386a5a72c9d41d83f244533892d409c10b58398c1d5showard            # from the related models incurs a DB query each time).
387a5a72c9d41d83f244533892d409c10b58398c1d5showard            base_object_id = getattr(related_object, foreign_key_field.attname)
388a5a72c9d41d83f244533892d409c10b58398c1d5showard            base_object = base_objects_by_id[base_object_id]
38968693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward            yield base_object, related_object
39068693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward
39168693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward
39268693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward    def _query_pivot_table(self, base_objects_by_id, pivot_table,
3937bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich                           pivot_from_field, pivot_to_field, related_model):
3940957a848a17b726836aaf9b5532bbfac691d983dshoward        """
3950957a848a17b726836aaf9b5532bbfac691d983dshoward        @param id_list list of IDs of self.model objects to include
3960957a848a17b726836aaf9b5532bbfac691d983dshoward        @param pivot_table the name of the pivot table
3970957a848a17b726836aaf9b5532bbfac691d983dshoward        @param pivot_from_field a field name on pivot_table referencing
3980957a848a17b726836aaf9b5532bbfac691d983dshoward        self.model
3990957a848a17b726836aaf9b5532bbfac691d983dshoward        @param pivot_to_field a field name on pivot_table referencing the
4000957a848a17b726836aaf9b5532bbfac691d983dshoward        related model.
4017bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich        @param related_model the related model
4027bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich
40368693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        @returns pivot list of IDs (base_id, related_id)
4040957a848a17b726836aaf9b5532bbfac691d983dshoward        """
4050957a848a17b726836aaf9b5532bbfac691d983dshoward        query = """
4060957a848a17b726836aaf9b5532bbfac691d983dshoward        SELECT %(from_field)s, %(to_field)s
4070957a848a17b726836aaf9b5532bbfac691d983dshoward        FROM %(table)s
4080957a848a17b726836aaf9b5532bbfac691d983dshoward        WHERE %(from_field)s IN (%(id_list)s)
4090957a848a17b726836aaf9b5532bbfac691d983dshoward        """ % dict(from_field=pivot_from_field,
4100957a848a17b726836aaf9b5532bbfac691d983dshoward                   to_field=pivot_to_field,
4110957a848a17b726836aaf9b5532bbfac691d983dshoward                   table=pivot_table,
41268693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward                   id_list=','.join(str(id_) for id_
41368693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward                                    in base_objects_by_id.iterkeys()))
4147bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich
4157bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich        # Chose the connection that's responsible for this type of object
4167bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich        # The databases for related_model and the current model will always
4177bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich        # be the same, related_model is just easier to obtain here because
4187bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich        # self is only a ExtendedManager, not the object.
4197bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich        cursor = connections[related_model.objects.db].cursor()
4200957a848a17b726836aaf9b5532bbfac691d983dshoward        cursor.execute(query)
42168693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        return cursor.fetchall()
4220957a848a17b726836aaf9b5532bbfac691d983dshoward
4230957a848a17b726836aaf9b5532bbfac691d983dshoward
42468693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward    def _many_to_many_pivot(self, base_objects_by_id, related_model,
42568693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward                            pivot_table, pivot_from_field, pivot_to_field):
42668693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        """
42768693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        @param pivot_table: see _query_pivot_table
42868693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        @param pivot_from_field: see _query_pivot_table
42968693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        @param pivot_to_field: see _query_pivot_table
43068693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        @returns a pivot iterator - see _get_pivot_iterator()
43168693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        """
43268693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        id_pivot = self._query_pivot_table(base_objects_by_id, pivot_table,
4337bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich                                           pivot_from_field, pivot_to_field,
4347bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich                                           related_model)
43568693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward
43668693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        all_related_ids = list(set(related_id for base_id, related_id
43768693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward                                   in id_pivot))
43868693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        related_objects_by_id = related_model.objects.in_bulk(all_related_ids)
4390957a848a17b726836aaf9b5532bbfac691d983dshoward
44068693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        for base_id, related_id in id_pivot:
44168693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward            yield base_objects_by_id[base_id], related_objects_by_id[related_id]
44268693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward
44368693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward
44468693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward    def populate_relationships(self, base_objects, related_model,
4450957a848a17b726836aaf9b5532bbfac691d983dshoward                               related_list_name):
4460957a848a17b726836aaf9b5532bbfac691d983dshoward        """
44768693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        For each instance of this model in base_objects, add a field named
44868693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        related_list_name listing all the related objects of type related_model.
44968693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        related_model must be in a many-to-one or many-to-many relationship with
45068693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        this model.
45168693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        @param base_objects - list of instances of this model
45268693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        @param related_model - model class related to this model
45368693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        @param related_list_name - attribute name in which to store the related
45468693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        object list.
4550957a848a17b726836aaf9b5532bbfac691d983dshoward        """
45668693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        if not base_objects:
4570957a848a17b726836aaf9b5532bbfac691d983dshoward            # if we don't bail early, we'll get a SQL error later
4580957a848a17b726836aaf9b5532bbfac691d983dshoward            return
45968693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward
46068693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        base_objects_by_id = dict((base_object._get_pk_val(), base_object)
46168693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward                                  for base_object in base_objects)
46268693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        pivot_iterator = self._get_pivot_iterator(base_objects_by_id,
46368693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward                                                  related_model)
46468693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward
46568693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        for base_object in base_objects:
46668693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward            setattr(base_object, related_list_name, [])
46768693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward
46868693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward        for base_object, related_object in pivot_iterator:
46968693f79df6953d7bc0fe8de8e13e5fd501a2aedshoward            getattr(base_object, related_list_name).append(related_object)
4700957a848a17b726836aaf9b5532bbfac691d983dshoward
4710957a848a17b726836aaf9b5532bbfac691d983dshoward
472e36562323bec14d14b7c583db94788a725514ff2jamesrenclass ModelWithInvalidQuerySet(dbmodels.query.QuerySet):
473e36562323bec14d14b7c583db94788a725514ff2jamesren    """
474e36562323bec14d14b7c583db94788a725514ff2jamesren    QuerySet that handles delete() properly for models with an "invalid" bit
475e36562323bec14d14b7c583db94788a725514ff2jamesren    """
476e36562323bec14d14b7c583db94788a725514ff2jamesren    def delete(self):
477e36562323bec14d14b7c583db94788a725514ff2jamesren        for model in self:
478e36562323bec14d14b7c583db94788a725514ff2jamesren            model.delete()
479e36562323bec14d14b7c583db94788a725514ff2jamesren
480e36562323bec14d14b7c583db94788a725514ff2jamesren
481e36562323bec14d14b7c583db94788a725514ff2jamesrenclass ModelWithInvalidManager(ExtendedManager):
482e36562323bec14d14b7c583db94788a725514ff2jamesren    """
483e36562323bec14d14b7c583db94788a725514ff2jamesren    Manager for objects with an "invalid" bit
484e36562323bec14d14b7c583db94788a725514ff2jamesren    """
485e36562323bec14d14b7c583db94788a725514ff2jamesren    def get_query_set(self):
486e36562323bec14d14b7c583db94788a725514ff2jamesren        return ModelWithInvalidQuerySet(self.model)
487e36562323bec14d14b7c583db94788a725514ff2jamesren
488e36562323bec14d14b7c583db94788a725514ff2jamesren
489e36562323bec14d14b7c583db94788a725514ff2jamesrenclass ValidObjectsManager(ModelWithInvalidManager):
4900afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    """
4910afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    Manager returning only objects with invalid=False.
4920afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    """
4930afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    def get_query_set(self):
4940afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        queryset = super(ValidObjectsManager, self).get_query_set()
4950afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        return queryset.filter(invalid=False)
4967c7852819d0611b4d0e8e69b3011b79e4016a770showard
4977c7852819d0611b4d0e8e69b3011b79e4016a770showard
498489b91d72cd225e902081dbd3f9e47448fe867f6Prashanth Bclass ModelExtensions(rdb_model_extensions.ModelValidators):
4990afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    """\
500489b91d72cd225e902081dbd3f9e47448fe867f6Prashanth B    Mixin with convenience functions for models, built on top of
501489b91d72cd225e902081dbd3f9e47448fe867f6Prashanth B    the model validators in rdb_model_extensions.
5020afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    """
5030afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    # TODO: at least some of these functions really belong in a custom
5040afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    # Manager class
5050afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
5063bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich
5073bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich    SERIALIZATION_LINKS_TO_FOLLOW = set()
5083bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich    """
5093bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich    To be able to send jobs and hosts to shards, it's necessary to find their
5103bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich    dependencies.
5113bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich    The most generic approach for this would be to traverse all relationships
5123bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich    to other objects recursively. This would list all objects that are related
5133bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich    in any way.
5143bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich    But this approach finds too many objects: If a host should be transferred,
5153bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich    all it's relationships would be traversed. This would find an acl group.
5163bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich    If then the acl group's relationships are traversed, the relationship
5173bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich    would be followed backwards and many other hosts would be found.
5183bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich
5193bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich    This mapping tells that algorithm which relations to follow explicitly.
5203bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich    """
5213bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich
522f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich
52386248502517d42d6c036a66a57e3a24d4a6dc3b8Fang Deng    SERIALIZATION_LINKS_TO_KEEP = set()
52486248502517d42d6c036a66a57e3a24d4a6dc3b8Fang Deng    """This set stores foreign keys which we don't want to follow, but
52586248502517d42d6c036a66a57e3a24d4a6dc3b8Fang Deng    still want to include in the serialized dictionary. For
52686248502517d42d6c036a66a57e3a24d4a6dc3b8Fang Deng    example, we follow the relationship `Host.hostattribute_set`,
52786248502517d42d6c036a66a57e3a24d4a6dc3b8Fang Deng    but we do not want to follow `HostAttributes.host_id` back to
52886248502517d42d6c036a66a57e3a24d4a6dc3b8Fang Deng    to Host, which would otherwise lead to a circle. However, we still
52986248502517d42d6c036a66a57e3a24d4a6dc3b8Fang Deng    like to serialize HostAttribute.`host_id`."""
53086248502517d42d6c036a66a57e3a24d4a6dc3b8Fang Deng
531f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich    SERIALIZATION_LOCAL_LINKS_TO_UPDATE = set()
532f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich    """
533f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich    On deserializion, if the object to persist already exists, local fields
534f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich    will only be updated, if their name is in this set.
535f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich    """
536f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich
537f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich
5380afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    @classmethod
5390afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    def convert_human_readable_values(cls, data, to_human_readable=False):
5400afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """\
5410afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        Performs conversions on user-supplied field data, to make it
5420afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        easier for users to pass human-readable data.
5430afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
5440afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        For all fields that have choice sets, convert their values
5450afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        from human-readable strings to enum values, if necessary.  This
5460afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        allows users to pass strings instead of the corresponding
5470afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        integer values.
5480afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
5490afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        For all foreign key fields, call smart_get with the supplied
5500afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        data.  This allows the user to pass either an ID value or
5510afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        the name of the object as a string.
5520afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
5530afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        If to_human_readable=True, perform the inverse - i.e. convert
5540afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        numeric values to human readable values.
5550afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
5560afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        This method modifies data in-place.
5570afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """
5580afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        field_dict = cls.get_field_dict()
5590afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        for field_name in data:
560e732ee7d450b11261c82df0950fde8e02f839b26showard            if field_name not in field_dict or data[field_name] is None:
5610afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski                continue
5620afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski            field_obj = field_dict[field_name]
5630afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski            # convert enum values
5640afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski            if field_obj.choices:
5650afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski                for choice_data in field_obj.choices:
5660afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski                    # choice_data is (value, name)
5670afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski                    if to_human_readable:
5680afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski                        from_val, to_val = choice_data
5690afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski                    else:
5700afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski                        to_val, from_val = choice_data
5710afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski                    if from_val == data[field_name]:
5720afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski                        data[field_name] = to_val
5730afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski                        break
5740afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski            # convert foreign key values
5750afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski            elif field_obj.rel:
576a4ea574950e15e9be4df38464046584bf8f380f7showard                dest_obj = field_obj.rel.to.smart_get(data[field_name],
577a4ea574950e15e9be4df38464046584bf8f380f7showard                                                      valid_only=False)
578f8b19046c4496f570c776b65288382108a633ab4showard                if to_human_readable:
5795a8c6ad7e67d86090ed6c4ef216b82e45c0f9283Paul Pendlebury                    # parameterized_jobs do not have a name_field
5805a8c6ad7e67d86090ed6c4ef216b82e45c0f9283Paul Pendlebury                    if (field_name != 'parameterized_job' and
5815a8c6ad7e67d86090ed6c4ef216b82e45c0f9283Paul Pendlebury                        dest_obj.name_field is not None):
582f8b19046c4496f570c776b65288382108a633ab4showard                        data[field_name] = getattr(dest_obj,
583f8b19046c4496f570c776b65288382108a633ab4showard                                                   dest_obj.name_field)
5840afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski                else:
585b0a73038e9bd3e67bfcb9b8f18d39024be89bf22showard                    data[field_name] = dest_obj
5860afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
5870afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
5880afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
5890afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
59074a314b490ff542c4dd2ae4aa0d11c6394d92960Dale Curtis    def _validate_unique(self):
5910afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """\
5920afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        Validate that unique fields are unique.  Django manipulators do
5930afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        this too, but they're a huge pain to use manually.  Trust me.
5940afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """
5950afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        errors = {}
5960afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        cls = type(self)
5970afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        field_dict = self.get_field_dict()
5980afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        manager = cls.get_valid_manager()
5990afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        for field_name, field_obj in field_dict.iteritems():
6000afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski            if not field_obj.unique:
6010afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski                continue
6020afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
6030afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski            value = getattr(self, field_name)
604bd18ab7a821be9735d0805c81918a4534847c0fashoward            if value is None and field_obj.auto_created:
605bd18ab7a821be9735d0805c81918a4534847c0fashoward                # don't bother checking autoincrement fields about to be
606bd18ab7a821be9735d0805c81918a4534847c0fashoward                # generated
607bd18ab7a821be9735d0805c81918a4534847c0fashoward                continue
608bd18ab7a821be9735d0805c81918a4534847c0fashoward
6090afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski            existing_objs = manager.filter(**{field_name : value})
6100afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski            num_existing = existing_objs.count()
6110afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
6120afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski            if num_existing == 0:
6130afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski                continue
6140afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski            if num_existing == 1 and existing_objs[0].id == self.id:
6150afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski                continue
6160afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski            errors[field_name] = (
6170afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski                'This value must be unique (%s)' % (value))
6180afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        return errors
6190afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
6200afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
621a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward    def _validate(self):
622a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward        """
623a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward        First coerces all fields on this instance to their proper Python types.
624a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward        Then runs validation on every field. Returns a dictionary of
625a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward        field_name -> error_list.
626a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward
627a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward        Based on validate() from django.db.models.Model in Django 0.96, which
628a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward        was removed in Django 1.0. It should reappear in a later version. See:
629a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward            http://code.djangoproject.com/ticket/6845
630a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward        """
631a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward        error_dict = {}
632a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward        for f in self._meta.fields:
633a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward            try:
634a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward                python_value = f.to_python(
635a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward                    getattr(self, f.attname, f.get_default()))
636a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward            except django.core.exceptions.ValidationError, e:
6371e0a4cef1096904103eb29cbde8d65ad892579a4jamesren                error_dict[f.name] = str(e)
638a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward                continue
639a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward
640a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward            if not f.blank and not python_value:
641a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward                error_dict[f.name] = 'This field is required.'
642a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward                continue
643a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward
644a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward            setattr(self, f.attname, python_value)
645a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward
646a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward        return error_dict
647a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward
648a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward
6490afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    def do_validate(self):
650a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward        errors = self._validate()
65174a314b490ff542c4dd2ae4aa0d11c6394d92960Dale Curtis        unique_errors = self._validate_unique()
6520afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        for field_name, error in unique_errors.iteritems():
6530afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski            errors.setdefault(field_name, error)
6540afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        if errors:
6550afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski            raise ValidationError(errors)
6560afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
6570afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
6580afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    # actually (externally) useful methods follow
6590afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
6600afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    @classmethod
6610afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    def add_object(cls, data={}, **kwargs):
6620afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """\
6630afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        Returns a new object created with the given data (a dictionary
6640afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        mapping field names to values). Merges any extra keyword args
6650afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        into data.
6660afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """
667489b91d72cd225e902081dbd3f9e47448fe867f6Prashanth B        data = dict(data)
668489b91d72cd225e902081dbd3f9e47448fe867f6Prashanth B        data.update(kwargs)
669489b91d72cd225e902081dbd3f9e47448fe867f6Prashanth B        data = cls.prepare_data_args(data)
670489b91d72cd225e902081dbd3f9e47448fe867f6Prashanth B        cls.convert_human_readable_values(data)
6710afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        data = cls.provide_default_values(data)
672489b91d72cd225e902081dbd3f9e47448fe867f6Prashanth B
6730afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        obj = cls(**data)
6740afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        obj.do_validate()
6750afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        obj.save()
6760afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        return obj
6770afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
6780afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
6790afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    def update_object(self, data={}, **kwargs):
6800afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """\
6810afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        Updates the object with the given data (a dictionary mapping
6820afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        field names to values).  Merges any extra keyword args into
6830afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        data.
6840afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """
685489b91d72cd225e902081dbd3f9e47448fe867f6Prashanth B        data = dict(data)
686489b91d72cd225e902081dbd3f9e47448fe867f6Prashanth B        data.update(kwargs)
687489b91d72cd225e902081dbd3f9e47448fe867f6Prashanth B        data = self.prepare_data_args(data)
688489b91d72cd225e902081dbd3f9e47448fe867f6Prashanth B        self.convert_human_readable_values(data)
6890afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        for field_name, value in data.iteritems():
690b0a73038e9bd3e67bfcb9b8f18d39024be89bf22showard            setattr(self, field_name, value)
6910afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        self.do_validate()
6920afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        self.save()
6930afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
6940afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
6958bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard    # see query_objects()
6968bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard    _SPECIAL_FILTER_KEYS = ('query_start', 'query_limit', 'sort_by',
6978bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard                            'extra_args', 'extra_where', 'no_distinct')
6988bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard
6998bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard
7008bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard    @classmethod
7018bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard    def _extract_special_params(cls, filter_data):
7028bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        """
7038bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        @returns a tuple of dicts (special_params, regular_filters), where
7048bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        special_params contains the parameters we handle specially and
7058bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        regular_filters is the remaining data to be handled by Django.
7068bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        """
7078bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        regular_filters = dict(filter_data)
7088bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        special_params = {}
7098bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        for key in cls._SPECIAL_FILTER_KEYS:
7108bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard            if key in regular_filters:
7118bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard                special_params[key] = regular_filters.pop(key)
7128bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        return special_params, regular_filters
7138bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard
7148bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard
7158bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard    @classmethod
7168bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard    def apply_presentation(cls, query, filter_data):
7178bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        """
7188bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        Apply presentation parameters -- sorting and paging -- to the given
7198bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        query.
7208bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        @returns new query with presentation applied
7218bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        """
7228bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        special_params, _ = cls._extract_special_params(filter_data)
7238bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        sort_by = special_params.get('sort_by', None)
7248bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        if sort_by:
7258bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard            assert isinstance(sort_by, list) or isinstance(sort_by, tuple)
7268b0ea2285c1327a686ff0b6ab245915e7fd20094showard            query = query.extra(order_by=sort_by)
7278bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard
7288bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        query_start = special_params.get('query_start', None)
7298bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        query_limit = special_params.get('query_limit', None)
7308bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        if query_start is not None:
7318bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard            if query_limit is None:
7328bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard                raise ValueError('Cannot pass query_start without query_limit')
7338bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard            # query_limit is passed as a page size
7347074b747cc3aa313ad4b70c036758f36dc4d84aeshoward            query_limit += query_start
7357074b747cc3aa313ad4b70c036758f36dc4d84aeshoward        return query[query_start:query_limit]
7368bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard
7378bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard
7380afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    @classmethod
7398bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard    def query_objects(cls, filter_data, valid_only=True, initial_query=None,
7408bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard                      apply_presentation=True):
7410afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """\
7420afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        Returns a QuerySet object for querying the given model_class
7430afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        with the given filter_data.  Optional special arguments in
7440afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        filter_data include:
7450afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        -query_start: index of first return to return
7460afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        -query_limit: maximum number of results to return
7470afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        -sort_by: list of fields to sort on.  prefixing a '-' onto a
7480afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski         field name changes the sort to descending order.
7490afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        -extra_args: keyword args to pass to query.extra() (see Django
7500afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski         DB layer documentation)
751a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward        -extra_where: extra WHERE clause to append
7528bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        -no_distinct: if True, a DISTINCT will not be added to the SELECT
7530afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """
7548bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        special_params, regular_filters = cls._extract_special_params(
7558bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard                filter_data)
7560afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
7577ac7b7aab4f7e8bf5685ba66197a6d007a267933showard        if initial_query is None:
7587ac7b7aab4f7e8bf5685ba66197a6d007a267933showard            if valid_only:
7597ac7b7aab4f7e8bf5685ba66197a6d007a267933showard                initial_query = cls.get_valid_manager()
7607ac7b7aab4f7e8bf5685ba66197a6d007a267933showard            else:
7617ac7b7aab4f7e8bf5685ba66197a6d007a267933showard                initial_query = cls.objects
7628bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard
7638bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        query = initial_query.filter(**regular_filters)
7648bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard
7658bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        use_distinct = not special_params.get('no_distinct', False)
7667ac7b7aab4f7e8bf5685ba66197a6d007a267933showard        if use_distinct:
7677ac7b7aab4f7e8bf5685ba66197a6d007a267933showard            query = query.distinct()
7680afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
7698bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        extra_args = special_params.get('extra_args', {})
7708bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        extra_where = special_params.get('extra_where', None)
7718bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        if extra_where:
7728bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard            # escape %'s
7738bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard            extra_where = cls.objects.escape_user_sql(extra_where)
7748bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard            extra_args.setdefault('where', []).append(extra_where)
7750afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        if extra_args:
7760afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski            query = query.extra(**extra_args)
7777bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich            # TODO: Use readonly connection for these queries.
7787bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich            # This has been disabled, because it's not used anyway, as the
7797bef8416138f662c39326bdb6f8a9b548c3d4b45Jakob Juelich            # configured readonly user is the same as the real user anyway.
7800afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
7818bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        if apply_presentation:
7828bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard            query = cls.apply_presentation(query, filter_data)
7838bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard
7848bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        return query
7850afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
7860afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
7870afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    @classmethod
788585c2abb80648f80e7cb649b06dc9f6a8690a790showard    def query_count(cls, filter_data, initial_query=None):
7890afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """\
7900afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        Like query_objects, but retreive only the count of results.
7910afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """
7920afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        filter_data.pop('query_start', None)
7930afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        filter_data.pop('query_limit', None)
794585c2abb80648f80e7cb649b06dc9f6a8690a790showard        query = cls.query_objects(filter_data, initial_query=initial_query)
795585c2abb80648f80e7cb649b06dc9f6a8690a790showard        return query.count()
7960afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
7970afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
7980afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    @classmethod
7990afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    def clean_object_dicts(cls, field_dicts):
8000afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """\
8010afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        Take a list of dicts corresponding to object (as returned by
8020afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        query.values()) and clean the data to be more suitable for
8030afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        returning to the user.
8040afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """
805e732ee7d450b11261c82df0950fde8e02f839b26showard        for field_dict in field_dicts:
806e732ee7d450b11261c82df0950fde8e02f839b26showard            cls.clean_foreign_keys(field_dict)
80721baa459ea14f96e06212f1f35fcddab9442b3fcshoward            cls._convert_booleans(field_dict)
808e732ee7d450b11261c82df0950fde8e02f839b26showard            cls.convert_human_readable_values(field_dict,
809e732ee7d450b11261c82df0950fde8e02f839b26showard                                              to_human_readable=True)
8100afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
8110afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
8120afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    @classmethod
8138bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard    def list_objects(cls, filter_data, initial_query=None):
8140afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """\
8150afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        Like query_objects, but return a list of dictionaries.
8160afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """
8177ac7b7aab4f7e8bf5685ba66197a6d007a267933showard        query = cls.query_objects(filter_data, initial_query=initial_query)
8188bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        extra_fields = query.query.extra_select.keys()
8198bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        field_dicts = [model_object.get_object_dict(extra_fields=extra_fields)
820e732ee7d450b11261c82df0950fde8e02f839b26showard                       for model_object in query]
8210afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        return field_dicts
8220afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
8230afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
8240afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    @classmethod
825a4ea574950e15e9be4df38464046584bf8f380f7showard    def smart_get(cls, id_or_name, valid_only=True):
8260afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """\
8270afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        smart_get(integer) -> get object by ID
8280afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        smart_get(string) -> get object by name_field
8290afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """
830a4ea574950e15e9be4df38464046584bf8f380f7showard        if valid_only:
831a4ea574950e15e9be4df38464046584bf8f380f7showard            manager = cls.get_valid_manager()
832a4ea574950e15e9be4df38464046584bf8f380f7showard        else:
833a4ea574950e15e9be4df38464046584bf8f380f7showard            manager = cls.objects
834a4ea574950e15e9be4df38464046584bf8f380f7showard
835a4ea574950e15e9be4df38464046584bf8f380f7showard        if isinstance(id_or_name, (int, long)):
836a4ea574950e15e9be4df38464046584bf8f380f7showard            return manager.get(pk=id_or_name)
8373e9f609dcf3c3d75daec1af5551b0e4022f5e629jamesren        if isinstance(id_or_name, basestring) and hasattr(cls, 'name_field'):
838a4ea574950e15e9be4df38464046584bf8f380f7showard            return manager.get(**{cls.name_field : id_or_name})
839a4ea574950e15e9be4df38464046584bf8f380f7showard        raise ValueError(
840a4ea574950e15e9be4df38464046584bf8f380f7showard            'Invalid positional argument: %s (%s)' % (id_or_name,
841a4ea574950e15e9be4df38464046584bf8f380f7showard                                                      type(id_or_name)))
8420afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
8430afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
844be3ec04ed2053527e19c1593eda0184f18799868showard    @classmethod
845be3ec04ed2053527e19c1593eda0184f18799868showard    def smart_get_bulk(cls, id_or_name_list):
846be3ec04ed2053527e19c1593eda0184f18799868showard        invalid_inputs = []
847be3ec04ed2053527e19c1593eda0184f18799868showard        result_objects = []
848be3ec04ed2053527e19c1593eda0184f18799868showard        for id_or_name in id_or_name_list:
849be3ec04ed2053527e19c1593eda0184f18799868showard            try:
850be3ec04ed2053527e19c1593eda0184f18799868showard                result_objects.append(cls.smart_get(id_or_name))
851be3ec04ed2053527e19c1593eda0184f18799868showard            except cls.DoesNotExist:
852be3ec04ed2053527e19c1593eda0184f18799868showard                invalid_inputs.append(id_or_name)
853be3ec04ed2053527e19c1593eda0184f18799868showard        if invalid_inputs:
8547a3ebe30d13f8dc7e8d3d0990b8b026be75771f3mbligh            raise cls.DoesNotExist('The following %ss do not exist: %s'
8557a3ebe30d13f8dc7e8d3d0990b8b026be75771f3mbligh                                   % (cls.__name__.lower(),
8567a3ebe30d13f8dc7e8d3d0990b8b026be75771f3mbligh                                      ', '.join(invalid_inputs)))
857be3ec04ed2053527e19c1593eda0184f18799868showard        return result_objects
858be3ec04ed2053527e19c1593eda0184f18799868showard
859be3ec04ed2053527e19c1593eda0184f18799868showard
8608bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard    def get_object_dict(self, extra_fields=None):
8610afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """\
8628bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        Return a dictionary mapping fields to this object's values.  @param
8638bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        extra_fields: list of extra attribute names to include, in addition to
8648bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        the fields defined on this object.
8650afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """
8668bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        fields = self.get_field_dict().keys()
8678bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard        if extra_fields:
8688bfb5cb38c7daca2378cd5b278ca8b5b249123f7showard            fields += extra_fields
8690afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        object_dict = dict((field_name, getattr(self, field_name))
870e732ee7d450b11261c82df0950fde8e02f839b26showard                           for field_name in fields)
8710afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        self.clean_object_dicts([object_dict])
872d3dc199703bfb8784a2f8f072d0514532c86c0a9showard        self._postprocess_object_dict(object_dict)
8730afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        return object_dict
8740afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
8750afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
876d3dc199703bfb8784a2f8f072d0514532c86c0a9showard    def _postprocess_object_dict(self, object_dict):
877d3dc199703bfb8784a2f8f072d0514532c86c0a9showard        """For subclasses to override."""
878d3dc199703bfb8784a2f8f072d0514532c86c0a9showard        pass
879d3dc199703bfb8784a2f8f072d0514532c86c0a9showard
880d3dc199703bfb8784a2f8f072d0514532c86c0a9showard
8810afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    @classmethod
8820afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    def get_valid_manager(cls):
8830afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        return cls.objects
8847c7852819d0611b4d0e8e69b3011b79e4016a770showard
8857c7852819d0611b4d0e8e69b3011b79e4016a770showard
8862bab8f45adedeacbf2d62d37b90255581adc3c7dshoward    def _record_attributes(self, attributes):
8872bab8f45adedeacbf2d62d37b90255581adc3c7dshoward        """
8882bab8f45adedeacbf2d62d37b90255581adc3c7dshoward        See on_attribute_changed.
8892bab8f45adedeacbf2d62d37b90255581adc3c7dshoward        """
8902bab8f45adedeacbf2d62d37b90255581adc3c7dshoward        assert not isinstance(attributes, basestring)
8912bab8f45adedeacbf2d62d37b90255581adc3c7dshoward        self._recorded_attributes = dict((attribute, getattr(self, attribute))
8922bab8f45adedeacbf2d62d37b90255581adc3c7dshoward                                         for attribute in attributes)
8932bab8f45adedeacbf2d62d37b90255581adc3c7dshoward
8942bab8f45adedeacbf2d62d37b90255581adc3c7dshoward
8952bab8f45adedeacbf2d62d37b90255581adc3c7dshoward    def _check_for_updated_attributes(self):
8962bab8f45adedeacbf2d62d37b90255581adc3c7dshoward        """
8972bab8f45adedeacbf2d62d37b90255581adc3c7dshoward        See on_attribute_changed.
8982bab8f45adedeacbf2d62d37b90255581adc3c7dshoward        """
8992bab8f45adedeacbf2d62d37b90255581adc3c7dshoward        for attribute, original_value in self._recorded_attributes.iteritems():
9002bab8f45adedeacbf2d62d37b90255581adc3c7dshoward            new_value = getattr(self, attribute)
9012bab8f45adedeacbf2d62d37b90255581adc3c7dshoward            if original_value != new_value:
9022bab8f45adedeacbf2d62d37b90255581adc3c7dshoward                self.on_attribute_changed(attribute, original_value)
9032bab8f45adedeacbf2d62d37b90255581adc3c7dshoward        self._record_attributes(self._recorded_attributes.keys())
9042bab8f45adedeacbf2d62d37b90255581adc3c7dshoward
9052bab8f45adedeacbf2d62d37b90255581adc3c7dshoward
9062bab8f45adedeacbf2d62d37b90255581adc3c7dshoward    def on_attribute_changed(self, attribute, old_value):
9072bab8f45adedeacbf2d62d37b90255581adc3c7dshoward        """
9082bab8f45adedeacbf2d62d37b90255581adc3c7dshoward        Called whenever an attribute is updated.  To be overridden.
9092bab8f45adedeacbf2d62d37b90255581adc3c7dshoward
9102bab8f45adedeacbf2d62d37b90255581adc3c7dshoward        To use this method, you must:
9112bab8f45adedeacbf2d62d37b90255581adc3c7dshoward        * call _record_attributes() from __init__() (after making the super
9122bab8f45adedeacbf2d62d37b90255581adc3c7dshoward        call) with a list of attributes for which you want to be notified upon
9132bab8f45adedeacbf2d62d37b90255581adc3c7dshoward        change.
9142bab8f45adedeacbf2d62d37b90255581adc3c7dshoward        * call _check_for_updated_attributes() from save().
9152bab8f45adedeacbf2d62d37b90255581adc3c7dshoward        """
9162bab8f45adedeacbf2d62d37b90255581adc3c7dshoward        pass
9172bab8f45adedeacbf2d62d37b90255581adc3c7dshoward
9182bab8f45adedeacbf2d62d37b90255581adc3c7dshoward
919116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich    def serialize(self, include_dependencies=True):
9203bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich        """Serializes the object with dependencies.
9213bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich
9223bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich        The variable SERIALIZATION_LINKS_TO_FOLLOW defines which dependencies
9233bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich        this function will serialize with the object.
9243bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich
925116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        @param include_dependencies: Whether or not to follow relations to
926116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich                                     objects this object depends on.
927116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich                                     This parameter is used when uploading
928116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich                                     jobs from a shard to the master, as the
929116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich                                     master already has all the dependent
930116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich                                     objects.
931116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich
9323bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich        @returns: Dictionary representation of the object.
9333bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich        """
9343bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich        serialized = {}
9355cfd96aca6204b6fd193ab2e15a24808756e6198MK Ryu        timer = autotest_stats.Timer('serialize_latency.%s' % (
9365cfd96aca6204b6fd193ab2e15a24808756e6198MK Ryu                type(self).__name__))
9375cfd96aca6204b6fd193ab2e15a24808756e6198MK Ryu        with timer.get_client('local'):
9385cfd96aca6204b6fd193ab2e15a24808756e6198MK Ryu            for field in self._meta.concrete_model._meta.local_fields:
9395cfd96aca6204b6fd193ab2e15a24808756e6198MK Ryu                if field.rel is None:
9405cfd96aca6204b6fd193ab2e15a24808756e6198MK Ryu                    serialized[field.name] = field._get_val_from_obj(self)
941df4d423700d566bd73fe1859740c9c6b9ed3bca2MK Ryu                elif field.name in self.SERIALIZATION_LINKS_TO_KEEP:
9425cfd96aca6204b6fd193ab2e15a24808756e6198MK Ryu                    # attname will contain "_id" suffix for foreign keys,
9435cfd96aca6204b6fd193ab2e15a24808756e6198MK Ryu                    # e.g. HostAttribute.host will be serialized as 'host_id'.
9445cfd96aca6204b6fd193ab2e15a24808756e6198MK Ryu                    # Use it for easy deserialization.
9455cfd96aca6204b6fd193ab2e15a24808756e6198MK Ryu                    serialized[field.attname] = field._get_val_from_obj(self)
9463bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich
947116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        if include_dependencies:
9485cfd96aca6204b6fd193ab2e15a24808756e6198MK Ryu            with timer.get_client('related'):
9495cfd96aca6204b6fd193ab2e15a24808756e6198MK Ryu                for link in self.SERIALIZATION_LINKS_TO_FOLLOW:
9505cfd96aca6204b6fd193ab2e15a24808756e6198MK Ryu                    serialized[link] = self._serialize_relation(link)
9513bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich
9523bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich        return serialized
9533bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich
9543bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich
9553bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich    def _serialize_relation(self, link):
9563bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich        """Serializes dependent objects given the name of the relation.
9573bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich
9583bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich        @param link: Name of the relation to take objects from.
9593bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich
9603bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich        @returns For To-Many relationships a list of the serialized related
9613bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich            objects, for To-One relationships the serialized related object.
9623bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich        """
9633bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich        try:
9643bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich            attr = getattr(self, link)
9653bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich        except AttributeError:
9663bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich            # One-To-One relationships that point to None may raise this
9673bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich            return None
9683bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich
9693bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich        if attr is None:
9703bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich            return None
9713bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich        if hasattr(attr, 'all'):
9723bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich            return [obj.serialize() for obj in attr.all()]
9733bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich        return attr.serialize()
9743bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich
9753bb7c8095d7c66d955462673dd3df5565c1b4a96Jakob Juelich
976f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich    @classmethod
977116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich    def _split_local_from_foreign_values(cls, data):
978116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        """This splits local from foreign values in a serialized object.
979116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich
980116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        @param data: The serialized object.
981116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich
982116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        @returns A tuple of two lists, both containing tuples in the form
983116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich                 (link_name, link_value). The first list contains all links
984116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich                 for local fields, the second one contains those for foreign
985116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich                 fields/objects.
986116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        """
987116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        links_to_local_values, links_to_related_values = [], []
988116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        for link, value in data.iteritems():
989116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich            if link in cls.SERIALIZATION_LINKS_TO_FOLLOW:
990116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich                # It's a foreign key
991116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich                links_to_related_values.append((link, value))
992116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich            else:
99386248502517d42d6c036a66a57e3a24d4a6dc3b8Fang Deng                # It's a local attribute or a foreign key
99486248502517d42d6c036a66a57e3a24d4a6dc3b8Fang Deng                # we don't want to follow.
995116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich                links_to_local_values.append((link, value))
996116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        return links_to_local_values, links_to_related_values
997116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich
998116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich
999f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich    @classmethod
1000f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich    def _filter_update_allowed_fields(cls, data):
1001f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich        """Filters data and returns only files that updates are allowed on.
1002f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich
1003f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich        This is i.e. needed for syncing aborted bits from the master to shards.
1004f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich
1005f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich        Local links are only allowed to be updated, if they are in
1006f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich        SERIALIZATION_LOCAL_LINKS_TO_UPDATE.
1007f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich        Overwriting existing values is allowed in order to be able to sync i.e.
1008f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich        the aborted bit from the master to a shard.
1009f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich
1010f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich        The whitelisting mechanism is in place to prevent overwriting local
1011f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich        status: If all fields were overwritten, jobs would be completely be
1012f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich        set back to their original (unstarted) state.
1013f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich
1014f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich        @param data: List with tuples of the form (link_name, link_value), as
1015f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich                     returned by _split_local_from_foreign_values.
1016f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich
1017f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich        @returns List of the same format as data, but only containing data for
1018f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich                 fields that updates are allowed on.
1019f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich        """
1020f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich        return [pair for pair in data
1021f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich                if pair[0] in cls.SERIALIZATION_LOCAL_LINKS_TO_UPDATE]
1022f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich
1023f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich
1024af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian    @classmethod
1025af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian    def delete_matching_record(cls, **filter_args):
1026af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian        """Delete records matching the filter.
1027af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian
1028af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian        @param filter_args: Arguments for the django filter
1029af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian                used to locate the record to delete.
1030af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian        """
1031af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian        try:
1032af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian            existing_record = cls.objects.get(**filter_args)
1033af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian        except cls.DoesNotExist:
1034af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian            return
1035af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian        existing_record.delete()
1036af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian
1037af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian
1038116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich    def _deserialize_local(self, data):
1039116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        """Set local attributes from a list of tuples.
1040116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich
1041116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        @param data: List of tuples like returned by
1042116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich                     _split_local_from_foreign_values.
1043116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        """
1044af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian        if not data:
1045af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian            return
1046af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian
1047116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        for link, value in data:
1048116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich            setattr(self, link, value)
1049116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        # Overwridden save() methods are prone to errors, so don't execute them.
1050116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        # This is because:
1051116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        # - the overwritten methods depend on ACL groups that don't yet exist
1052116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        #   and don't handle errors
1053116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        # - the overwritten methods think this object already exists in the db
1054116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        #   because the id is already set
1055116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        super(type(self), self).save()
1056116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich
1057116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich
1058116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich    def _deserialize_relations(self, data):
1059116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        """Set foreign attributes from a list of tuples.
1060116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich
1061116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        This deserialized the related objects using their own deserialize()
1062116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        function and then sets the relation.
1063116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich
1064116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        @param data: List of tuples like returned by
1065116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich                     _split_local_from_foreign_values.
1066116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        """
1067116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        for link, value in data:
1068116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich            self._deserialize_relation(link, value)
1069116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        # See comment in _deserialize_local
1070116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        super(type(self), self).save()
1071116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich
1072116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich
1073116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich    @classmethod
1074af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian    def get_record(cls, data):
1075af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian        """Retrieve a record with the data in the given input arg.
1076af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian
1077af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian        @param data: A dictionary containing the information to use in a query
1078af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian                for data. If child models have different constraints of
1079af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian                uniqueness they should override this model.
1080af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian
1081af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian        @return: An object with matching data.
1082af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian
1083af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian        @raises DoesNotExist: If a record with the given data doesn't exist.
1084af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian        """
1085af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian        return cls.objects.get(id=data['id'])
1086af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian
1087af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian
1088af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian    @classmethod
1089f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich    def deserialize(cls, data):
1090116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        """Recursively deserializes and saves an object with it's dependencies.
1091f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich
1092f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich        This takes the result of the serialize method and creates objects
1093116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        in the database that are just like the original.
1094116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich
1095116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        If an object of the same type with the same id already exists, it's
1096f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich        local values will be left untouched, unless they are explicitly
1097f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich        whitelisted in SERIALIZATION_LOCAL_LINKS_TO_UPDATE.
1098f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich
1099f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich        Deserialize will always recursively propagate to all related objects
1100116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        present in data though.
1101116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        I.e. this is necessary to add users to an already existing acl-group.
1102f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich
1103f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich        @param data: Representation of an object and its dependencies, as
1104f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich                     returned by serialize.
1105f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich
1106f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich        @returns: The object represented by data if it didn't exist before,
1107f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich                  otherwise the object that existed before and has the same type
1108f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich                  and id as the one described by data.
1109f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich        """
1110f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich        if data is None:
1111f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich            return None
1112f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich
1113116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        local, related = cls._split_local_from_foreign_values(data)
1114f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich        try:
1115af5166418be945356238768cfc8226f3c2aa7138Prashanth Balasubramanian            instance = cls.get_record(data)
1116f865d33cc3b0584f10b2f6f4de414a6d7f9e3000Jakob Juelich            local = cls._filter_update_allowed_fields(local)
1117f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich        except cls.DoesNotExist:
1118116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich            instance = cls()
1119f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich
11205cfd96aca6204b6fd193ab2e15a24808756e6198MK Ryu        timer = autotest_stats.Timer('deserialize_latency.%s' % (
11215cfd96aca6204b6fd193ab2e15a24808756e6198MK Ryu                type(instance).__name__))
11225cfd96aca6204b6fd193ab2e15a24808756e6198MK Ryu        with timer.get_client('local'):
11235cfd96aca6204b6fd193ab2e15a24808756e6198MK Ryu            instance._deserialize_local(local)
11245cfd96aca6204b6fd193ab2e15a24808756e6198MK Ryu        with timer.get_client('related'):
11255cfd96aca6204b6fd193ab2e15a24808756e6198MK Ryu            instance._deserialize_relations(related)
1126f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich
1127116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        return instance
1128f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich
1129f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich
1130a94efe60bc94c9aa10ecfe40bddf97518985c7c2Jakob Juelich    def sanity_check_update_from_shard(self, shard, updated_serialized,
1131a94efe60bc94c9aa10ecfe40bddf97518985c7c2Jakob Juelich                                       *args, **kwargs):
1132a94efe60bc94c9aa10ecfe40bddf97518985c7c2Jakob Juelich        """Check if an update sent from a shard is legitimate.
1133a94efe60bc94c9aa10ecfe40bddf97518985c7c2Jakob Juelich
1134a94efe60bc94c9aa10ecfe40bddf97518985c7c2Jakob Juelich        @raises error.UnallowedRecordsSentToMaster if an update is not
1135a94efe60bc94c9aa10ecfe40bddf97518985c7c2Jakob Juelich                legitimate.
1136a94efe60bc94c9aa10ecfe40bddf97518985c7c2Jakob Juelich        """
1137a94efe60bc94c9aa10ecfe40bddf97518985c7c2Jakob Juelich        raise NotImplementedError(
1138a94efe60bc94c9aa10ecfe40bddf97518985c7c2Jakob Juelich            'sanity_check_update_from_shard must be implemented by subclass %s '
1139a94efe60bc94c9aa10ecfe40bddf97518985c7c2Jakob Juelich            'for type %s' % type(self))
1140a94efe60bc94c9aa10ecfe40bddf97518985c7c2Jakob Juelich
1141a94efe60bc94c9aa10ecfe40bddf97518985c7c2Jakob Juelich
114275be1d3f881ef4f4f9cffe0c38fc3139338d8f84Prashanth Balasubramanian    @transaction.commit_on_success
1143116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich    def update_from_serialized(self, serialized):
1144116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        """Updates local fields of an existing object from a serialized form.
1145f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich
1146116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        This is different than the normal deserialize() in the way that it
1147116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        does update local values, which deserialize doesn't, but doesn't
1148116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        recursively propagate to related objects, which deserialize() does.
1149f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich
1150116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        The use case of this function is to update job records on the master
1151116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        after the jobs have been executed on a slave, as the master is not
1152116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        interested in updates for users, labels, specialtasks, etc.
1153116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich
1154116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        @param serialized: Representation of an object and its dependencies, as
1155116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich                           returned by serialize.
1156116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich
1157116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        @raises ValueError: if serialized contains related objects, i.e. not
1158116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich                            only local fields.
1159116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        """
1160116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        local, related = (
1161116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich            self._split_local_from_foreign_values(serialized))
1162116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        if related:
1163116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich            raise ValueError('Serialized must not contain foreign '
1164116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich                             'objects: %s' % related)
1165116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich
1166116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        self._deserialize_local(local)
1167f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich
1168f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich
1169f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich    def custom_deserialize_relation(self, link, data):
1170116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        """Allows overriding the deserialization behaviour by subclasses."""
1171f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich        raise NotImplementedError(
1172f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich            'custom_deserialize_relation must be implemented by subclass %s '
1173f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich            'for relation %s' % (type(self), link))
1174f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich
1175f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich
1176f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich    def _deserialize_relation(self, link, data):
1177116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        """Deserializes related objects and sets references on this object.
1178116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich
1179116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        Relations that point to a list of objects are handled automatically.
1180116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        For many-to-one or one-to-one relations custom_deserialize_relation
1181116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        must be overridden by the subclass.
1182116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich
1183116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        Related objects are deserialized using their deserialize() method.
1184116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        Thereby they and their dependencies are created if they don't exist
1185116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        and saved to the database.
1186116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich
1187116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        @param link: Name of the relation.
1188116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        @param data: Serialized representation of the related object(s).
1189116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich                     This means a list of dictionaries for to-many relations,
1190116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich                     just a dictionary for to-one relations.
1191116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        """
1192f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich        field = getattr(self, link)
1193f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich
1194f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich        if field and hasattr(field, 'all'):
1195f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich            self._deserialize_2m_relation(link, data, field.model)
1196f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich        else:
1197f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich            self.custom_deserialize_relation(link, data)
1198f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich
1199f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich
1200f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich    def _deserialize_2m_relation(self, link, data, related_class):
1201116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        """Deserialize related objects for one to-many relationship.
1202116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich
1203116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        @param link: Name of the relation.
1204116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        @param data: Serialized representation of the related objects.
1205116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich                     This is a list with of dictionaries.
1206ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng        @param related_class: A class representing a django model, with which
1207ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng                              this class has a one-to-many relationship.
1208116ff0fc2674082fe475afd64ce4dec998ed71b7Jakob Juelich        """
1209f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich        relation_set = getattr(self, link)
1210ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng        if related_class == self.get_attribute_model():
1211ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng            # When deserializing a model together with
1212ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng            # its attributes, clear all the exising attributes to ensure
1213ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng            # db consistency. Note 'update' won't be sufficient, as we also
1214ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng            # want to remove any attributes that no longer exist in |data|.
1215ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng            #
1216ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng            # core_filters is a dictionary of filters, defines how
1217ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng            # RelatedMangager would query for the 1-to-many relationship. E.g.
1218ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng            # Host.objects.get(
1219ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng            #     id=20).hostattribute_set.core_filters = {host_id:20}
1220ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng            # We use it to delete objects related to the current object.
1221ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng            related_class.objects.filter(**relation_set.core_filters).delete()
1222f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich        for serialized in data:
1223f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich            relation_set.add(related_class.deserialize(serialized))
1224f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich
1225f88fa938050d0e2b662a2b32ef7e931e01a4d8feJakob Juelich
1226ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng    @classmethod
1227ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng    def get_attribute_model(cls):
1228ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng        """Return the attribute model.
1229ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng
1230ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng        Subclass with attribute-like model should override this to
1231ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng        return the attribute model class. This method will be
1232ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng        called by _deserialize_2m_relation to determine whether
1233ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng        to clear the one-to-many relations first on deserialization of object.
1234ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng        """
1235ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng        return None
1236ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng
1237ff36159d40fc7e7b5124f4950b6a18103aebd922Fang Deng
12387c7852819d0611b4d0e8e69b3011b79e4016a770showardclass ModelWithInvalid(ModelExtensions):
12390afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    """
12400afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    Overrides model methods save() and delete() to support invalidation in
12410afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    place of actual deletion.  Subclasses must have a boolean "invalid"
12420afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    field.
12430afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    """
12440afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
1245a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward    def save(self, *args, **kwargs):
1246ddb90994ef51aed28feea1cee810ce6312efae51showard        first_time = (self.id is None)
1247ddb90994ef51aed28feea1cee810ce6312efae51showard        if first_time:
1248ddb90994ef51aed28feea1cee810ce6312efae51showard            # see if this object was previously added and invalidated
1249ddb90994ef51aed28feea1cee810ce6312efae51showard            my_name = getattr(self, self.name_field)
1250ddb90994ef51aed28feea1cee810ce6312efae51showard            filters = {self.name_field : my_name, 'invalid' : True}
1251ddb90994ef51aed28feea1cee810ce6312efae51showard            try:
1252ddb90994ef51aed28feea1cee810ce6312efae51showard                old_object = self.__class__.objects.get(**filters)
1253afd97de523a8c8c4e6657fa2db6214fda68d8086showard                self.resurrect_object(old_object)
1254ddb90994ef51aed28feea1cee810ce6312efae51showard            except self.DoesNotExist:
1255ddb90994ef51aed28feea1cee810ce6312efae51showard                # no existing object
1256ddb90994ef51aed28feea1cee810ce6312efae51showard                pass
12570afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
1258a5288b4bb2b09aafe914d0b7d5aab79a7e433eafshoward        super(ModelWithInvalid, self).save(*args, **kwargs)
12590afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
12600afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
1261afd97de523a8c8c4e6657fa2db6214fda68d8086showard    def resurrect_object(self, old_object):
1262afd97de523a8c8c4e6657fa2db6214fda68d8086showard        """
1263afd97de523a8c8c4e6657fa2db6214fda68d8086showard        Called when self is about to be saved for the first time and is actually
1264afd97de523a8c8c4e6657fa2db6214fda68d8086showard        "undeleting" a previously deleted object.  Can be overridden by
1265afd97de523a8c8c4e6657fa2db6214fda68d8086showard        subclasses to copy data as desired from the deleted entry (but this
1266afd97de523a8c8c4e6657fa2db6214fda68d8086showard        superclass implementation must normally be called).
1267afd97de523a8c8c4e6657fa2db6214fda68d8086showard        """
1268afd97de523a8c8c4e6657fa2db6214fda68d8086showard        self.id = old_object.id
1269afd97de523a8c8c4e6657fa2db6214fda68d8086showard
1270afd97de523a8c8c4e6657fa2db6214fda68d8086showard
12710afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    def clean_object(self):
12720afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """
12730afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        This method is called when an object is marked invalid.
12740afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        Subclasses should override this to clean up relationships that
1275afd97de523a8c8c4e6657fa2db6214fda68d8086showard        should no longer exist if the object were deleted.
1276afd97de523a8c8c4e6657fa2db6214fda68d8086showard        """
12770afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        pass
12780afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
12790afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
12800afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    def delete(self):
128174a314b490ff542c4dd2ae4aa0d11c6394d92960Dale Curtis        self.invalid = self.invalid
12820afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        assert not self.invalid
12830afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        self.invalid = True
12840afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        self.save()
12850afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        self.clean_object()
12860afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
12870afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
12880afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    @classmethod
12890afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    def get_valid_manager(cls):
12900afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        return cls.valid_objects
12910afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
12920afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski
12930afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski    class Manipulator(object):
12940afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """
12950afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        Force default manipulators to look only at valid objects -
12960afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        otherwise they will match against invalid objects when checking
12970afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        uniqueness.
12980afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        """
12990afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        @classmethod
13000afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski        def _prepare(cls, model):
13010afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski            super(ModelWithInvalid.Manipulator, cls)._prepare(model)
13020afbb6369aa5aa9a75ea67dd9e95ec4b21c0c181jadmanski            cls.manager = model.valid_objects
1303f8b19046c4496f570c776b65288382108a633ab4showard
1304f8b19046c4496f570c776b65288382108a633ab4showard
1305f8b19046c4496f570c776b65288382108a633ab4showardclass ModelWithAttributes(object):
1306f8b19046c4496f570c776b65288382108a633ab4showard    """
1307f8b19046c4496f570c776b65288382108a633ab4showard    Mixin class for models that have an attribute model associated with them.
1308f8b19046c4496f570c776b65288382108a633ab4showard    The attribute model is assumed to have its value field named "value".
1309f8b19046c4496f570c776b65288382108a633ab4showard    """
1310f8b19046c4496f570c776b65288382108a633ab4showard
1311f8b19046c4496f570c776b65288382108a633ab4showard    def _get_attribute_model_and_args(self, attribute):
1312f8b19046c4496f570c776b65288382108a633ab4showard        """
1313f8b19046c4496f570c776b65288382108a633ab4showard        Subclasses should override this to return a tuple (attribute_model,
1314f8b19046c4496f570c776b65288382108a633ab4showard        keyword_args), where attribute_model is a model class and keyword_args
1315f8b19046c4496f570c776b65288382108a633ab4showard        is a dict of args to pass to attribute_model.objects.get() to get an
1316f8b19046c4496f570c776b65288382108a633ab4showard        instance of the given attribute on this object.
1317f8b19046c4496f570c776b65288382108a633ab4showard        """
131874a314b490ff542c4dd2ae4aa0d11c6394d92960Dale Curtis        raise NotImplementedError
1319f8b19046c4496f570c776b65288382108a633ab4showard
1320f8b19046c4496f570c776b65288382108a633ab4showard
1321f8b19046c4496f570c776b65288382108a633ab4showard    def set_attribute(self, attribute, value):
1322f8b19046c4496f570c776b65288382108a633ab4showard        attribute_model, get_args = self._get_attribute_model_and_args(
1323f8b19046c4496f570c776b65288382108a633ab4showard            attribute)
1324f8b19046c4496f570c776b65288382108a633ab4showard        attribute_object, _ = attribute_model.objects.get_or_create(**get_args)
1325f8b19046c4496f570c776b65288382108a633ab4showard        attribute_object.value = value
1326f8b19046c4496f570c776b65288382108a633ab4showard        attribute_object.save()
1327f8b19046c4496f570c776b65288382108a633ab4showard
1328f8b19046c4496f570c776b65288382108a633ab4showard
1329f8b19046c4496f570c776b65288382108a633ab4showard    def delete_attribute(self, attribute):
1330f8b19046c4496f570c776b65288382108a633ab4showard        attribute_model, get_args = self._get_attribute_model_and_args(
1331f8b19046c4496f570c776b65288382108a633ab4showard            attribute)
1332f8b19046c4496f570c776b65288382108a633ab4showard        try:
1333f8b19046c4496f570c776b65288382108a633ab4showard            attribute_model.objects.get(**get_args).delete()
13341624542756819af6ac07685eb2a4c3cfb71d21b6showard        except attribute_model.DoesNotExist:
1335f8b19046c4496f570c776b65288382108a633ab4showard            pass
1336f8b19046c4496f570c776b65288382108a633ab4showard
1337f8b19046c4496f570c776b65288382108a633ab4showard
1338f8b19046c4496f570c776b65288382108a633ab4showard    def set_or_delete_attribute(self, attribute, value):
1339f8b19046c4496f570c776b65288382108a633ab4showard        if value is None:
1340f8b19046c4496f570c776b65288382108a633ab4showard            self.delete_attribute(attribute)
1341f8b19046c4496f570c776b65288382108a633ab4showard        else:
1342f8b19046c4496f570c776b65288382108a633ab4showard            self.set_attribute(attribute, value)
134326b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard
134426b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard
134526b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showardclass ModelWithHashManager(dbmodels.Manager):
134626b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard    """Manager for use with the ModelWithHash abstract model class"""
134726b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard
134826b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard    def create(self, **kwargs):
134926b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard        raise Exception('ModelWithHash manager should use get_or_create() '
135026b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard                        'instead of create()')
135126b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard
135226b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard
135326b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard    def get_or_create(self, **kwargs):
135426b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard        kwargs['the_hash'] = self.model._compute_hash(**kwargs)
135526b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard        return super(ModelWithHashManager, self).get_or_create(**kwargs)
135626b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard
135726b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard
135826b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showardclass ModelWithHash(dbmodels.Model):
135926b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard    """Superclass with methods for dealing with a hash column"""
136026b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard
136126b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard    the_hash = dbmodels.CharField(max_length=40, unique=True)
136226b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard
136326b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard    objects = ModelWithHashManager()
136426b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard
136526b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard    class Meta:
136626b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard        abstract = True
136726b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard
136826b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard
136926b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard    @classmethod
137026b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard    def _compute_hash(cls, **kwargs):
137126b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard        raise NotImplementedError('Subclasses must override _compute_hash()')
137226b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard
137326b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard
137426b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard    def save(self, force_insert=False, **kwargs):
137526b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard        """Prevents saving the model in most cases
137626b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard
137726b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard        We want these models to be immutable, so the generic save() operation
137826b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard        will not work. These models should be instantiated through their the
137926b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard        model.objects.get_or_create() method instead.
138026b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard
138126b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard        The exception is that save(force_insert=True) will be allowed, since
138226b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard        that creates a new row. However, the preferred way to make instances of
138326b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard        these models is through the get_or_create() method.
138426b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard        """
138526b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard        if not force_insert:
138626b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard            # Allow a forced insert to happen; if it's a duplicate, the unique
138726b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard            # constraint will catch it later anyways
138826b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard            raise Exception('ModelWithHash is immutable')
138926b7ec787d8fc566294ca39f6ceb7dcf3395a6c2showard        super(ModelWithHash, self).save(force_insert=force_insert, **kwargs)
1390