簡體   English   中英

Django通過“通過”關系表從模型中獲取所有相關對象

[英]Django get all related objects from model with 'through' relationship tables

在我們的應用程序中,我們有幾種關系和幾種模型,我試圖實現一種通用的方式來獲取對象的所有相關對象,甚至是反向對象。

如果我從模型Pessoa打印._meta.get_fields()._meta.get_fields()得到以下關系字段(我省略了“正常”字段):

<ManyToManyRel: cadastroimoveis.pessoa>
<ManyToOneRel: cadastroimoveis.pessoa_pessoa>
<ManyToOneRel: cadastroimoveis.pessoa_pessoa>
<ManyToOneRel: cadastroimoveis.pessoa_itr>
<ManyToManyRel: cadastroimoveis.doc>
<ManyToOneRel: cadastroimoveis.doc_pessoa>
cadastroimoveis.Pessoa.relacoes
cadastroimoveis.Pessoa.itrs

這個特定的模型僅具有M2M關系,並且所有關系都包含此處指定的“直通”模型。

如您所見,它重復了它們,一個重復用於模型,另一個重復用於“通過”中間表(我猜也是一個模型)。 在遞歸關系的情況下,它會重復兩次。

我的問題是,有沒有辦法讓這些不再重復?

一種知道哪個重復字段最終“指向”相同關系的方法(即使它向兩個表發送了垃圾郵件)? 因為如果穿透表中有字段,我想以其他方式顯示它們。

根據Model _meta API文檔,您將使用它來獲取所有相關對象:

[
    f for f in MyModel._meta.get_fields()
    if (f.one_to_many or f.one_to_one)
    and f.auto_created and not f.concrete
]

但是,“通過”表不被認為是自動創建的,而是具體的。

范例:

<ManyToManyRel: cadastroimoveis.ccir>
<ManyToOneRel: cadastroimoveis.ccir_pessoa>

這兩個字段“指向”相同的關系,一個是中間表,另一個是模型,是否有(自動)方式知道這兩個是相關的? 我找不到他們共享的任何屬性。

這樣做的原因是因為當穿透表具有字段時,我需要對其進行編輯,而不是模型本身的M2M字段

Models.py: http://pastebin.com/szDfhHQ3我打掃盡我所能

對於Django 1.10,以下代碼受BaseModelForm代碼(Django原始)的啟發。

如果您具有以下關系:

class Group(Model):
    field = ....

class Person(Model):
    groups = ManyToManyField(Group, through='Membership')

class Membership(Model):
    person = ForeignKey(Person)
    group = ForeignKey(Group)
    position = TextField(...)

然后可以像這樣查詢相關的字段和屬性:

opts = Person._meta
for f in chain(opts.many_to_many, opts.virtual_fields):
    if f.rel.through:
        # this would return "group"
        attr_field = f.m2m_reverse_field_name()

        # this is the Membership class (a class instance)
        m2m_model = f.rel.through

        # this would return "person"
        join_field = field.m2m_field_name()

        # to get all "Membership" objects for "person" personXY
        qs_filter = {join_field: personXY}
        qs = m2m_model.objects.filter(**qs_filter)

        # get the PKs of all groups where personXY is a member of
        lookup_by_pk = '{}__pk'.format(attr_field)
        current_pks = qs.values_list(lookup_by_pk, flat=True) if qs.exists() else []

例如,我們有這套模型。 我是從這個django示例中挑選出來的。

class Person(models.Model):
    name = models.CharField(max_length=50)

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(
        Person,
        through='Membership',
        through_fields=('group', 'person'),
    )

class Membership(models.Model):
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    inviter = models.ForeignKey(
        Person,
        on_delete=models.CASCADE,
        related_name="membership_invites",
    )
    invite_reason = models.CharField(max_length=64)

該解決方案看起來有些難看,但是可以根據您的需求進行優化。

def get_through_field(f):                                                                                              
    opts = f.through._meta                                                                                             
    if f.through_fields:
        return opts.get_field(f.through_fields[1])                                                                     
    for field in opts.fields:                                                                                          
        rel = getattr(field, 'remote_field', None)                                                                     
        if rel and rel.model == f.model:                                                                               
            return field

model = models.Person

rels = dict(
    (f.field, f) for f in model._meta.get_fields()
    if f.is_relation
)

excludes = set()
for f in model._meta.get_fields():
    if f.many_to_many:
        through = get_through_field(f)
        excludes.add(rels[through])

for f in model._meta.get_fields():
    if f not in excludes:
        print f.name, f

輸出

group <ManyToManyRel: m.group>
membership_invites <ManyToOneRel: m.membership>
id m.Person.id
name m.Person.name

如您所見,沒有membership字段。

其他答案無疑幫助我弄清楚了這一點,特別是在我的情況下,我所有的關系都是M2M並有一個通過表,而且所有操作都是在AJAX / Javascript中完成的,因此我將答案非常JSON-y。

目前,它僅遍歷m2m模型的所有表,因為您必須在其中創建對象才能創建關系,但是可以輕松地將其擴展為獲取所有其他關系

def get_relationships(model):
    fields = list(model._meta.get_fields())

    m2m_fields = {}
    #Getting m2m relationships first
    for i, field in enumerate(fields):
        print(field)
        if field.is_relation:
            if field.many_to_many:
                fields.pop(i)
                try:
                    #If its a forward field, we want the relationship instead
                    if not hasattr(field,'field'):
                        field = field.remote_field
                except AttributeError:
                    pass
                if hasattr(field,'through'):
                    through = field.through
                    #In case of recursive relationships, there will be duplicates so we don't need to do it again
                    if m2m_fields.get(through._meta.model.__name__):
                        continue
                    m2m_fields[through._meta.model.__name__] = {}
                    m2m = m2m_fields[through._meta.model.__name__]
                    #Finding the models which participate in the through table and the direction
                    m2m['owner'] = {'model' : field.model.__name__}
                    m2m['related'] = {'model' : field.related_model.__name__}
                    recursive = False
                    #Checking recursivity, will use this later
                    #Finding field names for the foreignkeys of the through table
                    for through_field in through._meta.get_fields():
                        if not (through_field.related_model is None):
                            if m2m['owner']['model'] == through_field.related_model.__name__ and not m2m['owner'].get('field'):
                                m2m['owner']['field'] = through_field.name
                            elif m2m['related']['model'] == through_field.related_model.__name__ and not m2m['related'].get('field'):
                                m2m['related']['field'] = through_field.name
                        elif not through_field.primary_key:
                            if not m2m.get('rel_fields'):
                                m2m['rel_fields'] = []
                            m2m['rel_fields'].append(through_field.name)
    #Now removing the through tables from the fields list, because they appear as a regular ManyToOne relationship otherwise
    for through_table in m2m_fields.keys():
        name = through_table
        for i, field in enumerate(fields):
            if field.many_to_one:
                if field.__name__ and field.related_model:
                    if field.related_model.__name__ == name:
                        fields.pop(i)
    #Todo : OneToOne and ManyToOne relationships

    return m2m_fields


for key,value in get_relationships(Pessoa).items():
    print(key, " = ", value)

這是一個丑陋的代碼的地獄,但我不太擅長Python,只是想學習一些東西,但我保證它對我的問題像個魅力

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM