如何在Django中搜索多对多字段?

1 投票
2 回答
1822 浏览
提问于 2025-04-18 10:36

我有一个叫做 Profile 的模型,它和另一个模型 Specialty 之间有很多对多的关系。

我想在 Profile 模型中简单搜索一下专业,并返回匹配的个人资料。目前,我的表单在模板中显示得很好,但提交后我什么也得不到。

models.py:

from django.db import models
from django.conf import settings

class Specialty(models.Model):
    title = models.CharField(max_length=255)

    class Meta:
        verbose_name_plural = 'Specialties'

    def __unicode__(self):
        return u"%s" % self.title

class Profile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL)
    specialties = models.ManyToManyField(Specialty, blank=True)

    def __unicode__(self):
        return u"%s" % (self.user.username)

    def get_absolute_url(self):
        return reverse("profile_detail", args=[str(self.user.username)])

forms.py:

from django import forms

from .profiles.models import Profile, Specialty

class ProfileSearchForm(forms.ModelForm):
    specialty = forms.ModelMultipleChoiceField(queryset=Specialty.objects.all(), widget=forms.CheckboxSelectMultiple, required=False)

    class Meta:
        model = Profile
        fields = ('specialty',)

views.py:

from django.views.generic.edit import FormView
from django.core.urlresolvers import reverse_lazy

from .forms import ProfileSearchForm
from .profiles.models import Profile

class IndexView(FormView):
    template_name = 'index.html'
    form_class = ProfileSearchForm
    success_url = reverse_lazy('index')

    def form_valid(self, form):
        specialty = form.cleaned_data['specialty']
        self.profile_list = Profile.objects.filter(specialty__in=specialty)
        return super(IndexView, self).form_valid(form)

index.html:

<form action="{% url 'index' %}" method="get">
    {{ form.as_p }}
    <p><input type="submit" value="Search"></p>
</form>

<ul>
{% for profile in profile_list %}
    <li><a href="{{ profile.get_absolute_url }}">{{ profile.user.get_full_name }}</a></li>
{% endfor %}
</ul>

我感觉问题可能和 self.profile_list 有关。我不太确定它是否应该放在 get_extra_context 里。因为在第一次访问时它是不存在的,所以我不知道该怎么让它存在或者怎么传递它。我也不太确定 Profile.objects.filter(specialty__in=specialty) 这种写法是否适合在很多对多的字段上查找。

如果有其他搜索建议,比如 Haystack,我也很乐意听听,前提是它们有优势。我更喜欢用一组复选框,但我觉得 Haystack 可能无法通过分面处理这个。

2 个回答

0

我觉得你想要的是 Profile.objects.filter(specialties__in=specialty) 这个写法。因为在这里,profile 并没有一个叫做 specialty 的字段,而是有一个叫 specialties 的字段。

1

谢谢你们,Gergo和Cameron。我现在已经解决了问题。你们说的那个问题确实是对的,但后面还有不少步骤需要处理。

  • 我真正想要的是一个ListView,加上简单搜索的功能,这应该是一个FormMixin,让我可以添加form_classsuccess_url,而不是全部用FormView
  • 当在ListView中指定了默认的model时,视图会把上下文清空,这样form就无法传递到模板中。get_context_data需要把表单重新添加到上下文中,文档中有一个示例
  • 应该去掉form_valid,因为搜索从来不是一个POST请求,尽管文档在FormMixin的“注意”部分提到需要form_validform_invalid
  • 我需要get_queryset来获取一个默认的查询集,或者读取GET请求中的specialties值,并相应地过滤结果。
  • 为了加分,get_form_kwargs需要把当前请求传递给表单,这样在页面刷新后,表单的初始值才能保持。比较棘手的是,当使用ModelMultipleChoiceField时,必须使用request.GETgetlist方法,而不是get方法来读取那个值的列表。

现在一起看看...

forms.py:

from django import forms
from .profiles.models import Profile, Specialty

class ProfileSearchForm(forms.ModelForm):
    specialties = forms.ModelMultipleChoiceField(queryset=Specialty.objects.all(), widget=forms.CheckboxSelectMultiple, required=False)

    class Meta:
        model = Profile
        fields = ('specialties',)

    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop('request')
        super(ProfileSearchForm, self).__init__(*args, **kwargs)
        self.fields['specialties'].initial = self.request.GET.getlist('specialties')

views.py:

from django.views.generic import ListView
from django.views.generic.edit import FormMixin
from django.core.urlresolvers import reverse_lazy
from .profiles.models import Profile
from .forms import ProfileSearchForm

class IndexView(FormMixin, ListView):
    model = Profile
    template_name = 'index.html'
    form_class = ProfileSearchForm
    success_url = reverse_lazy('index')

    def get_queryset(self):
        queryset = super(IndexView, self).get_queryset()
        specialties = self.request.GET.getlist('specialties')
        if specialties:
            queryset = queryset.filter(specialties__in=specialties).distinct('user')
        return queryset

    def get_form_kwargs(self):
        kwargs = super(IndexView, self).get_form_kwargs()
        kwargs['request'] = self.request
        return kwargs

    def get_context_data(self, **kwargs):
        context = super(IndexView, self).get_context_data(**kwargs)
        form_class = self.get_form_class()
        context['form'] = self.get_form(form_class)
        return context

撰写回答