Django filter and pagination

이다연·2021년 3월 29일
1

Django

목록 보기
20/33

https://www.youtube.com/watch?v=dkJ3uqkdCcY&t=459s

Problem

장고에서 필터와 페이징을 동시에 구현하면 새 페이지에서 필터가 리셋되는 문제가 생김.

Using Django filter and pagination together often breaks the other functionality.

After filtering the category, if you go to the next page, It resets the filtered result.

Previous filter

1. views

def HomeView(request):
    tutorials = Tutorial.objects.all()
    myFilter = TutorialFilter(request.GET, queryset=tutorials)
    tutorials = myFilter.qs
    
    return render(request, 'index.html',
    	{'tutorials': tutorials, 'myFilter': myFilter})

2. template

{% for item in tutorials %}

Filter & Paging

Filter

1. models.py

순서 명시해줘야 오류 안남.

class Tutorial(models.Model):
~
class Meta:
        ordering = ('-last_updated',)

2. filters.py

import django_filters
from django_filters import DateFilter
from .models import Tutorial
from django import forms


class TutorialFilter(django_filters.FilterSet):
    DATE_CHOICES        = (
                            ('newest', 'Newest'),
                            ('oldest', 'Oldest')
    )
    date_sort           = django_filters.ChoiceFilter(
                            label     ='Sort by Date',
                            choices   =DATE_CHOICES,
                            method    ='filter_by_date',
                            # widget  =forms.Select(attrs={'size': 4})
    )


    class Meta:
        model = Tutorial
        fields = {
                            'title'     : ['icontains'],
                            'instructor': ['icontains'],
                            'language'  : ['exact'],
                            'difficulty': ['exact'],
        }


    def filter_by_date(self, queryset, name, value):
        expression = 'last_updated' if value == 'oldest' else '-last_updated'
        return queryset.order_by(expression)

3. views

  • [ ] is a name of variable which will be passed to template
from .filters import TutorialFilter

def Test(request):
    context = {}
    
 # returns a list
    filtered_tutorials = TutorialFilter(
        request.GET,
        queryset=Tutorial.objects.all()

    )

    context['filtered_tutorials'] = filtered_tutorials

    return render(request, 'test_template.html', context=context)

4. templates

{% for item in filtered_tutorials.qs %}


<form method="get">

    {{ filtered_tutorials.form.as_p }}
    <input type="submit" value="Press">

</form>

{% for item in filtered_tutorials.qs %}
    <h2> {{item.title}} </h2>
{% endfor %}

Pagination

video 11:09

1. views

  • each page holds 2 objects in one page (from the list)
    Paginator(filtered_tutorials.qs, 2)

  • get pages
    request.GET.get('page')

  • page object which will contain page of entries
    paginated_filtered_tutorials.get_page(page_number)

#pagination

    paginated_filtered_tutorials = Paginator(filtered_tutorials.qs, 2) 
    page_number = request.GET.get('page') 
    tutorial_page_obj = paginated_filtered_tutorials.get_page(page_number) 
    
    context['tutorial_page_obj'] = tutorial_page_obj
    

template

1. wrong method

  • code below only works for pagination, when filter applied, it breaks the filtered result and returns non filtered list.
    -> because of url parameter

below is when I apply filter

below is when I click on pagination

2nd result url doesn't include any filter parameters

the only way our view knows how to display entries according to the filter is through the url. And it's not passing the filter parameters

the reason is because in html, href="?page={{ tutorial_page_obj.previous_page_number }}" this line eliminates filtered url.

code

  • pass tutorial_page_obj instead of filtered_tutorials.qs in for loop, otherwise pagination won't work.
  {% for item in tutorial_page_obj %}
  {{item}}
  {% endfor %}

print:
Django (3.0) Crash Course Tutorials | Dennis Ivy Build Three Django Projects | Code With Tomi 코딩 1시간만에 배우기 - 파이썬 (ft. 실리콘밸리 엔지니어) | Teccboi Wonie 파이썬(Python) | 동빈나 Python Django Tutorial: Full-Featured Web App | Corey Schafer



{% for item in tutorial_page_obj %}
    <h2> {{item.title}} </h2>
{% endfor %}

<div class="pagination">
    <span class="step-links">
        {% if tutorial_page_obj.has_previous %}
         <a href="?page=1">&laquo; First</a>
         <a href="?page={{ tutorial_page_obj.previous_page_number }}"> Previous</a>
        {% endif %}


        <span class="current">
            Page {{tutorial_page_obj.number}} of {{ tutorial_page_obj.paginator.num_pages}}
        </span>


        {% if tutorial_page_obj.has_next %}
         <a href="?page={{ tutorial_page_obj.next_page_number }}"> Next</a>
         <a href="?page={{ tutorial_page_obj.paginator.num_pages }}">&raquo;Last</a>
        {% endif %}
    </span>
</div>

2. Solution

first create three dir, files inside app directory.

1. a directory'templatetags'
2. a file '_ init _.py' (two underscores) -package
3. a file 'appname_extras.py'

Django has built-in template tags, but we are going to create a custom template tags to tackle the problem. by modify existing template tag.

https://docs.djangoproject.com/en/3.1/howto/custom-template-tags/

from django import template

register = template.Library()

if you are using the main app, you need to register templatetags in settings.py

INSTALLED_APPS = [
  'hub.apps.HubConfig',  ]

hub_extras.py

need further study..

from django import template

register = template.Library()



@register.simple_tag
def my_url(value, field_name, urlencode=None): #value will pass page number, field_name pass string page and URL
    url = f'?{field_name}={value}'

    if urlencode:
        querystring = urlencode.split('&')
        filtered_querystring = filter(lambda p: p.split('=')[0]!=field_name, querystring) #take query string list, take each string and split it by 2, if the first string is equel to the field.
        encoded_querystring = '&'.join(filtered_querystring)
        url = f'{url}&{encoded_querystring}'

    return url

template

{% load hub_extras %}

< a href="?page=1  
->  

{% my_url 1 'page' request.GET.urlencode %} ">&laquo; First< /a>




< a href="?page={{ tutorial_page_obj.previous_page_number }} 
-> 

{% my_url tutorial_page_obj.previous_page_number 'page' request.GET.urlencode %} "> Previous< /a>


< a href="?page={{ tutorial_page_obj.next_page_number }} 
-> 
{% my_url tutorial_page_obj.next_page_number 'page' request.GET.urlencode %} "> Next</a>


<a href="?page={{ tutorial_page_obj.paginator.num_pages }} 
-> 
{% my_url tutorial_page_obj.paginator.num_pages 'page' request.GET.urlencode %}  ">&raquo;Last</a>

another problem

UnorderedObjectListWarning: Pagination may yield inconsistent results

solution: add order to meta

models.py

class Tutorial(models.Model):
~
class Meta:
        ordering = ('-last_updated',)


https://forum.djangoproject.com/t/unorderedobjectlistwarning-pagination-may-yield-inconsistent-results-with-an-unordered-object-list-class-myapp-models-movies-queryset-this-is-the-code-snippets-https-github-com-coderbang1-movie-app1/4549/2

Bonus

(remove whitespace when using)
& laquo; - left pointing
& raquo; - right pointing

profile
Dayeon Lee | Django & Python Web Developer

0개의 댓글