Python Django:合并对象

Python Django:合并对象,python,django,foreign-keys,cascade,Python,Django,Foreign Keys,Cascade,我有这样的模型: 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)
    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"
然后像这样合并

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更新所有其他模型,以使用新值放置。但这不是一个很好的解决方案,因为我必须将每个新型号都添加到此列表中

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


或者,如果有人对此感兴趣,可能还有其他解决方案可以进行/避免合并,这里有一些通用代码:

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)

根据接受答案中评论中提供的片段,我能够开发以下内容。此代码不处理GenericForeignKey。我不认为这是因为它们的使用,因为我相信这表明您使用的模型存在问题

在这个答案中,我使用了很多代码来实现这一点,但我已经更新了我的代码,以使用上面提到的django super重复数据消除程序。当时,django super Duplicater并没有很好地处理非托管模型。我提交了一个问题,看起来很快就会得到纠正。我还使用django审计日志,我不想合并这些记录。我保留了签名和
@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

在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

现在存在两个库,它们具有最新的模型合并功能,可合并相关模型:

Django Extensions的管理命令


我正在Django Admin中寻找合并记录的解决方案,并找到了一个正在执行此操作的包()

如何使用:

安装程序包:
pip安装django adminactions

将adminactions添加到已安装的应用程序:

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)
将服务url添加到url.py:
url(r'^adminactions/',包括('adminactions.url'),


刚刚试过,它对我很有用。

FWIW我发现这个例子更全面:代码片段似乎不再适用于我,在ForeignKey上失败。加上交易折旧,以支持原子。加上iteritems()成为python3中的items()。(最后两个问题很容易解决,第一个不是)。在解决第一个问题时,我发现问题可能与django guardian的groupobjectpermissions有关。但无法解决此问题:(
\u meta.get\u所有相关的\u对象
在Django 1.8中被弃用。虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,则仅链接的答案可能无效。-嗯……我不知道写m的更好方法是什么y回答…我只是想帮助其他与我相同的人使用现成的解决方案,而不是编写自己的代码。最佳做法是发布解决问题的代码,特别是解释其工作原理。未来几年,当您使用的链接不再活动时,可能会有人参考此答案…我有e文在stackexchange上找到了我自己五年前使用的答案…:)我明白了。好的。只是在这个特殊情况下,我不能发布所有代码,因为它是一个完成所有工作的包。但是我添加了关于如何安装这个包的说明(从包的文档中复制粘贴)@J.Win。请检查现在是否看起来更好,我做了一些更改:)