Django 模型多对多反向过滤器
Django Model ManyToMany Reverse Filter
以下是我的模型(类似于)的摘录:
class Person(models.Model):
name = models.CharField(max_length=20)
relationships = models.ManyToManyField('self',
through='Relationship',
symmetrical=False,
related_name='related_to',
)
def __str__(self):
return self.name
class Relationship(models.Model):
from_person = models.ForeignKey(Person,
related_name='from_people',
on_delete=models.CASCADE,
)
to_person = models.ForeignKey(Person,
related_name='to_people',
on_delete=models.CASCADE,
)
status = models.CharField(max_length=20)
def __str__(self):
return "{} is {} {}".format(
self.from_person.name, self.status, self.to_person.name)
这是我的数据库的内容:
>>> Person.objects.all()
<QuerySet [<Person: A>, <Person: B>, <Person: C>]>
>>> Relationship.objects.all()
<QuerySet [<Relationship: B is Following C>]>
如果我想查看给定的人正在关注谁,我可以在 Person 中构建一个新方法 class:
def get_following(self):
return self.relationships.filter(
to_people__status='Following',
to_people__from_person=self)
这个有效:
>>> p2.get_following()
<QuerySet [<Person: C>]>
我想做相反的事情。与其问"Who does this person follow?",不如问"Who follows this person?"。我可以这样做(尽管它是 returns 关系对象,而不是 Person 对象):
>>> Relationship.objects.filter(to_person=p3, status='Following')
<QuerySet [<Relationship: B is Following to C>]>
我的尝试是这样的(returns 一个空的查询集):
def get_following(self):
return self.relationships.filter(
from_people__status='Following',
from_people__to_person=self)
感谢您的帮助!
编辑:
这是我选择的答案:
def get_followers(self):
return self.related_to.filter(from_people__status='Following')
查看 this 文档。否则这里有一些其他方法...
self.relationships.from_people.objects.all()
将 return 具有或具有相关名称 from_people
的所有对象。
但是,我会稍微更改一些代码,以便使用 self.relationships.from_people.objects.filter(status='Following')
另一种方法(虽然不是最有效的)是传入人物模型,并使用 person.pk 作为过滤器传入关系模型。
def get_following(self, pk):
person = Person.objects.get(pk=pk)
relationships = Relationship.objects.filter(to_person=person.id)
return relationships
您得到了这样的查询集:<QuerySet [<Relationship: B is Following C>]>
。想想有一天(我猜这是提议)'person' 有很多追随者,它可能 return 这么多追随者,像这样:<QuerySet [<Relationship: B is Following C>, <Relationship: A is Following C>]>
。
所以,我会使用 values_list() [1]:
Relationship.objects.filter(to_person=p3, status='Following').values_list('from_person__name', flat=True)
Returns: <QuerySet [A, B, ...]>
如果只传入单个字段,也可以传入flat参数。如果为 True,这将意味着 returned 结果是单个值,而不是元组。
或创建一个方法:
def get_followers(self):
follower_of_person = []
for value in relation_of_person:
follower_of_p3.append(value.from_person.name)
return follower_of_person
Returns: [A, B, ...]
values_list 还是更好,因为你直接在数据库上工作。
[1] https://docs.djangoproject.com/en/2.0/ref/models/querysets/#values-list
(这里有一个很好的 ManyToMany 例子)。
这是我选择的答案:
def get_followers(self):
return self.related_to.filter(from_people__status='Following')
以防万一其他人需要另一种实现方法 "Followers" 与您描述的完全一样,但使用不同的建模方案:
# project/account/models.py
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from model_utils import Choices
class User(AbstractBaseUser, PermissionsMixin):
class Meta:
# ...
username = models.CharField(...)
email = models.EmailField(...)
# ...
### Custom app-specific relationships (database scheme) ###
# symmetrical=False is needed for this reason:
following = models.ManyToManyField('self', related_name='followers', blank=True, symmetrical=False)
# project/account/forms.py
from django import forms
from django.contrib.auth import get_user_model
from django.contrib.admin.widgets import FilteredSelectMultiple
from .models import User
class UserChangeForm(forms.ModelForm):
# ...
# new:
following = forms.ModelMultipleChoiceField(
queryset=User.objects.all(),
required=False,
widget=FilteredSelectMultiple(
verbose_name='Following',
is_stacked=False
)
)
# new:
followers = forms.ModelMultipleChoiceField(
queryset=User.objects.all(),
required=False,
widget=FilteredSelectMultiple(
verbose_name='Followers',
is_stacked=False
)
)
class Meta:
model = get_user_model()
# add 'following' and 'followers' to the fields:
fields = ('email', 'password', ..., 'following', 'followers')
# also needed to initialize properly:
def __init__(self, *args, **kwargs):
super(UserChangeForm, self).__init__(*args, **kwargs)
# Filter out the self user in the lists and initialize followers list:
if self.instance and self.instance.pk:
self.fields['following'] = forms.ModelMultipleChoiceField(
queryset=User.objects.all().exclude(pk=self.instance.pk),
required=False,
widget=FilteredSelectMultiple(
verbose_name='Following',
is_stacked=False
)
)
self.fields['followers'] = forms.ModelMultipleChoiceField(
queryset=User.objects.all().exclude(pk=self.instance.pk),
required=False,
widget=FilteredSelectMultiple(
verbose_name='Followers',
is_stacked=False
)
)
self.fields['followers'].initial = self.instance.followers.all()
# project/account/admin.py
from django.contrib.auth import get_user_model
from django.contrib import admin
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from .models import User
from .forms import UserChangeForm, UserCreationForm
class Admin(BaseUserAdmin):
add_form = UserCreationForm
form = UserChangeForm
model = get_user_model()
# The fields to be used in displaying the User model.
# These override the definitions on the base UserAdmin
# that reference specific fields on auth.User.
list_display = ['email', 'username', 'is_admin']
list_filter = ('is_admin',)
fieldsets = (
(None, {'fields': ('email', 'password')}),
('Personal info', {'fields': ('username',)}),
('Permissions', {'fields': ('is_admin', 'is_superuser', 'is_staff')}),
# new:
('Following / Followers', {'fields': ('following', 'followers')}),
)
# other fields
# ...
# new:
filter_horizontal = ('following', 'followers')
admin.site.register(User, Admin)
admin.site.unregister(Group)
如果您随后启动服务器并转到 localhost:8000/admin/
并导航到用户的详细信息页面,您应该会在屏幕上看到类似这样的内容:
我没有添加计数器,因此您可以立即在 list_view 中看到关注者的数量。
注意第二个带有关注者的 FormField 在管理面板中是只读的。一个用户不能选择其他用户关注他。
以下是我的模型(类似于)的摘录:
class Person(models.Model):
name = models.CharField(max_length=20)
relationships = models.ManyToManyField('self',
through='Relationship',
symmetrical=False,
related_name='related_to',
)
def __str__(self):
return self.name
class Relationship(models.Model):
from_person = models.ForeignKey(Person,
related_name='from_people',
on_delete=models.CASCADE,
)
to_person = models.ForeignKey(Person,
related_name='to_people',
on_delete=models.CASCADE,
)
status = models.CharField(max_length=20)
def __str__(self):
return "{} is {} {}".format(
self.from_person.name, self.status, self.to_person.name)
这是我的数据库的内容:
>>> Person.objects.all()
<QuerySet [<Person: A>, <Person: B>, <Person: C>]>
>>> Relationship.objects.all()
<QuerySet [<Relationship: B is Following C>]>
如果我想查看给定的人正在关注谁,我可以在 Person 中构建一个新方法 class:
def get_following(self):
return self.relationships.filter(
to_people__status='Following',
to_people__from_person=self)
这个有效:
>>> p2.get_following()
<QuerySet [<Person: C>]>
我想做相反的事情。与其问"Who does this person follow?",不如问"Who follows this person?"。我可以这样做(尽管它是 returns 关系对象,而不是 Person 对象):
>>> Relationship.objects.filter(to_person=p3, status='Following')
<QuerySet [<Relationship: B is Following to C>]>
我的尝试是这样的(returns 一个空的查询集):
def get_following(self):
return self.relationships.filter(
from_people__status='Following',
from_people__to_person=self)
感谢您的帮助!
编辑: 这是我选择的答案:
def get_followers(self):
return self.related_to.filter(from_people__status='Following')
查看 this 文档。否则这里有一些其他方法...
self.relationships.from_people.objects.all()
将 return 具有或具有相关名称 from_people
的所有对象。
但是,我会稍微更改一些代码,以便使用 self.relationships.from_people.objects.filter(status='Following')
另一种方法(虽然不是最有效的)是传入人物模型,并使用 person.pk 作为过滤器传入关系模型。
def get_following(self, pk):
person = Person.objects.get(pk=pk)
relationships = Relationship.objects.filter(to_person=person.id)
return relationships
您得到了这样的查询集:<QuerySet [<Relationship: B is Following C>]>
。想想有一天(我猜这是提议)'person' 有很多追随者,它可能 return 这么多追随者,像这样:<QuerySet [<Relationship: B is Following C>, <Relationship: A is Following C>]>
。
所以,我会使用 values_list() [1]:
Relationship.objects.filter(to_person=p3, status='Following').values_list('from_person__name', flat=True)
Returns: <QuerySet [A, B, ...]>
如果只传入单个字段,也可以传入flat参数。如果为 True,这将意味着 returned 结果是单个值,而不是元组。
或创建一个方法:
def get_followers(self):
follower_of_person = []
for value in relation_of_person:
follower_of_p3.append(value.from_person.name)
return follower_of_person
Returns: [A, B, ...]
values_list 还是更好,因为你直接在数据库上工作。
[1] https://docs.djangoproject.com/en/2.0/ref/models/querysets/#values-list (这里有一个很好的 ManyToMany 例子)。
这是我选择的答案:
def get_followers(self):
return self.related_to.filter(from_people__status='Following')
以防万一其他人需要另一种实现方法 "Followers" 与您描述的完全一样,但使用不同的建模方案:
# project/account/models.py
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from model_utils import Choices
class User(AbstractBaseUser, PermissionsMixin):
class Meta:
# ...
username = models.CharField(...)
email = models.EmailField(...)
# ...
### Custom app-specific relationships (database scheme) ###
# symmetrical=False is needed for this reason:
following = models.ManyToManyField('self', related_name='followers', blank=True, symmetrical=False)
# project/account/forms.py
from django import forms
from django.contrib.auth import get_user_model
from django.contrib.admin.widgets import FilteredSelectMultiple
from .models import User
class UserChangeForm(forms.ModelForm):
# ...
# new:
following = forms.ModelMultipleChoiceField(
queryset=User.objects.all(),
required=False,
widget=FilteredSelectMultiple(
verbose_name='Following',
is_stacked=False
)
)
# new:
followers = forms.ModelMultipleChoiceField(
queryset=User.objects.all(),
required=False,
widget=FilteredSelectMultiple(
verbose_name='Followers',
is_stacked=False
)
)
class Meta:
model = get_user_model()
# add 'following' and 'followers' to the fields:
fields = ('email', 'password', ..., 'following', 'followers')
# also needed to initialize properly:
def __init__(self, *args, **kwargs):
super(UserChangeForm, self).__init__(*args, **kwargs)
# Filter out the self user in the lists and initialize followers list:
if self.instance and self.instance.pk:
self.fields['following'] = forms.ModelMultipleChoiceField(
queryset=User.objects.all().exclude(pk=self.instance.pk),
required=False,
widget=FilteredSelectMultiple(
verbose_name='Following',
is_stacked=False
)
)
self.fields['followers'] = forms.ModelMultipleChoiceField(
queryset=User.objects.all().exclude(pk=self.instance.pk),
required=False,
widget=FilteredSelectMultiple(
verbose_name='Followers',
is_stacked=False
)
)
self.fields['followers'].initial = self.instance.followers.all()
# project/account/admin.py
from django.contrib.auth import get_user_model
from django.contrib import admin
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from .models import User
from .forms import UserChangeForm, UserCreationForm
class Admin(BaseUserAdmin):
add_form = UserCreationForm
form = UserChangeForm
model = get_user_model()
# The fields to be used in displaying the User model.
# These override the definitions on the base UserAdmin
# that reference specific fields on auth.User.
list_display = ['email', 'username', 'is_admin']
list_filter = ('is_admin',)
fieldsets = (
(None, {'fields': ('email', 'password')}),
('Personal info', {'fields': ('username',)}),
('Permissions', {'fields': ('is_admin', 'is_superuser', 'is_staff')}),
# new:
('Following / Followers', {'fields': ('following', 'followers')}),
)
# other fields
# ...
# new:
filter_horizontal = ('following', 'followers')
admin.site.register(User, Admin)
admin.site.unregister(Group)
如果您随后启动服务器并转到 localhost:8000/admin/ 并导航到用户的详细信息页面,您应该会在屏幕上看到类似这样的内容:
我没有添加计数器,因此您可以立即在 list_view 中看到关注者的数量。
注意第二个带有关注者的 FormField 在管理面板中是只读的。一个用户不能选择其他用户关注他。