Guest User

Array many to many patches for django 3.1.4

a guest
Dec 3rd, 2020
197
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 10.85 KB | None | 0 0
  1. # VERY IMPORTANT
  2. # this code in core.lib.django_postgres_extensions.array_m2m_patches
  3. # needs to be update in every django version update in order to avoid
  4. # issues, our own addition to django's patched functions are made
  5. # between `OUR CUSTOM CODE` comments, so the rest of the original
  6. # django code must be update regularly
  7. import copy
  8.  
  9. from django.core import exceptions
  10. from django.core.exceptions import FieldError
  11. from django.db import transaction
  12. from django.db.models.constants import LOOKUP_SEP
  13. from django.db.models.sql.constants import CURSOR
  14. from django.db.models.sql.subqueries import UpdateQuery as BaseUpdateQuery
  15.  
  16.  
  17. def prefetch_one_level(instances, prefetcher, lookup, level):
  18.     """
  19.    Helper function for prefetch_related_objects().
  20.  
  21.    Run prefetches on all instances using the prefetcher object,
  22.    assigning results to relevant caches in instance.
  23.  
  24.    Return the prefetched objects along with any additional prefetches that
  25.    must be done due to prefetch_related lookups found from default managers.
  26.    """
  27.     # prefetcher must have a method get_prefetch_queryset() which takes a list
  28.     # of instances, and returns a tuple:
  29.  
  30.     # (queryset of instances of self.model that are related to passed in instances,
  31.     #  callable that gets value to be matched for returned instances,
  32.     #  callable that gets value to be matched for passed in instances,
  33.     #  boolean that is True for singly related objects,
  34.     #  cache or field name to assign to,
  35.     #  boolean that is True when the previous argument is a cache name vs a field name).
  36.  
  37.     # The 'values to be matched' must be hashable as they will be used
  38.     # in a dictionary.
  39.  
  40.     rel_qs, rel_obj_attr, instance_attr, single, cache_name, is_descriptor = (
  41.         prefetcher.get_prefetch_queryset(instances, lookup.get_current_queryset(level)))
  42.     # We have to handle the possibility that the QuerySet we just got back
  43.     # contains some prefetch_related lookups. We don't want to trigger the
  44.     # prefetch_related functionality by evaluating the query. Rather, we need
  45.     # to merge in the prefetch_related lookups.
  46.     # Copy the lookups in case it is a Prefetch object which could be reused
  47.     # later (happens in nested prefetch_related).
  48.     additional_lookups = [
  49.         copy.copy(additional_lookup) for additional_lookup
  50.         in getattr(rel_qs, '_prefetch_related_lookups', ())
  51.     ]
  52.     if additional_lookups:
  53.         # Don't need to clone because the manager should have given us a fresh
  54.         # instance, so we access an internal instead of using public interface
  55.         # for performance reasons.
  56.         rel_qs._prefetch_related_lookups = ()
  57.  
  58.     all_related_objects = list(rel_qs)
  59.     # OUR CUSTOM CODE
  60.     is_multi_reference = getattr(rel_qs, 'is_multi_reference', False)
  61.     # END OF OUR CUSTOM CODE
  62.  
  63.     rel_obj_cache = {}
  64.     # OUR CUSTOM CODE
  65.     if not is_multi_reference:
  66.         # END OF OUR CUSTOM CODE
  67.         for rel_obj in all_related_objects:
  68.             rel_attr_val = rel_obj_attr(rel_obj)
  69.             rel_obj_cache.setdefault(rel_attr_val, []).append(rel_obj)
  70.  
  71.     to_attr, as_attr = lookup.get_current_to_attr(level)
  72.     # Make sure `to_attr` does not conflict with a field.
  73.     if as_attr and instances:
  74.         # We assume that objects retrieved are homogeneous (which is the premise
  75.         # of prefetch_related), so what applies to first object applies to all.
  76.         model = instances[0].__class__
  77.         try:
  78.             model._meta.get_field(to_attr)
  79.         except exceptions.FieldDoesNotExist:
  80.             pass
  81.         else:
  82.             msg = 'to_attr={} conflicts with a field on the {} model.'
  83.             raise ValueError(msg.format(to_attr, model.__name__))
  84.  
  85.     # Whether or not we're prefetching the last part of the lookup.
  86.     leaf = len(lookup.prefetch_through.split(LOOKUP_SEP)) - 1 == level
  87.  
  88.     for obj in instances:
  89.         instance_attr_val = instance_attr(obj)
  90.         # OUR CUSTOM CODE
  91.         if is_multi_reference:
  92.             vals = [rel_obj for rel_obj in all_related_objects if rel_obj_attr(rel_obj, instance_attr_val)]
  93.         else:
  94.             # END OF OUR CUSTOM CODE
  95.             vals = rel_obj_cache.get(instance_attr_val, [])
  96.  
  97.         if single:
  98.             val = vals[0] if vals else None
  99.             if as_attr:
  100.                 # A to_attr has been given for the prefetch.
  101.                 setattr(obj, to_attr, val)
  102.             elif is_descriptor:
  103.                 # cache_name points to a field name in obj.
  104.                 # This field is a descriptor for a related object.
  105.                 setattr(obj, cache_name, val)
  106.             else:
  107.                 # No to_attr has been given for this prefetch operation and the
  108.                 # cache_name does not point to a descriptor. Store the value of
  109.                 # the field in the object's field cache.
  110.                 obj._state.fields_cache[cache_name] = val
  111.         else:
  112.             if as_attr:
  113.                 setattr(obj, to_attr, vals)
  114.             else:
  115.                 manager = getattr(obj, to_attr)
  116.                 if leaf and lookup.queryset is not None:
  117.                     qs = manager._apply_rel_filters(lookup.queryset)
  118.                 else:
  119.                     qs = manager.get_queryset()
  120.                 qs._result_cache = vals
  121.                 # We don't want the individual qs doing prefetch_related now,
  122.                 # since we have merged this into the current work.
  123.                 qs._prefetch_done = True
  124.                 obj._prefetched_objects_cache[cache_name] = qs
  125.     return all_related_objects, additional_lookups
  126.  
  127.  
  128. def as_sql(self, compiler, connection):
  129.     """
  130.    Generate the full
  131.       LEFT OUTER JOIN sometable ON sometable.somecol = othertable.othercol, params
  132.    clause for this join.
  133.    """
  134.     join_conditions = []
  135.     params = []
  136.     qn = compiler.quote_name_unless_alias
  137.     qn2 = connection.ops.quote_name
  138.  
  139.     # Add a join condition for each pair of joining columns.
  140.     for lhs_col, rhs_col in self.join_cols:
  141.         # OUR CUSTOM CODE
  142.         if hasattr(self.join_field, 'get_join_on'):
  143.             join_condition = self.join_field.get_join_on(qn(self.parent_alias), qn2(lhs_col), qn(self.table_alias),
  144.                                                          qn2(rhs_col))
  145.             join_conditions.append(join_condition)
  146.         else:
  147.             # END OF OUR CUSTOM CODE
  148.             join_conditions.append('%s.%s = %s.%s' % (
  149.                 qn(self.parent_alias),
  150.                 qn2(lhs_col),
  151.                 qn(self.table_alias),
  152.                 qn2(rhs_col),
  153.             ))
  154.  
  155.     # Add a single condition inside parentheses for whatever
  156.     # get_extra_restriction() returns.
  157.     extra_cond = self.join_field.get_extra_restriction(
  158.         compiler.query.where_class, self.table_alias, self.parent_alias)
  159.     if extra_cond:
  160.         extra_sql, extra_params = compiler.compile(extra_cond)
  161.         join_conditions.append('(%s)' % extra_sql)
  162.         params.extend(extra_params)
  163.     if self.filtered_relation:
  164.         extra_sql, extra_params = compiler.compile(self.filtered_relation)
  165.         if extra_sql:
  166.             join_conditions.append('(%s)' % extra_sql)
  167.             params.extend(extra_params)
  168.     if not join_conditions:
  169.         # This might be a rel on the other end of an actual declared field.
  170.         declared_field = getattr(self.join_field, 'field', self.join_field)
  171.         raise ValueError(
  172.             "Join generated an empty ON clause. %s did not yield either "
  173.             "joining columns or extra restrictions." % declared_field.__class__
  174.         )
  175.     on_clause_sql = ' AND '.join(join_conditions)
  176.     alias_str = '' if self.table_alias == self.table_name else (' %s' % self.table_alias)
  177.     sql = '%s %s%s ON (%s)' % (self.join_type, qn(self.table_name), alias_str, on_clause_sql)
  178.     return sql, params
  179.  
  180.  
  181. def update(self, **kwargs):
  182.     """
  183.    Update all elements in the current QuerySet, setting all the given
  184.    fields to the appropriate values.
  185.    """
  186.     self._not_support_combined_queries('update')
  187.     assert not self.query.is_sliced, \
  188.         "Cannot update a query once a slice has been taken."
  189.     self._for_write = True
  190.     # OUR CUSTOM CODE is UpdateQuery
  191.     query = self.query.chain(UpdateQuery)
  192.     query.add_update_values(kwargs)
  193.     # Clear any annotations so that they won't be present in subqueries.
  194.     query.annotations = {}
  195.     with transaction.mark_for_rollback_on_error(using=self.db):
  196.         rows = query.get_compiler(self.db).execute_sql(CURSOR)
  197.     self._result_cache = None
  198.     return rows
  199.  
  200.  
  201. update.alters_data = True
  202.  
  203.  
  204. def _update(self, values):
  205.     """
  206.    A version of update() that accepts field objects instead of field names.
  207.    Used primarily for model saving and not intended for use by general
  208.    code (it requires too much poking around at model internals to be
  209.    useful at that level).
  210.    """
  211.     assert not self.query.is_sliced, \
  212.         "Cannot update a query once a slice has been taken."
  213.     query = self.query.chain(UpdateQuery)
  214.     # OUR CUSTOM CODE
  215.     values = [value for value in values if not getattr(value[0], 'many_to_many_array', False)]
  216.     # END OF OUR CUSTOM CODE
  217.  
  218.     query.add_update_fields(values)
  219.     # Clear any annotations so that they won't be present in subqueries.
  220.     query.annotations = {}
  221.     self._result_cache = None
  222.     return query.get_compiler(self.db).execute_sql(CURSOR)
  223.  
  224.  
  225. _update.alters_data = True
  226. _update.queryset_only = False
  227.  
  228.  
  229. class UpdateQuery(BaseUpdateQuery):
  230.     def add_update_values(self, values):
  231.         """
  232.        Convert a dictionary of field name to value mappings into an update
  233.        query. This is the entry point for the public update() method on
  234.        querysets.
  235.        """
  236.         values_seq = []
  237.         for name, val in values.items():
  238.             # OUR CUSTOM CODE
  239.             if '__' in name:
  240.                 indexes = name.split('__')
  241.                 field_name = indexes.pop(0)
  242.                 field = self.get_meta().get_field(field_name)
  243.                 val = field.get_update_type(indexes, val)
  244.                 model = field.model
  245.             else:
  246.                 # END OF OUR CUSTOM CODE
  247.                 field = self.get_meta().get_field(name)
  248.                 direct = not (field.auto_created and not field.concrete) or not field.concrete
  249.                 model = field.model._meta.concrete_model
  250.                 if not direct or (field.is_relation and field.many_to_many):
  251.                     raise FieldError(
  252.                         'Cannot update model field %r (only non-relations and '
  253.                         'foreign keys permitted).' % field
  254.                     )
  255.                 if model is not self.get_meta().concrete_model:
  256.                     self.add_related_update(model, field, val)
  257.                     continue
  258.             values_seq.append((field, model, val))
  259.         return self.add_update_fields(values_seq)
  260.  
Advertisement
Add Comment
Please, Sign In to add comment