I'm having a problem that's somewhat difficult to explain, so please bear with me. First of all, here are the relevant versions in case this ends up mattering: Django 2.0.3, Python 3.6.4, PostgreSQL 10.3.
Essentially, I have this structure for a few of my Django models:
class Capability(models.Model):
relationships = models.ManyToManyField('self',
symmetrical=False,
through='CapabilityRelationship',
blank=True)
name = models.CharField(max_length=255)
class CapabilityRelationship(models.Model):
from_capability = models.ForeignKey(Capability,
on_delete=models.CASCADE,
related_name='from_set',
blank=True,
null=True)
to_capability = models.ForeignKey(Capability,
on_delete=models.CASCADE,
related_name='to_set')
number = models.PositiveSmallIntegerField(validators=[MinValueValidator(1)])
def _get_complete_numbers(self):
# generate the complete numbers using depth-first search
def save(self, *args, **kwargs):
# Get the complete numbers before saving. If we're not able to generate the complete numbers, something is
# wrong, an error will be raised, and we don't want to save.
complete_numbers = self._get_complete_numbers()
super().save(*args, **kwargs) # Call the "real" save() method.
# Delete all old complete numbers.
self.capabilityrelationshipcompletenumber_set.all().delete()
# Save the new complete numbers.
for complete_number in complete_numbers:
CapabilityRelationshipCompleteNumber.objects.create(capability_relationship=self,
complete_number=complete_number)
class CapabilityRelationshipCompleteNumber(models.Model):
capability_relationship = models.ForeignKey(CapabilityRelationship, on_delete=models.CASCADE)
complete_number = models.CharField(max_length=255, unique=True)
To describe these models in words, I have a Capability
model, which has a many-to-many relationship with itself (captured in CapabilityRelationship
). In practice, this will be a "tree" where each node can have multiple children and multiple parents (i.e., it's a directed acyclic graph). Finally, each relationship instance can have multiple "complete numbers" (captured in CapabilityRelationshipCompleteNumber
).
The idea behind "numbers" and "complete numbers" is essentially the Dewey decimal system. A complete number of 1.2.3.4 would have 4 levels of Capability
objects, where 1 is the top-most level (i.e., root node) and 4 is the leaf. Because the structure I laid out above is a DAG and not actually a tree, a node can actually have multiple of these "complete" numbers since there can be multiple paths from any given node to its root.
If this description doesn't make since, please let me know, and I can mock something up in Paint.
I'm overriding the CapabilityRelationship.save()
method because I need to recompute the complete numbers each time the relationship is changed, since number
could've changed. So what I want to do is simply compute the new complete numbers, delete all of the old complete numbers, and then save the new ones.
The problem I'm encountering is that I simply cannot delete the old complete numbers, and it's baffling to me. I'm wondering if there's something about overriding CapabilityRelationship.save()
that I'm simply not getting. For example:
def save(self, *args, **kwargs):
complete_numbers = self._get_complete_numbers()
print('complete numbers: {}'.format(complete_numbers))
super().save(*args, **kwargs) # Call the "real" save() method.
print('before delete: {}'.format(self.capabilityrelationshipcompletenumber_set.all()))
self.capabilityrelationshipcompletenumber_set.all().delete()
print('after delete: {}'.format(self.capabilityrelationshipcompletenumber_set.all()))
for complete_number in complete_numbers:
CapabilityRelationshipCompleteNumber.objects.create(capability_relationship=self,
complete_number=complete_number)
print('after save: {}'.format(self.capabilityrelationshipcompletenumber_set.all()))
If I visit the admin site and set a leaf node's number
to 1, save it, and then modify it to 2, I get this output:
complete numbers: {'1.2.1.1', '2.2.1.1', '1.3.1.1', '1.1.1.1'}
before delete: <QuerySet []>
after delete: <QuerySet []>
after save: <QuerySet [<CapabilityRelationshipCompleteNumber: 1.1.1.1>, <CapabilityRelationshipCompleteNumber: 1.2.1.1>, <CapabilityRelationshipCompleteNumber: 1.3.1.1>, <CapabilityRelationshipCompleteNumber: 2.2.1.1>]>
complete numbers: {'1.1.1.2', '1.2.1.2', '2.2.1.2', '1.3.1.2'}
before delete: <QuerySet [<CapabilityRelationshipCompleteNumber: 1.1.1.1>, <CapabilityRelationshipCompleteNumber: 1.2.1.1>, <CapabilityRelationshipCompleteNumber: 1.3.1.1>, <CapabilityRelationshipCompleteNumber: 2.2.1.1>]>
after delete: <QuerySet []>
after save: <QuerySet [<CapabilityRelationshipCompleteNumber: 1.1.1.2>, <CapabilityRelationshipCompleteNumber: 1.2.1.2>, <CapabilityRelationshipCompleteNumber: 1.3.1.2>, <CapabilityRelationshipCompleteNumber: 2.2.1.2>]>
Now, all of this looks great, but when I visit the admin site to get the listing of all of the complete numbers, I see all 8 of the complete numbers computed so far, not the 4 that are currently correct after changing number
to 2. If I open up a Python shell and list out the complete numbers, I see all of them created so far:
> ./manage.py shell
Python 3.6.4 (default, Dec 19 2017, 15:24:51)
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.39.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from src.apps.api.models.capability import *
>>> CapabilityRelationshipCompleteNumber.objects.all()
<QuerySet [<CapabilityRelationshipCompleteNumber: 1.1.1.1>, <CapabilityRelationshipCompleteNumber: 1.1.1.2>, <CapabilityRelationshipCompleteNumber: 1.2.1.1>, <CapabilityRelationshipCompleteNumber: 1.2.1.2>, <CapabilityRelationshipCompleteNumber: 1.3.1.1>, <CapabilityRelationshipCompleteNumber: 1.3.1.2>, <CapabilityRelationshipCompleteNumber: 2.2.1.1>, <CapabilityRelationshipCompleteNumber: 2.2.1.2>]>
I see the same thing if I look directly at the DB using psql.
Clearly, for whatever reason, the delete call isn't actually happening. I've tried CapabilityRelationshipCompleteNumber.objects.filter(capability_relationship=self).delete()
, CapabilityRelationshipCompleteNumber.objects.all().delete()
, and issuing a raw SQL DELETE FROM api_capabilityrelationshipcompletenumber;
using connection.cursor()
. Nothing seems to work at all. I don't understand what's going on. I've read the Django documentation on deleting a QuerySet and overriding save()
, but I don't see anything that could help me diagnose my problem.
Does anyone know what's going on here? Any help is greatly appreciated. Please let me know if I can clarify any of this.