Як розподілити сторінку Django за допомогою інших змінних get?


77

У мене проблеми з використанням пагінації в Django . Візьмемо URL-адресу нижче як приклад:

http://127.0.0.1:8000/users/?sort=first_name

На цій сторінці я сортую список користувачів за їх ім'ям. Без змінної GET сортування за замовчуванням сортується за ідентифікатором.

Тепер, якщо я натисну наступне посилання, я очікую наступну URL-адресу:

http://127.0.0.1:8000/users/?sort=first_name&page=2

Натомість я втрачаю всі змінні get і закінчую цим

http://127.0.0.1:8000/users/?page=2

Це проблема, оскільки друга сторінка сортується за ідентифікатором замість first_name.

Якщо я використовую request.get_full_path, у підсумку у мене вийде потворна URL-адреса:

http://127.0.0.1:8000/users/?sort=first_name&page=2&page=3&page=4

Яке рішення? Чи є спосіб отримати доступ до змінних GET у шаблоні та замінити значення сторінки?

Я використовую пагінацію, як описано в документації Django, і я бажаю продовжувати використовувати її. Код шаблону, який я використовую, подібний до цього:

{% if contacts.has_next %}
    <a href="?page={{ contacts.next_page_number }}">next</a>
{% endif %}

Відповіді:


61

Я вважав запропоновані спеціальні теги занадто складними, ось що я зробив у шаблоні:

<a href="?{% url_replace request 'page' paginator.next_page_number %}">

І функція тегу:

@register.simple_tag
def url_replace(request, field, value):

    dict_ = request.GET.copy()

    dict_[field] = value

    return dict_.urlencode()

Якщо url_param ще немає в url, він буде доданий зі значенням. Якщо воно вже є, воно буде замінено новим значенням. Це просте рішення, яке мене влаштовує, але не працює, коли url має кілька параметрів з однаковим іменем.

Вам також потрібно, щоб екземпляр запиту RequestContext був наданий до вашого шаблону з вашого подання. Більше інформації тут:

http://lincolnloop.com/blog/2008/may/10/getting-requestcontext-your-templates/


4
Ви навіть можете здійснити рефакторинг, щоб його не потрібно передавати requestяк параметр. Дивіться цей відповідь stackoverflow.com/a/2160298/1272513
alfetopito

42

Я думаю, що рішення url_replace може бути переписано більш елегантно, як

from urllib.parse import urlencode
from django import template

register = template.Library()

@register.simple_tag(takes_context=True)
def url_replace(context, **kwargs):
    query = context['request'].GET.copy()
    query.update(kwargs)
    return query.urlencode()

з рядком шаблону, спрощеним до

<a href="?{% url_replace page=paginator.next_page_number %}">

4
Дякую, це працює! Для Python 3 використовуйте urllib.parse.urlencode(). Дивіться це питання .
arogachev

А для Python 2.7 це буде import urllibі return urllib.urlencode(query).
S_M

Щоб цей тег працював, django.template.context_processors.requestслід увімкнути контекстний процесор. Хоча це ввімкнено за замовчуванням.
skoval00

1
Для параметрів GET з кількома значеннями для одного і того ж ключа краще використовувати: query = context['request'].GET.copy()іreturn query.urlencode()
jakubste

7
мінуси - він створює повторюваний параметр у url так:&p=2&p=3&p=4
Shaig Khaligli

13

Після декількох розіграшів я знайшов рішення ... хоча я не знаю, чи воно справді хороше. Я вважаю за краще більш елегантне рішення.

У будь-якому випадку я передаю запит шаблону і маю доступ до всіх змінних GET через request.GET. Потім я перебираю словник GET, і якщо змінна не є сторінкою, я друкую її.

{% if contacts.has_previous %}
    <a href="?page={{ contacts.previous_page_number }}{% for key,value in request.GET.items %}{% ifnotequal key 'page' %}&{{ key }}={{ value }}{% endifnotequal %}{% endfor %}">previous</a>
{% endif %}

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

{# I have all of this in one line in my code (like in the previous section), but I'm putting spaces here for readability.  #}
{% if contacts.has_next %}
    <a href="?page={{ contacts.next_page_number }}
        {% for key,value in request.GET.items %}
            {% ifnotequal key 'page' %}
                &{{ key }}={{ value }}
            {% endifnotequal %}
        {% endfor %}
    ">next</a>
{% endif %}

2
Цей підхід працює, але має кілька недоліків: 1. Він порушує принцип СУХОСТІ - ви повторюєте свій код, що означає, що якщо ви хочете щось змінити в ньому, вам доведеться змінити його у всіх місцях, куди ви його скопіювали. 2. Він незначно порушує шаблон дизайну Model-View-Controller (або Model-Template-View, як це називає творець Django) - Шаблони повинні використовуватися для простої візуалізації даних. 3. Це спричиняє постійне передавання надмірних / безглуздих параметрів GET - це, мабуть, не є великою проблемою, але на мій погляд, витонченіше фільтрувати такі параметри.
Томаш Зелінський

2
Додаток до попереднього коментаря: Якщо ви наполягаєте на обробці цього в шаблоні, то, я думаю, вам слід написати власний тег шаблону, який би прийняв requestяк параметр, а потім надрукувати рядок параметрів назад до шаблону.
Томаш Зелінський

Крім того, це, схоже, не працює з полями для вибору, де можна вибрати кілька варіантів.
Ліам,

Це не порушує принцип СУХОСТОТИ, якщо ви використовуєте успадкування шаблону для пагінації
Гійом Лебретон,

9

У вашому випадку views.pyви якось отримаєте доступ до критеріїв, за якими ви сортуєте, наприклад first_name. Вам потрібно буде передати це значення шаблону та вставити його туди, щоб запам'ятати.

Приклад:

{% if contacts.has_next %}
    <a href="?sort={{ criteria }}&page={{ contacts.next_page_number }}">next</a>
{% endif %}

5

Можна створити контекстний процесор, щоб використовувати його там, де застосовується пагінація.

Наприклад, у my_project/my_app/context_processors.py:

def getvars(request):
    """
    Builds a GET variables string to be uses in template links like pagination
    when persistence of the GET vars is needed.
    """
    variables = request.GET.copy()

    if 'page' in variables:
        del variables['page']

    return {'getvars': '&{0}'.format(variables.urlencode())}

Додайте процесор контексту до налаштувань проекту Django:

TEMPLATE_CONTEXT_PROCESSORS = (
    'django.contrib.auth.context_processors.auth',
    'django.contrib.messages.context_processors.messages',
    'django.core.context_processors.i18n',
    'django.core.context_processors.request',
    'django.core.context_processors.media',
    'django.core.context_processors.static',
     ...
    'my_project.my_app.context_processors.getvars',
)

Потім у своїх шаблонах ви можете використовувати це під час розміщення сторінок:

<div class="row">
    {# Initial/backward buttons #}
    <div class="col-xs-4 col-md-4 text-left">
        <a href="?page=1{{ getvars }}" class="btn btn-rounded">{% trans 'first' %}</a>
        {% if page_obj.has_previous %}
            <a href="?page={{ page_obj.previous_page_number }}{{ getvars }}" class="btn btn-rounded">{% trans 'previous' %}</a>
        {% endif %}
    </div>

    {# Page selection by number #}
    <div class="col-xs-4 col-md-4 text-center content-pagination">
        {% for page in page_obj.paginator.page_range %}
            {% ifequal page page_obj.number %}
                <a class="active">{{ page }}</a>
            {% else %}
                <a href="?page={{ page }}{{ getvars }}">{{ page }}</a>
            {% endifequal %}
        {% endfor %}
    </div>

    {# Final/forward buttons #}
    <div class="col-xs-4 col-md-4 text-right">
        {% if page_obj.has_next %}
            <a href="?page={{ page_obj.next_page_number }}{{ getvars }}" class="btn btn-rounded">{% trans 'next' %}</a>
        {% endif %}
        <a href="?page={{ paginator.num_pages }}{{ getvars }}" class="btn btn-rounded">{% trans 'last' %}</a>
    </div>
</div>

Незалежно від змінних GET, які ви маєте у своєму запиті, вони будуть додані після ?page=параметра GET.


4

Поліпшення цього шляхом:

Використовуйте urlencodeз djangoзамість того urllib, щоб запобігти UnicodeEncodeErrorпомилки з unicodeаргументами.

Тег шаблону:

from django.utils.http import urlencode

@register.simple_tag(takes_context=True)
def url_replace(context, **kwargs):
    query = context['request'].GET.dict()
    query.update(kwargs)
    return urlencode(query)

Шаблон:

<!-- Pagination -->
<div class="pagination">
 <span class="step-links">
   {% if coupons.has_previous %}
    <a href="?{% url_replace page=objects.previous_page_number %}">Prev</a>
   {% endif %}
   <span class="current">
    Page {{ objects.number }} of {{ objects.paginator.num_pages }}
   </span>
   {% if objects.has_next %}
    <a href="?{% url_replace page=objects.next_page_number %}">Next</a>
   {% endif %}
  </span>
</div>

4

У мене виникла ця проблема під час використання django-bootstrap3. (Просте) рішення без будь-яких тегів шаблону використовує:

{% bootstrap_pagination page_obj extra=request.GET.urlencode %}

Мені знадобився час, щоб це з’ясувати ... Нарешті, завдяки цьому посту я це зробив .


3

Моє рішення грунтується на цьому один вище з невеликим поліпшенням видалення &page=з'являтися кілька разів. Дивіться цей коментар

    @register.simple_tag(takes_context=True)
    def url_replace(context, **kwargs):
        query = context['request'].GET.copy()
        query.pop('page', None)
        query.update(kwargs)
        return query.urlencode()

Цей рядок query.pop('page', None)мовчки видаляє сторінку з URL-адреси


2

Ось корисний спеціальний тег шаблону для побудови рядків запитів.

<a href="?{% make_query_string page=obj_list.next_page_number %}">Next page</a>

Якщо URL-адреса http://example.com/django/page/?search=sometext , згенерований HTML повинен бути приблизно таким:

<a href="?search=sometext&page=2">Next page</a>

Інші приклади:

<!-- Original URL -->
<!-- http://example.com/django/page/?page=1&item=foo&item=bar -->

<!-- Add or replace arguments -->
{% make_query_string page=2 item="foo2" size=10 %}
<!-- Result: page=2&item=foo2&size=10 -->

<!-- Append arguments -->
{% make_query_string item+="foo2" item+="bar2" %}
<!-- Result: page=1&item=foo&item=bar&item=foo2&item=bar2 -->

<!-- Remove a specific argument -->
{% make_query_string item-="foo" %}
<!-- Result: page=1&item=bar -->

<!-- Remove all arguments with a specific name -->
{% make_query_string item= %}
<!-- Result: page=1 -->

Нарешті, вихідний код (написаний мною):

# -*- coding: utf-8 -*-
from django import template
from django.utils.encoding import force_text  # Django 1.5+ only

register = template.Library()


class QueryStringNode(template.Node):
    def __init__(self, tag_name, parsed_args, var_name=None, silent=False):
        self.tag_name = tag_name
        self.parsed_args = parsed_args
        self.var_name = var_name
        self.silent = silent

    def render(self, context):
        # django.core.context_processors.request should be enabled in
        # settings.TEMPLATE_CONTEXT_PROCESSORS.
        # Or else, directly pass the HttpRequest object as 'request' in context.
        query_dict = context['request'].GET.copy()
        for op, key, value in self.parsed_args:
            if op == '+':
                query_dict.appendlist(key, value.resolve(context))
            elif op == '-':
                list_ = query_dict.getlist(key)
                value_ = value.resolve(context)
                try:
                    list_.remove(value_)
                except ValueError:
                    # Value not found
                    if not isinstance(value_, basestring):
                        # Try to convert it to unicode, and try again
                        try:
                            list_.remove(force_text(value_))
                        except ValueError:
                            pass
            elif op == 'd':
                try:
                    del query_dict[key]
                except KeyError:
                    pass
            else:
                query_dict[key] = value.resolve(context)
        query_string = query_dict.urlencode()
        if self.var_name:
            context[self.var_name] = query_string
        if self.silent:
            return ''
        return query_string


@register.tag
def make_query_string(parser, token):
    # {% make_query_string page=1 size= item+="foo" item-="bar" as foo [silent] %}
    args = token.split_contents()
    tag_name = args[0]
    as_form = False
    if len(args) > 3 and args[-3] == "as":
        # {% x_make_query_string ... as foo silent %} case.
        if args[-1] != "silent":
            raise template.TemplateSyntaxError(
                "Only 'silent' flag is allowed after %s's name, not '%s'." %
                (tag_name, args[-1]))
        as_form = True
        silent = True
        args = args[:-1]
    elif len(args) > 2 and args[-2] == "as":
        # {% x_make_query_string ... as foo %} case.
        as_form = True
        silent = False

    if as_form:
        var_name = args[-1]
        raw_pairs = args[1:-2]
    else:
        raw_pairs = args[1:]

    parsed_args = []
    for pair in raw_pairs:
        try:
            arg, raw_value = pair.split('=', 1)
        except ValueError:
            raise template.TemplateSyntaxError(
                "%r tag's argument should be in format foo=bar" % tag_name)
        operator = arg[-1]
        if operator == '+':
            # item+="foo": Append to current query arguments.
            # e.g. item=1 -> item=1&item=foo
            parsed_args.append(('+', arg[:-1], parser.compile_filter(raw_value)))
        elif operator == '-':
            # item-="bar": Remove from current query arguments.
            # e.g. item=1&item=bar -> item=1
            parsed_args.append(('-', arg[:-1], parser.compile_filter(raw_value)))
        elif raw_value == '':
            # item=: Completely remove from current query arguments.
            # e.g. item=1&item=2 -> ''
            parsed_args.append(('d', arg, None))
        else:
            # item=1: Replace current query arguments, e.g. item=2 -> item=1
            parsed_args.append(('', arg, parser.compile_filter(raw_value)))

    if as_form:
        node = QueryStringNode(tag_name, parsed_args,
                               var_name=var_name, silent=silent)
    else:
        node = QueryStringNode(tag_name, parsed_args)

    return node

2

@ Skoval00 «s відповідь є найбільш елегантним, однак він додає повторювані &page=параметри запиту в URL.

Ось виправлення:

from urllib.parse import urlencode
from django import template

register = template.Library()

@register.simple_tag(takes_context=True)
def url_replace(context, next_page):
    query = context['request'].GET.copy().urlencode()

    if '&page=' in query:
        url = query.rpartition('&page=')[0] # equivalent to .split('page='), except more efficient 
    else:
        url = query
    return f'{url}&page={next_page}'

1

Це простий спосіб, як я це роблю

З огляду на:

path = ''
path += "%s" % "&".join(["%s=%s" % (key, value) for (key, value) in request.GET.items() if not key=='page' ])

Потім у шаблоні:

href="?page={{ objects.next_page_number }}&{{path}}"

1

Ще одне рішення щодо рішення url_encode, у цьому випадку спрощене skoval00.

У мене було кілька проблем із цією версією. По-перше, він не підтримував кодування Unicode, а два, він не працював для фільтрів із кількома однаковими клавішами (наприклад, віджет MultipleSelect). Через перетворення .dict () усі значення, крім одного, втрачаються. Моя версія підтримує Unicode і кілька однакових ключів:

from django import template
from django.utils.html import mark_safe

register = template.Library()

@register.simple_tag(takes_context=True)
def url_replace(context, **kwargs):
    query = context['request'].GET.copy()

    for kwarg in kwargs:
        try:
            query.pop(kwarg)
        except KeyError:
            pass

    query.update(kwargs)

    return mark_safe(query.urlencode())

Це створює копію QueryDict, а потім видаляє всі ключі, що відповідають kwargs (оскільки оновлення для QueryDict додається замість заміни). Mark_safe був потрібен через проблему подвійного кодування.

Ви б використовували його так (не забудьте завантажити теги):

<a class="next" href="?{% url_replace p=objects.next_page_number%}">Next</a>

де? p = 1 - це наш синтаксис пагінації у поданні.


До речі, практична сторона, якщо у вас багато переглядів з пагінацією: створіть загальний шаблон пагінації. Тоді ви можете просто включити це у кожне представлення, де ви хочете перейти на сторінки: {% include "core/pagination.html" with objects=ads_list %} об'єкти - це загальне ім'я того, що ви розміщуєте на сторінках для загального шаблону, і ви можете призначити йому те, що воно називається у цьому конкретному шаблоні (у цьому випадку - список_відписів).
Apollo Data

1

@Elrond підтримує Моніку

@register.simple_tag(takes_context=True)
def url_replace(context, **kwargs):
    query = context['request'].GET.copy()
    for key in kwargs:
        query[key] = kwargs[key]
    return query.urlencode()

Використовувати в шаблоні

<a class="page-link" href="?{% url_replace p=1 q='bar'%}">

0

Кожне таке посилання, яке ви ставите на свій погляд, має бути забезпечене відповідними параметрами. Немає неявної магії, яка перетворювала б:

http://127.0.0.1:8000/users/?page=2

в:

http://127.0.0.1:8000/users/?sort=first_name&page=2

Отже, вам потрібен якийсь Sorterоб’єкт / клас / функція / фрагмент (все, що може поміститися тут, не перестараючись), який діяв би подібно до django.core.paginator.Paginator, але обробляв би sortпараметр GET.

Це може бути так просто:

sort_order = request.GET.get('sort', 'default-criteria')

<paginate, sort>

return render_to_response('view.html', {
    'paginated_contacts': paginated_contacts,  # Paginator stuff
    'sort_order': sort_order if sort_oder != 'default-criteria' else ''
})

Тоді, на ваш погляд:

{% if contacts.has_next %}
    <a href="?page={{ contacts.next_page_number }}{%if sort_order%}&sort={{sort_oder}}{%endif%}">next</a>
{% endif %}

Я міг би стати більш загальним, але я сподіваюся, ви зрозуміли цю концепцію.


0

Я б сказав, згенеруйте наступне та попереднє посилання з вашого контролера, а потім передайте його у подання та використайте звідти. Я наведу вам приклад (більше схожий на псевдокод):

("next_link", "?param1="+param1+"&param2="+param2+"&page_nr="+(Integer.parseInt(page_nr)-1)

то, на ваш погляд, використовуйте його так:

{% if contacts.has_next %}
<a href="?page={{ contacts.next_link }}">next</a>
{% endif %}

0

Вам потрібно буде повернути GET, як зазначено вище. Ви можете передати частину URL-адреси запиту GET, зателефонувавши

render_dict['GET'] = request.GET.urlencode(True)
return render_to_response('search/search.html',
                          render_dict,
                          context_instance=RequestContext(request))

потім ви можете використовувати це в шаблоні, щоб створити свою URL-адресу, наприклад

href="/search/client/{{ page.no }}/10/?{{ GET }}

0

З допомогою Pagination від Django - зберегти параметри GET дуже просто.

Спочатку скопіюйте параметри GET у змінну (на увазі):

GET_params = request.GET.copy()

і надішліть його в шаблон за допомогою контекстного словника:

return render_to_response(template,
                        {'request': request, 'contact': contact, 'GET_params':GET_params}, context_instance=RequestContext(request))

Друге, що вам потрібно зробити, це використовувати його, вказати в url-викликах (href) у шаблоні - приклад (розширення базової пагінації html для обробки додаткових умов param):

{% if contacts.has_next %}
    {% if GET_params %}
        <a href="?{{GET_params.urlencode}}&amp;page={{ contacts.next_page_number }}">next</a>
    {% else %}
        <a href="?page={{ contacts.next_page_number }}">next</a>
    {% endif %}
{% endif %}

Джерело


0

Ще одна невелика модифікація skoval00 та відновлення Моніки, щоб повністю позбутися дублювання та уникнути негарної?&page=1 частини:

from urllib.parse import urlencode
from django import template

register = template.Library()

@register.simple_tag(takes_context=True)
def url_replace(context, next_page):
    if query.startswith('page') or not len(query):
        new_url = f'page={next_page}'
    elif '&page=' in query:
        get_params = query.rpartition('&page=')[0] # equivalent to .split('page='), except more efficient 
        new_url = f'{get_params}&page={next_page}'
    else:
        new_url = f'{query}&page={next_page}'
    return new_url

0

ваш код повинен бути таким:

{% if contacts.has_next %}
<a href="?page={{ contacts.next_page_number }}{% for key,value in request.GET.items %}{% ifnotequal key 'page' %}&{{ key }}={{ value }}{% endifnotequal %}{% endfor %}">next</a>
{% endif %}

-1

'шлях': request.get_full_path (). rsplit ('& сторінка') [0],


Це не вдається, якщо сторінка не є останнім елементом отримання.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.