calling object.delete() in a m2m receiver

Hello Django Community

These are my models: (The blue arrows represent m2m relations, the black arrows represent foreign keys)

And in Code:

class Device(models.Model):
    function_classes = models.ManyToManyField(
        FunctionClass,
        blank=True,
    )
class FunctionClass(models.Model):
    variable_collection = models.ManyToManyField(
        Variable,
        related_name='functionclass_set',
        blank=True,
    )

class VariableDeviceDescription(models.Model):
    class Meta:
        constraints = [
            models.UniqueConstraint(fields=['variable', 'device'], name="unique_variable_device")
        ]

    variable = models.ForeignKey(
        Variable,
        on_delete=models.CASCADE,
    )

    device = models.ForeignKey(
        Device,
        on_delete=models.CASCADE,
    )
class Variable(models.Model):
    pass

I am currently using the admin area exclusively, as I want to have the database with its mechanisms down perfectly before working on the page for the users. My problem is the following: users will have choose one or more FunctionClass instances. These FunctionClasses hold one or more Variable instances.

Now for each Variable that is selected in this manner I automatically generate a single VariableDeviceDescription filled with default values (this model holds device-specific data of a variable. The user has to change fields if the default values are not sufficient.)

I use a receiver for automatic instance creation:

@receiver(m2m_changed, sender=Device.function_classes.through)
def create_vdds(sender, instance, action, reverse, model, pk_set, **kwargs):

    if action == 'pre_add':
        fc_pre_add(instance, pk_set)

Lo and behold, when I save a Device instance, the VariableDeviceDescription instances are created, and they even show up in the inline that I prepared for them in the DeviceAdmin. The user can now go ahead and change stuff in the form if necessary.

I then applied the same logic to try and delete VariableDeviceDescription instances after Function Classes are removed from the m2m field in Device:

@receiver(m2m_changed, sender=Device.function_classes.through)
def device_m2m_changed(sender, instance, action, reverse, model, pk_set, **kwargs):

    if action == 'post_remove':
        removed_vars = get_vars_of_function_classes(pk_set)
        #gives me a queryset of all variables of the functionclasses that were removed
        for var in removed_vars:
            vdd_to_remove = VariableDeviceDescription.objects.filter(device=instance, variable=var).first()
            #gives me a queryset of the vdd which corresponds to the current removed variable
            vdd_to_remove.delete()

And well it just does not work. Once I press save, I can follow the debugger through this code, it seems to delete the element of the queryset without issue / exception, but then the page reloads and the VariableDeviceDescriptions are unchanged.

I have tried using a custom signal, which is sent out when the clean() method of the Device is called.

I have tried using a pre/post_save receiver on device, where I get the same effect.

I have a theory that the objects get deleted correctly when using these approaches, but get resurrected because their data is held in the inline form when I press save. However, I am terrible with forms / formsets and have no idea where to begin.

I have also tried using a pre/post_init receiver, despite the documentation warning me against making queries in receivers of this type. I had some success there, as the objects actually got deleted. For some reason, “Device” didn’t show up in the app list to the left anymore. Plus I had an error message telling me to “correct the error below” in the formset of the Device, which I could do nothing about. So I abandoned this idea as well.

I appreciate any pointers or ideas as to the solution to this problem :slight_smile:

Ok I’ve tried a few things. Turns out this actually works if I don’t use the “django-nested-admin” package.