Django ManyToMany relation to 'self' without backward relations

后端 未结 1 1239
遥遥无期
遥遥无期 2020-12-31 05:34

There are several question about backwards relations here, but I\'m either too dumb to understand them or think they don\'t fit my case.

I have model



        
相关标签:
1条回答
  • 2020-12-31 06:26

    My understanding of the question is that the ManyToMany relationship should be one way, in that if the following is true:

                 --------------           ----------------
                 | mymodelone |---------->|  mymodeltwo  |
                 --------------     |     ----------------
                                    |
                                    |     ----------------
                                    ----->| mymodelthree |
                                          ----------------
    

    Then there should not be an implicit relationship in the other direction:

                 --------------           ----------------
                 | mymodelone |<-----/----|  mymodeltwo  |
                 --------------           ----------------
    
                 --------------           ----------------
                 | mymodelone |<-----/----| mymodelthree |
                 --------------           ----------------
    

    ManyToMany fields have a symmetrical property which by default is True, see: here.

    To create a new app to demonstrate the non symmetric ManyToMany field:

    Create a new app:

    $ python ./manage.py startapp stuff
    

    Add stuff app in settings.py:

    ...
    INSTALLED_APPS = (
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'stuff'
    )
    ....
    

    edit `stuff/models.py:

    from django.db import models
    
    class MyModel(models.Model):
        stuff = models.ManyToManyField('self', related_name = 'combined+', symmetrical=False, blank = True, null = True, verbose_name = "description")
    
        def __unicode__(self):
            return "MyModel%i" % self.id
    

    Sync the db:

    $ python ./manage.py syncdb
    Creating tables ...
    Creating table stuff_mymodel_stuff
    Creating table stuff_mymodel
    Installing custom SQL ...
    Installing indexes ...
    Installed 0 object(s) from 0 fixture(s)
    

    and then test in the django shell:

    $ python ./manage.py shell
    >>> from stuff.models import MyModel
    >>> MyModel().save()
    >>> MyModel().save()
    >>> MyModel().save()
    >>> MyModel.objects.all()
    [<MyModel: MyModel1>, <MyModel: MyModel2>, <MyModel: MyModel3>]
    >>> m1 = MyModel.objects.get(pk=1)
    >>> m2 = MyModel.objects.get(pk=2)
    >>> m3 = MyModel.objects.get(pk=3)
    >>> m1.stuff.all()
    []
    >>> m1.stuff.add(m2)
    >>> m1.stuff.add(m3)
    >>> m1.stuff.all()
    [<MyModel: MyModel2>, <MyModel: MyModel3>]
    >>> m2.stuff.all()
    []
    >>> m3.stuff.all()
    []
    >>> 
    

    Edit - ManyToMany relationship on existing models

    The symmetry of the ManyToManyField is created when the models are written to the database, rather than when they are read. If we alter the model to:

    from django.db import models
    
    class MyModel(models.Model):
        stuff = models.ManyToManyField('self', related_name = 'combined+')
    
    def __unicode__(self):
        return "MyModel%i" % self.id
    

    Create new MyModel instances:

    >>> MyModel().save()
    >>> MyModel().save()
    >>> MyModel.objects.all()
    [<MyModel: MyModel1>, <MyModel: MyModel2>, <MyModel: MyModel3>, <MyModel: MyModel4>, <MyModel: MyModel5>]
    >>> m4 = MyModel.objects.get(pk=4)
    >>> m5 = MyModel.objects.get(pk=5)
    >>> m4.stuff.add(m5)
    >>> m4.stuff.all()
    [<MyModel: MyModel5>]
    >>> m5.stuff.all()
    [<MyModel: MyModel4>]
    

    As expected the stuff ManyToManyField is creating symmetrical relations. If we then set the ManyToManyField to symmetrical = False:

    >>> from stuff.models import MyModel
    >>> MyModel().save()
    >>> MyModel().save()
    >>> MyModel.objects.all()
    [<MyModel: MyModel1>, <MyModel: MyModel2>, <MyModel: MyModel3>, <MyModel: MyModel4>, <MyModel: MyModel5>, <MyModel: MyModel6>, <MyModel: MyModel7>]
    >>> m6 = MyModel.objects.get(pk=6)
    >>> m7 = MyModel.objects.get(pk=7)
    >>> m6.stuff.all()
    []
    >>> m7.stuff.all()
    []
    >>> m6.stuff.add(m7)
    >>> m6.stuff.all()
    [<MyModel: MyModel7>]
    >>> m7.stuff.all()
    []
    >>> m5 = MyModel.objects.get(pk=5)
    >>> m4 = MyModel.objects.get(pk=4)
    >>> m4.stuff.all()
    [<MyModel: MyModel5>]
    >>> m5.stuff.all()
    [<MyModel: MyModel4>]
    

    It can be seen the new ManyToMany relation between m6 and m7 is not symmetrical, however the existing one, between m4 and m5 is still symmetrical as the model stated when those objects were created.

    Edit - additional database constraints with a symmetrical foreign key

    Apologies to the reader to the length of this answer, we seem to be exploring this problem at some depth.

    In sql a many to many relation is modeled by creating a table that holds all the information unique to the relation - usually just the primary key value of the two tables.

    So for our MyModel, django creates two tables:

                 -----------------           -----------------------
                 | stuff_mymodel |---------->| stuff_mymodel_stuff |
                 -----------------           -----------------------
                          ^                              |
                          |                              |
                          --------------------------------
    

    The links shown in the diagram are represented by primary key or id values within the schema:

    mysql> describe stuff_mymodel;
    +-------+---------+------+-----+---------+----------------+
    | Field | Type    | Null | Key | Default | Extra          |
    +-------+---------+------+-----+---------+----------------+
    | id    | int(11) | NO   | PRI | NULL    | auto_increment |
    +-------+---------+------+-----+---------+----------------+
    1 row in set (0.00 sec)
    
    mysql> describe stuff_mymodel_stuff;
    +-----------------+---------+------+-----+---------+----------------+
    | Field           | Type    | Null | Key | Default | Extra          |
    +-----------------+---------+------+-----+---------+----------------+
    | id              | int(11) | NO   | PRI | NULL    | auto_increment |
    | from_mymodel_id | int(11) | NO   | MUL | NULL    |                |
    | to_mymodel_id   | int(11) | NO   | MUL | NULL    |                |
    +-----------------+---------+------+-----+---------+----------------+
    3 rows in set (0.00 sec)
    

    And shown as the output from the Django manage.py script:

    $ python ./manage.py sql stuff
        BEGIN;
    CREATE TABLE `stuff_mymodel_stuff` (
        `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
        `from_mymodel_id` integer NOT NULL,
        `to_mymodel_id` integer NOT NULL,
        UNIQUE (`from_mymodel_id`, `to_mymodel_id`)
    )
    ;
    CREATE TABLE `stuff_mymodel` (
        `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY
    )
    ;
    ALTER TABLE `stuff_mymodel_stuff` ADD CONSTRAINT `from_mymodel_id_refs_id_7fa00238` FOREIGN KEY (`from_mymodel_id`) REFERENCES `stuff_mymodel` (`id`);
    ALTER TABLE `stuff_mymodel_stuff` ADD CONSTRAINT `to_mymodel_id_refs_id_7fa00238` FOREIGN KEY (`to_mymodel_id`) REFERENCES `stuff_mymodel` (`id`);
    COMMIT;
    

    This sql is the same irrespective if the django ManyToManyField is symmetrical or not. The only difference is the number of rows created in the stuff_mymodel_stuff table:

    mysql> select * from stuff_mymodel_stuff;
    +----+-----------------+---------------+
    | id | from_mymodel_id | to_mymodel_id |
    +----+-----------------+---------------+
    |  1 |               1 |             2 |
    |  2 |               1 |             3 |
    |  3 |               4 |             5 |
    |  4 |               5 |             4 |
    |  5 |               6 |             7 |
    +----+-----------------+---------------+
    5 rows in set (0.00 sec)
    

    The link m4 -> m5 being symmetrical and the others not. Digging around in the Django source we can find the code responsible for creating the 'mirror' entry in the sql, if symmetrical is True:

        # If the ManyToMany relation has an intermediary model,
        # the add and remove methods do not exist.
        if rel.through._meta.auto_created:
            def add(self, *objs):
                self._add_items(self.source_field_name, self.target_field_name, *objs)
    
                # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table
                if self.symmetrical:
                    self._add_items(self.target_field_name, self.source_field_name, *objs)
            add.alters_data = True
    

    This is currently line 605 on github: https://github.com/django/django/blob/master/django/db/models/fields/related.py

    Hope this answers all your queries.

    0 讨论(0)
提交回复
热议问题