Search code examples
pythondjangowagtail

How to wrap a search function in a class that inherits from wagtail Page?


Is there a way to make a search form using a wagtail template inheriting from Page? Instead of using a simple view.

And how can I render it in the template?

I find it more enriching to be able to use the wagtail page attributes to better style the search page, add more fields and make it translatable into multiple languages ​​using for example wagtail localyze.

class SearchPage(Page):
    # Database fields
    heading = models.CharField(default="Search", max_length=60)
    intro = models.CharField(default="Search Page", max_length=275)
    placeholder = models.CharField(default="Enter your search", max_length=100)
    results_text = models.CharField(default="Search results for", max_length=100)
    cover_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )

    # Editor panels configuration
    content_panels = Page.content_panels + [
        FieldPanel('cover_image'),
        FieldPanel('heading'),
        FieldPanel('intro'),
        FieldPanel('placeholder'),
        FieldPanel('results_text'),
    ]

    def search(self, request):
        search_query = request.GET.get("query", None)
        page = request.GET.get("page", 1)
        active_lang = Locale.get_active()
        # Search
        if search_query:
            search_results = Page.objects.filter(locale_id=active_lang.id, live=True).search(search_query)
            # To log this query for use with the "Promoted search results" module:
            # query = Query.get(search_query)
            # query.add_hit()
        else:
            search_results = Page.objects.none()
        # Pagination
        paginator = Paginator(search_results, 10)
        try:
            search_results = paginator.page(page)
        except PageNotAnInteger:
            search_results = paginator.page(1)
        except EmptyPage:
            search_results = paginator.page(paginator.num_pages)

        return TemplateResponse(
            request,
            "search/search_page.html",
            {
                "search_query": search_query,
                "search_results": search_results,
            },
        )

My layout.html file:

{% block content %}

    {% image page.cover_image width-1024 as header_bg_img %}
<section class="pt-8 pb-8" style="background:url({{ header_bg_img.url }}) no-repeat center center; background-size:cover; background-blend-mode: multiply;">
<div class="container">
    <div class="row d-flex justify-content-between align-items-center"></div>
            {# <!-- Row end --> #}
</div>
</section>
<section class="pt-2 pb-8">
<div class="container">
<div class="row d-flex justify-content-lg-between align-items-center">
{# <!-- Inner intro title --> #}
<div class="col-12">
{# <!-- breadcrumb --> #}
<nav class="mb-5" aria-label="breadcrumb">
    {% if self.get_ancestors|length > 1 %}
        <ol class="breadcrumb">
            {% for p in self.get_ancestors %}
                {% if p.is_root == False %}
                    <li class="breadcrumb-item"><a href="{{ p.url }}">{{ p.title }}</a></li>
                {% endif %}
            {% endfor %}
            <li class="breadcrumb-item active">{{ self.title }}</li>
        </ol>
    {% endif %}
</nav>
{# <!-- Title --> #}
<h4>{{ page.heading }}</h4>
<form class="big-dark-search form-dark form-line position-relative w-100 mt-3 mb-7"
      action="{% pageurl page %}" method="get">
    <div class="input-group-lg input-text-black-stroke mb-0">
        <input name="query" class="form-control font-heading caret-primary mb-0 pe-6" type="text"
               name="query"{% if search_query %} value="{{ search_query }}"{% endif %}
               placeholder="{{ page.placeholder }}">
        <span class="mt-1 focus-border"></span>
    </div>
    <button type="submit" value="Search"
            class="position-absolute end-0 top-0 btn pb-3 text-primary-hover h-100"><i
            class="bi bi-search display-8"></i></button>
</form>
<div class="searchResultsKeyCount d-none d-lg-block mb-4">
    <h4><b>{{ search_results.paginator.count }}</b> {{ page.results_text }}
        <b>"{{ search_query }}"</b></h4>
</div>
{% if page.search_results %}
    <ul class="list-inline pt-3">
        {% for result in page.search_results %}
            <li class="box">
                {% image result.specific.main_image width-200 as search_item_img %}
                {% if result.specific.tags %}
                    <h5 class="box-tag">{{ result.specific.tags.all.0 }}</h5>
                {% endif %}
                <a href="{% pageurl result %}">{{ result }} <img class="searchItem-image"
                                                                 src="{{ search_item_img.url }}"></a>
                {% if result.search_description %}
                    {{ result.search_description }}
                {% endif %}
                <span class="box-date">{{ result.specific.date }}</span>
            </li>
        {% endfor %}
    </ul>

    {% if page.search_results.has_previous %}
        <a href="
                {#{% url 'search' %}#}?query={{ search_query|urlencode }}&amp;page={{ search_results.previous_page_number }}">Previous</a>
    {% endif %}

    {% if search_results.has_next %}
        <a href="{% url 'page.search' %}?query={{ search_query|urlencode }}&amp;page={{ search_results.next_page_number }}">Next</a>
    {% endif %}
    {#                    {% elif search_query %}#}
    {#                        No results found#}
{% endif %}
</div>
</div>
</div>
</section>

{% endblock %}

I tried the cissus code but it doesn't work.

Can someone help me?


Solution

  • In Wagtail, the equivalent process to a Django view function happens in a method named serve. You could get most of the way there by renaming your search method to serve.

    (When writing Django views, you can name the function whatever you want, because you're hooking that function up to a URL in urls.py - but there's no mechanism like that in Wagtail, so you need to use the recognised name serve. A method named search won't get called.)

    Next, you need to make sure that the appropriate variables are available on your template. In your template code, you're using both {{ self.title }} and {{ page.heading }}, but self and page actually refer to the same object - the Page object that's currently handling the response. I'd recommend standardising on page as the variable name in your template - {{ page.title }}, {{ page.heading }} and so on.

    Since you're overriding Wagtail's default page serving mechanism, which would normally take care of passing page to the template, you need to add that back to your TemplateResponse call:

        return TemplateResponse(
            request,
            "search/search_page.html",
            {
                "page": self,
                "search_query": search_query,
                "search_results": search_results,
            },
        )
    

    Now the usual page properties such as {{ page.title }} will be available. However, search_query and search_results are not properties of the page object here - they're just additional variables that you're passing to the template alongside page - so lines such as {% if page.search_results %} should be {% if search_results %} instead.

    In fact, a neater way of achieving the same thing would be to override the get_context method instead. You don't need to replace Wagtail's default serving mechanism completely, right down to rendering the template response - all you're really doing here is inserting the search_query and search_results variables into what Wagtail is doing already. This would look like:

    def get_context(self, request, *args, **kwargs):
        context = super().get_context(request, *args, **kwargs)
    
        # ... include your existing search logic here, not including the
        # 'return TemplateResponse' part ...
    
        # Add extra variables and return the updated context
        context['search_query'] = search_query
        context['search_results'] = search_results
        return context