Search code examples
djangodjango-modelsdjango-auth-models

Django Auth : How are many-to-many relationship models created?


django.contrib.auth.models have only 3 models, User, Group, Permission, as per the doc and code.

Doc does say groups and permissions are many-to-many relationships.

But when we migrate how come other relations user_group, user_permissions, group_permissions are created?


Solution

  • The tables for ManyToMany relationship are created by lines with models.ManyToManyField string that are in the migration file.

    In this case they are created by the initial migration django/contrib/auth/migrations/0001_initial.py. It is very similar to django/contrib/auth/models.py.

    The related SQL commands can be read by

    $ python manage.py sqlmigrate auth 0001_initial
    

    See the lines:
    CREATE TABLE "auth_user_groups"...
    CREATE TABLE "auth_group_permissions"...
    CREATE TABLE "auth_user_user_permissions"...

    Especially interesting two texts are around the words REFERENCES. That are the foreign keys to base tables, e.g.:
    ... "user_id" integer NOT NULL REFERENCES "auth_user" ("id") ...


    EDIT:

    You probably know a ManyToMany relationship joined by an explicit intermediate model specified by the parameter through=SomeModel with an extra field.

    I explain it in example how you can use a hidden intermediate table in your code.

    Imagine that you want to make a note to some permission why you added it to some person.

    You can explore the model structure from command line, because in "auth/models.py" you could read only the name "user_parmissions".

    >>> User._meta.local_many_to_many
    [<django.db.models.fields.related.ManyToManyField: groups>,
     <django.db.models.fields.related.ManyToManyField: user_permissions>]
    >>> User.user_permissions.through
    <class 'django.contrib.auth.models.User_user_permissions'>
    >>> User.user_permissions.through._meta.local_fields
    [<django.db.models.fields.AutoField: id>,
     <django.db.models.fields.related.ForeignKey: user>,
     <django.db.models.fields.related.ForeignKey: permission>]
    

    your_app/models.py

    from django.contrib.auth.models import User, Group, Permission
    from django.db import models
    
    # This line is instead of import, because the implicit intermediate table
    # can not be imported.
    UserPermission = User.user_permissions.through
    
    class UserPermissionReason(models.Model)
        user_permission = models.ForeignKey(UserPermission, ...)
        reason = models.CharField(help_text="Why I added that permission", ...)
    

    makemigrations, migrate, shell...

    Example: Create a user permission with your note

    >>> from your_app.models import UserPermissionReason, UserPermission
    >>> from django.contrib.auth.models import User, Group, Permission
    >>> permission = Permission.objects.get(name='Can add user',
                                            content_type__app_label='auth')
    >>> permission
    <Permission: auth | user | Can add user>
    >>> user_permission = UserPermission.objects.create(user=me_superuser,
                                                        permission=permission)
    >>> why = UserPermissionReason.objects.create(
            user_permission=user_permission,
            reason="Because I can :-)"
        )
    

    If you don't need to reference the intermediate table you would use a normal simple some_ruser.user_permissions.add(permission)