Re: Makemigrations hooks for third-party apps?

2021-12-17 Thread Javier Buzzi
A bit late, unrelated: what is the reason behind not having django just add 
the extension `HStoreExtension` when it detects `hstorefield`? From what i 
can see, there there is a `create .. if not exists` on the sql command, and 
seems like a no brainer.

Thanks

On Wednesday, June 7, 2017 at 2:42:34 AM UTC+2 charettes wrote:

> Hello Craig,
>
> There's unfortunately no way to hook into the migration auto-detector to 
> specify
> additional operations to be added.
>
> What I suggest you do instead is dynamically inject operations in the plan 
> that
> is about to be run by connecting a pre_migrate signal. When you detect
> CreateModel operations for subclasses of your AbtractModel you could insert
> operations to be performed before and after them. This is pattern we use
> internally to perform ContentType rename on RenameModel operations[0].
>
> As for the makemigrations hooks I think it would require a lot of thought 
> and
> efforts to get right. Right now the auto-detector is a black box that 
> deals with
> dependencies and model state deltas resolution.
>
> In an ideal world model/fields objects would be able to generate the list 
> of
> operations they require to move from state A to B as a public API and the
> auto-detector would simply iterate over the existing migrations state 
> models and
> compare them against the current state of the project.
>
> Best,
> Simon
>
> [0] 
> https://github.com/django/django/blob/525dc283a68c0d47f5eb2192cc4a20111d561ae0/django/contrib/contenttypes/management/__init__.py#L45-L84
>
>
> Le mardi 6 juin 2017 16:45:05 UTC-4, Craig de Stigter a écrit :
>>
>> Hi there
>>
>> I'm in the early stages of developing a third party app which makes heavy 
>> use of postgres extensions. The app provides an abstract model, so I won't 
>> have direct control of user's models or migrations. I'm having trouble 
>> getting it to generate sensible migrations for users.
>>
>> At present the migration process for users is going to be "copy and paste 
>> my migration file template into your project, and change the model names to 
>> match your app". As far as I can tell there is no way to generate 
>> migrations with anything custom.
>>
>> The operations I need to generate are:
>>
>>- CreateExtension()
>>- RunSQL() - (to create slightly non-standard constraints)
>>
>> e.g. perhaps my model could supply a couple of methods:
>>
>>- Model.Meta.pre_creation_operations() - returns a list of operations 
>>to apply before the auto-detected model operations, e.g. creating 
>>extensions etc
>>- Model.Meta.post_creation_operations() - the same but applied after 
>>the auto-detected operations, e.g. adding constraints/indexes.
>>
>> I'm envisaging that the operations returned from both methods would be 
>> deconstructed and copied into migration files when makemigrations is run.
>>
>> Has there been any discussion about this previously? I haven't found any.
>>
>> Thanks
>> Craig de Stigter
>>
>

-- 
You received this message because you are subscribed to the Google Groups 
"Django developers  (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-developers+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-developers/1891cd07-6fa2-4e94-8105-bbae0f86a96dn%40googlegroups.com.


Feature Request: Add through_defaults as argument for m2m_changed signal

2021-12-17 Thread Shmuel Treiger


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)  PolicyStatus(models.Model): status = 
models.CharField(<>) Policy(models.Model) status = 
models.ForeignKey(PolicyStatus, <>) domains = 
models.ManyToManyField(Domain, through="Domain_Policy_m2m", <>)  
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 receiver, per the documentation 

:
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() 
I could alternatively override Domain_Policy_m2m.save(<>) and use 
Domain_Policy_m2m.objects.create(<>) but that's just seems less easily 
maintainable for future developers.

I'll also note that the Django documentation (linked above and here 
)
 
indicates that this would be incorrect implementation:

Using add() with a many-to-many relationship, however, will not call any 
save() methods (the bulk argument doesn’t exist), but rather create the 
relationships using QuerySet.bulk_create(). If you need to execute some 
custom logic when a relationship is created, listen to the m2m_changed 
signal, which will trigger pre_add and post_add actions.

-- 

This email may contain confidential material; unintended recipients must 
not disseminate, use, or act upon any information in it. If you received 
this email in error, please contact the sender and permanently delete the 
email.

-- 
You received this message because you are subscribed to the Google Groups 
"Django developers  (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-developers+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-developers/812b7b81-14f8-4840-8756-e58c972d769fn%40googlegroups.com.