Django 使用 'through' 关系表从模型中获取所有相关对象
Django get all related objects from model with 'through' relationship tables
在我们的应用程序中,我们有多个关系和多个模型,我正在尝试实现一种通用方法来获取一个对象的所有相关对象,甚至是反向对象。
如果我从我的模型 Pessoa
打印 ._meta.get_fields()
,我得到这些关系字段(我省略了 'normal' 的):
<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 关系,并且它们都包含指定的 'through' 模型 Here。
如您所见,它重复了它们,一个用于模型,一个用于 'through' 中间体 table(我猜也是一个模型)。在递归关系的情况下它重复两次。
我的问题是,有没有办法让这些不重复?
一种方法可以知道哪些重复字段 'point' 最终与相同的关系相关(即使它发送了两个 table 垃圾邮件)?因为如果通过 table 有字段,我想以不同的方式显示它们。
并且根据 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
]
但是 'through' table 不被考虑 auto_created 并且是具体的。
示例:
<ManyToManyRel: cadastroimoveis.ccir>
<ManyToOneRel: cadastroimoveis.ccir_pessoa>
这两个字段'point'相同的关系,一个是中间table另一个是模型,有没有(自动)方式知道这两个是相关的?我找不到他们共享的任何属性。
这是因为当直通 table 有字段时,我需要编辑它而不是模型本身的 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 []
比如我们有这组模型。我从 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
字段。
其他答案肯定帮助我解决了这个问题,特别是在我的情况下,我所有的关系都是 M2M 并且有一个通过 table,而且一切都在 AJAX/Javascript 中完成所以我的答案很JSON-y.
现在它只获取了 m2m 模型的所有 tables,因为你必须在其中创建对象来创建关系,但它可以很容易地扩展到获取所有其他关系
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 并且只是想学习一些东西,但我保证它对我的问题很有帮助
在我们的应用程序中,我们有多个关系和多个模型,我正在尝试实现一种通用方法来获取一个对象的所有相关对象,甚至是反向对象。
如果我从我的模型 Pessoa
打印 ._meta.get_fields()
,我得到这些关系字段(我省略了 'normal' 的):
<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 关系,并且它们都包含指定的 'through' 模型 Here。
如您所见,它重复了它们,一个用于模型,一个用于 'through' 中间体 table(我猜也是一个模型)。在递归关系的情况下它重复两次。
我的问题是,有没有办法让这些不重复?
一种方法可以知道哪些重复字段 'point' 最终与相同的关系相关(即使它发送了两个 table 垃圾邮件)?因为如果通过 table 有字段,我想以不同的方式显示它们。
并且根据 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
]
但是 'through' table 不被考虑 auto_created 并且是具体的。
示例:
<ManyToManyRel: cadastroimoveis.ccir>
<ManyToOneRel: cadastroimoveis.ccir_pessoa>
这两个字段'point'相同的关系,一个是中间table另一个是模型,有没有(自动)方式知道这两个是相关的?我找不到他们共享的任何属性。
这是因为当直通 table 有字段时,我需要编辑它而不是模型本身的 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 []
比如我们有这组模型。我从 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
字段。
其他答案肯定帮助我解决了这个问题,特别是在我的情况下,我所有的关系都是 M2M 并且有一个通过 table,而且一切都在 AJAX/Javascript 中完成所以我的答案很JSON-y.
现在它只获取了 m2m 模型的所有 tables,因为你必须在其中创建对象来创建关系,但它可以很容易地扩展到获取所有其他关系
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 并且只是想学习一些东西,但我保证它对我的问题很有帮助