user3234309
user3234309

Reputation: 179

Django: Creating and saving another model in a save override

I have three models: Node, Link, and Path. A Link is a relationship between two nodes, and a path is a list of nodes. I'm trying to override the Path save function to create a Link between all adjacent nodes in the path. I wrote an add_link function in the Path model and am calling it in the save function in the Path model for all adjacent pairs. Although the Path is saving properly, and I can create Links by using the add_link function in the console, they are not being created in Path's save function. What am I missing?

Here are the models:

class Node(models.Model):
    title = models.CharField(max_length=200, blank=True)
    links = models.ManyToManyField('self', through='Link',
                                           symmetrical=False,
                                           related_name='related_to+')
    def add_link(self, other, symm=True):
        link, created = Link.objects.get_or_create(
            from_node=self,
            to_node=other)
        if symm:
            # avoid recursion by passing `symm=False`
            other.add_link(self, False)
        return link

class Link(models.Model):
    from_node = models.ForeignKey(Node, related_name="from")
    to_node = models.ForeignKey(Node, related_name="to")

class Path(models.Model):
    nodes = models.ManyToManyField(Node, related_name="nodes",through='PathNodeRelationship')

    def save(self, *args, **kwargs):
        super(Path, self).save(*args, **kwargs)

        # save all not-existent links on this path
        nodes = self.nodes.all()
        if nodes:
            f = nodes[0]
            i = 1
            while i < len(nodes):
                s = nodes[i]

                f.add_link(s)
                f = s
                i += 1

class PathNodeRelationship(models.Model):
    node = models.ForeignKey(Node)
    path = models.ForeignKey(Path)
    order_index = models.IntegerField()

**Edit: the links are created when calling path.save() in the console, but they are not when using the admin interface. This is how I am doing the admin. **

class NodeInline(admin.TabularInline):
    model = Path.nodes.through
    extra = 1

class PathAdmin(admin.ModelAdmin):
      inlines = (NodeInline,)
admin.site.register(Path, PathAdmin)

2nd edit: Looks like around 3-4 years ago this was an issue with admin m2m that has some hacky fixes... I haven't found out if there's anything better nowadays though.

Upvotes: 2

Views: 1494

Answers (1)

Kevin Cherepski
Kevin Cherepski

Reputation: 1483

I'm not sure what could be going wrong for you because when I test your code it works.

For what it's worth, one recommendation though would be to make this code a little more elegant...

    def save(self, *args, **kwargs):
        super(Path, self).save(*args, **kwargs)

        # save all not-existent links on this path
        previous_node = None
        for node in self.nodes.all():
            if previous_node is not None:
                previous_node.add_link(node)
            previous_node = node

Here is a test I ran via shell...

>>> Link.objects.all()
[]
>>> path = Path.objects.get(id=2)
>>> path.save()
>>> Link.objects.all()
[<Link: Link object>, <Link: Link object>, <Link: Link object>, <Link: Link object>, <Link: Link object>, <Link: Link object>]

As you can see, Link objects went from an empty list to being populated after running save against a Path object that has some PathNodeRelationships set.

Upvotes: 3

Related Questions