Django - Paginating in Template using multiple GET parameters - python

I am using a Django Paginator and I want to have multiple available get parameters, such as:
page=1
sort_by=price
However, in my template tags I have:
Showing items sorted by {{ SORT_PARAM }}.
Showing {{ ITEMS_PER_PAGE }} items per page.
{% if has_prev %}
Previous |
{% endif %}
However, this does not preserve the other GET variables. What I mean is, if I'm viewing
page/?page=1&sort_by=price
and I click the link in the template fragment above, I will go to
page=2
instead of
page=2&sort_by=price
What I mean is, the a href does not preserve the other GET parameters.
One solution is I could type all the possible GET parameters in the a href, such as
Previous
but this will become less scalable the more arguments I want to add to my browsing. I'm guessing there should be an automated way to obtain all GET parameters, and then pass those and one more?

This one http://djangosnippets.org/snippets/1592/ looks cleaner

You can create a 'parameter-string'. Let's supose that in your code you have:
my_view( request, page, options):
sort_choices = {P:'price',N:'name', ...}
n_item_choices = {'S':5, 'L':50, 'XL':100)
ascending_descending_choices = {'A':'', 'D':'-'}
...
then you can concatenat options as:
options='P-S-D' #order by price, 5 items per page, descending order
encode opions as:
Previous
then, in urls.py capture options and in view:
my_view( request, page, options):
... #choides ....
try:
optionsArray = options.split('-')
sort_by = sort_choices[ optionsArray[0] ]
n_ites_page = n_item_choices[ optionsArray[1] ]
asc_or_desc = ascending_descending_choices[ optionsArray[2] ]
...
except:
somebody is playing ....
with this method you are free to add more paginations options without modify urls.py, all you need is to append options at the end of string options . This has advantages but also some dangers: I hope you can identify risks.

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 - Posted same answer.

Related

Django: How can I take the choice in drop-down list and redirect to another page?

I'm currently just learning Django and I'm doing electronic grade book. I have tried everything, have read all the documentation, but nothing helps. It seems I miss a simple logic somewhere. I need to make two pages:
The first one "teacher_interface" is a simple inteface for the teacher with just one drop-down list, teacher chooses the necessary class (i.e 1C, 2B, 4C) and the button "Students", which should somehow take the chosen class from drop-down list input and redirect to the second page "class_students".
The second "class_students" is alike the "teacher_interface", but with the table of students of the chosen class.
I have the One-to-many relation between classes Student and Class:
Firstly, I tried redirecting from "teacher_interface" to "class_students", using in template:
{% url "name" %}
Parts of code: 1) models.py https://dpaste.org/eqxm 2) urls.py https://dpaste.org/eUEO 3) views.py https://dpaste.org/ap8D#L 4) template teacher_interface.html https://dpaste.org/v4m9 5) template class_students.html https://dpaste.org/0gXK
But it shows me: Reverse for 'class_students' with no arguments not found. 1 pattern(s) tried: ['school/teacher/(?P<class_id>[0-9]+)/class/$']
I tried everything, but nothing helped, this and the similar: Django - getting Error "Reverse for 'detail' with no arguments not found. 1 pattern(s) tried:" when using {% url "music:fav" %} I understood maybe this two options of redirect will not work in my case:
{% url 'class_students' class.id %}
{% url 'class_students' class_id %}
I also don't know if it's possible to do on the same page.
So I decided to redirect using redirect from django.shortcuts. I changed my teacher_interface view, so that it took the id of the chosen by the teacher class if request method is POST and redirected. I also made this change in my template "teacher_interface.html":
from
action="{% url 'class_students' %}"
to
action=""
Changed view:
def teacher_interface(request):
class_queryset = Class.objects.order_by("class_number", "group")
class_id = None
if request.method == "POST":
class_id = Class.objects.get("id")
return redirect("class_students", class_id)
context = {
"class_queryset": class_queryset,
"class_id": class_id,
}
return render(request, "teacher_interface.html", context)
But when I choose the class and click the "Students" button, it shows me: Cannot resolve keyword 'i' into field. Choices are: class_number, curriculum, discipline, group, id, student, task, type_of_class, type_of_class_id. Id is certainly is a key, but it tries to resolve only "i".
I tried/read everything here, but nothing works.
I even wrote the default like this:
class_id = Class.objects.get("id", "default")
I am sure I just don't understand properly how to get teacher's choice, pass it to another or the same function and redirect, saving this information. I will be really grateful for you help, even if you just advise what I can read to figure it out.
Ok, you are missing some basic conpects.
on your views.py
def teacher_interface(request):
class_queryset = Class.objects.order_by("class_number", "group")
context = {
"class_queryset": class_queryset,
}
return render(request, "teacher_interface.html", context)
this is correct, you will pass you query to your template
on your template change some things to look like this:
<form method="POST" >{% csrf_token %}
<select name="input1">
{% for class in class_queryset %}
<option value="{{ class.id }}">{{ class }}</option>
{% endfor %}
</select>
<input type="submit" value="Students"/>
</form>
then you need to change your teacher_interface view:
You need to import redirect on your views.py
def teacher_interface(request):
class_queryset = Class.objects.order_by("class_number", "group")
context = {
"class_queryset": class_queryset,
}
if request.method == 'POST':
class_id = request.POST.get('input1') # I'm not sure if this will get the {{class.id}} value, if don't, print(request.POST.get) and check how to get the value
return redirect('class_students', class_id=class_id) # will make a get request on the class_students view
return render(request, "teacher_interface.html", context)
def class_students(request, class_id):
# the parameter need to be 'class_id' because this is what you put on your urls '<int:class_id>', if possible, remove that /class.
# ADD CLASS ID AS PARAMETER, THAT WILL ENABLE YOU TO ACESS AN SPECIFIC CLASS
# Import get_object_or_404 (google it and you will find easily)
class = get_object_or_404(Class, pk=class_id) # this avoid internal server error.
# pass your class on the context
return render(request, "class_students.html")

Django dynamic url parameter names

Basically trying to do
{% url dynamic_url_name dynamic_parameter_name=dynamic_parameter_value %}
Tried the simplest approach of
{{entry}}
{% include 'mainsite/children/title_template.html' with
the_title=title_text
is_title_page=True
entries_of="title"
belongs_to="profile"
belongs_to_url_arg="user"
belongs_to_url_arg_value="author"
%}
But unfortunately this resulted in utter failure of
From this I can tell that parameters can't be context variables, so what I can try to do next is to simply unpack a dictionary as I would do inside python with something like
{% url **{dynamic_parameter_name:dynamic_parameter_value} %}
But I have no idea if it is possible inside django templates, and if possible how?
My urls look like
re_path(r'^title/(?P<title_text>[a-zA-Z0-9-]+)/$', TitlePage.as_view(), name='title')
re_path(r'^user/(?P<user>[a-zA-Z0-9-]+)/$', ProfilePage.as_view() ,name='profile')
And I the url is choosen either by a context variable or in an include tag, hence it is a variable.
{% url url_variable xxx=value %}
Now, url_variable is already a part of django, url tag accepts variable as it's first argument. But the xxx is not always the same, rather it changes according to url_variable, in this particular case; if url_variable is title, I want xxx to be title_text and if it is profile I want it to be user.
The parameter name is held in belongs_to, so if this was a regular python function, I could've simply done
url(url_variable, **{belongs_to: value})
and it would've unpacked it with the correct parameter name. So I need some kind of equivalency of this in template processor
I think you're overcomplicating things. You haven't shown your views themselves, but I can't see why they couldn't all take a commonly-named parameter - say, param - that does the specific work. So the URLs could be:
re_path(r'^title/(?P<param>[a-zA-Z0-9-]+)/$', TitlePage.as_view(), name='title')
re_path(r'^user/(?P<param>[a-zA-Z0-9-]+)/$', ProfilePage.as_view() ,name='profile')
and now you can do
{% url dynamic_url_name param=dynamic_parameter_value %}
**kwargs are not specifically supported as a parameter for the {% url %} tag, but there are a few workwarounds.
If you have a path in urlpatterns defined as:
path('myurl/<str:some>/<str:thing>', name='myurl')
Then you could have a filetoinclude.html:
My Url
And then in your main.html:
{% include 'filetoinclude.html' with urlname='myurl' some="aaa" thing="bbb" %}
And when you render you will have something like:
My Url
But obviously the issue is that maybe you want to address specific parameters of the URL when you reverse it. For this reason you could create a templatetag like:
from django.urls import reverse
from django import template
register = template.Library()
#register.simple_tag
def dynamic_url(urlname, **kwargs):
return reverse(urlname, kwargs=kwargs)
and then in your filetoinclude.html you could do:
{% load url_extended %}
{% dynamic_url urlname some=some thing=thing %}
which will yield the same URL as before.

How to add custom dataset facet / solr filter in CKAN's Web UI?

Question:
How am I able to reproduce the result from the url below from the package_search API endpoint with CKAN's web UI?
https://demo.ckan.org/api/3/action/package_search?fq=num_resources:[1%20TO%20*]
I want to let a user filter by Packages with resources or without (0 num_resources or 1 to * num_resources).
What I've tried:
I've looked at and tried adding some facets and sorting already.
The facet allows filtering by Packages with X number of resources (e.g. 1). The sorting allows you to Sort all data sets by order of number of resources e.g. Packages with 10 resources first then 9 resources, then 8, etc...
I've tried to duplicate the API URI with the below
https://demo.ckan.org/dataset?num_resources=[1%20TO%20*]
If I add the fq portion it does not work either. The search() action will grab num_resources=[1 TO *] and append it to the solr fq param (can be seen here with log statements).
However, after trouble shooting this I've found that the CKAN Package controller's search() action DOES allow you to add values to the solr filter option fq like in the API call BUT that it first converts the params to a string num_resources:"[1 TO *]". This works fine to get a single value but not the range I'm after. If I use this exact param (with the quotes and not like the above url/api endpoint) with the API I get the incorrect result as well.
General Info:
using ckan 2.8.1
source install, deployed to production following the docs but using as my dev box
UPDATES:
2018-12-20:
I have since found that q=num_resources:[1%20TO%20*] as a query string works as this is not escaped in the search() action. The q parameters are extracted before the encoding takes place.
However, this isn't ideal as it updates the search input and overrides any existing query strings unless you append to the query string and adding this to the filters so far is a pain.
# I've switched spaces to + to help with readability.
https://demo.ckan.org/dataset?q=num_resources:[1+TO+*]+Test
2018-12-21:
Working on implementing IPackageController from within an extension. This seems to be the proper way to go about this given the circumstances. Will add implementation after.
However, I feel an updated implementation of the params could be done in ckan's package_search
Turns out the search index and organization read are implemented quite a bit differently so the exact same implementation wont work. extra params are actually included as part of the q parameters instead of fq like search.
As per my last update, it appears the best way to approach this is through IPackageController from an extension and use before_search() to modify the search params.
This works well however, it would be nice if CKAN allowed a way to pass in additional fq filters on it's main search pages (dataset and organization ?fq=num_resources:[1 TO *] and append to fq). Also, it seems that datasets is slightly different in assigning params to fq than organizations. You can see this in these 2 lines from their actions (dataset search vs organization read). In my case I decided to only handle this for dataset searching.
Main pieces
# In plugin class implement IPackageController.
class CustomPlugin(plugins.SingletonPlugin, toolkit.DefaultDatasetForm):
...
plugins.implements(plugins.IPackageController)
# Lower in plugin class add needed functions from IPackageController,
# I decided to add them all and leave them untouched to avoid various
# errors I was getting.
def before_search(self, search_params):
u'''Extensions will receive a dictionary with the query parameters,
and should return a modified (or not) version of it.
Basically go over all search_params and look for values that contain my
additional filter and remove the double quotes. All fq values are a
single string, so do exact match to not remove other escaping / quotes.
In query string in URL if you do `?num_resources=0` you get datasets with no
resources (the opposite of this query string).
'''
for (param, value) in search_params.items():
if param == 'fq' and 'num_resources:"[' in value:
v = value.replace('num_resources:"[1 TO *]"', 'num_resources:[1 TO *]')
search_params[param] = v
return search_params
def after_search(self, search_results, search_params):
return search_results
def before_index(self, pkg_dict):
return pkg_dict
def before_view(self, pkg_dict):
return pkg_dict
def read(self, entity):
return entity
def create(self, entity):
return entity
def edit(self, entity):
return entity
def delete(self, entity):
return entity
def after_create(self, context, pkg_dict):
return pkg_dict
def after_update(self, context, pkg_dict):
return pkg_dict
def after_delete(self, context, pkg_dict):
return pkg_dict
def after_show(self, context, pkg_dict):
return pkg_dict
Then for the UI I added a custom facet list on the search.html template.
<div>
<section class="module module-narrow module-shallow">
{% block facet_list_heading %}
<h2 class="module-heading">
<i class="fa fa-filter"></i>
{% set title = 'Resources (data)' %}
{{ title }}
</h2>
{% endblock %}
{% block facet_list_items %}
{% set title = 'Has Resources (data)' %}
<nav>
<ul class="{{ nav_class or 'list-unstyled nav nav-simple nav-facet' }}">
{% set href = h.remove_url_param('num_resources',
extras=extras,
alternative_url=alternative_url)
if c.fields_grouped['num_resources']
else h.add_url_param(new_params={'num_resources': '[1 TO *]' },
alternative_url=alternative_url) %}
<li class="{{ nav_item_class or 'nav-item' }}{% if c.fields_grouped['num_resources'] %} active{% endif %}">
<a href="{{ href }}" title="{{ title }}">
<span>{{ title }}</span>
</a>
</li>
</ul>
</nav>
{% endblock %}
</section>
</div>
Doing it this way it doesn't add a new facet using IFacets as this adds a facet list for num_resources that gives the filter options of 0, 1, 2, 3, ... (or whatever makes sense for your setup e.g. if a dataset has 15 resources it shows that as an option).
I also did some modifications to the search_form.html snippet to get the facet filter to display how I wanted it but that was just extra.

Django pass render_to_response template in other template

this is probably a question for absolute beginners since i'm fairly new to progrmaming. I've searched for couple of hours for an adequate solution, i don't know what else to do.
Following problem. I want to have a view that displays. e.g. the 5 latest entries & 5 newest to my database (just an example)
#views.py
import core.models as coremodels
class LandingView(TemplateView):
template_name = "base/index.html"
def index_filtered(request):
last_ones = coremodels.Startup.objects.all().order_by('-id')[:5]
first_ones = coremodels.Startup.objects.all().order_by('id')[:5]
return render_to_response("base/index.html",
{'last_ones': last_ones, 'first_ones' : first_ones})
Index.html shows the HTML content but not the content of the loop
#index.html
<div class="col-md-6">
<p> Chosen Items negative:</p>
{% for startup in last_ones %}
<li><p>{{ startup.title }}</p></li>
{% endfor %}
</div>
<div class="col-md-6">
<p> Chosen Items positive:</p>
{% for startup in first_ones %}
<li><p>{{ startup.title }}</p></li>
{% endfor %}
Here my problem:
How can I get the for loop to render the specific content?
I think Django show render_to_response in template comes very close to my problem, but i don't see a valid solution there.
Thank you for your help.
Chris
--
I edited my code and problem description based on the solutions provided in this thread
the call render_to_response("base/showlatest.html"... renders base/showlatest.html, not index.html.
The view responsible for rendering index.html should pass all data (last_ones and first_ones) to it.
Once you have included the template into index.html
{% include /base/showlatest.html %}
Change the view above (or create a new one or modify the existing, changing urls.py accordingly) to pass the data to it
return render_to_response("index.html",
{'last_ones': last_ones, 'first_ones' : first_ones})
The concept is that the view renders a certain template (index.html), which becomes the html page returned to the client browser.
That one is the template that should receive a certain context (data), so that it can include other reusable pieces (e.g. showlatest.html) and render them correctly.
The include command just copies the content of the specified template (showlatest.html) within the present one (index.html), as if it were typed in and part of it.
So you need to call render_to_response and pass it your data (last_ones and first_ones) in every view that is responsible for rendering a template that includes showlatest.html
Sorry for the twisted wording, some things are easier done than explained.
:)
UPDATE
Your last edit clarified you are using CBV's (Class Based Views).
Then your view should be something along the line:
class LandingView(TemplateView):
template_name = "base/index.html"
def get_context_data(self, **kwargs):
context = super(LandingView, self).get_context_data(**kwargs)
context['last_ones'] = coremodels.Startup.objects.all().order_by('-id')[:5]
context['first_ones'] = coremodels.Startup.objects.all().order_by('id')[:5]
return context
Note: personally I would avoid relying on the id set by the DB to order the records.
Instead, if you can alter the model, add a field to mark when it was created. For example
class Startup(models.Model):
...
created_on = models.DateTimeField(auto_now_add=True, editable=False)
then in your view the query can become
def get_context_data(self, **kwargs):
context = super(LandingView, self).get_context_data(**kwargs)
qs = coremodels.Startup.objects.all().order_by('created_on')
context['first_ones'] = qs[:5]
context['last_ones'] = qs[-5:]
return context

Passing multiple template variables to template tags in django

My problem occurs when I try to pass 3 variables to my template tag which I guess takes one or 2 inputs. So any ideas on how to pass 3 template variables to my template tag and here is my code:
views.py:
from shoghlanah.models import *
register = template.Library()
#register.simple_tag
def get_messages(sender_id,receiver_id,task_id):
sender = UserProfile.objects.get(id = sender_id)
receiver =UserProfile.objects.get(id = receiver_id)
task = Task.objects.get(id=task_id)
return messages
message.html :
the url.id and the task_id are template variables
{{ get_messages request.user.id usr.id task_id }}
it gives me an error : Could not parse the remainder: ' request.user.id usr.id task_id' from 'get_messages request.user.id usr.id task_id'
For a django tag, you need to use {% %}, not the double curly brackets. The double braces signify outputting a value.
See the docs for more.
(Just as a note, I presume that is an extract, but you will also need to {% load %} your tag too.)

Categories