Search code examples
pythondjangodjango-rest-frameworkmodel

How to auto add value from ForeignKey to ManyToManyField?


So I need auto add author to coworkers.

class Board(models.Model):
    """Model definition for Board."""

    author = models.ForeignKey(
        to=User,
        on_delete=models.CASCADE,
        related_name="author",
    )
    name = models.CharField(max_length=24, blank=False)
    coworkers = models.ManyToManyField(
        to=User,
        blank=True,
    )

For now I use serializer and view solution. Is it possible do with model?


Solution

  • Is it possible do with model?

    I would really advise to keep this in the serializer, or if that is not possible, the view, since overriding the .save() method often is risky: it can eventually get one into infinite recursion, or in case of a .bulk_create(..) [Django-doc], it will skip the .save() method, and this not work properly.

    If you really want to do this, you can add this after you perform the .save() on the super level, so:

    from django.conf import settings
    
    
    class Board(models.Model):
        """Model definition for Board."""
    
        author = models.ForeignKey(
            to=settings.AUTH_USER_MODEL,
            on_delete=models.CASCADE,
            related_name='author',
        )
        name = models.CharField(max_length=24, blank=False)
        coworkers = models.ManyToManyField(
            to=settings.AUTH_USER_MODEL,
            blank=True,
        )
    
        def save(self, *args, **kwargs):
            super().save(*args, **kwargs)
            self.coworkers.add(self.author)

    but perhaps it is simply not necessary to add it to the co-workers: you can add a property to obtain all people that work on this with:

    from django.conf import settings
    from django.contrib.auth import get_user_model
    
    
    class Board(models.Model):
        """Model definition for Board."""
    
        author = models.ForeignKey(
            to=settings.AUTH_USER_MODEL,
            on_delete=models.CASCADE,
            related_name='author',
        )
        name = models.CharField(max_length=24, blank=False)
        extra_workers = models.ManyToManyField(
            to=settings.AUTH_USER_MODEL,
            blank=True,
        )
    
        @property
        def coworkers(self):
            return get_user_model().objects.filter(
                Q(pk=self.author_id) | Q(board_set=self)
            )

    Then you can use my_board.coworkers (or my_board.coworkers.all()) to get the author and the extra_workers combined. The advantage of not saving the author explicitly in the ManyToManyField, is that if you later change the author that is not something to worry about, the coworkers will then return the "new" .author as well as the .extra_workers.


    Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.


    Note: The related_name=… parameter [Django-doc] is the name of the relation in reverse, so from the User model to the Board model in this case. Therefore it (often) makes not much sense to name it the same as the forward relation. You thus might want to consider renaming the author relation to authored_boards.