技术笔记——Django+Nginx+uwsgi搭建自己的博客(十五)

这篇博客中,我们继续研究haystack的使用。在上一篇博客中,我们对haystack提供的SearchView类进行了扩展,建立了我们自己的blogSearchView类用于添加我们自己的额外数据。然而,基于类的view在使用上还是有诸多不便,比如我们无法通过request对象拿到当前登录的user,并将user相关的信息返回到搜索结果页面上,造成页面内容的缺失:

上图中的"用户:"后面为空,也没有显示消息数量,就是因为我们在基于类的view里不好拿到request对象,进而无法拿到user信息导致。因此,我们在这期博客里要抛弃haystack提供的现成的SearchView,而是使用haystack提供的其他对象的API构建一个自己的SearchView函数。

为了实现我们自己的SearchView函数,我们不妨先看一看haystack提供的SearchView类是如何实现搜索的。

首先来看SearchView类的构造函数以及成员变量定义:

# haystack.views.SearchView
class SearchView(object):
    template = "search/search.html"
    extra_context = {}
    query = ""
    results = EmptySearchQuerySet()
    request = None
    form = None
    results_per_page = RESULTS_PER_PAGE

    def __init__(
        self,
        template=None,
        load_all=True,
        form_class=None,
        searchqueryset=None,
        results_per_page=None,
    ):
        self.load_all = load_all
        self.form_class = form_class
        self.searchqueryset = searchqueryset

        if form_class is None:
            self.form_class = ModelSearchForm

        if results_per_page is not None:
            self.results_per_page = results_per_page

        if template:
            self.template = template

haystack在源码里就将template写死为了search/search.html,这也就是为什么我们要用search.html作为默认View模板的原因;extra_context用于存放用户在子类中存放的其他数据,我们在上一篇博客中已经用到过;results是初始的搜索结果,默认为空搜索集;由于SearchView会对结果进行自动分页,因此设置了默认每页的搜索结果为RESULTS_PER_PAGE,该值我们也已在myblog/settings.py中设置过。

让我们进入构造函数部分:load_all是个布尔型开关,当load_all为True时,在之后的代码中会对建立索引的所有对象进行搜索;form_class为此View默认使用的表单类,若为空的话则会使用ModelSearchForm作为搜索表单。剩余的代码都是一些初始化代码,在此就不赘述了。

#haystack.views.SearchView
# ...
    def build_form(self, form_kwargs=None):
        """
        Instantiates the form the class should use to process the search query.
        """
        data = None
        kwargs = {"load_all": self.load_all}
        if form_kwargs:
            kwargs.update(form_kwargs)

        if len(self.request.GET):
            data = self.request.GET

        if self.searchqueryset is not None:
            kwargs["searchqueryset"] = self.searchqueryset

        return self.form_class(data, **kwargs)

在build_form函数中,haystack将load_all和searchqueryset都存在了kwargs参数中,并通过form_class的构造函数将这两个值传递给了对应的Form类。由于python的特性,build_form的最后的return等价于以下语句:

return ModelSearchForm(data,**kwargs)

因此,我们就很有必要看一下这个ModelSearchForm是如何实现的了。ModelSearchForm是SearchForm的子类,它会对所有注册的model进行搜索,并为每个模型提供一个复选框(然而在一般情况下我们用不到那些复选框,只是用它的全部搜索特性)。

我们先来看SearchForm类的实现:

# haystack.forms.SearchForm
class SearchForm(forms.Form):
    q = forms.CharField(
        required=False,
        label=_("Search"),
        widget=forms.TextInput(attrs={"type": "search"}),
    )

    def __init__(self, *args, **kwargs):
        self.searchqueryset = kwargs.pop("searchqueryset", None)
        self.load_all = kwargs.pop("load_all", False)

        if self.searchqueryset is None:
            self.searchqueryset = SearchQuerySet()

        super(SearchForm, self).__init__(*args, **kwargs)

    def no_query_found(self):

        return EmptySearchQuerySet()

    def search(self):
        if not self.is_valid():
            return self.no_query_found()

        if not self.cleaned_data.get("q"):
            return self.no_query_found()

        sqs = self.searchqueryset.auto_query(self.cleaned_data["q"])

        if self.load_all:
            sqs = sqs.load_all()

        return sqs

    def get_suggestion(self):
        if not self.is_valid():
            return None

        return self.searchqueryset.spelling_suggestion(self.cleaned_data["q"])

在构造函数中,SearchForm会使用可变参数中的load_all和searchqueryset为自身的load_all和searchqueryset赋值,因此在刚才的build_form函数中需要通过字典将View中的这两个参数传进来;在no_query_found函数中,返回一个空结果集作为找不到结果的“结果”返回出来。

search函数是我们的重头戏,通过它我们才能得到搜索结果。当表单没有通过验证时,直接返回空集作为结果;当搜索框中没有拿到数据时,也直接返回一个空集。而当搜索框有了关键字时,会调用searchqueryset的auto_query方法,以搜索框关键字作为条件进行搜索。SearchQuerySet是haystack提供的一个对象,不管是名字还是用法上都与django自己的QuerySet对象十分相似,支持filter,exclude,order_by等多种ORM操作;此外,同QuerySet一样,SearchQuerySet也支持链式操作。

如果当前表单的load_all属性为True的话,会对返回的结果集做load_all操作,其效果是在单次查询中把相似的对象归为一组。换句话说,就是一次query只查一种类型的对象,可提高搜索效率。

最后的一个函数get_suggestion会根据搜索框的关键字提供拼写建议,这里不作为重点。

有了这么强大的基类,ModelSearchForm类就比较简单了:

# haystack.forms.ModelSearchForm
class ModelSearchForm(SearchForm):
    def __init__(self, *args, **kwargs):
        super(ModelSearchForm, self).__init__(*args, **kwargs)
        self.fields["models"] = forms.MultipleChoiceField(
            choices=model_choices(),
            required=False,
            label=_("Search In"),
            widget=forms.CheckboxSelectMultiple,
        )

    def get_models(self):
        """Return a list of the selected models."""
        search_models = []

        if self.is_valid():
            for model in self.cleaned_data["models"]:
                search_models.append(haystack_get_model(*model.split(".")))

        return search_models

    def search(self):
        sqs = super(ModelSearchForm, self).search()
        return sqs.models(*self.get_models())

在ModelSearchForm中主要就是获得了所有的注册model,并将其加入到了基类的搜索结果中(这里具体要看SearchQuerySet的源码,我们只需知道ModelSearchForm会返回对所有model的搜索结果就好......)。

让我们回到SearchView的实现中。现在我们知道,build_form函数其实就是建立了一个load_all为True且使用空searchqueryset的ModelSearchForm对象。

# haystack.views.SearchView
    def get_query(self):

        if self.form.is_valid():
            return self.form.cleaned_data["q"]

        return ""
    def get_results(self):

        return self.form.search()

    def build_page(self):

        try:
            page_no = int(self.request.GET.get("page", 1))
        except (TypeError, ValueError):
            raise Http404("Not a valid number for page.")

        if page_no < 1:
            raise Http404("Pages should be 1 or greater.")

        start_offset = (page_no - 1) * self.results_per_page
        self.results[start_offset : start_offset + self.results_per_page]

        paginator = Paginator(self.results, self.results_per_page)

        try:
            page = paginator.page(page_no)
        except InvalidPage:
            raise Http404("No such page!")

        return (paginator, page)

    def extra_context(self):

        return {}

这四个小函数都比较简单,这里简要说一下:get_query用于返回搜索框的结果;get_results则是调用了上面讲的form的search方法;而build_page是使用了django的Paginator对象对搜索结果进行分页,该对象在django的文档上介绍比较详细,可自行参考;而extra_content就是用于返回开发者添加的额外数据的函数,上一篇博客中我们在子类中实现过。

然后就是对页面元素的拼接了:

# haystack.views.SearchView
    def get_context(self):
        (paginator, page) = self.build_page()

        context = {
            "query": self.query,
            "form": self.form,
            "page": page,
            "paginator": paginator,
            "suggestion": None,
        }

        if (
            hasattr(self.results, "query")
            and self.results.query.backend.include_spelling
        ):
            context["suggestion"] = self.form.get_suggestion()

        context.update(self.extra_context())

        return context

在get_context里,首先使用build_page()对搜索结果进行分页,然后再将各个元素集中在context字典中;接着,若在子类中实现了extra_context函数,则将其内容也一块加入到context中,最后返回context。

最后,SearchView使用__call__接口和create_response函数完成最后的渲染:

# haystack.views.SearchView
    def __call__(self, request):
        """
        Generates the actual response to the search.

        Relies on internal, overridable methods to construct the response.
        """
        self.request = request

        self.form = self.build_form()
        self.query = self.get_query()
        self.results = self.get_results()

        return self.create_response()

    def create_response(self):
        """
        Generates the actual HttpResponse to send back to the user.
        """

        context = self.get_context()

        return render(self.request, self.template, context)

在__call__接口中,先调用之前的各个小函数为成员变量赋值,然后再调用熟悉的render渲染前端页面。

在看完了SearchView的实现后,我们实现自己的SearchView就十分简单了,可分为两步进行:

1. 建立搜索表单

2. 使用SearchQuerySet返回搜索结果

我们可以继续使用SearchForm来作为我们的搜索表单,毕竟我们只有一个blog model会被搜索。我们的newsearchView代码如下:

# myblog/views.py
def newsearchView(request):
    if request.user.is_authenticated:
        user = request.user
    else:
        user = get_user(request)
    username = request.user.username
    searchForm = SearchForm()
    if request.method == 'GET':
        keyword = request.GET['q']
        all_result = SearchQuerySet().filter(content=AutoQuery(keyword))
        content = {'searchResult':all_result,
                   'curruser':user,
                   'msgcount':getmsgcount(username),
                   'searchform':searchForm,
                   'query':keyword}
    else:
        content = {
                   'curruser': user,
                   'msgcount': getmsgcount(username),
                   'searchform': searchForm
                }
    return render(request,'myblog/newsearch.html',content)

这里在SearchQuerySet中使用了filter+AutoQuery对象的方法,而不是auto_query方法。haystack文档推荐我们使用AutoQuery对象,这样使用更灵活。

现在,我们的搜索结果页面就会有用户信息了:

在此篇博客中,为大家分析了一点haystack的源码,还实现了我们自己的SearchView。在下期博客中,我们将继续研究haystack和whoosh的相关内容,希望大家继续关注~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值