scherand
scherand

Reputation: 2358

Run code when "foreign" object is added to set

I have a foreign key relationship in my Django (v3) models:

class Example(models.Model):
    title = models.CharField(max_length=200)  # this is irrelevant for the question here
    not_before = models.DateTimeField(auto_now_add=True)
    ...

class ExampleItem(models.Model):
    myParent = models.ForeignKey(Example, on_delete=models.CASCADE)
    execution_date = models.DateTimeField(auto_now_add=True)
    ....

Can I have code running/triggered whenever an ExampleItem is "added to the list of items in an Example instance"? What I would like to do is run some checks and, depending on the concrete Example instance possibly alter the ExampleItem before saving it.

To illustrate

Let's say the Example's class not_before date dictates that the ExampleItem's execution_date must not be before not_before I would like to check if the "to be saved" ExampleItem's execution_date violates this condition. If so, I would want to either change the execution_date to make it "valid" or throw an exception (whichever is easier). The same is true for a duplicate execution_date (i.e. if the respective Example already has an ExampleItem with the same execution_date).

So, in a view, I have code like the following:

def doit(request, example_id):
    # get the relevant `Example` object
    example = get_object_or_404(Example, pk=example_id)
    # create a new `ExampleItem`
    itm = ExampleItem()
    # set the item's parent
    itm.myParent = example  # <- this should trigger my validation code!
    itm.save()              # <- (or this???)

The thing is, this view is not the only way to create new ExampleItems; I also have an API for example that can do the same (let alone that a user could potentially "add ExampleItems manually via REPL). Preferably the validation code must not be duplicated in all the places where new ExampleItems can be created.

I was looking into Signals (Django docu), specifically pre_save and post_save (of ExampleItem) but I think pre_save is too early while post_save is too late... Also m2m_changed looks interesting, but I do not have a many-to-many relationship.

What would be the best/correct way to handle these requirements? They seem to be rather common, I imagine. Do I have to restructure my model?

Upvotes: 0

Views: 45

Answers (1)

bruno desthuilliers
bruno desthuilliers

Reputation: 77912

The obvious solution here is to put this code in the ExampleItem.save() method - just beware that Model.save() is not invoked by some queryset bulk operations.

Using signals handlers on your own app's models is actually an antipattern - the goal of signal is to allow for your app to hook into other app's lifecycle without having to change those other apps code.

Also (unrelated but), you can populate your newly created models instances directly via their initializers ie:

itm = ExampleItem(myParent=example)
itm.save()

and you can even save them directly:

# creates a new instance, populate it AND save it
itm = ExampleItem.objects.create(myParent=example)

This will still invoke your model's save method so it's safe for your use case.

Upvotes: 1

Related Questions