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

私はそれらを多くのソースからインポートしており、私のWebサイトのユーザーは新しい場所を追加できるので、管理インターフェースからそれらをマージする方法が必要です。問題は、名前はさまざまな方法で綴ることができるため、あまり信頼できないということです。私は次のようなものを使用するのに慣れています。

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を使用する他のすべてのモデルを新しい値で配置するように更新する必要があります。しかし、このリストにすべての新しいモデルを追加する必要があるため、これはあまり良い解決策ではありません。

一部のオブジェクトを削除する前に、すべての外部キーを「カスケード更新」するにはどうすればよいですか?

または、マージを実行/回避する他のソリューションがあるかもしれません

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

Django1.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

現在、関連するモデルを組み込んだ最新のモデルマージ関数を備えた2つのライブラリが存在します。

DjangoExtensionsのmerge_model_instances管理コマンド。

Django Super Deduper

于 2017-12-16T20:14:40.593 に答える
1

Django Adminでレコードをマージするソリューションを探していて、それを実行しているパッケージを見つけました(https://github.com/saxix/django-adminactions)。

使い方:

パッケージのインストール: pip install django-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を追加します。url(r'^adminactions/', include('adminactions.urls')),

今それを試してみました、それは私のために働きます。

于 2018-06-09T11:55:48.880 に答える