OverIQ.com

Searching Snippets

Last updated on July 27, 2020


In this lesson, we will add a view to allow users to search snippets via keywords.

Let's start by creating a search form.

In the forms.py, define SearchForm class towards the end of the file as follows:

djangobin/django_project/djangobin/forms.py

1
2
3
4
5
6
#...

class SearchForm(forms.Form):
    query = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control',
                                                              'placeholder': 'Search'}))
    mysnippet = forms.BooleanField(required=False)

The query field is where the users will enter their search term. The mysnippet field will be visible only to the users who are logged in. If selected, it allows users to search through his snippets only.

Next, add a new view named search() in views.py just below the profile() view as follows:

djangobin/django_project/djangobin/views.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#...
from django.db.models import Q
from .forms import SnippetForm, ContactForm, LoginForm, CreateUserForm, \
            SettingForm, SearchForm
#...


def search(request):
    f = SearchForm(request.GET)
    snippets = []

    if f.is_valid():

        query = f.cleaned_data.get('query')
        mysnippets = f.cleaned_data.get('mysnippet')

        # if mysnippet field is selected, search only logged in user's snippets
        if mysnippets:
            snippet_list = Snippet.objects.filter(
                Q(user=request.user),
                Q(original_code__icontains=query) | Q(title__icontains=query)
            )

        else:
            qs1 = Snippet.objects.filter(
                Q(exposure='public'),
                Q(original_code__icontains = query) | Q(title__icontains = query)
                # Q(user=request.user)
            )

            # if the user is logged in then search his snippets
            if request.user.is_authenticated:
               qs2 = Snippet.objects.filter(Q(user=request.user),
                                            Q(original_code__icontains=query) | Q(title__icontains=query))
               snippet_list = (qs1 | qs2).distinct()

            else:
                snippet_list = qs1

        snippets = paginate_result(request, snippet_list, 5)

    return render(request, 'djangobin/search.html', {'form': f, 'snippets': snippets })

This view function works as follows:

  1. In line 9, we instantiate a SearchForm instance by passing request.GET data to form constructor. The reason we are binding data to the form at the outset is that the search() function will be will be called only when the user has submitted the query in the search box at the top of the page. Further, the query is submitted using the GET request this allows users to bookmark the search if they want to.

  2. In line 12, we use is_valid() method to determine whether the form is valid or not. If the form is not valid, we render an empty form without any data; otherwise, the course of action depends upon how the form is submitted. There are two possible scenarios:

If the form is submitted with mysnippet field checked, then this will trigger the execution of the following if block.

1
2
3
4
5
6
7
8
#...
        # if mysnippet field is selected, search only logged in user's snippets
        if mysnippets:
            snippet_list = Snippet.objects.filter(
                Q(user=request.user),
                Q(original_code__icontains=query) | Q(title__icontains=query)
            )
#...

On the other hand, If the form is submitted without the mysnippet field checked, then this will trigger the execution of the else block.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#...
        else:
            qs1 = Snippet.objects.filter(
                Q(exposure='public'),
                Q(original_code__icontains = query) | Q(title__icontains = query)
                # Q(user=request.user)
            )

            # if the user is logged in then search his snippets
            if request.user.is_authenticated:
               qs2 = Snippet.objects.filter(Q(user=request.user),
                                            Q(original_code__icontains=query) | Q(title__icontains=query))
               snippet_list = (qs1 | qs2).distinct()

            else:
                snippet_list = qs1
#...

If the user is logged in, we create a new queryset containing the snippets created by the user. The querysets qs1 and qs2 may contain duplicate results. To remove the duplicates combine two querysets using | (bitwise OR) operator and then apply the distinct() method on the resulting queryset. We are now left with unique results.

In line 40, we call paginate_result() to get paginated results.

Finally, in line 42, we render the template.

Add a URL pattern named search to urls.py as follows:

djangobin/django_project/djangobin/urls.py

1
2
3
4
5
6
#...
urlpatterns = [
    #...
    url('^delete/(?P<snippet_slug>[\d]+)/$', views.delete_snippet, name='delete_snippet'),
    url('^search/$', views.search, name='search'),
]

Create a template named search.html and add the following code to it:

djangobin/django_project/djangobin/templates/djangobin/search.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
{% extends 'djangobin/base.html' %}

{% block title %}
    {{ request.GET.query }} - {{ block.super }}
{% endblock %}

{% block main %}

    <form action="" class="form-inline">

        <div class="form-group">
            {{ form.query }}
        </div>

        {% if request.user.is_authenticated %}
            <div class="checkbox">
                <label>
                    {{ form.mysnippet }} Only search my snippets.
                </label>
            </div>
        {% endif %}

        <button type="submit" class="btn btn-primary">Search</button>
    </form>

    <hr>

    {% for snippet in snippets %}

        {% if forloop.first %}
            <h5>{{ snippets.paginator.count }} record{{ snippets.paginator.count|pluralize }} found.</h5>
            <hr>
        {% endif %}

        <h4><a href="{{ snippet.get_absolute_url }}">{{ snippet.title }}</a></h4>
        <p>{{ snippet.original_code|truncatechars:250 }}</p>
        <hr>

    {% empty %}
        <h5>No records found.</h5>
    {% endfor %}

    {% if snippets.paginator.num_pages > 1 %}
        <nav aria-label="...">
            <ul class="pager">

                <li>Page {{ snippets.number }} of {{ snippets.paginator.num_pages }}</li>

                {% if snippets.has_previous %}
                    <li><a href="?query={{ request.GET.query }}&page={{ snippets.previous_page_number }}">Previous</a></li>
                {% endif %}

                {% if snippets.has_next %}
                    <li><a href="?query={{ request.GET.query }}&page={{ snippets.next_page_number }}">Next</a></li>
                {% endif %}
            </ul>
        </nav>
    {% endif %}

{% endblock %}

Next, update <form> element base.html as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{# ... #}
                <li {% if request.path == '/about/' %}class='active'{% endif %}>
                    <a href="">About</a>
                </li>
                <li {% if request.path == '/contact/' %}class='active'{% endif %}>
                    <a href="{% url 'djangobin:contact' %}">Contact</a>
                </li>
            </ul>

            <form action="{% url 'djangobin:search' %}" class="navbar-form navbar-left" method="get">
                <div class="form-group">
                    <input type="text" name="query" class="form-control"
                           placeholder="Search" value="{{ request.GET.query }}">
                </div>
            </form>
        
            <ul class="nav navbar-nav navbar-right">
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
                       aria-haspopup="true" aria-expanded="false">
                        {% if request.user.is_authenticated %}
                            {{ request.user.username|upper }}
                        {% else %}
{# ... #}

Now, open your browser and navigate to http://localhost:8000/. Enter a query in the search box at the top of the page and hit enter. You should see search results as follows:

If you are logged in then you will see mysnippet select box beside the search box as follows:

Submitting the form with mysnippet field checked will limit the search results to your snippets only.