Dynamic URL and Jinja templates - python

I've been trying to create user interface to filter out results from my database. Important thing is that I want the filters to be 'additive'. So if user selects one filter, page redirects and displays results. After that, user can select another filter and the results are narrowed down to both filters. This should continue for any number of filters.
This is how it looks now
#app.route('/')
def home():
kind = request.args.get('kind')
price = request.args.get('price')
category = request.args.get('category')
filters = {}
if price is not None: filters['params.price'] = {'$lt' : int(price) }
if kind is not None: filters['kind'] = kind
if category is not None: filters['category'] = category
posts = db.collection.find(filters)
return render_template('home.html', posts=posts)
and links for my hrefs using jinja2 templates look like
<li>Label</<li>
<li>Label</li>
<li>Label</li>
... many more similar links
Currently this works as override for the URL. If I click any of those links it just replaces the whole URL and uses the variable from the link.
first link: http://127.0.0.1/?kind=m
second link: http://127.0.0.1/?price=5000
third link: http://127.0.0.1/?category=p
What I'd like it to do is to append the query - If i click any of the links it remembers previous selected filters and 'adds' last clicked link. Below I show how I expect for it to work.
first link: http://127.0.0.1/?kind=m
second link: http://127.0.0.1/?kind=m?price=50000
second link: http://127.0.0.1/?kind=m?price=50000?category=p

You could pass all filter values (None initially) to the view, and add them as arguments to the url_for calls. Filters which are None will not be included in the links.

Related

Safely store data from GET request - Django

Alright,
Let's say we need to create a website with Django where people can book a lodge for the weekends.
We add a search form on the homepage where people can fill in the check-in and check-out date
to filter for all available lodges.
We use a generic Listview to create an overview of all the lodges and we overwrite the queryset to grab the search parameters from the GET request to create a filtered view.
views.py
class ListingsView(ListView):
"""Return all listings"""
model = Listing
template_name = 'orders/listings.html'
def get_queryset(self):
"""
Visitors can either visit the page with- or without a search query
appended to the url. They can either use the form to perform a search
or supply an url with the appropriate parameters.
"""
# Get the start and end dates from the url
check_in = self.request.GET.get('check_in')
check_out = self.request.GET.get('check_out')
queryset = Calendar.objects.filter(
date__gte=check_in,
date__lt=check_out,
is_available=True
)
return queryset
Now this code is simplified for readability, but what I would like to do, is store the check-in and check-out date people are searching for.
Updated views.py
class ListingsView(ListView):
"""Return all listings"""
model = Listing
template_name = 'orders/listings.html'
def get_queryset(self):
"""
Visitors can either visit the page with- or without a search query
appended to the url. They can either use the form to perform a search
or supply an url with the appropriate parameters.
"""
# Get the start and end dates from the url
check_in = self.request.GET.get('check_in')
check_out = self.request.GET.get('check_out')
queryset = Calendar.objects.filter(
date__gte=check_in,
date__lt=check_out,
is_available=True
)
Statistics.objects.create(
check_in=check_in,
check_out=check_out
)
return queryset
We created a "Statistics" model to store all dates people are looking for.
We essentially add data to a model by using a GET request and I'm wondering if this is the right way of doing things? Aren't we creating any vulnerabilities?
The search form uses hidden text inputs, so there's always the possibility of not knowing what data is coming in. Is cleaning or checking the datatype from these input enough, or will this always be in string format?
Any ideas?
Greetz,

Django determine the value of a checkbox in a template from views

I have been trying to design a page in Django that works as follows.
My "list_books.html" page lists every book object handed to it.
I have a number of functions in views.py that determine what values would be used to determine the books shown on that page (i.e. all books by an author, all books in a series, all books with the same publication year)
ex.
#with_person_email
def book_list_author(request, person):
return show_book_list(request, person.books, { 'author': person })
def show_book_list(request, blist, template_args, **kwargs):
# this is just the defaults, will be replaced by data.update below
data = { 'genre': None }
try:
# filters the list based on the keyword arguments
blist = dataview.book_list(blist, **kwargs)
except dataview.DataViewError as e:
blist = None
data['error'] = str(e)
try:
data['books'] = RequestPages(request, blist, desc=True)
except Exception as e:
if not utils.is_db_regex_exception(e):
raise
data['books'] = None
data['error'] = 'Invalid regex.'
data['genres'] = models.Genre.objects.order_by('kind', 'name')
data.update(kwargs)
data.update(template_args)
return render(request, 'book_list.html', data)
book_list.html has a for loop that goes through each book and prints information about it. However, I have a boolean on the book model called "is_archived".
I want to be able to both set "is_archived" on the book from book_list.html, and filter the books shown between archived and not. I can do both of these things currently using a form that calls the following function handing it only archived books. However, this form has no idea what the previous criteria was to sort the books, so it shows all the archived books.
def commit_list_archived(request):
return show_commit_list(request, models.Books.objects.filter(is_archived=True), { 'archived': True })
Settings the boolean is accomplished with a simple button that calls a view which changes the boolean field, and then returns to the previous page.
I want to be able to toggle between archived and non archived books. I tried using <input type="hidden" name="next" value="{{ request.path }}"> on the form to the archived posts to determine the previous criteria (author, year, genre, etc), however this doesn't seem to work.
I also considered using a checkbox that would toggle the books being shown, but I couldnt determine how to access the information of the checkbox form views.
For cleanliness sake I would like to remain on the books_list.html page, and just hand it either archived or none archived books. Again the problem is finding some way to call the right function both before and after view the archived books, to ensure I am still sorting by the same criteria.
Any help would be much appreciated.
Disregard I figured it out. I just sent a query parameter ?archived=true and have the views check for this parameter and filter the commits they send to the html template accordingly

Next Page / Previous Page not working for Django pagination

I was hoping someone could help me with a pagination question.
I am trying to use Django pagination following the information on this page (https://docs.djangoproject.com/en/2.2/topics/pagination/). Whilst I have successfully displayed the correct number of items on the first page and the last page works, the next and previous pages keep taking me to the first page.
I think the issue may revolve around the ‘request’ element and I’m not sure if I am picking up an incorrect version. The example states:-
def listing(request):
contact_list = Contacts.objects.all()
paginator = Paginator(contact_list, 25) # Show 25 contacts per page
page = request.GET.get('page')
contacts = paginator.get_page(page)
return render(request, 'list.html', {'contacts': contacts})
The command:
page = request.GET.get(‘page’)
returns “AttributeError: 'Request' object has no attribute 'GET'”
By replacing this code with:
page = request.args.get('page', type=int)
the code successfully renders the first (and last) page but next and previous do not work.
As background I built my system on the Flask megatutorial but I have been unable to use that pagination, I understand because I haven’t used the Flask SQL Alchemy for creating and updating databases. My routes file has
from flask import request
Should I replace this with another utility's “request” and if so, which?
It seems the problem was in a missing () within the HTML file:-
next
listed within the example should have been:
next

mentions/internal links in Django

I have a bunch of models. All these models has a method get_absolute_url and a field text. I want to make internal links in the text field just like wikipedia does.
Wikipedia's internal links in pages only refer to other pages. I need to link to all my models.
I could make a pattern for internal links and replacing this pattern with a hardcoded url to an url but it's really not a good idea because the links can change. So it would be best if I could refer to get_absolute_url.
Another option would be to use a template tag to change a specific pattern to links.
How should it be done? Are there any open source projects in which this has already been done?
I wanted to answer this same problem just a few days ago, and I did it with a template filter. My links are relative URLs, not absolute, but you could tweak that pretty easily, and you could also tweak the regex pattern to match whatever link markup you prefer.
Using the filter, the link is only looked up at display time, so if your view's URL has changed, that should automatically update with the reverse() lookup.
I also use Markdown to process my description fields, so I make the link return a markdown-formatted link instead of HTML, but you could tweak that too. If you use Markdown, you'd want to put this filter first.
So to display a description TextField with internal links, in the template would be something like this:
{{ entity.description|internal_links|markdown }}
(See the Django docs on writing your own custom filters for more details on writing and registering filters.)
As for the specific filter itself, I did it like this:
from django import template
from django.core.urlresolvers import reverse
from my.views import *
register = template.Library()
#register.filter
def internal_links(value):
"""
Takes a markdown textfield, and filters
for internal links in the format:
{{film:alien-1979}}
...where "film" is the designation for a link type (model),
and "alien-1979" is the slug for a given object
NOTE: Process BEFORE markdown, as it will resolve
to a markdown-formatted linked name:
[Alien](http://opticalpodcast.com/cinedex/film/alien-1979/)
:param value:
:return:
"""
try:
import re
pattern = '{{\S+:\S+}}'
p = re.compile(pattern)
#replace the captured pattern(s) with the markdown link
return p.sub(localurl, value)
except:
# If the link lookup fails, just display the original text
return value
def localurl(match):
string = match.group()
# Strip off the {{ and }}
string = string[2:-2]
# Separate the link type and the slug
link_type, link_slug = string.split(":")
link_view = ''
# figure out what view we need to display
# for the link type
if(link_type == 'film'):
link_view = 'film_detail'
elif(link_type == 'person'):
link_view = 'person_detail'
else:
raise Exception("Unknown link type.")
link_url = reverse(link_view, args=(link_slug,))
entity = get_object_or_404(Entity, slug=link_slug)
markdown_link = "[" + entity.name + "](" + link_url + ")"
return markdown_link

Django pagination | get current index of paginated item in page index, (not the page index range itself)

I am trying to build a photo gallery with Django.
It is set up by category.
I have paginated the results of a category by n amount of images per page. I want to also use the paginator on the page that shows just the single image and have a prev/next button for the prev/next image in that category.
My thought was to get the current index for the image itself and have that be the link to the /category/CUR_IMG_ID_PAGINATION_LIST/ as the result of paginating the entire set would yield the same index as the current image index in the paginated results.
For instance if the image i want is image 45 out of 150 images total for a category, then when i paginate the 150 images the 45 will be the actual number of the page I want.
If there's an easier way to do this, let me know. Django 1.1
I think the way you're describing it would work ok because behind the scenes I believe what Django is doing is using an SQL LIMIT to simply let the database do the heavy lifting of sorting out what and how much data to return. Because the database is optimized for doing this type of thing it's probably a reasonable way to perform this.
The key will probably be to keep the query the same and as you've demonstrated you could use the same view to do that. The view could simply have a mode which is a fancy way of changing the pagination page count.
You could end up with urls like this...
# View all "landscape" items in gallery mode starting on page 3
http://www.example.com/gallery/landscape/multi/3
# View the 45th landscape item in singular mode
http://www.example.com/gallery/landscape/single/45
When the template is rendered, the paginator will offer the has_next and has_previous methods letting you know if you can use render a Next/Previous link.
Here's what I'm thinking for the view, or something along these lines (this is totally un-tested and written off the top of my head)...
url(r'gallery/(?P<category>.+)/(?P<mode>.+)/(?P<offset>\d+)$', 'whatever.views.media_gallery'),
def media_gallery(request, category, mode, offset):
"""
Render a media gallery.
category = media item category filter
mode = ( multi | single )
offset = The pagination offset in multi mode or the media ID in single mode
"""
if mode == 'multi':
per_page = 20 # or however many items per page
elif mode == 'single':
per_page = 1
else:
pass # handle this however
# Queryitems
raw_media_items = Media.objects.filter(category=category)
# Setup paginator
paginator = Paginator(raw_media_items, per_page)
try:
# in multi mode offset is the page offset
# in single mode offset is the media ID
page = int(offset)
except:
page = 1
try:
media_items = paginator.page(page)
except (EmptyPage, InvalidPage):
media_items = paginator.page(paginator.num_pages)
if len(paginated_items) == 1:
# Render single view
return render_to_response('gallery/gallery_view.html',
{ 'media_item':media_items[0], 'paginator':paginator },
context_instance=RequestContext(request) )
else:
# Render gallery view
return render_to_response('gallery/gallery_view.html',
{ 'media_items':media_items, 'paginator':paginator },
context_instance=RequestContext(request) )

Categories

Resources