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