I'm using Django endles-pagination to load the pages in infinite scroll. I also have some filters that filter the data according to the criteria (for eg, price slider filtering according to price). Now when the page loads, the filter right now filters only from the page loaded, though I want it to filter it from all the pages that have been or are to be loaded. Is there a way to do this (by making some ajax request or something)?
Any help on this would be great. Thanks a lot.
To filter the data you have to redefine get_queryset() method in the views requesting the filtered query.
For example I request the current language in template to filter the Blog posts based on the language:
class Blog(AjaxListView):
context_object_name = "posts"
template_name = 'cgapp/blog.html'
page_template = 'cgapp/post_list.html'
def get_queryset(self):
if self.request.LANGUAGE_CODE == 'en': #request value of the current language
return News.objects.filter(language='en') #return filtered object if the current language is English
else:
return News.objects.filter(language='uk')
To filter the queryset based on the users input, you may refer to POST method:
from app.forms import BlogFilterForm
class Blog(LoginRequiredMixin, AjaxListView):
context_object_name = "posts"
template_name = 'blog/blog.html'
page_template = 'blog/post_list.html'
success_url = '/blog'
def get_queryset(self): # define queryset
queryset = Post.objects.all() # default queryset
if self.request.method == 'POST': # check if the request method is POST
form = BlogFilterForm(self.request.POST) # define form
if form.is_valid():
name = form.cleaned_data['name'] # retrieve data from the form
if name:
queryset = queryset.filter(name=name) # filter queryset
else:
queryset = queryset
return queryset
def get_context_data(self, **kwargs):
context = super(Blog, self).get_context_data(**kwargs)
context['form'] = BlogFilterForm() # define context to render the form on GET method
return context
def post(self, request, *args, **kwargs): # define post method
return super(Blog, self).get(request, args, kwargs)
The endless pagination should work fine.
Related
View down below should show the tasks for the logged user but when I paginate the tasks I got
Cannot filter a query once a slice has been taken.
how should I filter the context to avoid the error for pagination?
class TaskList(LoginRequiredMixin, ListView):
model = Task
context_object_name = 'tasks'
template_name = 'TaskList.html'
paginate_by = 2
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['tasks'] = context['tasks'].filter(user=self.request.user)
return context
Use get_queryset() for filtering objects in ListView:
def get_queryset(self): # noqa: D102
queryset = super().get_queryset()
return queryset.filter(user=self.request.user)
The filtering in get_queryset() method should fix the issue - also remove get_context_data() method overload, it's not neccessary.
How can I get pagination for the current code? I can't change the DetailView to View or ListView, so in this class I get a filter of products.
class CategoryDetailView(DetailView):
model = Category
queryset = Category.objects.all()
context_object_name = 'category'
template_name = 'category_products.html'
...
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
...
products = Product.objects.filter(id__in=[pf_['product_id'] for pf_ in pf])
context['category_products'] = products
return context
Typically one picks the class-based view that is the most convenient to do the job. You can see your view as a ListView of Products that are related to a certain category. By doing this, you make it more convenient to retrieve the related Products, and less convenient to obtain the relevant Category, but the last one is easier.
We can implement the view as:
from django.shortcuts import get_object_or_404
class CategoryDetailView(ListView):
model = Product
context_object_name = 'category_products'
template_name = 'category_products.html'
paginate_by = 1
# …
def get_queryset(self):
qs = super().get_queryset().filter(
category__slug=self.kwargs['slug']
)
qd = self.request.GET.copy()
qd.pop(self.page_kwarg, None)
if 'search' in self.request.GET:
qs = qs.filter(title__icontains=self.request.GET['search'])
elif qd:
qs = qs.filter(
features__value__in=[v for _, vs in qd.lists() for v in vs]
)
if 'sort' in self.request.GET:
qs = qs.order_by(self.request.GET['sort'])
return qs
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['category'] = get_object_or_404(Category, pk=self.kwargs['pk'], slug=self.kwargs['slug'])
return context
The get_queryset here thus implements the advanced querying you implemented, and we add the category by overriding the get_queryset. This queryset will then automatically get paginated by the logic of the ListView.
I would however advise to simplify the logic, usually advanced filtering only makes it more error prone. Furthermore ordering by an arbitrary parameter introduces a security vulnerability, one could for example try to filter on some_foreignkey__some_secure_field to expose data. You thus might want to validate that the order is in a list of acceptable values.
I am trying to create a Django page where something can be updated and something can be viewed in a paginated table. The model looks like this:
class CostGroup(models.Model):
name = models.CharField(max_length=200)
description = models.CharField(max_length=200)
def get_absolute_url(self):
return reverse(
'costgroup_detail',
kwargs={
'costgroup_pk': self.pk,
}
)
class Cost(models.Model):
cost_group = models.ForeignKey(CostGroup)
amount = models.DecimalField(max_digits=50, decimal_places=2)
def get_absolute_url(self):
return reverse(
'cost_detail',
kwargs={
'cost_pk': self.pk,
}
)
So the edit form is for the name and description fields of the CostGroup model and the table should show a list of the 'amounts`
I previously had it working by just having an UpdateView for the form and the table included in the form template. Now though, as I want to include pagination on the table, I need to use two views on the same page. The page I have designed should look something like this in the end:
I am not worried about the styling at the moment my main focus at the moment is getting the form and the table on the same page. In its current state the only thing that I don't have is the pagination for the table:
The view currently looks like this:
class CostDetail(UpdateView):
model = models.Cost
pk_url_kwarg = 'cost_pk'
template_name = 'main/cost_detail.html'
form_class = forms.CostDetailEditForm
success_url = reverse_lazy('cost_list')
I have a feeling that leveraging the underlying mixins that the Django CBVs use is probably the way to go but I am not sure how to begin with this.
Any help would be much appreciated
Thanks for your time
(This clarification seemed to work better as a new answer)
It looks like you're dealing with both of the tables. The object level is using CostGroup, while the List view is showing the child records from Cost linked to a CostGroup. Assuming that is true, here's how I would proceed:
class CostDetail(ModelFormMixin, ListView):
model = CostGroup # Using the model of the record to be updated
form_class = YourFormName # If this isn't declared, get_form_class() will
# generate a model form
ordering = ['id']
paginate_by = 10
template_name = 'main/cost_detail.html' # Must be declared
def get_queryset(self):
# Set the queryset to use the Cost objects that match the selected CostGroup
self.queryset = Cost.objects.filter(cost_group = get_object())
# Use super to add the ordering needed for pagination
return super(CostDetail,self).get_queryset()
# We want to override get_object to avoid using the redefined get_queryset above
def get_object(self,queryset=None):
queryset = CostGroup.objects.all()
return super(CostDetail,self).get_object(queryset))
# Include the setting of self.object in get()
def get(self, request, *args, **kwargs):
# from BaseUpdateView
self.object = self.get_object()
return super(CostDetail,self).get(request, *args, **kwargs)
# Include the contexts from both
def get_context_data(self, **kwargs):
context = ModelFormMixin.get_context_data(**kwargs)
context = ListView.get_context_data(**context)
return context
# This is the post method found in the Update View
def post(self, request, *args, **kwargs):
# From BaseUpdateView
self.object = self.get_object()
# From ProcessFormView
form = self.get_form()
self.form = form
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)
I haven't tried to run this, so there may be errors. Good luck!
(Remember ccbv.co.uk is your friend when digging into Class-based Views)
An app I'm working on now uses a similar approach. I start with the ListView, bring in the FormMixin, and then bring in post() from the FormView.
class LinkListView(FormMixin, ListView):
model = Link
ordering = ['-created_on']
paginate_by = 10
template_name = 'links/link_list.html'
form_class = OtherUserInputForm
#=============================================================================#
#
# Handle form input
#
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form = self.get_form()
self.form = form
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)
def get_success_url(self):
return reverse('links')
You may also wish to override get_object(), get_queryset(), and get_context().
I have a django Listview and i am using django-el-pagination's ajax endless pagination to paginate my results and it works well. The problem is I need to apply some filters to it and have no clue on how to do it.
Can i send parameters through GET to the view? Have searched a lot and seems like no one had this problem before.
Data is always returned in the get_queryset() function.
If you want to simply filter from the database, you can just return objects.
class IndexView(AjaxListView):
template_name = '****'
context_object_name = '****'
page_template = '****'
def get_queryset(self):
return your_model.objects.filter(title='***').order_by('**')
Else if you want to get data from non-database, you need to implement a proxy accordding to this answer.
If not,the pagination ajax will request all data, then slice it.The proxy make your data sliced while querying.
This is my filter that getting data from ElasticSearch.
class IndexView(AjaxListView):
template_name = '****'
context_object_name = '****'
page_template = '****'
def get_queryset(self):
params = {}
# get query params
for item in self.request.GET.items():
if item[0] == 'page' or item[0] == 'querystring_key':
continue
params[item[0]] = item[1]
# no filter
if len(params) == 0:
return ****.objects.filter().order_by('-date')
else:
return ESResult(params)
class ESResult(object):
def __init__(self, params):
self.params = params
def __len__(self):
s = self.search_es()
if s:
s = s[:1]
r = s.execute()
return r['hits']['total']
else:
return 0
def __getitem__(self, item):
assert isinstance(item, slice)
result = []
s = self.search_es()
if s:
s = s[item.start:item.stop] # slice before querying
r = s.execute()
for a in r.to_dict()['hits']['hits']:
one = a['_source']
one['id'] = int(a['_id'])
result.append(one)
return result
def search_es():
...
# filter request here
...
The list object used by AjaxListView is defined by get_queryset() method. To filter the queryset based on the users input, you may refer to POST method:
from app.forms import BlogFilterForm
class Blog(LoginRequiredMixin, AjaxListView):
context_object_name = "posts"
template_name = 'blog/blog.html'
page_template = 'blog/post_list.html'
success_url = '/blog'
def get_queryset(self): # define queryset
queryset = Post.objects.all() # default queryset
if self.request.method == 'POST': # check if the request method is POST
form = BlogFilterForm(self.request.POST) # define form
if form.is_valid():
name = form.cleaned_data['name'] # retrieve data from the form
if name:
queryset = queryset.filter(name=name) # filter queryset
else:
queryset = queryset
return queryset
def get_context_data(self, **kwargs):
context = super(Blog, self).get_context_data(**kwargs)
context['form'] = BlogFilterForm() # define context to render the form on GET method
return context
def post(self, request, *args, **kwargs): # define post method
return super(Blog, self).get(request, args, kwargs)
The endless pagination should work fine.
using filters with django-endless-pagination
I have a page in my web app that can be accessed by the URL like this:
http://localhost:8000/organizations/list_student/?school_id=19
I'd like access to the school_id from the URL above for one of the form mixins, named PhoneNumberMixin (please see below). Could someone who's knowledgeable about Django tell me how I should pass that URL parameter into custom form mixin like PhoneNumberMixin? Thank you.
In models.py:
class Student(models.Model):
school = models.ForeignKey(School)
phone_number = models.CharField(max_length=15, blank=True)
In urls.py:
urlpatterns = patterns('',
# There are more, but to save space, only relevant part is included
url(r'^list_student/$', StudentListView.as_view(), name='list_student'),
)
In views.py for the page:
class StudentListView(LoginRequiredMixin, FormView):
form_class = SchoolAddStudentForm
template_name = 'organizations/list_student.html'
def get_success_url(self):
return reverse_lazy('organizations:list_student') + '?school_id=' + self.request.GET['school_id']
def get_form(self, form_class):
request = self.request
return form_class(request, **self.get_form_kwargs())
def get_context_data(self, **kwargs):
# add stuff to data to pass to HTML page here
return data
def form_valid(self, form):
data = form.cleaned_data
# save cleaned data to DB here
return HttpResponseRedirect(self.get_success_url())
In forms.py,
# Note PhoneNumberFormMixin below. It is used to clean phone numbers
# such duplicate checking against the existing numbers in the DB
class SchoolAddStudentForm(PhoneNumberFormMixin, forms.Form):
phone_numbers = forms.CharField(widget=forms.Textarea(attrs=form_attrs))
def __init__(self, request, *args, **kwargs):
super(SchoolAddStudentForm, self).__init__(*args, **kwargs)
self.fields['phone_numbers'].label = 'Step 1 (required): Add comma-separated list of phone numbers [E.g., 5856261234, 8613910912345]:'
In mixins.py:
class PhoneNumberFormMixin(object):
"""
Custom form mixin for validating phone numbers
"""
def clean_phone_numbers(self):
data = self.data
numbers = []
sid = #!!!! this is where I'd like to access school_id from the URL
qs = Student.objects.filter(school_id=sid)
# do something with the qs
return ','.join(numbers)
I'm not sure I have a full picture as you're missing views.py & urls.py. But generally, field cleaning methods should only check that an input is correctly formatted, and the actual application logic should be located in your view's form_valid() method. Form() methods don't have access to HTTP request information precisely because it is outside the scope of their functionality.
From your view, you can access the URL parameter with:
self.request.GET.get('school_id', None)
Read up on form_valid() -- this is where you should add code to modify an object + field values before it's saved, and / or create related objects if needed.
In my usecase I'm doing a search which displays a list of search results.
I ended up using this:
class SearchView(FormMixin, ListView):
def get_queryset(self):
qs = super().get_queryset()
# TODO implement filtering
return qs
def get_form_kwargs(self):
# use GET parameters as the data
kwargs = super().get_form_kwargs()
if self.request.method in ('GET'):
kwargs.update({
'data': self.request.GET,
})
return kwargs