【问题标题】:Django Model ManyToMany Reverse FilterDjango 模型多对多反向过滤器
【发布时间】:2018-01-12 00:56:36
【问题描述】:

以下是(类似于)我的模型的摘录:

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 类中构建一个新方法:

def get_following(self):
  return self.relationships.filter(
    to_people__status='Following',
    to_people__from_person=self)

这行得通:

>>> p2.get_following()
<QuerySet [<Person: C>]>

我想做这个的反向操作。与其问“这个人关注谁?”,我想问“谁关注这个人?”。我可以这样做(尽管它返回的是关系对象,而不是个人对象):

>>> Relationship.objects.filter(to_person=p3, status='Following')
<QuerySet [<Relationship: B is Following to C>]>

我的尝试是这样的(它返回一个空的 QuerySet):

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')

【问题讨论】:

  • 你想从人那里,还是向人,还是所有人?
  • 关注给定人员的所有人员的查询集。

标签: python django filter manytomanyfield


【解决方案1】:

以防万一其他人需要另一种实现“追随者”的方式,就像你描述的那样,但使​​用不同的建模方案:

# 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: https://stackoverflow.com/a/42040848/3433137
    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 在管理面板中是只读的。用户不能选择其他用户关注他。

【讨论】:

    【解决方案2】:

    你有一个这样的查询集:&lt;QuerySet [&lt;Relationship: B is Following C&gt;]&gt;。想想有一天(我猜是那个提议)“人”有很多追随者,它可能会返回这么多追随者,就像这样:&lt;QuerySet [&lt;Relationship: B is Following C&gt;, &lt;Relationship: A is Following C&gt;]&gt;。 所以,我会使用 values_list() [1]:

    Relationship.objects.filter(to_person=p3, status='Following').values_list('from_person__name', flat=True)
    

    返回:&lt;QuerySet [A, B, ...]&gt;

    如果只传入单个字段,也可以传入flat参数。如果为 True,这将意味着返回的结果是单个值,而不是一个元组。

    或者创建一个方法:

    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
    

    返回:[A, B, ...]

    values_list 仍然更好,因为您直接在数据库上工作。

    [1]https://docs.djangoproject.com/en/2.0/ref/models/querysets/#values-list (这里有一个很好的多对多示例)。

    【讨论】:

    • 如果你不想要这个名字,删除name,你会得到类似&lt;QuerySet [&lt;Person: A&gt;, &lt;Person: B&gt;, ...]&gt;的东西
    • 我可以继续使用 QuerySet。这只是一个玩具示例,因此会有很多关系。看看我添加的使用“related_name”向后工作的答案。
    【解决方案3】:

    查看this 文档。否则这里有一些其他的方法...

    self.relationships.from_people.objects.all() 将返回具有相关名称 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
    

    【讨论】:

    • 对于您的前两个代码块,我被告知:'AttributeError: 'ManyRelatedManager' 对象没有属性 'objects'。最后,我使用“related_name”向后工作。
    【解决方案4】:

    这是我选择的答案:

    def get_followers(self):
      return self.related_to.filter(from_people__status='Following')
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-18
      • 2011-01-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-20
      相关资源
      最近更新 更多