6

我有这样的模型:

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"

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

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

或者也许还有其他解决方案可以做/避免合并

4

5 回答 5

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)
于 2010-08-06T09:11:04.290 回答
3

根据已接受答案的评论中提供的片段,我能够开发以下内容。此代码不处理 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
于 2016-12-22T20:24:25.443 回答
2

在 Django 1.10 上测试。希望它可以服务。

def merge(primary_object, alias_objects, model):
"""Merge 2 or more objects from the same django model
The alias objects will be deleted and all the references 
towards them will be replaced by references toward the 
primary object
"""
if not isinstance(alias_objects, list):
    alias_objects = [alias_objects]

if not isinstance(primary_object, model):
    raise TypeError('Only %s instances can be merged' % model)

for alias_object in alias_objects:
    if not isinstance(alias_object, model):
        raise TypeError('Only %s instances can be merged' % model)

for alias_object in alias_objects:
    # Get all the related Models and the corresponding field_name
    related_models = [(o.related_model, o.field.name) for o in alias_object._meta.related_objects]
    for (related_model, field_name) in related_models:
        relType = related_model._meta.get_field(field_name).get_internal_type()
        if relType == "ForeignKey":
            qs = related_model.objects.filter(**{ field_name: alias_object })
            for obj in qs:
                setattr(obj, field_name, primary_object)
                obj.save()
        elif relType == "ManyToManyField":
            qs = related_model.objects.filter(**{ field_name: alias_object })
            for obj in qs:
                mtmRel = getattr(obj, field_name)
                mtmRel.remove(alias_object)
                mtmRel.add(primary_object)
    alias_object.delete()
return True
于 2017-11-06T21:39:39.850 回答
2

现在有两个库具有包含相关模型的最新模型合并功能:

Django Extensions 的merge_model_instances管理命令。

Django 超级重复数据删除器

于 2017-12-16T20:14:40.593 回答
1

我正在寻找一种在 Django Admin 中合并记录的解决方案,并找到了一个正在执行此操作的包(https://github.com/saxix/django-adminactions)。

如何使用:

安装包: pip install django-adminactions

将 adminactions 添加到您的 INSTALLED_APPS:

INSTALLED_APPS = (
    'adminactions',
    'django.contrib.admin',
    'django.contrib.messages',
)

将操作添加到admin.py

from django.contrib.admin import site
import adminactions.actions as actions

actions.add_to_site(site)

将服务网址添加到您的 urls.py:url(r'^adminactions/', include('adminactions.urls')),

刚刚试了一下,对我有用。

于 2018-06-09T11:55:48.880 回答