#33369: Add through_defaults as argument for m2m_changed signal
-------------------------------------+-------------------------------------
               Reporter:             |          Owner:  nobody
  ShmuelTreiger                      |
                   Type:  New        |         Status:  new
  feature                            |
              Component:  Database   |        Version:  4.0
  layer (models, ORM)                |
               Severity:  Normal     |       Keywords:  m2m_changed
           Triage Stage:             |      Has patch:  0
  Unreviewed                         |
    Needs documentation:  0          |    Needs tests:  0
Patch needs improvement:  0          |  Easy pickings:  0
                  UI/UX:  0          |
-------------------------------------+-------------------------------------
 Use case:

 In English:
 I have many policies, each of which has a status from a table of statuses
 and is not unique to that policy. I associate these policies to my many
 domains through the many_to_many table. Domains can have many policies,
 but only one of each type of policy. Likewise, the same policy can apply
 to multiple domains. (In practice, there are a few thousand domains and a
 dozen or so policies.) In the following code, I have successfully
 implemented this relationship at the database level.

 In pseudocode:


 {{{
 Domain(models.Model)
     <fields>

 PolicyStatus(models.Model):
     status = models.CharField(<>)

 Policy(models.Model)
     status = models.ForeignKey(PolicyStatus, <>)
     domains = models.ManyToManyField(Domain, through="Domain_Policy_m2m",
 <>)
     <fields>

 Domain_Policy_m2m(models.Model):
     class Meta:
         constraints = [
             models.UniqueConstraint(
                 fields=["domain", "status", ],
                 name="unique_constraint"
             )
         ]
     domain = models.ForeignKey(Domain, <>)
     policy = models.ForeignKey(Policy, <>)
     status = models.ForeignKey(PolicyStatus, <>)
 }}}

 Scenario:
 I go to associate a new policy with a domain:
 `        domain.policy_set.add(policy, through_defaults={"status":
 policy.status})`

 However, the domain already has a policy with this status and raises
 IntegrityError. Instead, I would like to replace the existing policy with
 the new one. I can do this in my code:


 {{{
         new_policy = Policy(<>)
         existing_policy =
 domain.policy_set.filter(status=new_policy.status).first()
         if existing_policy:
             domain.policy_set.remove(existing_policy)
         domain.policy_set.add(policy, through_defaults={"status":
 policy.status})
 }}}

 Instead, I wanted to take care of this in a more permanent way, so I
 created a m2m_change signal receive:

 {{{
 from <> import Policy

 @receiver(m2m_changed, sender=Domain_Policy_m2m)
 def delete_old_m2m(action, instance, pk_set, **kwargs):
     if action == "pre_add":

         pk = min(pk_set)
         policy = Policy.objects.get(pk=pk)
         status = policy.status
         existing_policy = Domain_Policy_m2m.objects.filter(status=status,
 domain=instance)

         if existing_policy.exists():
             existing_policy.delete()
 }}}

 This works, but it's not pretty. It would be excellent if I could have the
 through_defaults as an argument, so I could do something like:

 {{{
 @receiver(m2m_changed, sender=Domain_Policy_m2m)
 def delete_old_m2m(action, instance, through_defaults, **kwargs):
     if action == "pre_add":

         status = through_defaults["status"]
         existing_policy = Domain_Policy_m2m.objects.filter(status=status,
 domain=instance)

         if existing_policy.exists():
             existing_policy.delete()
 }}}

 Even the object(s) being added would be so much more convenient than the
 pk:

 {{{
 @receiver(m2m_changed, sender=Domain_Policy_m2m)
 def delete_old_m2m(action, instance, model_set, **kwargs):
     if action == "pre_add":

         model = min(model_set)
         existing_policy =
 Domain_Policy_m2m.objects.filter(status=model.status, domain=instance)

         if existing_policy.exists():
             existing_policy.delete()
 }}}

-- 
Ticket URL: <https://code.djangoproject.com/ticket/33369>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/056.c48c7a3b8cb85d3e8929b1595c73932a%40djangoproject.com.

Reply via email to