Reputation: 1711
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.
Upvotes: 19
Views: 38857
Reputation: 33407
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
Upvotes: 3
Reputation: 1711
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.
Upvotes: 20