Whether it’s the list of latest articles or the list of hottest articles, all of the article data is presented to the user.

But if the user only cares about certain types of articles, it becomes inconvenient and inefficient to extract all the data.

Therefore, it is useful to provide users with a search function that provides them with several articles of interest.

The preparatory work

logic

Although the details are different, search and lists have a lot in common: they both start by retrieving a few article objects and presenting them to the user. As mentioned in the previous chapter, code duplication is the root of all evil, and good practice is to reuse as many similarly functional modules as possible. With this in mind, we’re going to continue to build on the article_list() and make it even more powerful.

As the project grows larger, it becomes necessary to break down complex modules into simpler ones. We don’t have to worry about that at the moment.

Even cooler, we want the articles to be sorted by time, popularity, and so on. Therefore, we need to construct a new parameter search that can be queried jointly with the previous order parameter.

GET or POST?

Text submitted when a user searches for content can be submitted using either a GET request or a POST request. Select based on actual requirements.

Because the order is submitted using GET, and turning pages is a GET request, choosing GET to submit search text can be easily combined with the previous module.

We’ve already used the form component

to request submission of data via POST. Form components can also submit GET requests by removing the method=”POST” attribute.

Q object

Model.objects.all() returns all the objects in the table.

Model.objects.filter(**kwargs) returns partial objects that match a given parameter.

Also, model.objects. exclude(**kwargs) returns an object that does not match the given argument

What if you want to query multiple parameters? For example, query the title and body of the article at the same time. That’s where you need the Q object.

view

Modify article_list() as described above:

article/views.py

...

# introduce Q objects
from django.db.models import Q

def article_list(request):
    search = request.GET.get('search')
    order = request.GET.get('order')
    # user search logic
    if search:
        if order == 'total_views':
            # syndicate search with Q objects
            article_list = ArticlePost.objects.filter(
                Q(title__icontains=search) |
                Q(body__icontains=search)
            ).order_by('-total_views')
        else:
            article_list = ArticlePost.objects.filter(
                Q(title__icontains=search) |
                Q(body__icontains=search)
            )
    else:
        Reset the search parameter to null
        search = ' '
        if order == 'total_views':
            article_list = ArticlePost.objects.all().order_by('-total_views')
        else:
            article_list = ArticlePost.objects.all()

    paginator = Paginator(article_list, 3)
    page = request.GET.get('page')
    articles = paginator.get_page(page)
    
    Add search to context
    context = { 'articles': articles, 'order': order, 'search': search }
    
    return render(request, 'article/list.html', context)

...
Copy the code

Key knowledge is as follows:

  • The new parameter search is used to store the text to be searched. If search is not empty, a specific article object is retrieved.

  • Note the use of the Q object in filter. Q(title__icontains=search) means to search in the title field of the model. Icontains is a case-insensitive contain separated by two underscores. Search is the text to be queried. With a pipeline operator | Q objects, and to reach the purpose of the federated query.

    Icontains is case insensitive, and contains is case sensitive

  • Why is the search = ‘statement needed? If the user does not search, search = request.get.GET (‘search’) makes search = None, which is incorrectly converted to a “None” string when passed to the template! Equivalent to the user searching for the keyword “None”, which is clearly wrong.

    After completing this chapter, you can delete this statement to see the effect

There is also a bit of code optimization: repeat order = request.get.GET (‘order’) at the top to clean up the module a bit.

The template

Or the template file for modifying the list of articles.

Need to modify the content of a little more, carefully do not read wrong:

templates/article/list.html

...

<div class="container">
    <! -- Modify the href of bread crumbs to add the search parameter -->
    <nav aria-label="breadcrumb">
        <ol class="breadcrumb">
            <li class="breadcrumb-item">
                <a href="{% url 'article:article_list' %}? search={{ search }}">The latest</a>
            </li>
            <li class="breadcrumb-item">
                <a href="{% url 'article:article_list' %}? order=total_views&search={{ search }}">The hottest</a>
            </li>
        </ol>
    </nav>

    <! -- New, search bar -->
    <div class="row">
        <div class="col-auto mr-auto">
            <form class="form-inline" >
                <label class="sr-only">content</label>
                <input type="text" 
                    class="form-control mb-2 mr-sm-2" 
                    name="search" 
                    placeholder="Search for articles..." 
                    required
                >
            </form>
        </div>
    </div>

    <! -- Added, search prompt -->
    {% if search %}
        {% if articles %}
            <h4><span style="color: red">"{{ search }}"</span>Search results are as follows:</h4>
            <hr>        
        {% else %}
            <h4>no<span style="color: red">"{{ search }}"</span>About the article.</h4>
            <hr>
        {% endif %}
    {% endif %}
        
            
...
    
<! Href add the search parameter -->
<a href="? page=1&order={{ order }}&search={{ search }}" class="btn btn-success">.<a href="? page={{ articles.previous_page_number }}&order={{ order }}&search={{ search }}" class="btn btn-secondary">.<a href="? page={{ articles.next_page_number }}&order={{ order }}&search={{ search }}" class="btn btn-secondary">.<a href="? page={{ articles.paginator.num_pages }}&order={{ order }}&search={{ search }}"class="btn btn-success">.Copy the code
  • The breadcrumb component, the page number component have all been changed href: addedsearchparameter
  • New search bar for GET request submissionsearchParameters;requiredProperty prevents the user from submitting blank text
  • Added search prompt. A good UI must make the user aware of the current state

Emmm… You don’t have to change anything else.

Start testing!

test

Again open the article list page:

The search bar appears! And turn the page, hot and other functions are normal.

Type “PYTHON” in the search bar and the results are as follows:

Successfully retrieved articles with the keyword “Python” in the title or body, and are case insensitive. Click on the hottest button to order search results by number of views, and the page-turning function also works. Good, we got our goal!

Readers who have learned this should be proud: you use the same URL, integrate many functions, and present different content! This is not easy for beginners to do.

There is a minor downside to this approach: sometimes urls contain meaningless strings like search= “(null), which is a bit of an obsession. Fortunately, this doesn’t matter, as most users don’t care what your URL looks like, as long as it looks good.

conclusion

This chapter completes a simple search function that should be sufficient for personal blogs.

More sophisticated, deeply customized searches can be done with third-party modules such as Haystack.

In addition, the author’s search is not necessarily optimal. You already have multiple ways to implement search functionality (POST requests? Search for dedicated views? Write another URL?) Give it a try.

  • If you have any questions please leave a message on Doucet’s personal website and I will reply as soon as possible.
  • Or Email me a private message: [email protected]
  • Project code: Django_blog_tutorial

Please indicate the source of reprint.