Django How to get GET parameters in template - python

I'm working on a django project.
I'm wondering how to get GET parameters in template so that I can make corresponding tab active.
I tried the code below, but it didn't work.
<a class="list-group-item {% if '?q=' in request.path %}active{% endif %}" href="{% url 'blah'%}"> Foo </a>
Thank you in advance.

Get it in view and send it as parameter in render
active = ('?q=' in request.path)
render(..., context={"active": active})
and use it in template
class="list-group-item {% if active %}active{% endif %}"
Or get it as
if '?q=' in request.path:
extra_class = "active"
else:
extra_class = ""
render(..., context={"extra_class": extra_class})
and set in template without if
class="list-group-item {{ extra_class }}"
BTW:
You could get it also as
query = request.GET.get('q', '')
render(..., context={"query": query})
and use it in template to set class and to display query
You search: {{ query }}
class="list-group-item {% if query %}active{% endif %}"

Related

request.path does not match expected dynamic url

I'm trying to highlight specific buttons in the navigation bar when I'm on a specific page and hyperlink to it when i'm not. The url for this page is a dynamic url however and using the {% url ... as var %} syntax is not making the specific buttons highlighted when they should be.
The issue here is that the button does not even show up at all. I narrowed it down to the request.path not matching with the dynamic url path. Normal urls (without the /str:something) seem to match perfectly.
in navbar.html i've tried to define the comparison_url (before all other code) as follows, but nothing seems to work so far:
{% url 'comparison' as comp_url %}
{% url 'comparison' sub_name as comp_url %}
{% url 'comparison' suburb_name as comp_url %}
{% url 'comparison' sub_name=suburb_name as comp_url %}
{% url 'comparison' vergelijking.suburb as comp_url %}
urls are defined as follows in urls.py:
urlpatterns = [
path("vergelijking/<str:sub_name>", views.ComparisonView.as_view(), name="comparison"),
path("signalen/<str:sub_name>", views.RadarView.as_view(), name="signals"),
path("oorzaken/<str:sub_name>", views.CausationsView.as_view(), name="causation"),
]
in navbar.html, where the actual problem lies:
{% url 'comparison' sub_name as comp_url %}
{% url 'signals' sub_name as sig_url %}
{% url 'causation' sub_name as comp_url %}
{% if request.path == comp_url %}
<li class="nav-item mx-0 mx-lg-1"><a class="nav-link py-3 px-0 px-lg-3 rounded active">Vergelijking</a></li>
{% elif request.path == sig_url or request.path == caus_url %}
<li class="nav-item mx-0 mx-lg-1"><a class="nav-link py-3 px-0 px-lg-3 rounded" href="{% url 'comparison' sub_name=suburb_name%}">Vergelijking</a></li>
{% endif %}
my base.html includes the navbar.html and the comparison.html extends base.html. There is no view for the navbar.html or base.html. My comparisonview looks like this (municipality details are loaded into the context):
class ComparisonView(LoginRequiredMixin, TemplateView):
"""map of municipality and suburbs with tooltip info"""
template_name = 'comparison.html'
def get_context_data(self, **kwargs):
context = super(ComparisonView, self).get_context_data(**kwargs)
context['suburb_name'] = self.kwargs['sub_name']
municipality_name = Suburb.objects.get(name=self.kwargs['sub_name']).municipality.name
context['municipality_name'] = municipality_name
suburb_list = Suburb.objects.filter(municipality__name=municipality_name)
suburbs_signals_dict = {}
for s in suburb_list:
suburbs_signals_dict[s.name] = s.get_signals()
context['suburb_signals'] = dumps(suburbs_signals_dict)
return context
I think the mistake is in the way i define my comp_url but i'm not exactly sure. Does anyone know what I'm doing wrong?
the view was missing a request as a parameter (whenever i was calling on request.path, it was equating the to be matched link with null as request.path did not exist). I solved this by adding the path to the context:
context['path'] = quote(self.request.path)
The quote was in my case necessary since self.request.path can have spaces in a url whereas comp_url has '%20' for spaces. After I could call on path like so:
{% if path == comp_url %}
And the problem was solved

Django Get Root Path From Current URL

I am developing a Django website using the Wagtail CMS. I have a navbar at the top of the page where using template tags, it loops through pages in the navigation variable.
{% for item in navigation.menu_items.all %}
<a class="nav-link {% if request.get_full_path == item.link %}active{% endif %}" href="{{ item.link }}" {% if item.open_in_new_tab %} target="_blank"{% endif %}>{{ item.title }}</a>
{% endfor %}
Say that the URL is http://localhost:8000/blog/ and the page URL is the same, then the active class is applied to that iteration.
The problem arises when I am on a page with the URL such as http://localhost:8000/blog/example-blog-post/, this does not match with http://localhost:8000/blog/ and the active class is not applied, even though I am in the blog.
Is there a way to strip the URL and only keeping the root path, so http://localhost:8000/blog/example-blog-post/ becomes http://localhost:8000/blog/ so that the active class can be applied to subpages in the directory?
You can use slice filter
{% if request.path|slice:":5" == item.link %} active{% endif %}
OR
You can use in operator.
So instead {% if request.get_full_path == item.link %} do {% if item.link in request.get_full_path %} or to catch homepage {% if request.get_full_path in item.link and request.get_full_path != '/' or request.get_full_path == item.link %}
This probably isn't the most efficient way, but at the moment for me, it's the only way.
I created a custom template tag which takes in the context and the menu item object, then returns the active class name if the current URL matches the URL of the nav-item (item)
In the HTML, as each nav-item is iterated, the item is passed to the get_active method (a custom template tag that I made)
{% load menu_tags %}
{% get_menu "MAIN" as navigation %}
{% for item in navigation.menu_items.all %}
{% get_active item as active_class %}
<a class="nav-link {{ active_class }}" href="{{ item.link }}" {% if item.open_in_new_tab %} target="_blank"{% endif %}>{{ item.title }}</a>
{% endfor %}
Template tag:
#register.simple_tag(takes_context=True)
def get_active(context, item):
request = context['request']
currentURL = request.path
linkURL = item.link
currentURLStripped = str(currentURL).replace("/", "")
linkURLStripped = str(linkURL).replace("/", "")
if linkURLStripped=="" and currentURLStripped=="":
return "active"
elif linkURLStripped in currentURLStripped and linkURLStripped!="":
return "active"
else:
return ""
The code above simply takes the URL of the page the user is currently on, for example, if the user is on http://localhost:8000/blog then currentURL will be /blog/. The linkURL is the URL property of the item object, for the item object which links to the contact me page, its URL property will be /contact-me/ and thus the linkURL will be the same.
The method simply strips the "/" from the URL strings. If the URL is for the homepage (i.e. it's /) then the variable will be empty. if linkURLStripped=="" and currentURLStripped=="": catches the homepage.
elif linkURLStripped in currentURLStripped and linkURLStripped!="": catches the other pages and ignores the homepage.

dynamic pagination with django

Okay so this is first time using pagination with Django and I am trying to prevent it from reloading my view on each page turn.
I'm handling the pagination in the view like this:
page = request.GET.get('page', 1)
print page
paginator = Paginator(list(od.iteritems())[:24], 12)
try:
data = paginator.page(page)
except PageNotAnInteger:
data = paginator.page(1)
except EmptyPage:
data = paginator.page(paginator.num_pages)
print data
save_query_form = SaveQueryForm(request.POST or None)
#if request.method == 'POST':
if save_query_form.is_valid():
profile = save_query_form.save(commit=False)
profile.user = request.user
profile.save()
context = {
"title":"Search",
'data': data,#list(od.iteritems()),
'tools': od_tools.iteritems(),
'methods': od_methods.iteritems(),
'data4': od_data.iteritems(),
'search_phrase': " ".join(instanceValuesString),
'json_dump': js_data,
'form': save_query_form,
}
return render(request, 'results.html', context)
and the pagination is handled in the html:
{% if data.has_other_pages %}
<div id='page-slide'>
<ul class="pagination" start='$offset'>
{% if data.has_previous %}
<li>«</li>
{% else %}
<li class="disabled"><span>«</span></li>
{% endif %}
{% for i in data.paginator.page_range %}
{% if data.number == i %}
<li class="active"><span>{{ i }} <span class="sr-only">(current)</span></span></li>
{% else %}
<li>{{ i }}</li>
{% endif %}
{% endfor %}
{% if data.has_next %}
<li>»</li>
{% else %}
<li class="disabled"><span>»</span></li>
{% endif %}
</ul>
</div>
{% endif %}
The issue that I am having is that whenever I switch to another page my entire view will run again and the data will does not reflect the original search query and instead defaults to an empty query.
I was wondering if there is a simple way to either handle pagination dynamically or prevent the page reload when toggling between pages?
Any help is appreciated, thanks.
Update Search Form:
<form action="{% url 'results-view' %}" method="POST" class="autocomplete-me ui-widget" id="myform" >
{% csrf_token %}
<div class="ui-widget" style="text-align:center;">
<input type="text" id="id_q" name="q" placeholder="{{ search_phrase }}">
<br></br>
<div style="text-align:center;" id='adjust-button'>
<input type='submit' class='btn btn-secondary btn-lg' id ='search-btn' value='Search'/>
<a class='btn btn-secondary btn-lg' id ='clear-btn' href="{% url 'inital' %}">Clear</a>
</div>
</div>
</form>
You noted in a comment that you get your search value with instanceValuesString = request.POST.get(u"q").encode('utf-8').strip(). As one commenter correctly pointed out, this means that when you click your "next page" links (making a GET request), your view doesn't receive the information it needs to return search results.
One way to fix this would be to get your instanceValuesString from a GET request instead of a POST request. For instance, perhaps your list view is at
http://example.com/StuffList
You could look for URLs that provide a search querystring:
http://example.com/StuffList?search=goodstuff
And then grab that in your view:
instanceValuesString = request.GET.get('search', None)
if instanceValuesString is not None:
#you have detected a search query; filter results, process request, etc.
One side effect here is that the way you currently construct your next/previous page URLs will break. Consider the example search URL; your current template would construct a link for page 2 like so:
http://example.com/StuffList?search=goodstuff?page=2
This won't work; it should be &page=2. Fortunately there's an easy fix; check out the second answer to this question: Altering one query parameter in a url (Django). Using that url_replace instead of constructing those links with the basic url template tag will solve this part of the issue.
This is very much simplified with below package
http://django-simple-pagination.readthedocs.io/en/latest/

Changing the active class of a link with the twitter bootstrap css in python/flask

I got the following html snippet from my page template.html.
<ul class='nav'>
<li class="active"><a href='/'>Home</a></li>
<li><a href='/lorem'>Lorem</a></li>
{% if session['logged_in'] %}
<li>Account</li>
<li>Projects
<li>Logout</li>
{% endif %}
{% if not session['logged_in'] %}
<li>Login</li>
<li>Register</li>
{% endif %}
</ul>
As you can see on line 2, there's the class active. This highlights the active tab with the twitter bootstrap css file. Now, this will work fine if I would visit www.page.com/ but not when I would visit www.page.com/login for example. It would still highlight the home link as the active tab.
Of course, I could easily do this with Javascript/jQuery but I'd rather not use that in this situation.
There's already a working solution for ruby on rails but I don't know how to convert that into python/jinja (I'm rather new to jinja/flask, never worked with ruby at all)
Have you looked at this ? https://jinja.palletsprojects.com/en/3.0.x/tricks/#highlighting-active-menu-items
Highlighting Active Menu Items
Often you want to have a navigation bar with an active navigation item. This is really simple to achieve. Because assignments outside of blocks in child templates are global and executed before the layout template is evaluated it’s possible to define the active menu item in the child template:
{% extends "layout.html" %}
{% set active_page = "index" %}
The layout template can then access active_page. Additionally it makes sense to define a default for that variable:
{% set navigation_bar = [
('/', 'index', 'Index'),
('/downloads/', 'downloads', 'Downloads'),
('/about/', 'about', 'About')
] -%}
{% set active_page = active_page|default('index') -%}
...
<ul id="navigation">
{% for href, id, caption in navigation_bar %}
<li{% if id == active_page %} class="active"{% endif
%}>{{ caption|e }}
</li>
{% endfor %}
</ul>
Here is another simpler way if you have menus distributed all over the page. This way uses inline if statements to print out the class active.
<ul>
<li class="{{ 'active' if active_page == 'menu1' else '' }}">
Link 1
</li>
<li class="{{ 'active' if active_page == 'menu2' else '' }}">
Link 2
</li>
</ul>
Class active is for highlighting
You still need to set the variable on every page to mark them
{% extends "master.html" %}
{% set active_page = "menu1" %}
or
{% set active_page = "menu2" %}
For jinja/flask/bootstrap users:
If you define your nav like they did in the blog example http://getbootstrap.com/examples/blog/
simply assign ids to your links that match your url_for arguments and you just need to modify the layout-template, the rest just works #magic.
<nav class="blog-nav">
<a id="allposts" class="blog-nav-item" href="{{ url_for('allposts')}}">All Posts</a>
<a id="index" class="blog-nav-item" href="{{ url_for('index')}}">Index</a>
<a id="favorites" class="blog-nav-item" href="{{ url_for('favorites')}}">Favorites</a>
</nav>
At the bottom of your base/layout template just add this
<script>
$(document).ready(function () {
$("#{{request.endpoint}}").addClass("active"); })
</script>
and the right elements will be set active.
EDIT:
If you have a layout with elements in a list, like this:
<nav class="blog-nav">
<ul class="nav navbar-nav">
<li>
<a id="allposts" class="blog-nav-item" href="{{ url_for('allposts')}}">All Posts</a>
</li>
<li>
<a id="index" class="blog-nav-item" href="{{ url_for('index')}}">Index</a>
</li>
<li>
<a id="favorites" class="blog-nav-item" href="{{ url_for('favorites')}}">Favorites</a>
</li>
</ul>
</nav>
use the parent() function to get the li element instead of the link.
<script>
$(document).ready(function () {
$("#{{request.endpoint}}").parent().addClass("active"); })
</script>
we can make class active by using jinja if statements
<ul class="nav navbar-nav">
<li class="{% if request.endpoint=='home' %}active{%endif %}">home</li>
<li class="{% if request.endpoint=='add_client' %}active{%endif %}">Add Report</li>
</li>
</ul>
I liked #philmaweb's approach, but there's really no reason to require duplicating the endpoint in the id of each element.
base.js:
$(document).ready(function () {
var scriptElement = $('#baseScript')[0];
var path = scriptElement.getAttribute('data-path');
$('a[href="'+path+'"]').addClass("active");
});
base.html
<script id="baseScript" src="{{ url_for('static', filename='js/base.js') }}"
data-path="{{ request.path }}"></script>
Why not just put this script inline? You could, of course, but allowing inline JS is a security nightmare. You should be using a CSP on your site (e.g. Flask-Talisman) which will not allow inline JS. With data-* attributes, it's not hard to do this in a secure way.
NB: If you have multiple links leading to the same, current page and you want only ONE of them to be marked "active"—then this approach may not work for you.
I tried different solution for this for the solution 1st by Codegeek didn't work as I have multiple Ul and li under it so I just include my navbar in template.html
{% include 'sidebar.html' %}
then in Navbar file in the li class you can set active with help of "request.endpoint" but then again it will return you entire route instead use split and take last route name and set active if same for exmaple
<li class="{% if request.endpoint.split('.')[1] == 'index' %} active {% else %} {% endif %}">
request.endpoint.split('.')[1] will return the route eg localhost/example. You will get example which you can compare and use. If you won't split and use request.endpoint than you will get 'file.example' (entire route).
Add the following CSS somewhere on your page:
a[href $= {{ page_name|default("'/'"|safe) }}]{ [INSERT YOUR ACTIVE STYLING HERE] }
Now, on each template define page_name, for example:
{% extends "template.html" %}
{% set page_name = "gallery" %}
This seems much simpler and easier to build on, than other options.
EDIT:
Almost 1 year later I'm returning to make this a much simpler fix, because setting the page name on every page is pretty inefficient.
Instead create a function like so:
#app.context_processor
def context_processor():
out = {}
out['request'] = request # Make sure you import request from flask
return out
This will allow you to pass variables implicitly to jinja, in this case we are passing the request for access to request.url_rule which contains the route the user is accessing. In the previous version, we just change {{ page_name|default("'/'"|safe) }} to "{{ request.url_rule|safe }}". Much cleaner.
I did not want to have to define the ID in the child pages, as many of the links I have do not have a specific child template.
Using the request.base_url and if it matches the _external url_for the route, then render that nav item as active.
{% set nav_items = [
("public.home", "Home"),
("public.downloads", "Downloads"),
("public.about", "About")
("account.login", "Login"),
]
-%}
...
<ul class="navbar-nav mr-auto">
{% for route, display_text in nav_items %}
<li class={% if request.base_url == url_for(route, _external=True) %}"nav-item active"{% else %}"nav-item"{% endif %}>
<a class="nav-link" href="{{ url_for(route) }}">{{ display_text }}
{% if request.base_url == url_for(route, _external=True) %}<span class="sr-only">(current)</span>{% endif %}
</a>
</li>
{% endfor %}
</ul>

Django NoReverseMatch error with valid url config and views

I'm getting a NoReverseMatch error in my template rendering.
Here's the relevant template:
<ul id='comments'>
{% for comment in comments %}
<li class='comment'>
<img class='gravatar' src='{{ comment.User|gravatar:50}}' alt='{{ comment.User.get_full_name }}' \>
<a href='{% url 'dashboard.views.users.profile' comment.User.id %}' class='user'>
{{comment.User.get_full_name}}
</a>
<p class='comment-timestamp'>{{comment.created}}</p>
<p class='comment-content'>{{comment.comment|striptags}}<br>
{% if user == comment.user or user = report.user %}
Delete</p>
{% endif %}
</li>
{% endfor %}
The error is given on the url 'mokr.delete_comment' line
Here's the view:
def delete_comment(request, comment_id):
comment = get_object_or_404(ReportComment, id = comment_id)
report = comment.MgmtReport
comment.delete()
project = report.project
return HttpResponseRedirect(reverse('show_post', args=(project.url_path, report.id)))
and the section of urls.py
(r'^mokr/comment/(\d+)/delete/$', mokr.delete_comment),
url(r'^mokr/show/([^\.]*)/(\d+)/$', mokr.show, name='show_post'),
You're passing two arguments to the template in your call to reverse in the delete_comment view; args=(project.url_path, report.id) but your urls.py lists;
(r'^mokr/comment/(\d+)/delete/$', mokr.delete_comment),
Which can only accept one parameter.
Alter your urls.py to add a name argument to your delete comment url.
(r'^mokr/comment/(\d+)/delete/$', mokr.delete_comment, name="delete_comment"),
Then try using this in your template;
{% url 'delete_comment' comment.id %}
See naming URL patterns and reverse resolution of URLs

Categories

Resources