0

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?

5
  • What error do you get with self.users.add(self.owner)? Commented Mar 22, 2020 at 15:53
  • No errors at all Commented Mar 22, 2020 at 16:00
  • post_save can be used just as well since you want to add the the owner when the the family is created/saved. If you you want to check if the owner is removed from the users, you can connect to the m2m_changed signal. The only sensible approach listed here is the first one: self.pk should be None on the first save attempt. Unless you are not showing the full code, e.g. if family had a UUIDField as pk. Commented Mar 22, 2020 at 20:11
  • This is the full code, And post_save doesn't work with m2m relations Commented Mar 22, 2020 at 20:12
  • @schwobaseggl Thanks, but all of them don't work, This is the problem, Something prevents .users.add from working Commented Mar 22, 2020 at 20:14

1 Answer 1

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?

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.