Adding an item to many to many after creation in django

情到浓时终转凉″ 提交于 2021-01-29 09:15:19

问题


Recently I've been trying to do something with this. Think of the family as a facebook group.

class Family(models.Model):
    name = models.CharField(max_length=50)
    owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='owned_families')
    users = models.ManyToManyField(User, related_name='families', blank=True)

let's assume we have this family object called fm, for illustration purpose.

My problem is, The owner is one of the users right? I mean, When someone creates a family, He's now the owner right? he owns it but he's still a user listed in it's users list. Now, when I create a new family fm , I want to add the fm.owner to fm.users.

Let's talk about what I've tried.

  • post_save signal doesn't work with m2m. X

  • m2m_changed happens when the field is changed, not created. X

  • Overriding save method, lemme illustrate what I tried to acheive. ?
    def save(self, *args, **kwargs):
        old = self.pk
        super(Family, self).save(*args, **kwargs)

        if old is None:
            print('This actually shows up')
            self.users.add(self.owner)

Basically, this saves the pk each time, First time a family is created, Before calling super..... it has no .pk so I'm counting on this to check if it had no pk (On creation).

The problem is self.users.add(self.owner) doesn't work.

I've tried to clone the object as whole and keep track of it like

    def save(self, *args, **kwargs):
        old = self
        super(Family, self).save(*args, **kwargs)

        if old is None:
            print("This actually doesn't show up")
            self.users.add(self.owner)

This actually is terrible, It takes a refernce to self and when calling super...., The selfand it's reference old gets mutated, I just wanted to show this as this question itself might solve someone's problem.

So I solved this by.

import copy
    def save(self, *args, **kwargs):
        old = copy.deepcopy(self)
        super(Family, self).save(*args, **kwargs)

        if old is None:
            print('This actually shows up')
            self.users.add(self.owner)

but self.users.add(self.owner) still doesn't work.

What am I missing?


回答1:


The problem is probably that in the django admin, the instance is saved first, and only after that the inline formsets and m2m-fields are saved. If the owner is not in there, it will be removed. You can override some functionality in the admin to remedy this:

class FamilyAdmin(ModelAdmin):
    def save_related(self, request, form, formsets, change):
        super(FamilyAdmin, self).save_related(request, form, formsets, change)
        form.instance.users.add(form.instance.owner)

Furthermore, you can try (note that there are other ways to remove the owner that are not picked up by any signal or other hook) to prevent code from removing the owner:

from django.db.models.signals import m2m_changed
from django.dispatch import receiver

@receiver(m2m_changed, sender=Family.users.through)
def famliy_users_changed(sender, **kwargs):
    family = kwargs['instance']
    pk_set = kwargs['pk_set']
    action = kwargs['action']
    if action == "pre_add":
        pk_set.add(family.owner_id)
    if action == "pre_remove":
        pk_set.remove(family.owner_id)
    if action == "post_clear":
        family.users.add(family.owner)

But generally speaking, you are jumping through those hoops because you are denormalizing your data (putting the owner in users makes that information redundant, forcing you to keep your data correct). Since you always know the owner is one of the users, why not wrap that in a method

class Family(...):
    # ...
    def members(self):
        return User.objects.filter(Q(pk__in=self.users.all()|Q(pk=self.owner_id)))

and access family members through that method?



来源:https://stackoverflow.com/questions/60801443/adding-an-item-to-many-to-many-after-creation-in-django

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!