def _lazy_contribute_to_class(self, model): # Sanity check assert (self.model == model) # Get foreign keys in the grouping field_fks = { field.name: field for field_name in self.unique_for_fields for field in (model._meta.get_field(field_name), ) if not field.auto_created and field.many_to_one } # Extract all associated generic relations generic_fks = { field.name: field for field in model._meta.local_fields if (field.many_to_one and not field.remote_field) # find generic fks and (field.name in field_fks or field.fk_field in field_fks ) # associated with this grouping and field_fks.pop(field.name, True) # and discard their fields and field_fks.pop(field.fk_field, True) } # from the field_fks list # Queue creation of remote order accessors for field in field_fks.values(): model._meta.apps.lazy_model_operation( partial(self.contribute_to_related_class, field=field), make_model_tuple( resolve_relation(model, field.remote_field.model)))
def create_many_to_many_intermediary_model(field, klass): from django.db import models def set_managed(model, related, through): through._meta.managed = model._meta.managed or related._meta.managed to_model = resolve_relation(klass, field.remote_field.model) name = '%s_%s' % (klass._meta.object_name, field.name) lazy_related_operation(set_managed, klass, to_model, name) to = make_model_tuple(to_model)[1] from_ = klass._meta.model_name if to == from_: to = 'to_%s' % to from_ = 'from_%s' % from_ meta = type( str('Meta'), (object, ), { 'db_table': field._get_m2m_db_table(klass._meta), 'auto_created': klass, 'app_label': klass._meta.app_label, 'db_tablespace': klass._meta.db_tablespace, 'unique_together': (from_, to), 'verbose_name': _('%(from)s-%(to)s relationship') % { 'from': from_, 'to': to }, 'verbose_name_plural': _('%(from)s-%(to)s relationships') % { 'from': from_, 'to': to }, 'apps': field.model._meta.apps, }) # Construct and return the new class. return type( str(name), (models.Model, ), { 'Meta': meta, '__module__': klass.__module__, from_: models.ForeignKey( klass, related_name='%s+' % name, db_tablespace=field.db_tablespace, db_constraint=field.remote_field.db_constraint, on_delete=DATABASE_CASCADE, ), to: models.ForeignKey( to_model, related_name='%s+' % name, db_tablespace=field.db_tablespace, db_constraint=field.remote_field.db_constraint, on_delete=DATABASE_CASCADE, ) })
def create_sortable_many_to_many_intermediary_model(field, klass, sort_field_name, base_classes=None): def set_managed(model, related, through): through._meta.managed = model._meta.managed or related._meta.managed to_model = resolve_relation(klass, field.remote_field.model) name = '%s_%s' % (klass._meta.object_name, field.name) lazy_related_operation(set_managed, klass, to_model, name) base_classes = base_classes if base_classes else (models.Model,) # TODO : use autoincrement here ? sort_field = models.IntegerField(default=0) to = make_model_tuple(to_model)[1] from_ = klass._meta.model_name if to == from_: to = 'to_%s' % to from_ = 'from_%s' % from_ meta = type('Meta', (), { 'db_table': field._get_m2m_db_table(klass._meta), # pylint: disable=protected-access 'auto_created': klass, 'app_label': klass._meta.app_label, 'db_tablespace': klass._meta.db_tablespace, 'unique_together': (from_, to), 'ordering': (sort_field_name,), 'verbose_name': _('%(from)s-%(to)s relationship') % {'from': from_, 'to': to}, 'verbose_name_plural': _('%(from)s-%(to)s relationships') % {'from': from_, 'to': to}, 'apps': field.model._meta.apps, }) # Construct and return the new class. return type(force_str(name), base_classes, { 'Meta': meta, '__module__': klass.__module__, from_: models.ForeignKey( klass, related_name='%s+' % name, db_tablespace=field.db_tablespace, db_constraint=field.remote_field.db_constraint, on_delete=models.CASCADE, ), to: models.ForeignKey( to_model, related_name='%s+' % name, db_tablespace=field.db_tablespace, db_constraint=field.remote_field.db_constraint, on_delete=models.CASCADE, ), # Sort fields sort_field_name: sort_field, '_sort_field_name': sort_field_name, })
def create_many_to_many_intermediary_model(field, klass): from django.db import models def set_managed(model, related, through): through._meta.managed = model._meta.managed or related._meta.managed to_model = resolve_relation(klass, field.remote_field.model) name = '%s_%s' % (klass._meta.object_name, field.name) lazy_related_operation(set_managed, klass, to_model, name) to = make_model_tuple(to_model)[1] from_ = klass._meta.model_name if to == from_: to = 'to_%s' % to from_ = 'from_%s' % from_ meta = type(str('Meta'), (object,), { 'db_table': field._get_m2m_db_table(klass._meta), 'auto_created': klass, 'app_label': klass._meta.app_label, 'db_tablespace': klass._meta.db_tablespace, 'unique_together': (from_, to), 'verbose_name': _('%(from)s-%(to)s relationship') % {'from': from_, 'to': to}, 'verbose_name_plural': _('%(from)s-%(to)s relationships') % {'from': from_, 'to': to}, 'apps': field.model._meta.apps, }) # Construct and return the new class. return type(str(name), (models.Model,), { 'Meta': meta, '__module__': klass.__module__, from_: models.ForeignKey( klass, related_name='%s+' % name, db_tablespace=field.db_tablespace, db_constraint=field.remote_field.db_constraint, on_delete=DATABASE_CASCADE, ), to: models.ForeignKey( to_model, related_name='%s+' % name, db_tablespace=field.db_tablespace, db_constraint=field.remote_field.db_constraint, on_delete=DATABASE_CASCADE, ) })
def create_versioned_many_to_many_intermediary_model( field, cls, field_name): # TODO: Verify functionality against # django.db.models.fields.related:1048 # Let's not care too much on what flags could potentially be set on # that intermediary class (e.g. managed, etc) # Let's play the game, as if the programmer had specified a class # within his models... Here's how. # FIXME: VersionedManyToManyModels do not get registered in the # apps models. # FIXME: This is usually done at django/db/models/base.py:284, # invoked by create_many_to_many_intermediary_model at # django.db.models.fields.related:1048 def set_managed(model, related, through): through._meta.managed = model._meta.managed or \ related._meta.managed to_model = resolve_relation(cls, field.remote_field.model) name = '%s_%s' % (cls._meta.object_name, field_name) lazy_related_operation(set_managed, cls, to_model, name) # Force 'to' to be a string (and leave the hard work to Django) to = make_model_tuple(to_model)[1] from_ = cls._meta.model_name if to == from_: from_ = 'from_%s' % from_ to = 'to_%s' % to meta = type( 'Meta', (object, ), { 'db_table': field._get_m2m_db_table(cls._meta), 'auto_created': cls, 'app_label': cls._meta.app_label, 'db_tablespace': cls._meta.db_tablespace, # 'unique_together' is not applicable as is, due to multiple # versions to be allowed to exist. # 'unique_together': (from_, to), 'verbose_name': '%(from)s-%(to)s relationship' % { 'from': from_, 'to': to }, 'verbose_name_plural': '%(from)s-%(to)s relationships' % { 'from': from_, 'to': to }, 'apps': field.model._meta.apps, }) return type( str(name), (Versionable, ), { 'Meta': meta, '__module__': cls.__module__, from_: VersionedForeignKey( cls, related_name='%s+' % name, db_tablespace=field.db_tablespace, db_constraint=field.remote_field.db_constraint, auto_created=name, on_delete=DO_NOTHING, ), to: VersionedForeignKey( to_model, related_name='%s+' % name, db_tablespace=field.db_tablespace, db_constraint=field.remote_field.db_constraint, auto_created=name, on_delete=DO_NOTHING, ), })
def __new__(cls, name, bases, attrs): super_new = super(MemModelBase, cls).__new__ # Also ensure initialization is only performed for subclasses of Model # (excluding Model class itself). parents = [b for b in bases if isinstance(b, MemModelBase)] if not parents: return super_new(cls, name, bases, attrs) # Create the class. module = attrs.pop('__module__') new_class = super_new(cls, name, bases, {'__module__': module}) attr_meta = attrs.pop('Meta', None) abstract = getattr(attr_meta, 'abstract', False) if not attr_meta: meta = getattr(new_class, 'Meta', None) else: meta = attr_meta base_meta = getattr(new_class, '_meta', None) app_label = None # Look for an application configuration to attach the model to. app_config = apps.get_containing_app_config(module) if getattr(meta, 'app_label', None) is None: if app_config is None: if not abstract: raise RuntimeError( "Model class %s.%s doesn't declare an explicit " "app_label and isn't in an application in " "INSTALLED_APPS." % (module, name)) else: app_label = app_config.label new_class.add_to_class('_meta', Options(meta, app_label)) if not abstract: new_class.add_to_class( 'DoesNotExist', subclass_exception( str('DoesNotExist'), tuple(x.DoesNotExist for x in parents if hasattr(x, '_meta') and not x._meta.abstract) or (ObjectDoesNotExist, ), module, attached_to=new_class)) new_class.add_to_class( 'MultipleObjectsReturned', subclass_exception( str('MultipleObjectsReturned'), tuple(x.MultipleObjectsReturned for x in parents if hasattr(x, '_meta') and not x._meta.abstract) or (MultipleObjectsReturned, ), module, attached_to=new_class)) if base_meta and not base_meta.abstract: # Non-abstract child classes inherit some attributes from their # non-abstract parent (unless an ABC comes before it in the # method resolution order). if not hasattr(meta, 'ordering'): new_class._meta.ordering = base_meta.ordering if not hasattr(meta, 'get_latest_by'): new_class._meta.get_latest_by = base_meta.get_latest_by if getattr(new_class, '_default_manager', None): # Multi-table inheritance doesn't inherit default manager from # parents. new_class._default_manager = None new_class._base_manager = None # Add all attributes to the class. for obj_name, obj in attrs.items(): new_class.add_to_class(obj_name, obj) # All the fields of any type declared on this model new_fields = chain(new_class._meta.local_fields, new_class._meta.local_many_to_many, new_class._meta.virtual_fields) field_names = {f.name for f in new_fields} new_class._meta.concrete_model = new_class # Collect the parent links for multi-table inheritance. parent_links = {} for base in reversed([new_class] + parents): # Conceptually equivalent to `if base is Model`. if not hasattr(base, '_meta'): continue # Skip concrete parent classes. if base != new_class and not base._meta.abstract: continue # Locate OneToOneField instances. for field in base._meta.local_fields: if isinstance(field, OneToOneField): related = resolve_relation(new_class, field.remote_field.model) parent_links[make_model_tuple(related)] = field # Do the appropriate setup for any model parents. for base in parents: if not hasattr(base, '_meta'): # Things without _meta aren't functional models, so they're # uninteresting parents. continue parent_fields = base._meta.local_fields + base._meta.local_many_to_many # Check for clashes between locally declared fields and those # on the base classes (we cannot handle shadowed fields at the # moment). for field in parent_fields: if field.name in field_names: raise FieldError('Local field %r in class %r clashes ' 'with field of similar name from ' 'base class %r' % (field.name, name, base.__name__)) if not base._meta.abstract: # Concrete classes... base = base._meta.concrete_model base_key = make_model_tuple(base) if base_key in parent_links: field = parent_links[base_key] #elif not is_proxy: else: attr_name = '%s_ptr' % base._meta.model_name field = OneToOneField( base, on_delete=CASCADE, name=attr_name, auto_created=True, parent_link=True, ) # Only add the ptr field if it's not already present; # e.g. migrations will already have it specified if not hasattr(new_class, attr_name): new_class.add_to_class(attr_name, field) new_class._meta.parents[base] = field else: base_parents = base._meta.parents.copy() # .. and abstract ones. for field in parent_fields: new_field = copy.deepcopy(field) new_class.add_to_class(field.name, new_field) # Replace parent links defined on this base by the new # field as it will be appropriately resolved if required. if field.one_to_one: for parent, parent_link in base_parents.items(): if field == parent_link: base_parents[parent] = new_field # Pass any non-abstract parent classes onto child. new_class._meta.parents.update(base_parents) # Inherit managers from the abstract base classes. new_class.copy_managers(base._meta.abstract_managers) # Inherit virtual fields (like GenericForeignKey) from the parent # class for field in base._meta.virtual_fields: if base._meta.abstract and field.name in field_names: raise FieldError('Local field %r in class %r clashes ' 'with field of similar name from ' 'abstract base class %r' % (field.name, name, base.__name__)) new_class.add_to_class(field.name, copy.deepcopy(field)) if abstract: # Abstract base models can't be instantiated and don't appear in # the list of models for an app. We do the final setup for them a # little differently from normal models. attr_meta.abstract = False new_class.Meta = attr_meta return new_class new_class._prepare() new_class._meta.apps.register_model(new_class._meta.app_label, new_class) return new_class
def _check_relationship_model(self, from_model=None, **kwargs): if hasattr(self.remote_field.through, '_meta'): qualified_model_name = "%s.%s" % ( self.remote_field.through._meta.app_label, self.remote_field.through.__name__) else: qualified_model_name = self.remote_field.through errors = [] if self.remote_field.through not in self.opts.apps.get_models( include_auto_created=True): # The relationship model is not installed. errors.append( checks.Error( ("Field specifies a many-to-many relation through model " "'%s', which has not been installed.") % qualified_model_name, hint=None, obj=self, id='fields.E331', )) else: assert from_model is not None, ( "ManyToManyField with intermediate " "tables cannot be checked if you don't pass the model " "where the field is attached to.") # Set some useful local variables to_model = resolve_relation(from_model, self.remote_field.model) from_model_name = from_model._meta.object_name if isinstance(to_model, six.string_types): to_model_name = to_model else: to_model_name = to_model._meta.object_name relationship_model_name = self.remote_field.through._meta.object_name self_referential = from_model == to_model # Check symmetrical attribute. if (self_referential and self.remote_field.symmetrical and not self.remote_field.through._meta.auto_created): errors.append( checks.Error( 'Many-to-many fields with intermediate tables must not be symmetrical.', hint=None, obj=self, id='fields.E332', )) # Count foreign keys in intermediate model if self_referential: seen_self = sum( from_model == getattr(field.remote_field, 'model', None) for field in self.remote_field.through._meta.fields) if seen_self > 2 and not self.remote_field.through_fields: errors.append( checks.Error( ("The model is used as an intermediate model by " "'%s', but it has more than two foreign keys " "to '%s', which is ambiguous. You must specify " "which two foreign keys Django should use via the " "through_fields keyword argument.") % (self, from_model_name), hint=("Use through_fields to specify which two " "foreign keys Django should use."), obj=self.remote_field.through, id='fields.E333', )) else: # Count foreign keys in relationship model # HERE IS THE ACTUAL CHANGE # Look at models _meta.concrete_model to make typed models work seen_from = len([ field for field in self.remote_field.through._meta.fields if hasattr(field.remote_field, 'model') and from_model._meta.concrete_model == field.remote_field.model._meta.concrete_model ]) seen_to = len([ field for field in self.remote_field.through._meta.fields if hasattr(field.remote_field, 'model') and to_model._meta.concrete_model == field.remote_field.model._meta.concrete_model ]) if seen_from > 1 and not self.remote_field.through_fields: errors.append( checks.Error( ("The model is used as an intermediate model by " "'%s', but it has more than one foreign key " "from '%s', which is ambiguous. You must specify " "which foreign key Django should use via the " "through_fields keyword argument.") % (self, from_model_name), hint= ('If you want to create a recursive relationship, ' 'use ForeignKey("self", symmetrical=False, ' 'through="%s").') % relationship_model_name, obj=self, id='fields.E334', )) if seen_to > 1 and not self.remote_field.through_fields: errors.append( checks.Error( ("The model is used as an intermediate model by " "'%s', but it has more than one foreign key " "to '%s', which is ambiguous. You must specify " "which foreign key Django should use via the " "through_fields keyword argument.") % (self, to_model_name), hint=('If you want to create a recursive ' 'relationship, use ForeignKey("self", ' 'symmetrical=False, through="%s").') % relationship_model_name, obj=self, id='fields.E335', )) if seen_from == 0 or seen_to == 0: errors.append( checks.Error( ("The model is used as an intermediate model by " "'%s', but it does not have a foreign key to '%s' or '%s'." ) % (self, from_model_name, to_model_name), hint=None, obj=self.remote_field.through, id='fields.E336', )) # Validate `through_fields`. if self.remote_field.through_fields is not None: # Validate that we're given an iterable of at least two items # and that none of them is "falsy". if not (len(self.remote_field.through_fields) >= 2 and self.remote_field.through_fields[0] and self.remote_field.through_fields[1]): errors.append( checks.Error( ("Field specifies 'through_fields' but does not " "provide the names of the two link fields that should be " "used for the relation through model " "'%s'.") % qualified_model_name, hint=("Make sure you specify 'through_fields' as " "through_fields=('field1', 'field2')"), obj=self, id='fields.E337', )) # Validate the given through fields -- they should be actual # fields on the through model, and also be foreign keys to the # expected models. else: assert from_model is not None, ( "ManyToManyField with intermediate " "tables cannot be checked if you don't pass the model " "where the field is attached to.") source, through, target = from_model, self.remote_field.through, self.remote_field.model source_field_name, target_field_name = self.remote_field.through_fields[: 2] for field_name, related_model in ((source_field_name, source), (target_field_name, target)): possible_field_names = [] for f in through._meta.fields: if hasattr(f, 'remote_field') and getattr( f.remote_field, 'model', None) == related_model: possible_field_names.append(f.name) if possible_field_names: hint = ("Did you mean one of the following foreign " "keys to '%s': %s?") % ( related_model._meta.object_name, ', '.join(possible_field_names)) else: hint = None try: field = through._meta.get_field(field_name) except exceptions.FieldDoesNotExist: errors.append( checks.Error( ("The intermediary model '%s' has no field '%s'." ) % (qualified_model_name, field_name), hint=hint, obj=self, id='fields.E338', )) else: if not (hasattr(field, 'remote_field') and getattr(field.remote_field, 'model', None) == related_model): errors.append( checks.Error( "'%s.%s' is not a foreign key to '%s'." % (through._meta.object_name, field_name, related_model._meta.object_name), hint=hint, obj=self, id='fields.E339', )) return errors
def _check_relationship_model(self, from_model=None, **kwargs): if hasattr(self.remote_field.through, '_meta'): qualified_model_name = "%s.%s" % ( self.remote_field.through._meta.app_label, self.remote_field.through.__name__) else: qualified_model_name = self.remote_field.through errors = [] if self.remote_field.through not in self.opts.apps.get_models(include_auto_created=True): # The relationship model is not installed. errors.append( checks.Error( ("Field specifies a many-to-many relation through model " "'%s', which has not been installed.") % qualified_model_name, hint=None, obj=self, id='fields.E331', ) ) else: assert from_model is not None, ( "ManyToManyField with intermediate " "tables cannot be checked if you don't pass the model " "where the field is attached to." ) # Set some useful local variables to_model = resolve_relation(from_model, self.remote_field.model) from_model_name = from_model._meta.object_name if isinstance(to_model, six.string_types): to_model_name = to_model else: to_model_name = to_model._meta.object_name relationship_model_name = self.remote_field.through._meta.object_name self_referential = from_model == to_model # Check symmetrical attribute. if (self_referential and self.remote_field.symmetrical and not self.remote_field.through._meta.auto_created): errors.append( checks.Error( 'Many-to-many fields with intermediate tables must not be symmetrical.', hint=None, obj=self, id='fields.E332', ) ) # Count foreign keys in intermediate model if self_referential: seen_self = sum(from_model == getattr(field.remote_field, 'model', None) for field in self.remote_field.through._meta.fields) if seen_self > 2 and not self.remote_field.through_fields: errors.append( checks.Error( ("The model is used as an intermediate model by " "'%s', but it has more than two foreign keys " "to '%s', which is ambiguous. You must specify " "which two foreign keys Django should use via the " "through_fields keyword argument.") % (self, from_model_name), hint=("Use through_fields to specify which two " "foreign keys Django should use."), obj=self.remote_field.through, id='fields.E333', ) ) else: # Count foreign keys in relationship model # HERE IS THE ACTUAL CHANGE # Look at models _meta.concrete_model to make typed models work seen_from = len([ field for field in self.remote_field.through._meta.fields if hasattr(field.remote_field, 'model') and from_model._meta.concrete_model == field.remote_field.model._meta.concrete_model ]) seen_to = len([ field for field in self.remote_field.through._meta.fields if hasattr(field.remote_field, 'model') and to_model._meta.concrete_model == field.remote_field.model._meta.concrete_model ]) if seen_from > 1 and not self.remote_field.through_fields: errors.append( checks.Error( ("The model is used as an intermediate model by " "'%s', but it has more than one foreign key " "from '%s', which is ambiguous. You must specify " "which foreign key Django should use via the " "through_fields keyword argument.") % (self, from_model_name), hint=('If you want to create a recursive relationship, ' 'use ForeignKey("self", symmetrical=False, ' 'through="%s").') % relationship_model_name, obj=self, id='fields.E334', ) ) if seen_to > 1 and not self.remote_field.through_fields: errors.append( checks.Error( ("The model is used as an intermediate model by " "'%s', but it has more than one foreign key " "to '%s', which is ambiguous. You must specify " "which foreign key Django should use via the " "through_fields keyword argument.") % (self, to_model_name), hint=('If you want to create a recursive ' 'relationship, use ForeignKey("self", ' 'symmetrical=False, through="%s").') % relationship_model_name, obj=self, id='fields.E335', ) ) if seen_from == 0 or seen_to == 0: errors.append( checks.Error( ("The model is used as an intermediate model by " "'%s', but it does not have a foreign key to '%s' or '%s'.") % ( self, from_model_name, to_model_name ), hint=None, obj=self.remote_field.through, id='fields.E336', ) ) # Validate `through_fields`. if self.remote_field.through_fields is not None: # Validate that we're given an iterable of at least two items # and that none of them is "falsy". if not (len(self.remote_field.through_fields) >= 2 and self.remote_field.through_fields[0] and self.remote_field.through_fields[1]): errors.append( checks.Error( ("Field specifies 'through_fields' but does not " "provide the names of the two link fields that should be " "used for the relation through model " "'%s'.") % qualified_model_name, hint=("Make sure you specify 'through_fields' as " "through_fields=('field1', 'field2')"), obj=self, id='fields.E337', ) ) # Validate the given through fields -- they should be actual # fields on the through model, and also be foreign keys to the # expected models. else: assert from_model is not None, ( "ManyToManyField with intermediate " "tables cannot be checked if you don't pass the model " "where the field is attached to." ) source, through, target = from_model, self.remote_field.through, self.remote_field.model source_field_name, target_field_name = self.remote_field.through_fields[:2] for field_name, related_model in ((source_field_name, source), (target_field_name, target)): possible_field_names = [] for f in through._meta.fields: if hasattr(f, 'remote_field') and getattr(f.remote_field, 'model', None) == related_model: possible_field_names.append(f.name) if possible_field_names: hint = ("Did you mean one of the following foreign " "keys to '%s': %s?") % (related_model._meta.object_name, ', '.join(possible_field_names)) else: hint = None try: field = through._meta.get_field(field_name) except exceptions.FieldDoesNotExist: errors.append( checks.Error( ("The intermediary model '%s' has no field '%s'.") % ( qualified_model_name, field_name), hint=hint, obj=self, id='fields.E338', ) ) else: if not (hasattr(field, 'remote_field') and getattr(field.remote_field, 'model', None) == related_model): errors.append( checks.Error( "'%s.%s' is not a foreign key to '%s'." % ( through._meta.object_name, field_name, related_model._meta.object_name), hint=hint, obj=self, id='fields.E339', ) ) return errors
def create_versioned_many_to_many_intermediary_model(field, cls, field_name): # TODO: Verify functionality against # django.db.models.fields.related:1048 # Let's not care too much on what flags could potentially be set on # that intermediary class (e.g. managed, etc) # Let's play the game, as if the programmer had specified a class # within his models... Here's how. # FIXME: VersionedManyToManyModels do not get registered in the # apps models. # FIXME: This is usually done at django/db/models/base.py:284, # invoked by create_many_to_many_intermediary_model at # django.db.models.fields.related:1048 def set_managed(model, related, through): through._meta.managed = model._meta.managed or \ related._meta.managed to_model = resolve_relation(cls, field.remote_field.model) name = '%s_%s' % (cls._meta.object_name, field_name) lazy_related_operation(set_managed, cls, to_model, name) # Force 'to' to be a string (and leave the hard work to Django) to = make_model_tuple(to_model)[1] from_ = cls._meta.model_name if to == from_: from_ = 'from_%s' % from_ to = 'to_%s' % to meta = type('Meta', (object,), { 'db_table': field._get_m2m_db_table(cls._meta), 'auto_created': cls, 'app_label': cls._meta.app_label, 'db_tablespace': cls._meta.db_tablespace, # 'unique_together' is not applicable as is, due to multiple # versions to be allowed to exist. # 'unique_together': (from_, to), 'verbose_name': '%(from)s-%(to)s relationship' % {'from': from_, 'to': to}, 'verbose_name_plural': '%(from)s-%(to)s relationships' % { 'from': from_, 'to': to}, 'apps': field.model._meta.apps, }) return type(str(name), (Versionable,), { 'Meta': meta, '__module__': cls.__module__, from_: VersionedForeignKey( cls, related_name='%s+' % name, db_tablespace=field.db_tablespace, db_constraint=field.remote_field.db_constraint, auto_created=name, on_delete=DO_NOTHING, ), to: VersionedForeignKey( to_model, related_name='%s+' % name, db_tablespace=field.db_tablespace, db_constraint=field.remote_field.db_constraint, auto_created=name, on_delete=DO_NOTHING, ), })
def __new__(mcs, name, bases, attrs, **kwargs): super_new = super().__new__ # Also ensure initialization is only performed for subclasses of Model # (excluding Model class itself). parents = [b for b in bases if isinstance(b, ModelMeta)] if not parents: return super_new(mcs, name, bases, attrs, **kwargs) # Create the class. module = attrs.pop('__module__') new_attrs = {'__module__': module} classcell = attrs.pop('__classcell__', None) if classcell is not None: new_attrs['__classcell__'] = classcell attr_meta = attrs.pop('Meta', None) # Pass all attrs without a (Django-specific) contribute_to_class() # method to type.__new__() so that they're properly initialized # (i.e. __set_name__()). contributable_attrs = {} for obj_name, obj in list(attrs.items()): if _has_contribute_to_class(obj): contributable_attrs[obj_name] = obj else: new_attrs[obj_name] = obj new_class = super_new(mcs, name, bases, new_attrs, **kwargs) abstract = getattr(attr_meta, 'abstract', False) meta = attr_meta or getattr(new_class, 'Meta', None) base_meta = getattr(new_class, '_meta', None) app_label = None new_class.add_to_class('_meta', Options(meta, app_label)) if not abstract: new_class.add_to_class( 'DoesNotExist', subclass_exception( 'DoesNotExist', tuple(x.DoesNotExist for x in parents if hasattr(x, '_meta') and not x._meta.abstract) or (ObjectDoesNotExist, ), module, attached_to=new_class)) new_class.add_to_class( 'MultipleObjectsReturned', subclass_exception( 'MultipleObjectsReturned', tuple(x.MultipleObjectsReturned for x in parents if hasattr(x, '_meta') and not x._meta.abstract) or (MultipleObjectsReturned, ), module, attached_to=new_class)) if base_meta and not base_meta.abstract: # Non-abstract child classes inherit some attributes from their # non-abstract parent (unless an ABC comes before it in the # method resolution order). if not hasattr(meta, 'ordering'): new_class._meta.ordering = base_meta.ordering if not hasattr(meta, 'get_latest_by'): new_class._meta.get_latest_by = base_meta.get_latest_by is_proxy = new_class._meta.proxy # If the model is a proxy, ensure that the base class # hasn't been swapped out. if is_proxy and base_meta and base_meta.swapped: raise TypeError("%s cannot proxy the swapped model '%s'." % (name, base_meta.swapped)) # Add remaining attributes (those with a contribute_to_class() method) # to the class. for obj_name, obj in contributable_attrs.items(): new_class.add_to_class(obj_name, obj) # All the fields of any type declared on this model new_fields = chain(new_class._meta.local_fields, new_class._meta.local_many_to_many, new_class._meta.private_fields) field_names = {f.name for f in new_fields} # Basic setup for proxy models. new_class._meta.concrete_model = new_class # Collect the parent links for multi-table inheritance. parent_links = {} for base in reversed([new_class] + parents): # Conceptually equivalent to `if base is Model`. if not hasattr(base, '_meta'): continue # Skip concrete parent classes. if base != new_class and not base._meta.abstract: continue # Locate OneToOneField instances. for field in base._meta.local_fields: if isinstance( field, OneToOneField) and field.remote_field.parent_link: related = resolve_relation(new_class, field.remote_field.model) parent_links[make_model_tuple(related)] = field # Track fields inherited from base models. inherited_attributes = set() # Do the appropriate setup for any model parents. for base in new_class.mro(): if base not in parents or not hasattr(base, '_meta'): # Things without _meta aren't functional models, so they're # uninteresting parents. inherited_attributes.update(base.__dict__) continue parent_fields = base._meta.local_fields + base._meta.local_many_to_many if not base._meta.abstract: # Check for clashes between locally declared fields and those # on the base classes. for field in parent_fields: if field.name in field_names: raise FieldError( 'Local field %r in class %r clashes with field of ' 'the same name from base class %r.' % ( field.name, name, base.__name__, )) else: inherited_attributes.add(field.name) # Concrete classes... base = base._meta.concrete_model base_key = make_model_tuple(base) if base_key in parent_links: field = parent_links[base_key] elif not is_proxy: attr_name = '%s_ptr' % base._meta.model_name field = OneToOneField( base, on_delete=CASCADE, name=attr_name, auto_created=True, parent_link=True, ) if attr_name in field_names: raise FieldError( "Auto-generated field '%s' in class %r for " "parent_link to base class %r clashes with " "declared field of the same name." % ( attr_name, name, base.__name__, )) # Only add the ptr field if it's not already present; # e.g. migrations will already have it specified if not hasattr(new_class, attr_name): new_class.add_to_class(attr_name, field) else: field = None new_class._meta.parents[base] = field else: base_parents = base._meta.parents.copy() # Add fields from abstract base class if it wasn't overridden. for field in parent_fields: if (field.name not in field_names and field.name not in new_class.__dict__ and field.name not in inherited_attributes): new_field = copy.deepcopy(field) new_class.add_to_class(field.name, new_field) # Replace parent links defined on this base by the new # field. It will be appropriately resolved if required. if field.one_to_one: for parent, parent_link in base_parents.items(): if field == parent_link: base_parents[parent] = new_field # Pass any non-abstract parent classes onto child. new_class._meta.parents.update(base_parents) # Inherit private fields (like GenericForeignKey) from the parent # class for field in base._meta.private_fields: if field.name in field_names: if not base._meta.abstract: raise FieldError( 'Local field %r in class %r clashes with field of ' 'the same name from base class %r.' % ( field.name, name, base.__name__, )) else: field = copy.deepcopy(field) if not base._meta.abstract: field.mti_inherited = True new_class.add_to_class(field.name, field) # Copy indexes so that index names are unique when models extend an # abstract model. new_class._meta.indexes = [ copy.deepcopy(idx) for idx in new_class._meta.indexes ] new_class._prepare() return new_class
def create_sortable_many_to_many_intermediary_model( field, klass, sort_field_name, base_classes=None ): def set_managed(model, related, through): through._meta.managed = model._meta.managed or related._meta.managed to_model = resolve_relation(klass, field.remote_field.model) name = "%s_%s" % (klass._meta.object_name, field.name) lazy_related_operation(set_managed, klass, to_model, name) base_classes = base_classes if base_classes else (models.Model,) # TODO : use autoincrement here ? sort_field = models.IntegerField(default=0) to = make_model_tuple(to_model)[1] from_ = klass._meta.model_name if to == from_: to = "to_%s" % to from_ = "from_%s" % from_ meta = type( "Meta", (), { "db_table": field._get_m2m_db_table(klass._meta), "auto_created": klass, "app_label": klass._meta.app_label, "db_tablespace": klass._meta.db_tablespace, "unique_together": (from_, to), "ordering": (sort_field_name,), "verbose_name": _("%(from)s-%(to)s relationship") % {"from": from_, "to": to}, "verbose_name_plural": _("%(from)s-%(to)s relationships") % {"from": from_, "to": to}, "apps": field.model._meta.apps, }, ) # Construct and return the new class. return type( force_str(name), base_classes, { "Meta": meta, "__module__": klass.__module__, from_: models.ForeignKey( klass, related_name="%s+" % name, db_tablespace=field.db_tablespace, db_constraint=field.remote_field.db_constraint, on_delete=models.CASCADE, ), to: models.ForeignKey( to_model, related_name="%s+" % name, db_tablespace=field.db_tablespace, db_constraint=field.remote_field.db_constraint, on_delete=models.CASCADE, ), # Sort fields sort_field_name: sort_field, "_sort_field_name": sort_field_name, }, )