I'm using the django-filter package to provide a search functionality on my List View.
Now I want to add a pagination to that view as well.
I'm trying to combine pagination to a filtered queryset, but I have no clue on how to go on.
So far, I have tried the following on views.py:
def search(request):
qs = local_url.objects.filter(global_url__id=1).all()
paginator = Paginator(qs, 25)
page = request.GET.get('page')
try:
pub = paginator.page(page)
except PageNotAnInteger:
pub = paginator.page(1)
except EmptyPage:
pub = paginator.page(paginator.num_pages)
url_filter = PublicationFilter(request.GET, queryset=qs)
return render(request, 'ingester/search_list.html', {'filter': url_filter, 'publication':pub})
This worked for me:
in my template instead of using this
<li>{{ i }}</li>
I wrote this:
{% if 'whatever_parameter_you_use_to_filter' in request.get_full_path %}
<li><a href="{{ request.get_full_path }}&page={{ i }}"{{ i }}</a></li>
{% else %}
<li>{{ i }}</li>
{% endif %}
I hope it helps :)
To use Django Filter and paginate the filtered result you can do the following:
Create a filter class for your model:
On my_project/my_app/filters.py:
import django_filters
class MyModelFilter(django_filters.FilterSet):
class Meta:
model = MyModel
# Declare all your model fields by which you will filter
# your queryset here:
fields = ['field_1', 'field_2', ...]
Every FilterSet object has a .qs property which contains the filtered queryset and you can even override it if you want.
We will paginate the .qs property of our MyModelFilter:
On my_project/my_app/views.py:
from . import filters
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
def my_view(request):
# BTW you do not need .all() after a .filter()
# local_url.objects.filter(global_url__id=1) will do
filtered_qs = filters.MyModelFilter(
request.GET,
queryset=MyModel.objects.all()
).qs
paginator = Paginator(filtered_qs, YOUR_PAGE_SIZE)
page = request.GET.get('page')
try:
response = paginator.page(page)
except PageNotAnInteger:
response = paginator.page(1)
except EmptyPage:
response = paginator.page(paginator.num_pages)
return render(
request,
'your_template.html',
{'response': response}
)
And there you have it!
PS_1: Django filter in my experience, "plays" better with Django Rest Framework.
PS_2: If you are about to utilize DRF, I have written an example on how to use pagination in a function based view which you can easily combine with a FilterSet:
#api_view(['GET',])
def my_function_based_list_view(request):
paginator = PageNumberPagination()
filtered_set = filters.MyModelFilter(
request.GET,
queryset=MyModel.objects.all()
).qs
context = paginator.paginate_queryset(filtered_set, request)
serializer = MyModelSerializer(context, many=True)
return paginator.get_paginated_response(serializer.data)
To add to the answers, I did it with html tables too along with django-filters and Paginator. Below are my view and template files. The template tag is needed to make sure you pass the right parameters to the pagination url.
search_view.py
from django.shortcuts import render
from app.models.filters_model import ApiStatusFilter
from app.models.api_status import ApiStatus
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from datetime import datetime, timedelta
def status(request):
all_entries_ordered = ApiStatus.objects.values().order_by('-created_at')[:200]
for dictionarys in all_entries_ordered:
dictionarys
apistatus_list = ApiStatus.objects.values().order_by('-created_at')
apistatus_filter = ApiStatusFilter(request.GET, queryset=apistatus_list)
paginator = Paginator(apistatus_filter.qs, 10)
page = request.GET.get('page')
try:
dataqs = paginator.page(page)
except PageNotAnInteger:
dataqs = paginator.page(1)
except EmptyPage:
dataqs = paginator.page(paginator.num_pages)
return render(request, 'status_page_template.html', {'dictionarys': dictionarys, 'apistatus_filter': apistatus_filter, 'dataqs': dataqs, 'allobjects': apistatus_list})
status_template.html
{% load static %}
{% load my_templatetags %}
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" type="text/css" href="{% static 'css/table_styling.css' %}">
<meta charset="UTF-8">
<title>TEST</title>
</head>
<body>
<table>
<thead>
<tr>
{% for keys in dictionarys.keys %}
<th>{{ keys }}</th>
{% endfor %}
</tr>
</thead>
<form method="get">
{{ apistatus_filter.form.as_p }}
<button type="submit">Search</button>
{% for user in dataqs.object_list %}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.date_time }}</td>
<td>{{ user.log }}</td>
</tr>
{% endfor %}
</form>
</tbody>
</table>
<div class="pagination">
<span>
{% if dataqs.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ dataqs.number }} of {{ dataqs.paginator.num_pages }}.
</span>
{% if dataqs.has_next %}
next
last »
{% endif %}
</span>
</div>
</body>
</html>
my_templatetags.py
from django import template
register = template.Library()
#register.simple_tag
def query_transform(request, **kwargs):
updated = request.GET.copy()
for k, v in kwargs.items():
if v is not None:
updated[k] = v
else:
updated.pop(k, 0)
return updated.urlencode()
It took me some time to find the DRYer and cleaner solution to fix this issue and the best one, in my opinion, is the one using template tags.
from django import template
register = template.Library()
#register.simple_tag
def relative_url(value, field_name, urlencode=None):
url = '?{}={}'.format(field_name, value)
if urlencode:
querystring = urlencode.split('&')
filtered_querystring = filter(lambda p: p.split('=')[0] != field_name, querystring)
encoded_querystring = '&'.join(filtered_querystring)
url = '{}&{}'.format(url, encoded_querystring)
return url
and in your template
{{ i }}
Source: Dealing With QueryString Parameters
The most important part here is the how you construct your URLs in the template.
you probably have
{% if pages.has_previous %}
<li>Prev</li>
{% endif %}
which is perfectly fine if you are using only it for switching between the initial paginated results.
But the tricky part is when you use the django-fitler filters, the querystring (that part after the '?') gets totally new key-values pairs, disregarding your ?page=2 or similar.
So to make pagination work with filtered results, when you click the "Next" or "Prev" button - among the key-values from django-fitler you also need to pass the &page=5 as pair.
As #stathoula mentioned, you need to check if at least one of your filter fields is already present in the querystring. If it is, then you need to use the already present key-value pairs, followed by the new &page=3 pair.
It seems very simple, but I had to done small hackish not to repeat the &page=1 over and over again within the querystring as a user is clicking trough the arrows.
In my case I'm having 'title' as a filter, so I need to check if it's already present there.
Here's a snippet of what I've made working perfectly fine for my project.
templates/pagination.html
<div class="paginator">
{% with request.get_full_path as querystring %}
<ul class="pagination nav navbar-nav">
<!-- Previous page section -->
{% if pages.has_previous %}
{% if 'title' in querystring %}
{% if 'page' in querystring %}
<li class="paginator {% if pages.number == page %}active{% endif %}">
Prev
</li>
{% else %}
<li class="paginator {% if pages.number == page %}active{% endif %}">
Prev
</li>
{% endif %}
{% else %}
<li class="paginator {% if pages.number == page %}active{% endif %}">
Prev
</li>
{% endif %}
{% endif %}
<!-- All pages section -->
{% for page in pages.paginator.page_range %}
{% if 'title' in querystring %}
{% if 'page' in querystring %}
<li class="paginator {% if pages.number == page %}active{% endif %}">
{{ page }}
</li>
{% else %}
<li class="paginator {% if pages.number == page %}active{% endif %}">
{{ page }}
</li>
{% endif %}
{% else %}
<li class="paginator {% if pages.number == page %}active{% endif %}">
{{ page }}
</li>
{% endif %}
{% endfor %}
<!-- Next page section -->
{% if pages.has_next %}
{% if 'title' in querystring %}
{% if 'page' in querystring %}
<li class="paginator {% if pages.number == page %}active{% endif %}">
Next
</li>
{% else %}
<li class="paginator {% if pages.number == page %}active{% endif %}">
Next
</li>
{% endif %}
{% else %}
<li class="paginator {% if pages.number == page %}active{% endif %}">
Next
</li>
{% endif %}
{% endif %}
</ul>
{% endwith %}
</div>
Here is the view, just in case:
app/views.py
def index(request):
condo_list = Condo.objects.all().order_by('-timestamp_created')
condo_filter = CondoFilter(request.GET, queryset=condo_list)
paginator = Paginator(condo_filter.qs, MAX_CONDOS_PER_PAGE)
page = request.GET.get('page')
try:
condos = paginator.page(page)
except PageNotAnInteger:
condos = paginator.page(1)
except EmptyPage:
condos = paginator.page(paginator.num_pages)
return render(request, 'app/index.html', {
'title': 'Home',
'condos': condos,
'page': page,
'condo_filter': condo_filter,
})
Here's a working example:
.
My approach for "remember filter/query URL parameters" for paginated results: passing the current URL parameters as a context variable:
# views.py
class PublicationFilterView(FilterView):
model = Publication
filterset_class = PublicationFilter
paginate_by = 15
def get_context_data(self, *args, **kwargs):
_request_copy = self.request.GET.copy()
parameters = _request_copy.pop('page', True) and _request_copy.urlencode()
context = super().get_context_data(*args, **kwargs)
context['parameters'] = parameters
return context
# templates/path/to/pagination.html
<a href="?page={{ page_obj.next_page_number }}&{{ parameters }}">
Next
</a>
this one works 100% with me
views.py:
def search(request):
category=Category.objects.all()
try:
qs=request.GET["qs"]
products=Product.objects.filter(Q(name__icontains=qs) |Q(details__icontains=qs) | Q(category__name__icontains=qs) | Q(branch__child__icontains=qs) | Q(manufacturer__name__icontains=qs) | Q(color__name__icontains=qs)).distinct()
print(products)
search=f"qs={qs}"
except:
search=None
and in HTML
<ul class="shop-p__pagination">
{% if products.has_provious %}
<li>
<a class="fas fa-angle-left" href="?page={{ products.previous_page_number }}&{search}"></a></li>
{% endif %}
{% for i in products.paginator.page_range %}
{% if products.number == i %}
<li class="is-active">{{i}}</li>
{% else %}
<li>{{i}}</li>
{% endif %}
{% endfor %}
{% if products.has_next %}
<li>
<a class="fas fa-angle-right" href="?page={{ products.next_page_number }}&{{search}}"></a></li>
{% endif %}
</ul>
As I understood you goal is to paginate your filtered query set. If so, you can pass "qs" property of PublicationFilter object to Paginator constructor:
def search(request):
qs = local_url.objects.filter(global_url__id=1).all()
url_filter = PublicationFilter(request.GET, queryset=qs)
paginator = Paginator(url_filter.qs, 25)
page = request.GET.get('page')
try:
pub = paginator.page(page)
except PageNotAnInteger:
pub = paginator.page(1)
except EmptyPage:
pub = paginator.page(paginator.num_pages)
url_filter = PublicationFilter(request.GET, queryset=qs)
return render(request, 'ingester/search_list.html', {'publication':pub})
url_filter.qs contains filtered QuerySet
url_filter.queryset contains non-filtered QuerySet
Simple & sweet,
use this, pip install filter-and-pagination
https://pypi.org/project/filter-and-pagination/
Implementation Step
install package by pip install filter-and-pagination
import FilterPagination by from filter_and_pagination import FilterPagination in view.py
in your function writte code as bellow standards...
queryset = FilterPagination.filter_and_pagination(request, Customer)
serialize_data = CustomerSerializer(queryset['queryset'], many=True).data
resultset = {'dataset': serialize_data, 'pagination': queryset['pagination']}
in this code Customer is Django model &
CustomerSerializer is a DRF Serializer class
in the resultset it contains dataset & pagination data, In this format (API Response) link: https://github.com/ashish1997it/filter-pagination-dj#demo
For the API request follow PostMan collection link: https://github.com/ashish1997it/filter-pagination-dj#postman in the header section it will take a parameter & request you customize as per your requirement
If you still face any difficulty then contact me :)
In get_context_data() function:
form_submitted = 'csrfmiddlewaretoken' in self.request.GET
context['cleaned_full_path'] = '{}{}'.format(
self.request.get_full_path().split('&page' if form_submitted else '?page')[0],
'&' if form_submitted else '?'
)
Then, in your template, load in something like
<a href="{{ cleaned_full_path }}page={{ page_obj.paginator.num_pages }}"
In addition to #stathoula and in response to #Benbb96, I managed to erase the additional page parameters with a regular expression, overriding the setup method in the class based view:
import re
...
class MyView(ListView):
...
def setup(self, request, *args, **kwargs) -> None:
request.GET.get("page")
request.META["QUERY_STRING"] = re.sub("(&|\?)page=(.)*", "", request.META.get("QUERY_STRING", ""))
return super().setup(request, *args, **kwargs)
Hope it helps anyone!
More info:
Request and Response Objects - Django docs
as_view() - Django docs
Related
My issue is that when I have called an external webpage and displayed as {{ html }} in the for loop it prints for all of them is there a way to print just for one. The part after b'{"carpark_name": "multi-storey", "date": "01-11-21 12:46:55", "spaces_available": 332}'
this is the webpage
This is my views.py
from django.shortcuts import render
from .models import Carpark, Campus
from urllib.request import urlopen
def index(request):
campus_list = Campus.objects.all()
carpark_list = Carpark.objects.all()
noparking = Campus.objects.filter(carpark=None)
html = urlopen("https://mbezbradica.pythonanywhere.com/carparks/multi-storey").read()
context = {'carpark_list': carpark_list,
'campus_list': campus_list,
'noparking': noparking,
'html' : html
}
return render(request, "parkatdcu/index.html", context)
And this is my index.html code
<h1>Welcome to ParkAtDCU</h1>
{% for campus in campus_list %}
<h2>
{{ campus }}
</h2>
{% if campus in noparking %}
No carparks found
{% else %}
<ul>
{% for carpark in carpark_list %}
{% if campus == carpark.campus_id %}
<li> {{ carpark.name }}: {{ carpark.spaces}} spaces, {{carpark.disabled_spaces}} spaces for people with disabilities, {{ html }}</li>
{% endif %}
{% endfor %}
{{ url }}
</ul>
{% endif %}
{% endfor %}
What you are getting is a bytes object, you can decode it into a string object and use ast.literal_eval() to convert it into a dictionary. And then pass that dictionary into the template. Honestly this is a JSON object and you should not call it HTML. I'll give it the name data here. And then in the template just call whatever you can want to show:
from django.shortcuts import render
from .models import Carpark, Campus
from urllib.request import urlopen
import ast
def index(request):
campus_list = Campus.objects.all()
carpark_list = Carpark.objects.all()
noparking = Campus.objects.filter(carpark=None)
html = urlopen("https://mbezbradica.pythonanywhere.com/carparks/multi-storey").read()
data_string = html.decode("utf-8")
data = ast.literal_eval(test_string)
context = {'carpark_list': carpark_list,
'campus_list': campus_list,
'noparking': noparking,
'html' : data
}
return render(request, "parkatdcu/index.html", context)
And in your html file:
<h1>Welcome to ParkAtDCU</h1>
{% for campus in campus_list %}
<h2>
{{ campus }}
</h2>
{% if campus in noparking %}
No carparks found
{% else %}
<ul>
{% for carpark in carpark_list %}
{% if campus == carpark.campus_id %}
<li> {{ carpark.name }}: {{ carpark.spaces}} spaces, {{carpark.disabled_spaces}} spaces for people with disabilities, car park name: {{ html.carpark_name }}</li>
{% endif %}
{% endfor %}
{{ url }}
</ul>
{% endif %}
{% endfor %}
But the way you are doing it is very dirty (in my opinion). I suggest you install requests and use requests.get(url).json() it will give you a dictionary without going through this hassle, making you code cleaner (again, in my opinion).
You can install requests:
pip install requests
and do this:
from django.shortcuts import render
from .models import Carpark, Campus
from urllib.request import urlopen
import requests
def index(request):
campus_list = Campus.objects.all()
carpark_list = Carpark.objects.all()
noparking = Campus.objects.filter(carpark=None)
data = requests.get("https://mbezbradica.pythonanywhere.com/carparks/multi-storey").json()
context = {'carpark_list': carpark_list,
'campus_list': campus_list,
'noparking': noparking,
'html' : data
}
return render(request, "parkatdcu/index.html", context)
(I didn't change your html context variable because "if it works, don't touch it", I suggest you change your context variable name "html" to "data" or something like that).
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.
In short - I have a bootstrap carousel and it works nicely, however I can't get it to display only fields with 'featured' set to 'true'
I have tried doing for post in posts.objects.featured (the carousel literally does not show up at all then) and variations like posts.objects.filter(featured=True) (it says it can't parse the remainder).
Here's the code from the template where I am trying to display the carousel image only with items with featured=True
{% for post in posts.objects.featured %}
<div class="carousel-item {% if forloop.first %}active{% endif %} ">
{% image post.image fill-1920x500 %}
<div class="carousel-caption d-none d-md-block">
<h2 id="inner-carousel-title">{{post.title}}</h2>
<h4><a href="{% pageurl post %}" style="color:white;text-shadow:2px 2px 4px #000000" >something</a></h4>
</div>
</div>
{% endfor %}
Again, I just want the carousel to show up only with featured posts
As a side note- it'd be awesome if it only showed 3 posts.
EDIT - Here's my model.py for the page
class BlogPage(RoutablePageMixin, Page):
description = models.CharField(max_length=240, blank=True)
content_panels = Page.content_panels + \
[FieldPanel("description", classname="full")]
def get_context(self, request, *args, **kwargs):
context = super(BlogPage, self).get_context(request, *args, **kwargs)
context['posts'] = self.posts
context['blog_page'] = self
return context
If you really want to do this in the template do:
{% for post in posts %}
{% if post.featured %}
<div> ... <div/>
{% endif %}
{% endfor %}
But you can also pass only the featured posts to your template in your view. Just add:
...
featured_posts = Post.objects.filter(featured=True)[:4]
return render('post_list.html', {'featured_posts': featured_posts, ...})
If you’re using Django’s generic ListView and you’re only showing the featured posts, you can set the queryset property to filter only the featured posts. If you’re also showing the other posts in your ListView, add the featured_posts to your context by overriding get_context_data().
You can try this if the way I suggested in the comment doesn't work
{% for post in posts %}
{% if post.featured %}
// write down your stuff
{% endif %}
{% endfor %}
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/
I have 3 database model - Semester, Section and Notecard
The Notecard model has a "Known" field that I use to classify the Notecard objects into "piles" as Known (1) or Unknown (0):
class Notecard(models.Model):
notecard_name = models.CharField(max_length=50)
notecard_body = models.TextField()
section = models.ForeignKey(Section)
known = models.BooleanField()
I have two views - known_list and unkown_list that displays the corresponding piles (known_list below for reference):
def known_list(request, section_name):
try:
section = Section.objects.get(section_name__iexact = section_name)
except Section.DoesNotExist:
raise Http404
known_list = Notecard.objects.filter(known=1, section=section)
paginator = Paginator(known_list, 1)
if known_list:
try:
page = int(request.GET.get('page', '1'))
except ValueError:
page = 1
try:
known = paginator.page(page)
except (EmptyPage, InvalidPage):
known = paginator.page(paginator.num_pages)
context = RequestContext(request)
return render_to_response('notecards/known.html', {"known": known}, context_instance=context)
else:
url = reverse('notecard_list', kwargs={'section_name': section_name})
return HttpResponseRedirect(url)
This view brings in the section_name from the previous view to display all the Notecard objects that are in the section that was clicked on, and in the known pile.
In the template below, you can see that I paginate the notecards to one a page:
{% extends "base.html" %}
{% block content %}
<h1 class='title'>NoteCards!</h1>
{% for notecard in known.object_list %}
<h1 class='notecard'>{{ notecard.notecard_name }}</h1>
<h3 class='notecard'>{{ notecard.notecard_body }}</h3>
{% endfor %}
<div class="pagination">
<span class="step-links">
{% if known.has_previous %}
<a class="navlink" href="?page={{ known.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ known.number }} of {{ known.paginator.num_pages }}
</span>
{% if known.has_next %}
<a class="navlink" href="?page={{ known.next_page_number }}">next</a>
{% endif %}
</span>
</div>
{% endblock %}
urls.py
urlpatterns += patterns('',
url(r'^(?P<section_name>[\w|\W]+)/unknown/$', unknown_list, name="unknown_list"),
url(r'^(?P<section_name>[\w|\W]+)/known/', known_list, name="known_list"),
url(r'^semester/(?P<semester_name>[\w|\W]+)/', section_list, name="section_list"),
url(r'^section/(?P<section_name>[\w|\W]+)/', notecard_list, name="notecard_list"),
url(r'^notecard/(?P<notecard_name>[\w|\W]+)/', notecard_detail, name="notecard_detail"),
url(r'^$', semester_list, name="semester_list"),
)
That said, I would like to add a "Send to Unknown" button that will allow users to send the notecard whose page they are currently on to the unknown pile (Simply changing the known field to = 0, removing the notecard from the pagination list, and moving to the next page in the pagination).
I have tried replicating my new_notecard view which contains a full form of the model, but I was unable to figure out how to update a single field.
I have also tried using queryset.update() but was unable to figure out how to capture the pk from the specific notecard.
I've been trying to figure this out on my own for over a month, but I've been unsuccessful. Thank you in advance.
EDIT:
It seems like my hang up is pulling the pk of the notecard on each page of the pagination. For example, if I am on page 3 of the pagination - when the "Send to Unknown" button is pushed, how do I identify that notecard in my view and update it from known (1) to unknown (0)
you must create a specific view with a specific url to handle this, for example:
# urls.py
url(r'^movetounknown/(?P<notecard_id>[\w|\W]+)/', notecard_move_to_unknown)
# views.py
#require_POST
def notecard_move_to_unknown(request, notecard_id):
notecard = Notecard.objects.get(pk=notecard_id)
notecard.known = False
notecard.save()
return HttpResponseRedirect(request.POST['next'])
# template
{% for notecard in known.object_list %}
<h1 class='notecard'>{{ notecard.notecard_name }}</h1>
<h3 class='notecard'>{{ notecard.notecard_body }}</h3>
<form action="{% url views.move_to_unknown notecard.pk %}" method="post">
<input type="hidden" name="next" value="{% url known_list known.section.section_name %}?page={{known.paginator.number}}"/>
<input type="submit" value="Move to unknown list"/>
</form>
{% endfor %}
You also can pass the notecard id as a post parameter.
The next parameter tells where to go after the change, here I choose the same page of the known list because once the current card is removed the next one is at this index
Capturing the pk of a specific notecard object can be done by defining a specific url for that notecard. For example:-
# urls.py
url(r'^notecard/(?P<notecard_id>\d+)/$',
'notecard',
name='notecard'),
# corresponding views.py
def notecard(request, note_card_id):
notecard = get_object_or_404(Notecard, pk=note_card_id)
template = 'notecard/notecard.html'
template_vars = {'notecard': notecard}
render(request, template, template_vars)
# notecard/notecard.html
<h2>{{ notecard.notecard_name }}</h2>
<p>{{ notecard.notecard_body }}</p>
You can also define a form with the notecard id/pk being a hidden field for submission and updating into your database (and of course, you will need to update your view function correspondingly).
In essence, to update a specific notecard object, you will simply do in your view function (with form submission or, if you prefer, a pure ajax implementation in your listing page) like this
notecard.known = False
notecard.save()