ここのところDjangoを触り続けていてなかなか楽しいです。触っている中であるModelのオブジェクトを1つに統合する処理を書かなければいけなくなりました。そんなときに影響を受けるModelの洗い出しをどうするか調べたので残しておきます。
もうちょっと詳しく
こんな感じのモデルを例にします。
from django.db import models class Product(models.Model): name = models.CharField(verbose_name='商品名', max_length=100) price = models.IntegerField(verbose_name='価格') class ProductOrder(models.Model): product = models.ForeignKey('Product', verbose_name='注文商品', related_name='orderd_product') amount = models.IntegerField(verbose_name='注文数') order_date = models.DateField(verbose_name='注文日', auto_now=True) class ProductMakerName(models.Model): product = models.ForeignKey('Product', verbose_name='商品', related_name='makerd_product') maker_name = models.CharField(verbose_name='作成会社名', max_length=100)
Product
が中心にあり、ProductOrder
とProductMakerName
がそれを参照する関係にあります。
この時、例えばProduct
で本来同じであるはずの製品が何故か結構前から2個登録されており、そのままそれぞれにProductOrder
が紐付いてしまっていたとします。この2つになってしまったProduct
を1つに統合し、ProductOrder
等も統合しなければいけないケースがあったとします。
統合するとどちらかのオブジェクトは消えるわけですが、それを参照しているオブジェクトについては統合先のオブジェクトにリレーションを貼り直さなければいけません。そのためには、Product
をどのモデルが参照しているかの一覧が欲しくなります。
どうするのか
1.7より前のDjangoではModel._meta.user._meta.get_all_related_objects()
で関連するモデルを取得できたようですが、廃止されたようです。以下のドキュメントにget_all_related_objects
相当の結果を取得するためのコード例が示されていました。
https://docs.djangoproject.com/en/2.0/ref/models/meta/#migrating-from-the-old-api
これらの例をまとめると、こんな感じの処理で関連するModelが取れるようです。
def get_all_related_models(target_object): """ 被参照関係にあるモデルクラスの一覧を戻す :param model target_object: 捜索対象 :return: 被参照関係にあるモデルクラスのリスト :rtype: list[class] """ related_list = [ f.related_model for f in target_object._meta.get_fields() if (f.one_to_many or f.one_to_one or f.many_to_many) and f.auto_created and not f.concrete ] return related_list
対象のModelの全列をget_fields()
で取得し、関連を持っているかを確認した上でそのrelated_model
を参照することで関連するModelの一覧を列挙できるようです。
実際に実行してみます。
n [6]: prodct = Product.objects.get(pk=1) In [7]: In [7]: def get_all_related_models(target_object): ...: related_list = [ ...: f.related_model for f in target_object._meta.get_fields() ...: if (f.one_to_many or f.one_to_one or f.many_to_many) and f.auto_created and not f.concrete ...: ] ...: ...: return related_list ...: In [8]: get_all_related_models(prodct) Out[8]: [app1.models.ProductOrder, app1.models.ProductMakerName]
期待どおりに[app1.models.ProductOrder, app1.models.ProductMakerName]
という形で依存関係を持っているModelの一覧が期待通り取得できています。あとは、ProductOrder
やProductMakerName
側に対して対象のProduct
オブジェクトを参照しているオブジェクトを取得し、更新すれば完了です。
ProductOrder.objects.filter(product=product).update(product=merged_product)
実際はProductOrder
側等でユニーク制約等を考慮してすでにあるエントリは更新ではなく削除するなどの対応が必要になることもあると思いますが、概ねこの流れで良いと思います。