2012-12-11 52 views
9

我有一個遺留數據庫,其中有一個表格代表文件系統中的節點。節點的類型很少,例如A,B,C和不同類型具有不同的屬性。在當前的數據庫設計中,有一張表包含有關節點的信息。如果該節點的類型爲A,則僅設置與類型A相關的字段。 現在我想將A,B,C類型表示爲模型。所產生的問題是:Django多個模型,相同的表

  1. 我想有這樣的行爲,所有三種類型都有一個名字屬性。我想通過name屬性過濾文件系統中的所有節點,並獲取好類型的對象列表。

  2. 每個節點作爲父鏈接,表示爲數據庫中的外鍵,因此可能會發生某種形式的繼承。

是否有可能在Django?

+0

你想使用當前數據庫模式(傳統模式)嗎? – borges

回答

3

是的,這是可能的。下面是一個例子:

models.py

from django.db import models 

# Create your models here. 
class NodeA(models.Model): 

    name_a = models.CharField(max_length=75, blank=True, null=True) 

    class Meta: 
     db_table = 'Nodes' 
     managed = False 

class NodeB(models.Model): 

    name_b = models.CharField(max_length=75, blank=True, null=True) 

    class Meta: 
     db_table = 'Nodes' 
     managed = False 

class NodeC(models.Model): 

    name_c = models.CharField(max_length=75, blank=True, null=True) 

    class Meta: 
     db_table = 'Nodes' 
     managed = False 

數據庫模式(SQLITE)的概念的

Nodes { 
    id  integer primary key 
    name_a TEXT 
    name_b TEXT 
    name_c TEXT } 

證明

import NodeA, NodeB, NodeC 

a = NodeA() 
a.name_a = 'Node A' 
a.save() 

b = NodeB() 
b.name_b = 'Node B' 
b.save() 

c = NodeC() 
c.name_c = 'Node C' 
c.save() 

這產生:

id  name_a  name_b  name_c 
1   Node A 
2      Node B 
3          Node C 
+0

我從來沒有見過以這種方式使用db_table。它與syncdb和/或South搭配很好嗎?也許我會嘗試使用代理模型。 –

+0

它不能很好地與syncdb配合使用。它會嘗試爲每個使用的模型創建表格。它需要手動創建。我不能說與南方的兼容性。我甚至不能說整體兼容性。我從來沒有像這樣的模式,但在我創建的小測試項目中測試它,它工作正常。代理模型可能也適用於在MasterNode模型中定義的所有必需字段,而代理模型只使用他們想要的字段。 –

+0

將managed = False添加到第二個和第三個表定義的元將修復sync​​db的問題。 –

4

我使用了一種有點不同的方法,它通過創建一個透視圖與南方很好地玩。透視圖是重命名模型中某些字段的代理,但保留列的名稱。

對我來說,這是一個顯示django ORM靈活性的例子。我不確定你是否想在生產代碼中使用它。因此它沒有足夠的測試,但它會給你一些想法。

想法

透視讓用戶創建不同的模式,以一個表,它可以有自己的方法,並有不同的字段名稱,但共享基礎模型和桌子。

它可以在同一個表中存儲不同的類型,這對記錄或事件系統來說非常方便。每個透視圖只能看到它自己的條目,因爲它在字段名稱action_type上被過濾。

模型是非託管的,但有一個自定義管理器,所以南方不會爲它創建新表。

用法

該實現是一類的裝飾,該修改的django模型的元數據。它採用「基礎」模型和別名字段字典。

讓在一個示例性的第一外表:

class UserLog(models.Model): 
""" 
A user action log system, user is not in this class, because it clutters import 
""" 
date_created = models.DateTimeField(_("Date created"), auto_now_add=True) 

# Action type is obligatory 

action_type = models.CharField(_("Action Type"), max_length=255) 
integer_field1 = models.IntegerField() 
integer_field2 = models.IntegerField() 
char_field1 = models.CharField(max_length=255) 
char_field2 = models.CharField(max_length=255) 


@ModelPerspective({ 
    'x': 'integer_field1', 
    'y': 'integer_field2', 
    'target': 'char_field1' 
}, UserLog) 
class UserClickLog(models.Model): 
    pass 

這就產生了一個模型,該模型的屬性x到integer_field1映射,y以integer_field2和目標char_field1和其中底層表相同的表作爲用戶日誌。

用法與其他型號沒有什麼不同,南方只會創建用戶日誌表。

現在讓我們看看如何實現這一點。

實施

它是如何工作的?

如果評估類,裝飾器將接收該類。這將猴子補丁類,所以它的實例將反映基表提供。

添加別名

如果我們走路有點深入到代碼。讀取別名字典並查找每個字段的基本字段。如果我們在基表中找到該字段,名稱就會改變。這有一個小副作用,它也改變列。所以我們必須從基本字段中檢索字段列。然後使用contribute_to_class方法將該字段添加到課程中,該方法負責所有簿記。

然後將所有未被別名的屬性添加到模型中。這不是perse需要的,但我選擇添加它們。

設置屬性

現在我們擁有所有的領域,我們要設置幾個屬性。該屬性將欺騙南方忽略表,但它有副作用。班級不會有經理。 (我們稍後會解決這個問題)。我們還從基礎模​​型複製表名(db_table),並將action_type字段默認爲類名。

我們需要做的最後一件事是提供一名經理。必須小心,因爲django聲明只有一個QuerySet管理器。我們通過使用deepcopy複製管理器來解決此問題,然後添加一個過濾器語句,該語句對類名稱進行過濾。

deepcopy的(查詢集())。濾波器(ACTION_TYPE = CLS。

這讓我們的表僅返回相關記錄。現在將其包裝到裝飾器中並完成。

這是代碼:

from django.db import models 
from django.db.models.query import QuerySet 

def ModelPerspective(aliases, model): 
    """ 
    This class decorator creates a perspective from a model, which is 
    a proxy with aliased fields. 

    First it will loop over all provided aliases 
    these are pairs of new_field, old_field. 
    Then it will copy the old_fields found in the 
    class to the new fields and change their name, 
    but keep their columnnames. 

    After that it will copy all the fields, which are not aliased. 

Then it will copy all the properties of the model to the new model. 

    Example: 
    @ModelPerspective({ 
     'lusername': 'username', 
     'phonenumber': 'field1' 
    }, User) 
    class Luser(models.Model): 
     pass 

    """ 
    from copy import deepcopy 

    def copy_fields(cls): 

    all_fields = set(map(lambda x: x.name, model._meta.fields)) 
    all_fields.remove('id') 
    # Copy alias fields 

    for alias_field in aliases: 

     real_field = aliases[alias_field] 

     # Get field from model 
     old_field = model._meta.get_field(real_field) 
     oldname, columnname = old_field.get_attname_column() 
     new_field = deepcopy(old_field) 

     # Setting field properties 
     new_field.name = alias_field 
     new_field.db_column = columnname 
     new_field.verbose_name = alias_field 

     new_field.contribute_to_class(cls, "_%s" % alias_field) 
     all_fields.remove(real_field) 

    for field in all_fields: 
     new_field = deepcopy(model._meta.get_field(field)) 
     new_field.contribute_to_class(cls, "_%s" % new_field.name) 

    def copy_properties(cls): 
    # Copy db table 
    cls._meta.db_table = model._meta.db_table 


    def create_manager(cls): 
    from copy import deepcopy 
    field = cls._meta.get_field('action_type') 
    field.default = cls.__name__ 
    # Only query on relevant records 
    qs = deepcopy(cls.objects) 
    cls.objects = qs.filter(action_type=cls.__name__) 

    def wrapper(cls): 

    # Set it unmanaged 
    cls._meta.managed = False 

    copy_properties(cls) 
    copy_fields(cls) 
    create_manager(cls) 

    return cls 
    return wrapper 

這是爲生產做好準備?

我不會在生產代碼中使用它,對我來說,這是一個練習來展示django的靈活性,但是如果有足夠的測試,它可以在代碼中使用。

另一個反對在生產中使用的理由是代碼使用了django ORM的大量內部工作。我不確定它的api足夠穩定。

而這種解決方案並不是您能想到的最佳解決方案。解決這個在數據庫中存儲動態字段的問題有更多的可能性。