How to paginate Django with other get variables? - django
I am having problems using pagination in Django. Take the URL below as an example:
http://127.0.0.1:8000/users/?sort=first_name
On this page I sort a list of users by their first_name. Without a sort GET variable it defaults to sort by id.
Now if I click the next link I expect the following URL:
http://127.0.0.1:8000/users/?sort=first_name&page=2
Instead I lose all get variables and end up with
http://127.0.0.1:8000/users/?page=2
This is a problem because the second page is sorted by id instead of first_name.
If I use request.get_full_path I will eventually end up with an ugly URL:
http://127.0.0.1:8000/users/?sort=first_name&page=2&page=3&page=4
What is the solution? Is there a way to access the GET variables on the template and replace the value for the page?
I am using pagination as described in Django's documentation and my preference is to keep using it. The template code I am using is similar to this:
{% if contacts.has_next %}
next
{% endif %}
I thought the custom tags proposed were too complex, this is what I did in the template:
<a href="?{% url_replace request 'page' paginator.next_page_number %}">
And the tag function:
#register.simple_tag
def url_replace(request, field, value):
dict_ = request.GET.copy()
dict_[field] = value
return dict_.urlencode()
If the url_param is not yet in the url, it will be added with value. If it is already there, it will be replaced by the new value. This is a simple solution the suits me, but does not work when the url has multiple parameters with the same name.
You also need the RequestContext request instance to be provided to your template from your view. More info here:
http://lincolnloop.com/blog/2008/may/10/getting-requestcontext-your-templates/
I think url_replace solution may be rewritten more elegantly as
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()
with template string simplified to
<a href="?{% url_replace page=paginator.next_page_number %}">
After some playing around I found a solution... although I don't know if it's really a good one. I'd prefer a more elegant solution.
Anyway I pass the request to the template and am able to access all the GET variables via request.GET. Then I loop through the GET dictionary and as long as the variable isn't page I print it.
{% if contacts.has_previous %}
previous
{% 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 %}
In your views.py you will somehow access the criteria on which you sort, e.g. first_name. You'll need to pass that value to the template and insert it there to remember it.
Example:
{% if contacts.has_next %}
next
{% endif %}
One can create a context processor to use it wherever pagination is applied.
For example, in 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())}
Add the context processor to your Django project settings:
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',
)
Then, in your templates, you can use this when paginating:
<div class="row">
{# Initial/backward buttons #}
<div class="col-xs-4 col-md-4 text-left">
{% trans 'first' %}
{% if page_obj.has_previous %}
{% trans 'previous' %}
{% 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 %}
{{ page }}
{% endifequal %}
{% endfor %}
</div>
{# Final/forward buttons #}
<div class="col-xs-4 col-md-4 text-right">
{% if page_obj.has_next %}
{% trans 'next' %}
{% endif %}
{% trans 'last' %}
</div>
</div>
Whatever GET variables you have in your request, they will be appended after the ?page= GET parameter.
Improvement of this by:
Use urlencode from django instead of urllib, to prevent UnicodeEncodeError error with unicode arguments.
Template tag:
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)
Template:
<!-- Pagination -->
<div class="pagination">
<span class="step-links">
{% if coupons.has_previous %}
Prev
{% endif %}
<span class="current">
Page {{ objects.number }} of {{ objects.paginator.num_pages }}
</span>
{% if objects.has_next %}
Next
{% endif %}
</span>
</div>
This is a simple way how I do it
In view :
path = ''
path += "%s" % "&".join(["%s=%s" % (key, value) for (key, value) in request.GET.items() if not key=='page' ])
Then in template:
href="?page={{ objects.next_page_number }}&{{path}}"
I had this problem while using django-bootstrap3. The (easy) solution without any template tags is using:
{% bootstrap_pagination page_obj extra=request.GET.urlencode %}
Took me a while to find this out... I finally did thanks to this post.
My solution is based on this one above with the slight improvement to remove &page= from appearing multiple times. See this comment
#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()
This line query.pop('page', None) silently removes the page from the url
Another take on the url_encode solution, in this case as simplified by skoval00.
I had a few issues with that version. One, it didn't support Unicode encoding and two, it broke for filters with multiple of the same keys (like a MultipleSelect widget). Due to the .dict() conversion, all values but one are lost. My version supports unicode and multiple of the same key:
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())
This creates a QueryDict copy, then removes all keys that match kwargs (since update for a QueryDict adds instead of replacing). Mark_safe was needed due to a double encoding issue.
You would use it like this (don't forget to load the tags):
<a class="next" href="?{% url_replace p=objects.next_page_number%}">Next</a>
where ?p=1 is our pagination syntax in the View.
#skoval00 's answer is the most elegant, however it adds duplicate &page= query parameters to the url.
Here is the fix:
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]
else:
url = query
return f'{url}&page={next_page}'
Here's a useful custom template tag for constructing query strings.
Next page
If the URL is http://example.com/django/page/?search=sometext, the generated HTML should be something like:
Next page
More examples:
<!-- 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 -->
Finally, the source code (written by me):
# -*- 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
Another slight modification to skoval00 and Reinstate Monica to fully get rid of duplication and avoid the ugly ?&page=1 part:
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
#Elrond Supports Monica
#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()
Use in template
<a class="page-link" href="?{% url_replace p=1 q='bar'%}">
Every such link you put in your view has to be equipped with relevant parameters. There is no implicit magic that would convert:
http://127.0.0.1:8000/users/?page=2
into:
http://127.0.0.1:8000/users/?sort=first_name&page=2
So what you need is some Sorter object/class/function/snippet (whatever might fit here without overdoing it), that would act similarly to django.core.paginator.Paginator, but would handle sort GET parameter.
It could be as simple as this:
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 ''
})
Then, in your view:
{% if contacts.has_next %}
next
{% endif %}
I could be made more generic, but I hope you get the concept.
I would say generate the next and previous link from your controller, then pass it to the view and use it from there. I will give you an example (more like a pseudocode):
("next_link", "?param1="+param1+"¶m2="+param2+"&page_nr="+(Integer.parseInt(page_nr)-1)
then in your view use it like this:
{% if contacts.has_next %}
next
{% endif %}
You will need to return the GET as stated above. You can pass the GET request part of the url by calling
render_dict['GET'] = request.GET.urlencode(True)
return render_to_response('search/search.html',
render_dict,
context_instance=RequestContext(request))
you can then use this in the template to build your URL e.g.
href="/search/client/{{ page.no }}/10/?{{ GET }}
With Django's Pagination - preserving the GET params is simple.
First copy the GET params to a variable (in view):
GET_params = request.GET.copy()
and send it to the template in via context dictionary:
return render_to_response(template,
{'request': request, 'contact': contact, 'GET_params':GET_params}, context_instance=RequestContext(request))
Second thing you need to do is use it, specify it in the url calls (href) in the template - an example (extending the basic pagination html to handle extra param condition):
{% if contacts.has_next %}
{% if GET_params %}
next
{% else %}
next
{% endif %}
{% endif %}
Source
your code should be like:
{% if contacts.has_next %}
next
{% endif %}
this works for me and i find it simpler
from django.http import HttpRequest
def get_query_params_href(req: HttpRequest):
query_strings = req.GET.dict()
string = '?'
for i in query_strings:
string += f'{i}={query_strings[i]}&'
return string[0:string.__len__()-1]
'path': request.get_full_path().rsplit('&page')[0],
Related
Django Admin: How to dynamically set list_per_page
I have run into the case where I am managing the codebase for a project that utilizes the django-admin portion of a site. All of the functionality of the django admin exists in normal views as well, but for some reason the client prefers to work on the admin views as opposed to the function based views... Normally, adding in a dropdown and adjusting the pagination/filter would be easy in a function based view, but the only way I can see to modify this is with list_per_page How do I add a dropdown to the admin page (preferably with the pagination buttons) and then how do I retrieve the results on the server side to alter the list_per_page value dynamically based on what the user has selected? Would adding a form to the template and retrieving a POST in the admin work?
Inspired by plum-0 answer, I've ended up with this : import django.contrib.admin.views.main class DynPaginationChangeList(django.contrib.admin.views.main.ChangeList): def __init__(self, request, model, list_display, list_display_links, list_filter, date_hierarchy, search_fields, list_select_related, list_per_page, list_max_show_all, list_editable, model_admin, sortable_by): page_param = request.GET.get('list_per_page', None) if page_param is not None: # Override list_per_page if present in URL # Need to be before super call to be applied on filters list_per_page = int(page_param) super(DynPaginationChangeList, self).__init__(request, model, list_display, list_display_links, list_filter, date_hierarchy, search_fields, list_select_related, list_per_page, list_max_show_all, list_editable, model_admin, sortable_by) def get_filters_params(self, params=None): """ Return all params except IGNORED_PARAMS and 'list_per_page' """ lookup_params = super(DynPaginationChangeList, self).get_filters_params(params) if 'list_per_page' in lookup_params: del lookup_params['list_per_page'] return lookup_params class AdminDynPaginationMixin: def get_changelist(self, request, **kwargs): return DynPaginationChangeList If you use the javascript code propose in the original answer you just need to use this Mixin in your AdminClass and voilà. I personally override the pagination.html template like this : {% load admin_list %} {% load i18n %} <p class="paginator"> {% if pagination_required %} {% for i in page_range %} {% paginator_number cl i %} {% endfor %} {% endif %} {{ cl.result_count }} {% if cl.result_count == 1 %}{{ cl.opts.verbose_name }}{% else %}{{ cl.opts.verbose_name_plural }}{% endif %} {% if show_all_url %}{% translate 'Show all' %}{% endif %} {% with '5 10 25 50 100 250 500 1000' as list %} — {% translate 'Number of items per page' %} <select> {% if cl.list_per_page|slugify not in list.split %} <option selected>{{ cl.list_per_page }}</option> {% endif %} {% for i in list.split %} <option value="{{ i }}" {% if cl.list_per_page|slugify == i %}selected{% else %}onclick="var p = new URLSearchParams(location.search);p.set('list_per_page', '{{ i }}');window.location.search = p.toString();"{% endif %}>{{ i }}</option> {% endfor %} </select> {% endwith %} {% if cl.formset and cl.result_count %}<input type="submit" name="_save" class="default" value="{% translate 'Save' %}">{% endif %} </p>
DISCLAIMER: I'm answering my own question, so I am not sure if this is best practice or the best way to achieve this. However all of my searching yielded 0 results for this so I have decided to share it in case someone else needs this functionality. If anyone has any better ways to achieve this, or knows that I am doing something not secure please let me know! I would try appending a query parameter to the URL then retrieving that parameter through the request on the server side to set the list_per_page. For adding the dropdown as well as the parameter, modifying the Media class for a particular admin result to include some extra javascript should allow you to create the dropdown as well as append the query parameter. We will need to remove this parameter from the request.GET otherwise we will run into an issue inside of get_queryset() since the parameter does not match a field on the model. For that we will need to override the changelist_view() method inside admin.py admin_paginator_dropdown.js window.addEventListener('load', function() { (function($) { // Jquery should be loaded now // Table paginator has class paginator. We want to append to this var paginator = $(".paginator"); var list_per_page = $("<select id='list_per_page_selector'><option value=\"50\">50</option><option value=\"100\" selected>100</option><option value=\"150\">150</option><option value=\"200\">200</option><option value=\"250\">250</option></select>") var url = new URL(window.location); // Retrieve the current value for updating the selected dropdown on page refresh var initial_list_per_page = url.searchParams.get("list_per_page") paginator.append(list_per_page) if(initial_list_per_page === null) { // No choice has been made, set dropdown to default value $("#list_per_page_selector").val("100") } else{ // User has a query parameter with a selection. Update the selected accordingly $("#list_per_page_selector").val(initial_list_per_page) } $("#list_per_page_selector").on("change", function(event) { // Add the list_per_page parameter to the url to be used in admin.py url.searchParams.set("list_per_page", event.target.value); //Take us to the new page. window.location.href = url.href; }); })(django.jQuery); }); admin.py class someModelAdmin(admin.ModelAdmin) class Media: js = ("js/admin_paginator_dropdown.js",) def changelist_view(self, request, extra_context=None): # Copy the request.GET so we can modify it (no longer immutable querydict) request.GET = request.GET.copy() # Pop the custom non-model parameter off the request (Comes out as an array?) # Force it to int page_param = int(request.GET.pop('list_per_page', [100])[0]) # Dynamically set the django admin list size based on query parameter. self.list_per_page = page_param return super(someModelAdmin, self).changelist_view(request, extra_context)
Get list of all URL names in Django to make Active links in Navigation
I am putting together a template tag for active/currently visited links in a navigation bar so I can easily add the proper active CSS class to the link. I am have created code that works fine for either a mix of passed in urls with the same url-parameters included, but it does not allow me to pass in urls that have different params. {% make_active 'index' %} and {% make_active 'users' 1 %} could not be grouped together accurately as {% make_active 'index~users' 1 %} because I am using reverse() to see if the url exists. What I want is to just check the names from each of the url pattern files in my project and if the name exists, then I return the appropriate active class...but I cannot figure out how to simply grab the names. Is this possible, or can someone help with the code? #register.simple_tag(takes_context=True) def make_active(context, view_names, *args, **kwargs): print(args, kwargs) if not kwargs.pop('class', None): class_to_return = 'sidebar-item-active' else: class_to_return = kwargs.pop('class') request = context.get('request') if not request: raise Exception('A request must be passed in for this to work') names = view_names.split('~') print(names) current_url_active = False for view in names: print(view) try: # always include the client url args_to_use = [request.client_url] # append the passed args into the args for reversing the url name args_to_use.extend(args) reversed_path = reverse(view, args=args_to_use) print(reversed_path) current_url_active = True except NoReverseMatch: current_url_active = False continue if current_url_active: break return class_to_return if current_url_active else None
I figured out a way to gather the url names using dynamic imports, but after figuring out what I wanted to do I learned that I did not even need to go through the complexity of gathering all url names. Anyways here is that code: def get_url_names(): from django.apps import apps list_of_url_names = list() list_of_all_urls = list() for name, app in apps.app_configs.items(): mod_to_import = f'apps.{name}.urls' try: urls = getattr(importlib.import_module(mod_to_import), "urlpatterns") list_of_all_urls.extend(urls) except ImportError as ex: # is an app without urls pass for url in list_of_all_urls: list_of_url_names.append(url.name) return list_of_url_names While making that work I figured out all I needed to check was if I was on the current url name, which is easy to gather with request.path_info. So now my code can be changed like so #register.simple_tag(takes_context=True) def make_active(context, view_names, *args, **kwargs): if not kwargs.get('class', None): class_to_return = 'sidebar-item-active' else: class_to_return = kwargs.get('class') request = context.get('request') if not request: raise Exception('A request must be passed in for this to work') names = view_names.split('|') current_url_name = resolve(request.path_info).url_name for view in names: if view == current_url_name: return class_to_return And now I can return the correct active link CSS class with my url tag like so: <div class="collapse {% make_active 'users_index|users_actions|groups_index|groups_edit|users_create' class='show' %} " id="userCollapse"> <div class="card card-body"> {% if perms.users.view_customuser and perms.users.view_staff %} <a class="dropdown-item {% make_active 'users_create|users_index|users_actions' %}" href="{% url 'users_index' CLIENT.url_base %}"><i class="fas fa-users"></i> <span class="nav-item-text"> Users</span> </a> {% endif %} {% if perms.auth %} <div class="dropdown-divider"></div> <a class=" dropdown-item {% make_active 'groups_index|groups_edit' %}" href=" {% url 'groups_index' CLIENT.url_base %}"> <i class="far fa-user-plus"></i> <span class="nav-item-text"> Group Permissions</span> </a> {% endif %} </div> </div>
No reverse match error but the function exists?
I am trying to implement a renew function for a key inventory system. But when I render that page, it shows a Reversematcherror even though I mapped the correct URL and used the correct function name. Here is my template:(The URL tag is on the super long line all the way to the right) {% block content %} <h1>All Borrowed Keys</h1> {% if keyinstance_list %} <ul> {% for keyinst in keyinstance_list %} <li class="{% if keyinst.is_overdue %}text-danger{% endif %}"> {{keyinst.roomkey}} ({{ keyinst.due_back }}) {% if user.is_staff %} - {{ keyinst.borrower }} {% endif %} {% if perms.catalog.can_mark_returned %} - Renew {% endif %} </li> {% endfor %} </ul> {% else %} <p>There are no keys borrowed.</p> {% endif %} {% endblock %} My urls.py: path('key/<uuid:pk>/renew/', views.renew_key_user, name='renew-key-user'), path('key/<int:pk>/detail', views.KeyDetailView.as_view(), name='roomkey-detail'), Views.py: #permission_required('catalog.can_mark_returned') def renew_key_user(request, pk): """ View function for renewing a specific keyInstance by admin """ key_inst=get_object_or_404(KeyInstance, pk = pk) # If this is a POST request then process the Form data if request.method == 'POST': # Create a form instance and populate it with data from the request (binding): form = RenewKeyForm(request.POST) # Check if the form is valid: if form.is_valid(): # process the data in form.cleaned_data as required (here we just write it to the model due_back field) key_inst.due_back = form.cleaned_data['renewal_date'] key_inst.save() # redirect to a new URL: return HttpResponseRedirect(reverse('all-borrowed-keys') ) # If this is a GET (or any other method) create the default form. else: proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3) form = RenewKeyForm(initial={'renewal_date': proposed_renewal_date,}) return render(request, 'catalog/roomkey_renew_user.html', {'form': form, 'keyinst':key_inst}) class KeyDetailView(generic.DetailView): model = RoomKey The error is saying Reverse for 'views.renew_key_user' not found. 'views.renew_key_user' is not a valid view function or pattern name.
Update this line in your template. Renew as name in url is renew-key-user path('key/<uuid:pk>/renew/', views.renew_key_user, name='renew-key-user'),
Your URL name contains - hyphen not _ underscore change this renew_key_user to renew-key-user in your template Renew
Your template is asking for 'roomkey-detail' but the urls snippet you've provided only shows a url named 'renew-key-user'. Unless there are more url definitions you're not showing us, the code is failing as expected since it can't find a URL with the name you're asking for.
infinite pagination doesn't render variables
I'm trying to use django infinite pagination, but I'm getting this error: TemplateSyntaxError at / u'paginate' tag requires a variable name `as` argumnent if the queryset is provided as a nested context variable (prodmatrix.items). You must either pass a direct queryset (e.g. taking advantage of the `with` template tag) or provide a new variable name to store the resulting queryset (e.g. `paginate prodmatrix.items as objects`). This is my template: {% load endless %} **{% paginate prodmatrix.items %}** {% for key, values in prodmatrix.items %} <li class="span3"> <div class="product-box"> <span class="sale_tag"></span> <p><img src="{{ STATIC_URL }}{{values.1.0}}" alt="" /></p> <h4>{{ values.0.0 }}</h4><br/> <p class="category">{{values.2.0}} {{values.2.1}} {{values.2.2}}</p> </div> </li> {% endfor %} {% show_pages %} This is my view: def home(request): if request.user.is_authenticated(): print "login" user = request.user prods = Product.objects.all() i = 0 print 'numero de produtos ' + str(len(prods)) prodmatrix = {} for prod in prods: # 0 1 2 3 prodmatrix[str(i)] = [[prod.name], [prod.image], [], [prod.slug]] reviews = Review.objects.filter(product=prod.id) # ^ this is for tags print str(len(reviews)) if len(reviews) != 0: for review in reviews: rev_alltags = review.tag.all() for tags in rev_alltags[:3]: # print tags.name prodmatrix[str(i)][2].append(tags.name) # append only tags print str(i) i = i + 1 return render(request, 'home.html',{'prodmatrix':prodmatrix})
This error is occurring because you don't pass the template any variable called entries. I don't know enough about the lib to give a solution but I believe you will need to do something along the lines of: {% paginate prodmatrix.items %}
Navigation in django
I've just done my first little webapp in django and I love it. I'm about to start on converting an old production PHP site into django and as part its template, there is a navigation bar. In PHP, I check each nav option's URL against the current URL, in the template code and apply a CSS class if they line up. It's horrendously messy. Is there something better for django or a good way of handling the code in the template? To start, how would I go about getting the current URL?
You do not need an if to do that, have a look at the following code: tags.py #register.simple_tag def active(request, pattern): import re if re.search(pattern, request.path): return 'active' return '' urls.py urlpatterns += patterns('', (r'/$', view_home_method, 'home_url_name'), (r'/services/$', view_services_method, 'services_url_name'), (r'/contact/$', view_contact_method, 'contact_url_name'), ) base.html {% load tags %} {% url 'home_url_name' as home %} {% url 'services_url_name' as services %} {% url 'contact_url_name' as contact %} <div id="navigation"> <a class="{% active request home %}" href="{{ home }}">Home</a> <a class="{% active request services %}" href="{{ services }}">Services</a> <a class="{% active request contact %}" href="{{ contact }}">Contact</a> </div> that's it. for implementation details have a look at: gnuvince.wordpress.com 110j.wordpress.com
I use template inheritance to customize navigation. For example: base.html <html> <head>...</head> <body> ... {% block nav %} <ul id="nav"> <li>{% block nav-home %}Home{% endblock %}</li> <li>{% block nav-about %}About{% endblock %}</li> <li>{% block nav-contact %}Contact{% endblock %}</li> </ul> {% endblock %} ... </body> </html> about.html {% extends "base.html" %} {% block nav-about %}<strong class="nav-active">About</strong>{% endblock %}
I liked the cleanness of 110j above so I took most of it and refactored to solve the 3 problems I had with it: the regular expression was matching the 'home' url against all others I needed multiple URLs mapped to one navigation tab, so I needed a more complex tag that takes variable amount of parameters fixed some url problems Here it is: tags.py: from django import template register = template.Library() #register.tag def active(parser, token): args = token.split_contents() template_tag = args[0] if len(args) < 2: raise template.TemplateSyntaxError, "%r tag requires at least one argument" % template_tag return NavSelectedNode(args[1:]) class NavSelectedNode(template.Node): def __init__(self, patterns): self.patterns = patterns def render(self, context): path = context['request'].path for p in self.patterns: pValue = template.Variable(p).resolve(context) if path == pValue: return "active" # change this if needed for other bootstrap version (compatible with 3.2) return "" urls.py: urlpatterns += patterns('', url(r'/$', view_home_method, {}, name='home_url_name'), url(r'/services/$', view_services_method, {}, name='services_url_name'), url(r'/contact/$', view_contact_method, {}, name='contact_url_name'), url(r'/contact/$', view_contact2_method, {}, name='contact2_url_name'), ) base.html: {% load tags %} {% url home_url_name as home %} {% url services_url_name as services %} {% url contact_url_name as contact %} {% url contact2_url_name as contact2 %} <div id="navigation"> <a class="{% active request home %}" href="home">Home</a> <a class="{% active request services %}" href="services">Services</a> <a class="{% active request contact contact2 %}" href="contact">Contact</a> </div>
I'm the author of django-lineage which I wrote specifically to solve this question :D I became annoyed using the (perfectly acceptable) jpwatts method in my own projects and drew inspiration from 110j's answer. Lineage looks like this: {% load lineage %} <div id="navigation"> <a class="{% ancestor '/home/' %}" href="/home/">Home</a> <a class="{% ancestor '/services/' %}" href="/services/">Services</a> <a class="{% ancestor '/contact/' %}" href="/contact/">Contact</a> </div> ancestor is simply replaced with "active" if the argument matches the start of current page URL. Variable arguments, and full {% url %} type reverse resolution, is also supported. I sprinkled in a few configuration options and fleshed it out a little and packaged it up for everyone to use. If anyone is interested, read a bit more about it at: >> github.com/marcuswhybrow/django-lineage
Since Django 1.5: In all generic class-based views (or any class-based view inheriting from ContextMixin), the context dictionary contains a view variable that points to the View instance. So if you are using such views, you could add something likie breadcrumbs as a class level field and use it in your templates. Example view code: class YourDetailView(DetailView): breadcrumbs = ['detail'] (...) In your template you could use it in this way: <a href="/detail/" {% if 'detail' in view.breadcrumbs %}class="active"{% endif %}>Detail</a> If you want to additionally "highlight" parent navigation items, you need to extend breadcrumbs list: class YourDetailView(DetailView): breadcrumbs = ['dashboard', 'list', 'detail'] (...) ... and in your template: <a href="/dashboard/" {% if 'dashboard' in view.breadcrumbs %}class="active"{% endif %}>Dashboard</a> <a href="/list/" {% if 'list' in view.breadcrumbs %}class="active"{% endif %}>List</a> <a href="/detail/" {% if 'detail' in view.breadcrumbs %}class="active"{% endif %}>Detail</a> This is easy and clean solution and works pretty well with nested navigation.
You could apply a class or id to the body element of the page, rather than to a specific nav item. HTML: <body class="{{ nav_class }}"> CSS: body.home #nav_home, body.about #nav_about { */ Current nav styles */ }
I do it like this: <a class="tab {% ifequal active_tab "statistics" %}active{% endifequal %}" href="{% url Member.Statistics %}">Statistics</a> and then all I have to do is in my view add {'active_tab': 'statistics'} to my context dictionary. If you are using RequestContext you can get current path in your template as: {{ request.path }} And in your view: from django.template import RequestContext def my_view(request): # do something awesome here return template.render(RequestContext(request, context_dict))
I took the code from nivhab above and removed some wierdness and made it into a clean templatetag, modified it so that /account/edit/ will still make /account/ tab active. #current_nav.py from django import template register = template.Library() #register.tag def current_nav(parser, token): import re args = token.split_contents() template_tag = args[0] if len(args) < 2: raise template.TemplateSyntaxError, "%r tag requires at least one argument" % template_tag return NavSelectedNode(args[1]) class NavSelectedNode(template.Node): def __init__(self, url): self.url = url def render(self, context): path = context['request'].path pValue = template.Variable(self.url).resolve(context) if (pValue == '/' or pValue == '') and not (path == '/' or path == ''): return "" if path.startswith(pValue): return ' class="current"' return "" #template.html {% block nav %} {% load current_nav %} {% url home as home_url %} {% url signup as signup_url %} {% url auth_login as auth_login_url %} <ul class="container"> <li>Home</li> <li>Login</li> <li>Signup</li> </ul> {% endblock %}
This is just a variant of the css solution proposed by Toba above: Include the following in your base template: <body id="section-{% block section %}home{% endblock %}"> Then in your templates that extend the base use: {% block section %}show{% endblock %} You can then use css to highlight the current area based on the body tag (for example if we have a link with an id of nav-home): #section-home a#nav-home{ font-weight:bold; }
You could use the reverse function with the appropriate parameters to get the current url.
Thanks for your answers so far, gents. I've gone for something slightly different again.. In my template: <li{{ link1_active }}>...link...</li> <li{{ link2_active }}>...link...</li> <li{{ link3_active }}>...link...</li> <li{{ link4_active }}>...link...</li> Once I've worked out which page I'm on in the logic (usually in urls.py), I pass class="selected" as part of the context under the right name to the template. Eg if I'm on the link1 page, I'll append {'link1_active':' class="selected"'} to the context for the template to scoop up and inject. It appears to work and it's fairly clean. Edit: to keep HTML out of my controller/view, I've modified this a bit: <li{% if link1_active %} class="selected"{% endif %}>...link...</li> <li{% if link2_active %} class="selected"{% endif %}>...link...</li> ... It makes the template a little less readable, but I agree, it's better to not push through raw HTML from the urls file.
I found the best is to use an inclusion tag: templates/fnf/nav_item.html <li class="nav-item"> <a class="nav-link {% if is_active %}active{% endif %}" href="{% url url_name %}">{{ link_name }}</a> </li> This is just my basic bootstrap nav item I wish to render. It gets the href value, and optionally the link_name value. is_active is calculated based on the current request. templatetags/nav.py from django import template register = template.Library() #register.inclusion_tag('fnf/nav_item.html', takes_context=True) def nav_item(context, url_name, link_name=None): return { 'url_name': url_name, 'link_name': link_name or url_name.title(), 'is_active': context.request.resolver_match.url_name == url_name, } Then use it in a nav: templates/fnf/nav.html {% load nav %} <nav class="navbar navbar-expand-lg navbar-light bg-light"> <ul class="navbar-nav mr-auto"> {% nav_item 'dashboard' %} </ul>
I have multiple menus on the same page that are created dynamically through a loop. The posts above relating to the context gave me a quick fix. Hope this helps somebody. (I use this in addition to the active template tag - my fix solves the dynamic issue). It seems like a silly comparison, but it works. I chose to name the variables active_something-unique and something-unique, this way it works with nested menus. Here is a portion of the view (enough to understand what i am doing): def project_list(request, catslug): "render the category detail page" category = get_object_or_404(Category, slug=catslug, site__id__exact=settings.SITE_ID) context = { 'active_category': category, 'category': category, 'category_list': Category.objects.filter(site__id__exact=settings.SITE_ID), } And this is from the template: <ul> {% for category in category_list %} <li class="tab{% ifequal active_category category %}-active{% endifequal %}"> {{ category.cat }} </li> {% endfor %} </ul>
My solution was to write a simple context processor to set a variable based on the request path: def navigation(request): """ Custom context processor to set the navigation menu pointer. """ nav_pointer = '' if request.path == '/': nav_pointer = 'main' elif request.path.startswith('/services/'): nav_pointer = 'services' elif request.path.startswith('/other_stuff/'): nav_pointer = 'other_stuff' return {'nav_pointer': nav_pointer} (Don't forget to add your custom processor to TEMPLATE_CONTEXT_PROCESSORS in settings.py.) Then in the base template I use an ifequal tag per link to determine whether to append the "active" class. Granted this approach is strictly limited to the flexibility of your path structure, but it works for my relatively modest deployment.
I just wanted to share my minor enhancement to nivhab's post. In my application I have subnavigations and I did not want to hide them using just CSS, so I needed some sort of "if" tag to display the subnavigation for an item or not. from django import template register = template.Library() #register.tag def ifnaviactive(parser, token): nodelist = parser.parse(('endifnaviactive',)) parser.delete_first_token() import re args = token.split_contents() template_tag = args[0] if len(args) < 2: raise template.TemplateSyntaxError, "%r tag requires at least one argument" % template_tag return NavSelectedNode(args[1:], nodelist) class NavSelectedNode(template.Node): def __init__(self, patterns, nodelist): self.patterns = patterns self.nodelist = nodelist def render(self, context): path = context['request'].path for p in self.patterns: pValue = template.Variable(p).resolve(context) if path == pValue: return self.nodelist.render(context) return "" You can use this basically in the same way as the active tag: {% url product_url as product %} {% ifnaviactive request product %} <ul class="subnavi"> <li>Subnavi item for product 1</li> ... </ul> {% endifnaviactive %}
Just another ehnancement of the original solution. This accept multiple patterns and which is best also unnamed patterns written as relative URL wrapped in '"', like following: {% url admin:clients_client_changelist as clients %} {% url admin:clients_town_changelist as towns %} {% url admin:clients_district_changelist as districts %} <li class="{% active "/" %}">Home</li> <li class="{% active clients %}">Clients</li> {% if request.user.is_superuser %} <li class="{% active towns districts %}"> Settings <ul> <li>Towns</li> <li>Districts</li> </ul> </li> {% endif %} Tag goes like this: from django import template register = template.Library() #register.tag def active(parser, token): args = token.split_contents() template_tag = args[0] if len(args) < 2: raise template.TemplateSyntaxError, "%r tag requires at least one argument" % template_tag return NavSelectedNode(args[1:]) class NavSelectedNode(template.Node): def __init__(self, urls): self.urls = urls def render(self, context): path = context['request'].path for url in self.urls: if '"' not in url: cpath = template.Variable(url).resolve(context) else: cpath = url.strip('"') if (cpath == '/' or cpath == '') and not (path == '/' or path == ''): return "" if path.startswith(cpath): return 'active' return ""
I used jquery to highlight my navbars. This solution simply adds the css class "active" to the item which fits the css selector. <script type="text/javascript" src="/static/js/jquery.js"></script> <script> $(document).ready(function(){ var path = location.pathname; $('ul.navbar a.nav[href$="' + path + '"]').addClass("active"); }); </script>
A little enhancement over #tback's answer, without any %if% tags: # navigation.py from django import template from django.core.urlresolvers import resolve register = template.Library() #register.filter(name="activate_if_active", is_safe=True) def activate_if_active(request, urlname): if resolve(request.get_full_path()).url_name == urlname: return "active" return '' Use it in your template like that: {% load navigation %} <li class="{{ request|activate_if_active:'url_name' }}"> My View </li> And include "django.core.context_processors.request" in your TEMPLATE_CONTEXT_PROCESSORS setting.
Inspired by this solution, I started to use this approach: **Placed in templates as base.html** {% block tab_menu %} <ul class="tab-menu"> <li class="{% if active_tab == 'tab1' %} active{% endif %}">Tab 1</li> <li class="{% if active_tab == 'tab2' %} active{% endif %}">Tab 2</li> <li class="{% if active_tab == 'tab3' %} active{% endif %}">Tab 3</li> </ul> {% endblock tab_menu %} **Placed in your page template** {% extends "base.html" %} {% block tab_menu %} {% with active_tab="tab1" %} {{ block.super }} {% endwith %} {% endblock tab_menu %}
Slightly modifying Andreas' answer, it looks like you can pass in the name of the route from urls.py to the template tag. In my example my_tasks, and then in the template tag function use the reverse function to work out what the URL should be, then you can match that against the URL in the request object (available in the template context) from django import template from django.core.urlresolvers import reverse register = template.Library() #register.tag def active(parser, token): args = token.split_contents() template_tag = args[0] if len(args) < 2: raise template.TemplateSyntaxError, "%r tag requires at least one argument" % template_tag return NavSelectedNode(args[1:]) class NavSelectedNode(template.Node): def __init__(self, name): self.name = name def render(self, context): if context['request'].path == reverse(self.name[1]): return 'active' else: return '' urls.py url(r'^tasks/my', my_tasks, name = 'my_tasks' ), template.html <li class="{% active request all_tasks %}">Everyone</li>
I know I'm late to the party. I didn't like any of the popular solutions though: The block method seems wrong: I think the navigation should be self contained. The template_tag method seems wrong: I don't like that I have to get the url from the url-tag first. Also, I think the css-class should be defined in the template, not the tag. I therefore wrote a filter that doesn't have the drawbacks I described above. It returns True if a url is active and can therefore be used with {% if %}: {% load navigation %} <li{% if request|active:"home" %} class="active"{% endif %}>Home</li> The code: #register.filter(name="active") def active(request, url_name): return resolve(request.path_info).url_name == url_name Just make sure to use RequestContext on pages with navigation or to enable the request context_processor in your settings.py TEMPLATE_CONTEXT_PROCESSORS = ( ... 'django.core.context_processors.request', )
I've seen jpwatts', 110j's, nivhab's & Marcus Whybrow's answers, but they all seem to lack in something: what about the root path ? Why it's always active ? So I've made an other way, easier, which make the "controller" decides by itself and I think it resolve most of the big problems. Here is my custom tag: ## myapp_tags.py #register.simple_tag def nav_css_class(page_class): if not page_class: return "" else: return page_class Then, the "controller" declares CSS classes needed (in fact, the most important is it declares its presence to the template) ## views.py def ping(request): context={} context["nav_ping"] = "active" return render(request, 'myapp/ping.html',context) And finally, I render it in my navigation bar: <!-- sidebar.html --> {% load myapp_tags %} ... <a class="{% nav_css_class nav_home %}" href="{% url 'index' %}"> Accueil </a> <a class="{% nav_css_class nav_candidats %}" href="{% url 'candidats' %}"> Candidats </a> <a class="{% nav_css_class nav_ping %}" href="{% url 'ping' %}"> Ping </a> <a class="{% nav_css_class nav_stat %}" href="{% url 'statistiques' %}"> Statistiques </a> ... So each page has its own nav_css_class value to set, and if it's set, the template renders active: no need of request in template context, no URL parcing and no more problems about multi-URL pages or root page.
Here's my go at it. I ended up implementing a class in my views that contains my navigation structure (flat with some metadata). I then inject this to the template and render it out. My solution deals with i18n. It probably should be abstracted out a bit more but I haven't really bothered with that really. views.py: from django.utils.translation import get_language, ugettext as _ class Navi(list): items = (_('Events'), _('Users'), ) def __init__(self, cur_path): lang = get_language() first_part = '/' + cur_path.lstrip('/').split('/')[0] def set_status(n): if n['url'] == first_part: n['status'] == 'active' for i in self.items: o = {'name': i, 'url': '/' + slugify(i)} set_status(o) self.append(o) # remember to attach Navi() to your template context! # ie. 'navi': Navi(request.path) I defined the template logic using includes like this. Base template: {% include "includes/navigation.html" with items=navi %} Actual include (includes/navigation.html): <ul class="nav"> {% for item in items %} <li class="{{ item.status }}"> {{ item.name }} </li> {% endfor %} </ul> Hopefully someone will find this useful! I guess it would be pretty easy to extend that idea to support nested hierarchies etc.
Create an include template "intranet/nav_item.html": {% load url from future %} {% url view as view_url %} <li class="nav-item{% ifequal view_url request.path %} current{% endifequal %}"> {{ title }} </li> And include it in the nav element: <ul> {% include "intranet/nav_item.html" with view='intranet.views.home' title='Home' %} {% include "intranet/nav_item.html" with view='crm.views.clients' title='Clients' %} </ul> And you need to add this to settings: from django.conf import global_settings TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + ( 'django.core.context_processors.request', )
here is pretty simple solution, https://github.com/hellysmile/django-activeurl
from this SO Question {% url 'some_urlpattern_name' as url %} <a href="{{url}}"{% if request.path == url %} class="active"{% endif %}>Link</a> Repeat as necessary for each link.
I also used jQuery to highlight it and find it more elegant than cluttering the template with non-semantic Django template tags. The code below works with nested dropdowns in bootstrap 3 (highlights both the parent, and the child <li> element. // DOM Ready $(function() { // Highlight current page in nav bar $('.nav, .navbar-nav li').each(function() { // Count the number of links to the current page in the <li> var matched_links = $(this).find('a[href]').filter(function() { return $(this).attr('href') == window.location.pathname; }).length; // If there's at least one, mark the <li> as active if (matched_links) $(this).addClass('active'); }); }); It's also quite easy to add a click event to return false (or change the href attribute to #) for the current page, without changing the template/html markup: var matched_links = $(this).find('a[href]').filter(function() { var matched = $(this).attr('href') == window.location.pathname; if (matched) $(this).click(function() { return false; }); return matched; }).length;
I use a combination of this mixin for class based views: class SetActiveViewMixin(object): def get_context_data(self, **kwargs): context = super(SetActiveViewMixin, self).get_context_data(**kwargs) context['active_nav_menu'] = { self.request.resolver_match.view_name: ' class="pure-menu-selected"' } return context with this in the template: <ul> <li{{active_nav_menu.node_explorer }}>Explore</li> <li{{active_nav_menu.node_create }}>Create</li> <li{{active_nav_menu.node_edit }}>Edit</li> <li{{active_nav_menu.node_delete }}>Delete</li> </ul>
Mine is a bit similar to another JS approach submitted previously.. just without jQuery... Say we have in base.html the following: <div class="pure-u-1 pure-menu pure-menu-open pure-menu-horizontal header" > <ul class=""> <li id="home">Home</li> <li id="news">News</li> <li id="analysis">Analysis</li> <li id="opinion">Opinion</li> <li id="data">Data</li> <li id="events">Events</li> <li id="forum">Forum</li> <li id="subscribe">Subscribe</li> </ul> <script type="text/javascript"> (function(){ loc=/\w+/.exec(window.location.pathname)[0]; el=document.getElementById(loc).className='pure-menu-selected'; })(); </script> </div> I just made my hierarchy to follow a certain URL pattern... after the host address... i have my main category, eg, home, news, analysis, etc. and the regex just pulls the first word out of the location
** Just add url and name in jinja format like this ** <ul class="nav navbar-nav"> <li> Cities </li> <li> Cafes </li> </ul>