2016-07-04 8 views
1

CMSの一連のDjangoモデルを作成して、一連のProductを表示しました。Django:複数の子モデルタイプを持つ親モデル

class ProductBanner(ProductRow): 
    wide_image = models.ImageField(upload_to='product_images/banners/', max_length=100, null=False, blank=False) 
    top_heading_text = models.CharField(max_length=100, null=False, blank=False) 
    main_heading_text = models.CharField(max_length=200, null=False, blank=False) 
    ... 

class ProductMagazineRow(ProductRow): 
    title = models.CharField(max_length=50, null=False, blank=False) 
    show_descriptions = models.BooleanField(null=False, blank=False, default=False) 
    panel_1_product = models.ForeignKey(Product, related_name='+', null=False, blank=False) 
    panel_2_product = models.ForeignKey(Product, related_name='+', null=False, blank=False) 
    panel_3_product = models.ForeignKey(Product, related_name='+', null=False, blank=False) 
    ... 

class ProductTextGridRow(ProductRow): 
    title = models.CharField(max_length=50, null=False, blank=False) 
    col1_title = models.CharField(max_length=50, null=False, blank=False) 
    col1_product_1 = models.ForeignKey(Product, related_name='+', null=False, blank=False) 
    col1_product_2 = models.ForeignKey(Product, related_name='+', null=False, blank=False) 
    col1_product_3 = models.ForeignKey(Product, related_name='+', null=False, blank=False) 
    ... 

ので:

各ページには、一連の行が含まれているので、私は一般的な

class ProductRow(models.Model): 
    slug = models.SlugField(max_length=100, null=False, blank=False, unique=True, primary_key=True) 
    name = models.CharField(max_length=200,null=False,blank=False,unique=True) 
    active = models.BooleanField(default=True, null=False, blank=False) 

を持って、私は、行の種類ごとに、このモデルの子供たちのシリーズを、持っていますに。

はその後、私のProductPageに私はProductRowの一連の持っている:、

class ProductPage(models.Model): 
    slug = models.SlugField(max_length=100, null=False, blank=False, unique=True, primary_key=True) 
    name = models.CharField(max_length=200, null=False, blank=False, unique=True) 
    title = models.CharField(max_length=80, null=False, blank=False) 
    description = models.CharField(max_length=80, null=False, blank=False) 
    row_1 = models.ForeignKey(ProductRow, related_name='+', null=False, blank=False) 
    row_2 = models.ForeignKey(ProductRow, related_name='+', null=True, blank=True) 
    row_3 = models.ForeignKey(ProductRow, related_name='+', null=True, blank=True) 
    row_4 = models.ForeignKey(ProductRow, related_name='+', null=True, blank=True) 
    row_5 = models.ForeignKey(ProductRow, related_name='+', null=True, blank=True) 

私が持っている問題を、私はProductPageのもの5行が別の子タイプのいずれかとすることができるようにしたいということですProductRow。しかし、私は、このようなviews.py

としてそれらを反復する場合:

product_page_rows = [product_page.row_1,product_page.row_2,product_page.row_3,product_page.row_4,product_page.row_5] 

して、テンプレートに:

{% for row in product_page_rows %} 
    <pre>{{ row.XXXX }}</pre> 
{% endfor %} 

私はXXXXとしてどの子フィールドを参照することはできません。

Iはしようとすると、各行がどのクラスを区別するために、親と子供の両方に "type()" メソッドを追加しようとした:

class ProductRow(models.Model): 

    ... 

    @classmethod 
    def type(cls): 
     return "generic" 

class ProductTextGridRow(TourRow): 

    ... 

    @classmethod 
    def type(cls): 
     return "text-grid" 

私が変更された場合XXXX.type()のテンプレートでは、リスト内のすべての項目に対して"generic"が表示されます(データのさまざまな行タイプを定義していました)ので、すべてがProductRowラット彼女は適切な子供の種類よりも。私は子供が親のタイプではなく正しい子のタイプとしてアクセスできるようにする方法や、実際にどの子供のタイプであるかを判断する方法を見つけることができません(私はcatchを送ってAttributeErrorを試しました。

誰も共通の親を含むさまざまなモデルタイプのリストを適切に処理し、適切な子モデルタイプのフィールドにアクセスできるように助言できますか?

+0

何かはあなたがすべてのそれらのモデル内に複数の 'ForeignKey'sをしてもよろしいです...ここで間違って見えますか?それぞれの 'ForeignKey'は' 1-N'関係を表しています。例えば、あなたのモデル 'ProductRow'は' ProductPage'で異なることを表す5つの異なる関係を持っています。これがあなたが望むものかどうかを確認するだけです。 –

+0

はい、各商品ページには、さまざまなProductRowタイプのいずれか – alexbfree

+0

なり、それぞれが5つの行を持つことになります...と、各製品の行は、それが簡単になります場合は、指向より多くの標準オブジェクトを検討し、複数の異なるページ – alexbfree

答えて

5

これは(「常に」読み)は、一般的にこのような何か持っている悪いデザインです:

class MyModel(models.Model): 
    ... 
    row_1 = models.ForeignKey(...) 
    row_2 = models.ForeignKey(...) 
    row_3 = models.ForeignKey(...) 
    row_4 = models.ForeignKey(...) 
    row_5 = models.ForeignKey(...) 

それはスケーラブルではありませんが。もしあなたが5、1日(誰が知っていますか?)ではなく6行または4行を許可したいならば、新しい行を追加/削除し、データベーススキームを変更しなければなりません。 DRYではなく、コードの量は処理する行の数に依存し、多くのコピー・ペーストを伴います。

これは、5行ではなく100行を処理する必要がある場合、どのように行うのかと思えば、悪い設計であることが明らかになります。

少なくとも1行、最大で5行あるように、ManyToManyField()とカスタムロジックを使用する必要があります。

class ProductPage(models.Model): 
    ... 
    rows = models.ManyToManyField(ProductRow) 

あなたの行を注文する場合は、あなたがこのような明示的な中間モデルを使用することができます。私はN行(のは5としましょう)を許可したい

class ProductPageRow(models.Model): 

    class Meta: 
     order_with_respect_to = 'page' 

    row = models.ForeignKey(ProductRow) 
    page = models.ForeignKey(ProductPage) 

class ProductPage(models.Model): 
    ... 
    rows = model.ManyToManyField(ProductRow, through=ProductPageRow) 

、あなたはあなたを実装することができ自order_with_respect_toロジック:

from django.core.validators import MaxValueValidator 

class ProductPageRow(models.Model): 

    class Meta: 
     unique_together = ('row', 'page', 'ordering') 

    MAX_ROWS = 5 

    row = models.ForeignKey(ProductRow) 
    page = models.ForeignKey(ProductPage) 
    ordering = models.PositiveSmallIntegerField(
     validators=[ 
      MaxValueValidator(MAX_ROWS - 1), 
     ], 
    ) 

タプル('row', 'page', 'ordering')一意性が施行され、そして順序は5つの値(0〜4)に限定され、Tここではカップルの出現は5回以上ではありません('row', 'page')

ただし、あなたは(あなたのDBMSコンソールに直接SQLクエリの入力を含む)すべての平均によって、データベース内の複数のN行を追加する方法がないことを100%確実にする非常に良い理由がない限り、そこにそれをこのレベルに「ロック」する必要はありません。

「信頼できない」ユーザーはすべて、HTMLフォーム入力でのみデータベースを更新できる可能性が非常に高いです。 formsetsを使用すると、フォームに入力する際に​​最小と最大の両方の行が強制的に適用されます。

注:これは他のモデルにも当てはまります。 foobar_Nという名前のフィールドの集まり(Nは増分整数)は、非常に悪い データベース設計を裏付けています。


しかし、これはあなたの問題を解決しません。

親モデルインスタンスから子モデルインスタンスを戻す最も簡単な方法は、一致するインスタンスを取得するまで各子モデルをループすることです。

class ProductRow(models.Model): 
    ... 
    def get_actual_instance(self): 
     if type(self) != ProductRow: 
      # If it's not a ProductRow, its a child 
      return self 
     attr_name = '{}_ptr'.format(ProductRow._meta.model_name) 
     for possible_class in self.__subclasses__(): 
      field_name = possible_class._meta.get_field(attr_name).related_query_name() 
      try: 
       return getattr(self, field_name) 
      except possible_class.DoesNotExist: 
       pass 
     # If no child found, it was a ProductRow 
     return self 

しかし、それはそれぞれの試みのためにデータベースをヒットする必要。そしてそれはまだ非常にドライではありません。 ProductRowの子供のそれぞれである:あなたがmulti-table inheritanceを使用しているので、あなたのtype()方法は動作しません

from django.contrib.contenttypes.models import ContentType 

class ProductRow(models.Model): 
    ... 
    actual_type = models.ForeignKey(ContentType, editable=False) 

    def save(self, *args, **kwargs): 
     if self._state.adding: 
      self.actual_type = ContentType.objects.get_for_model(type(self)) 
     super().save(*args, **kwargs) 

    def get_actual_instance(self): 
     my_info = (self._meta.app_label, self._meta.model_name) 
     actual_info = (self.actual_type.app_label, self.actual_type.model) 
     if type(self) != ProductRow or my_info == actual_info: 
      # If this is already the actual instance 
      return self 
     # Otherwise 
     attr_name = '{}_ptr_id'.format(ProductRow._meta.model_name) 
     return self.actual_type.get_object_for_this_type(**{ 
      attr_name: self.pk, 
     }) 
+0

データベースの正規化とリンクテーブルの良い説明を提供するために時間を割いていただきありがとうございます。それが起こると、私は、この場合、固定された有限数の非正規化フィールドを好むいくつかの非常に特殊な理由があります。 「self.actual_type」ソリューションをありがとう。それは非常に巧妙で、正確に私が達成しようとしていたものです! – alexbfree

0

:それを得るための最も効率的な方法は、あなたの子供のタイプを教えてくれますフィールドを追加することです自動的に生成されたOneToOneFieldを使用してProductRowに接続された別のモデル。

  • あなたは、各ProductRowインスタンスは(3可能なもののうち)子の一種類のみを持っていることを確認した場合、その子がProductBannerであるかどうかを調べるための簡単な方法があり、ProductMagazineRowまたはProductTextGridRow、あなたがそうでなければ、その後、ProductRowの1つのインスタンスが同じティムに複数ProductBannerの1、ProductMagazineRowProductTextGridRowにリンクすることができ強制しない場合は、しかし、

    class ProductRow(models.Model): 
        ... 
        def get_type(self): 
         try: 
          self.productbanner 
          return 'product-banner' 
         except ProductBanner.DoesNotExist: 
          pass 
    
         try: 
          self.productmagazinerow 
          return 'product-magazine' 
         except ProductMagazineRow.DoesNotExist: 
          pass 
    
         try: 
          self.producttextgridrow 
          return 'product-text-grid' 
         except ProductTextGridRow.DoesNotExist: 
          pass 
    
         return 'generic' 
    
  • :し、適切なフィールドを使用するにはe。代わりに、特定のインスタンスで動作する必要があります:

    class ProductRow(models.Model): 
        ... 
        def get_productbanner(self): 
         try: 
          return self.productbanner 
         except ProductBanner.DoesNotExist: 
          return None 
    
        def get_productmagazinerow(self): 
         try: 
          return self.productmagazinerow 
         except ProductMagazineRow.DoesNotExist: 
          return None 
    
        def get_producttextgridrow(self) 
         try: 
          return self.producttextgridrow 
         except ProductTextGridRow.DoesNotExist: 
          return None 
    

は、データベース設計を改善するためのアントニオ・ピンサードの答えを持つとこれを組み合わせます。

+0

「子が重なっている場合」とはどういう意味ですか?子供たちとの関係は1対1であり、1人の子供が複数の子供を持つことはできません。 –

+0

ちょっとした明快さ: 'ProductRow'の '親'インスタンスは、' ProductBanner'、 'ProductMagazineRow'と' ProductTextGridRow'のインスタンスという、それぞれ 'child'を1つずつ持つことができます。私はこれを含めるように2番目のポイントの言語を変更します –

+0

ありがとう@YashTewari。 'self.productmagazinerow'などを試してみると、それらは特定の子タイプに固有のフィールドだと思いますか?だから本質的にアプローチは..特定の子タイプに固有のフィールドにアクセスしようとしています...それが存在しなければエラーをキャッチします。私はそれが好きです、賢い。 – alexbfree

関連する問題