Django:合并对象

Val*_*lev 6 python django cascade foreign-keys

我有这样的模特:

class Place(models.Model):
    name = models.CharField(max_length=80, db_index=True)
    city = models.ForeignKey(City)
    address = models.CharField(max_length=255, db_index=True)
    # and so on
Run Code Online (Sandbox Code Playgroud)

由于我从许多来源导入它们,并且我的网站用户能够添加新的地方,我需要一种方法来从管理界面合并它们.问题是,名称不是很可靠,因为它们可以用许多不同的方式拼写等等我以前习惯使用这样的东西:

class Place(models.Model):
    name = models.CharField(max_length=80, db_index=True) # canonical
    city = models.ForeignKey(City)
    address = models.CharField(max_length=255, db_index=True)
    # and so on

class PlaceName(models.Model):
    name = models.CharField(max_length=80, db_index=True)
    place = models.ForeignKey(Place)
Run Code Online (Sandbox Code Playgroud)

像这样查询

Place.objects.get(placename__name='St Paul\'s Cathedral', city=london)
Run Code Online (Sandbox Code Playgroud)

并像这样合并

class PlaceAdmin(admin.ModelAdmin):
    actions = ('merge', )

    def merge(self, request, queryset):
        main = queryset[0]
        tail = queryset[1:]

        PlaceName.objects.filter(place__in=tail).update(place=main)
        SomeModel1.objects.filter(place__in=tail).update(place=main)
        SomeModel2.objects.filter(place__in=tail).update(place=main)
        # ... etc ...

        for t in tail:
            t.delete()

        self.message_user(request, "%s is merged with other places, now you can give it a canonical name." % main)
    merge.short_description = "Merge places"
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,我必须使用FK将所有其他模型更新为具有新值的Place.但这不是一个很好的解决方案,因为我必须将每个新模型添加到此列表中.

如何在删除某些对象之前"级联更新"所有外键?

或者可能有其他解决方案可以/避免合并

Val*_*lev 6

如果有人有兴趣,这里真的是通用代码:

def merge(self, request, queryset):
    main = queryset[0]
    tail = queryset[1:]

    related = main._meta.get_all_related_objects()

    valnames = dict()
    for r in related:
        valnames.setdefault(r.model, []).append(r.field.name)

    for place in tail:
        for model, field_names in valnames.iteritems():
            for field_name in field_names:
                model.objects.filter(**{field_name: place}).update(**{field_name: main})

        place.delete()

    self.message_user(request, "%s is merged with other places, now you can give it a canonical name." % main)
Run Code Online (Sandbox Code Playgroud)

  • FWIW我发现这个例子更全面:http://djangosnippets.org/snippets/2283/ (6认同)
  • `_meta.get_all_related_objects` 在 Django 1.8 中被弃用 https://docs.djangoproject.com/en/1.8/ref/models/meta/#migrating-from-the-old-api (2认同)