Django ListView customising queryset

2020-02-10 14:52发布

Hopefully this should be a simple one to help me with.

I have a page with a dropdown menu containing three items:

<form method="GET">

    <select name="browse">

        <option>Cats</option>

        <option>Dogs</option>

        <option>Worms</option>

    </select>

 <input type="submit" value="Submit" />

</form>

<!-- Output table -->

  <table id="myTable">

      <thead>
          <tr>
            <th>Name</th>
            <th>Colour</th>
          </tr>
      </thead>

      <tbody>
      {% for object in object_list %}
          <tr>
            <td>{{ object.name }}</td>
            <td>{{ object.colour }}</td>
          </tr>
      {% endfor %}
      </tbody>

  </table>

<!-- Pagination controls -->

<div class="pagination">
    <span class="page-links">
        {% if page_obj.has_previous %}
            <a href="?page={{ page_obj.previous_page_number }}">previous</a>
        {% endif %}
        <span class="page-current">
            Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
        </span>
        {% if page_obj.has_next %}
            <a href="?page={{ page_obj.next_page_number }}">next</a>
        {% endif %}
    </span>
</div>

When the user selects an item and hits submit, they are given the results in a table as generated by the generic ListView:

class Browse(generic.ListView):
    template_name = 'app/browse.html'
    paginate_by = 25

    def get_queryset(self):
        queryset = Cats.objects.all()
        if self.request.GET.get("browse"):
            selection = self.request.GET.get("browse")
            if selection == "Cats":
                queryset = Cats.objects.all()
            elif selection == "Dogs":
                queryset = Dogs.objects.all()
            elif selection == "Worms":
                queryset = Worms.objects.all()
            else:
                queryset = Cats.objects.all()
        return queryset

However, when I attempt to turn a page using the pagination controls, the queryset resets to the first (default) item Cats, because (I think) the form data is reset.

Any idea how to circumvent this problem?

Thanks!

PS: Oh, on that note, is it possible to set the queryset to none to begin with? Much obliged!

UPDATE: When I use pagination on the Cats queryset it works fine so the bug is only displayed on the other two sets.

2条回答
Root(大扎)
2楼-- · 2020-02-10 15:14

To solve this problem I just modified the pagination HTML, to accommodate both the get request from the form and the page number in the url string, like so:

<div class="pagination">
    <span class="page-links">
        {% if page_obj.has_previous %}
            <a href="/browse/?browse={{ input }}&page={{ page_obj.previous_page_number }}">previous</a>
        {% endif %}
        <span class="page-current">
            Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
        </span>
        {% if page_obj.has_next %}
            <a href="/browse/?browse={{ input }}&page={{ page_obj.next_page_number }}">next</a>
        {% endif %}
    </span>
</div>

The {{ input }} here is a string containing the option submitted via the form, e.g. 'Cats' or 'Worms'.

To be able to pass this into the template, I modified the get_context_data method of the class based view as such:

class Browse(generic.ListView):
    template_name = 'app/browse.html'
    paginate_by = 25

    # Modifying the get_context_data method

    def get_context_data(self, **kwargs):
        context = super(Browse, self).get_context_data(**kwargs)
        q = self.request.GET.get("browse")
        context['input'] = q
        return context

    def get_queryset(self):
        queryset = Cats.objects.all()
        if self.request.GET.get("browse"):
            selection = self.request.GET.get("browse")
            if selection == "Cats":
                queryset = Cats.objects.all()
            elif selection == "Dogs":
                queryset = Dogs.objects.all()
            elif selection == "Worms":
                queryset = Worms.objects.all()
            else:
                queryset = Cats.objects.all()
        return queryset

That was it, the url string now reads something like:

/browse/?browse=Cats&page=3

So there it is, pagination now works alongside the get method of the form.

查看更多
淡お忘
3楼-- · 2020-02-10 15:33

I put together a template tag to help using queries based on Sirrah's answer. Example:

<a href="{% url view_url %}?{% query query_params page=num %}">{{ num }}</a>

If query params is a dictionary {'foo': 'bar'} passed in the context, it will be rendered to something like this:

<a href="myview/?foo=bar&page=2">2</a>

Syntax:

{% query var_name param=value 'name_only_param' other_param=value|default:'x' another_var %}

Variables can be lists, dicts, strings or None (None is skipped).

Code:

from django import template
from django.utils.encoding import force_text
from django.template.base import Node, TemplateSyntaxError, kwarg_re, FilterExpression

register = template.Library()

@register.tag
def query(parser, token):
    bits = token.split_contents()
    args = []
    asvar = None
    bits = bits[1:]
    if len(bits) >= 2 and bits[-2] == 'as':
        asvar = bits[-1]
        bits = bits[:-2]

    if len(bits):
        for bit in bits:
            match = kwarg_re.match(bit)
            if not match:
                raise TemplateSyntaxError("Malformed arguments to url tag")
            name, value = match.groups()
            if name:
                args.append({name: parser.compile_filter(value)})
            else:
                args.append(parser.compile_filter(value))

    return QueryNode(args, asvar)


class QueryNode(Node):
    def __init__(self, args, asvar):
        self.args = args
        self.asvar = asvar

    def render(self, context):
        def join(thing, lst):
            if isinstance(thing, dict):
                for k, v in thing.items():
                    if isinstance(v, FilterExpression):
                        v = force_text(v.resolve(context))
                    if v is None:
                        continue
                    lst.append('{}={}'.format(k, v))
            elif isinstance(thing, list):
                for it in thing:
                    if isinstance(it, FilterExpression):
                        it = it.resolve(context)
                    join(it, lst)
            elif isinstance(thing, str):
                lst.append(thing + '=')
            elif thing is None:
                pass
            else:
                raise TypeError('Cannot join: %r' % thing)

        query_lst = []
        join(self.args, query_lst)
        query = '&'.join(query_lst)

        if self.asvar:
            context[self.asvar] = query
            return ''
        else:
            return query
查看更多
登录 后发表回答