Django:合并对象
我有这样一个模型:
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
因为我从很多地方导入这些数据,而且我的网站用户可以添加新的地点,所以我需要一个方法来通过管理界面合并这些地点。问题是,地点的名称不太可靠,因为它们可能有很多不同的拼写方式等等。
我习惯使用这样的方式:
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)
像这样查询
Place.objects.get(placename__name='St Paul\'s Cathedral', city=london)
然后像这样合并
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"
正如你所看到的,我必须用新的值更新所有与地点相关的其他模型。但是这不是一个很好的解决方案,因为我必须把每个新模型都添加到这个列表中。
我该如何在删除某些对象之前“级联更新”所有外键?
或者,也许还有其他方法可以避免合并。
5 个回答
2
根据在接受的回答中评论里提供的代码片段,我开发了以下内容。这个代码没有处理通用外键(GenericForeignKeys)。我不太喜欢使用通用外键,因为我觉得这说明你使用的模型有问题。
在这个回答中,我用了很多代码来实现这个功能,但我已经更新了我的代码,使用了在这里提到的django-super-deduper。那时候,django-super-deduper对不受管理的模型处理得不好。我提交了一个问题,看起来很快就会修复。我还使用了django-audit-log,我不想把那些记录合并在一起。我保留了函数的签名和@transaction.atomic()
这个装饰器。这在出现问题时是很有帮助的。
from django.db import transaction
from django.db.models import Model, Field
from django_super_deduper.merge import MergedModelInstance
class MyMergedModelInstance(MergedModelInstance):
"""
Custom way to handle Issue #11: Ignore models with managed = False
Also, ignore auditlog models.
"""
def _handle_o2m_related_field(self, related_field: Field, alias_object: Model):
if not alias_object._meta.managed and "auditlog" not in alias_object._meta.model_name:
return super()._handle_o2m_related_field(related_field, alias_object)
def _handle_m2m_related_field(self, related_field: Field, alias_object: Model):
if not alias_object._meta.managed and "auditlog" not in alias_object._meta.model_name:
return super()._handle_m2m_related_field(related_field, alias_object)
def _handle_o2o_related_field(self, related_field: Field, alias_object: Model):
if not alias_object._meta.managed and "auditlog" not in alias_object._meta.model_name:
return super()._handle_o2o_related_field(related_field, alias_object)
@transaction.atomic()
def merge(primary_object, alias_objects):
if not isinstance(alias_objects, list):
alias_objects = [alias_objects]
MyMergedModelInstance.create(primary_object, alias_objects)
return primary_object
3
现在有两个库提供了最新的模型合并功能,这些功能可以处理相关的模型:
一个是Django Extensions里的merge_model_instances管理命令。
另一个是Django Super Deduper。
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)