2017-01-11 5 views
1

В нашем приложении у нас есть несколько отношений и несколько моделей, я пытаюсь получить общий способ получить все связанные объекты объекта, даже обратные.Django получить все связанные объекты из модели со «взаимозависимыми» таблицами

Если я печатаю ._meta.get_fields() из моей модели Pessoa, я получаю эти отношения поля (я опустил «нормальный» из них):

<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, и все из них содержат «сквозную» модель, как указано Here.

Как вы можете видеть, он повторяет их, один для модели и один для промежуточной таблицы «через» (также модель, я думаю). А в случае рекурсивных отношений это повторяется дважды.

Вопрос в том, есть ли способ получить эти повторения?

Способ узнать, какие повторяющиеся поля «указывают» на те же отношения в конце (даже если они спамают две таблицы)? Потому что, если в сквозной таблице есть поля, я хочу отобразить их по-другому.

И согласно документации 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 
] 

Но «через» таблицы не считаются auto_created и конкретны.

Пример:

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

Эти два поля «точка» такое же соотношение, один промежуточный стол, а другой является моделью, есть (автоматический) способ узнать, что эти два коррелируют? Я не мог найти атрибут, который они разделяют.

Причина этого заключается в том, потому что, когда через стол имеет поля, мне нужно изменить на нем вместо поля M2M на самой модели

Models.py: http://pastebin.com/szDfhHQ3 Я очистил все, что мог

+0

Могли бы вы также добавить сюда модель? – AKS

+0

Какую версию Django вы используете? – Risadinha

+0

@AKS извините за задержку, добавила мои модели, скоро проверит текущие ответы – Mojimi

ответ

1

Другие ответы определенно помогли мне разобраться в этом, особенно в моем случае все мои отношения - это 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 и просто пытаюсь научиться чему-то, но я гарантирую, что он работал как шарм для моего вопроса.

1

Для 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 [] 
+0

Этот пример не получил обратных ссылок – Mojimi

+0

Цитата: «Причина этого в том, что, когда сквозная таблица имеет поля, мне нужно редактировать на ней вместо поля M2M на самой модели »- вы можете сделать это с помощью этого кода, если у вас есть экземпляры Person и хотите сохранить дополнительные данные, которые хранятся в модели' Membership'. – Risadinha

1

К примеру у нас есть этот набор моделей. Я взял его с this django example.

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 поля.

+0

В какой версии это было проверено? Я попробовал его на 1.10, и он дал 'AttributeError: объект ManyToManyField 'не имеет атрибута' field'' в строке 13 – Mojimi

+0

В Django == 1.10.5 отлично работает. Просто исправлена ​​ошибка в коде, но она не связана с вашей проблемой. 'ManyToManyField' выглядит странно, потому что get_fields() возвращает не поля, а Rels. Даже в вашем примере: ', '. Видите? Не должно быть никаких полей. Пожалуйста, дважды проверьте это. – vasi1y

+0

Это было потому, что у моей модели также были «форвардные» отношения, которые появились как поле, просто пришлось отфильтровать их. – Mojimi