In a class based ListView, while rendering i want to dynamically change the no of items shown per page and the sorting order of those items.
I referred to this question and the answer by skoval00 works for me when i change the ordering and change the pages. But as soon as i change the no of items per page, my ordering is lost.
For ex: my generic view links to http://127.0.0.1:8000/marketplace/.
If i set the pagination to 10 items per page.
The url changes to http://127.0.0.1:8000/marketplace/?paginate_by=10
But now if i change my ordering too, i expect something like
http://127.0.0.1:8000/marketplace/?paginate_by=10&ordering=end_date
but i get
http://127.0.0.1:8000/marketplace/?ordering=end_date
And the pagination changes to the default value.
Same thing happens vice-versa. If i order first and then change pagination, then i lose the ordering once the pagination changes.
How do i preserve the first argument while adding the second one?
Here is the relevant class in views.py
class CatalogueListView(ListView):
model = Catalogue
context_object_name = 'catalogues'
template_name = 'marketplace/catalogue_list.html'
paginate_by = 5
ordering = ['end_date']
def get_queryset(self):
if self.request.GET.get('ordering'):
ordering = self.request.GET.get('ordering')
else:
ordering = 'end_date'
query_set = Catalogue.objects.filter(admin_approved=True, end_date__gt=timezone.now()).order_by(ordering)
return query_set
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.request.GET.get('paginate_by'):
context['paginate_by'] = self.request.GET.get('paginate_by')
else:
context['paginate_by'] = self.paginate_by
if self.request.GET.get('ordering'):
context['ordering'] = self.request.GET.get('ordering')
else:
context['ordering'] = self.ordering
return context
def get_paginate_by(self, queryset):
return self.request.GET.get('paginate_by', self.paginate_by)
ans
Related
First post and new to python and django.
I am trying to add context to a ListView so that I can display a list of top 5 most commented posts and also a list of top 5 most active users (users with most posts). I have got the first list working however can't work out the second list. Using the values method (i think it's a method) i've managed to query the database and get back a dictionary of user_id and posts count. My problem is that I want to display the username rather than the user_id. For some reason this query results in a dictionary rather than the full object. I've posted the code from views.py below.
class PostListView(ListView):
queryset = Article.objects.filter(published=True)
template_name = 'articles/index.html' #address of non default template
context_object_name = 'articles' #variable name passed to template
ordering = ['-pub_date']
#get query results for popular posts and most active users side bar
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['most_commented'] = Article.objects.annotate(comment_count=Count('comments')).order_by('-comment_count')[:5]
context['most_active'] = Article.objects.all().values('author').annotate(active=Count('author')).order_by('-active')[:5]
return context
Thank you for your help!
Nick
You're correct, values is a method that returns dicts. If you want the full object, there's no need to use values here:
context['most_active'] = Article.objects.all().annotate(active=Count('author')).order_by('-active')[:5]
You can then iterate through most_active in the template and access the username directly.
If you did want to use values, just pass the fields you want returned in the dict:
context['most_active'] = Article.objects.all().values('author__username').annotate(active=Count('author')).order_by('-active')[:5]
I think you are making it too complicated. You can annotate the User to obtain the most Users with the most published articles:
class PostListView(ListView):
queryset = Article.objects.filter(published=True)
template_name = 'articles/index.html' #address of non default template
context_object_name = 'articles' #variable name passed to template
ordering = ['-pub_date']
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['most_commented'] = Article.objects.annotate(
comment_count=Count('comments')
).order_by('-comment_count')[:5]
context['most_active'] = User.objects.annotate(
narticles=Count('article')
).order_by('-narticles')[:5]
return context
I have researched this issue for a couple of days and can't seem to find what I'm looking for exactly. I have searched ModelChoiceField as well as ChoiceField on StackOverflow as well as Google and there are many variations of my question but nothing exactly. In a nutshell, I am trying to use a Class Based FormView and then capture the user selection and pass it to a Class Based ListView. Here is my code.
Forms.Py
class BookByStatus(forms.Form):
dropdown = forms.ChoiceField(choices=[],required=False)
def __init__(self, user, *args, **kwargs):
super(BookByStatus, self).__init__(*args, **kwargs)
self.fields['dropdown'].widget.attrs['class'] = 'choices1'
self.fields['dropdown'].empty_label = ''
self.fields['dropdown'].choices =
Book.objects.values_list("author","author").distinct("Publisher")
The code above works fine, and shows me the output I'm looking for on my view. No issues there....Then I have my FormView...
class BookByStatusView(LoginRequiredMixin,FormView):
model = Book
form_class = BookByStatus
template_name = 'xyz123/publisher.html'
success_url = reverse_lazy('Book:book_by_list',kwargs=
{'dropdown':'dropdown'})
def get_form_kwargs(self):
kwargs = super(BookByStatusView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
self.request.POST['dropdown']
BookByStatusView = form.cleaned_data['dropdown']
return super(BookByStatusView, self).form_valid(form)
The code above works fine, but takes me to the ListView below which I can't seem to pass the dropdown value to....I've tried several different iterations of get_form_kwargs as well as changed my form to ModelChoiceField, but still can't seem to understand how to get a queryset based on the input from the user...
And finally the ListView...
class BookByStatusListView(LoginRequiredMixin,ListView):
model = Book
form_class = BookByStatus
context_object_name = 'book_list'
template_name = 'xyz123/book_by_status_list.html'
paginate_by = 15
def get_queryset(self, *kwargs):
form = self.form_class(self.request.GET)
dropdown = self.kwargs.get('dropdown', None)
if form.is_valid():
return Book.objects.filter(dropdown__icontains=form.
cleaned_data['dropdown'])
return Book.objects.all()
I'm trying to take the dropdown input from the FormView and then pass it to a list view using two separate views. I need to pass the value from the FormView to the ListView. I'm clear on how to get the data in the FormView in the ChoiceField, and how to display a ListView, but I can't seem to figure out how to pass the dropdown data from the FormView to the ListView. I can get the ListView to work, but only with the full queryset, not with a filtered one.
Here's the book model....
class Book(models.Model):
Author CHOICES = (
("New","New"),
("Old","Old"),
)
Author = models.CharField(choices=Author_CHOICES,max_length=10)
Here's the URL...
url(r'^book_by_list/(?
P<dropdown>\w+)/$',views.BookByStatusListView.as_view(),
name='book_by_list'),
Thanks in advance for any suggestions!
Updated Approach...Using request.session. My prior approach would not let me pass the value from the one view to the other, no matter how many get_context_data or get_form_kwargs combinations I tried. Based on the input I received, I began exploring the request.session approach and I've gotten much further. One last piece remains, getting the request.session value in my LISTVIEW so I can filter my querysets accordingly.
class BookByStatusView(LoginRequiredMixin,FormView):
model = Book
form_class = BookByStatus
template_name = 'xyz123/publisher.html'
success_url = reverse_lazy('Book:book_by_list')
def form_valid(self, form):
self.request.session['dropdown'] = form.cleaned_data['dropdown']
return super(BookByStatusView, self).form_valid(form)
def get_form_kwargs(self):
kwargs = super(BookByStatusView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
And in my html template, I leverage the request.session value as...
{{ request.session.dropdown }}
And I reverted the URL back to..
url(r'^book_by_list/$,views.BookByStatusListView.as_view(),
name='book_by_list'),
The last remaining piece is to figure out how to leverage the LISTVIEW with this approach.
My current Listview:
class BookByStatusListView(LoginRequiredMixin,ListView):
model = Book
form_class = BookByStatus
context_object_name = 'book_list'
template_name = 'xyz123/book_by_status_list.html'
paginate_by = 15
def get_queryset(self):
queryset = Book.objects.none()
dropdown = self.request.session.get('dropdown')
if dropdown == 'New':
queryset = Book.objects.all()
elif dropdown == 'Old':
queryset = Book.objects.none()
return queryset
I can't seem to figure out how to pass the dropdown value correctly to the ListView so the queryset is displayed properly. Based on my testing, I don't appear to be capturing dropdown properly in the get_queryset function. Any ideas?
I figured it out. I updated the get_queryset with the proper syntax. Thanks for all of the help to nudge me in the right direction. Last questions..is this the best way to pass a value from one view to another? Is there a better way to do this? Are there any concerns with this approach?
This doesn't work, because redirect creates a new request/response and data from previous are lost. If I understand what you want correctly, one of the options would be to save the drop-down value to session in BookByStatusView and then retrieve it in BookByStatusListView.
You save to session with:
request.session['dropdown_value'] = form.cleaned_data['dropdown']
and retrieve with:
dropdown_value = request.GET.get('dropdown_value')
Here is How to use session part of Django documentation.
EDIT: You can also pass the value as an url parameter like this:
author = 'michael cricthon'
title = 'kongo'
year = [1999, 2000, 2001]
type = ['electronic', 'print', 'hardcover', 'softcover']
params = '?author={}&title={}&&year={}&type={}'.format(
urllib.parse.quote_plus(author),
urllib.parse.quote_plus(title),
','.join(year),
','.join(type))
return HttpResponseRedirect(reverse('search') + params)
The link would look like this:
../search/?author=michael+crichton&title=kongo&year=1999,2000,2001&type=electronic,print,hardcover,softcover
You get parameters with
author = request.GET.get('author')
title = request.GET.get('title')
... etc.
I've filtering view of django_filters.FilterSet which is called right from urls.py
url(r'^$', FilterView.as_view(filterset_class=ProductFilter, template_name='products/products.html'), name='products'),
and it's has no pagination, but when i add paginate_by = 20 in
url(r'^$', FilterView.as_view(filterset_class=ProductFilter, template_name='products/products.html'), paginate_by = 20, name='products'),
it adds my custom pagination page, but it's not handling data restricted by filters. So i can apply a few filters and it reduces data to, say 40 rows, but clicking on a second page it loads my all data without any filter. Could I specify that I want to paginate data after filtering somehow?
At the end I decided to create separate view and add queryset directly to context object like:
class ProductView(ListView):
model = Product
template_name = 'products/products.html'
paginate_by = 5
def get_context_data(self, **kwargs):
context = super().get_context_data()
context['product_list'] = ProductFilter(self.request.GET, queryset=Product.objects.order_by('id')).qs
return context
I found this to be much simpler to achieve pagination with filters on a given view:
class ProductView(ListView):
model = Product
template_name = 'products/products.html'
paginate_by = 5
context_object_name = 'products'
def get_queryset(self):
return Product.objects.filter(my_field=my_criteria)
I'm using the generic ListView of Django (1.9.1). I customized the name of the queryset (I called it content_list) to be put in the context. But surprisingly when I look at the context content, I can see object_list along with content_list. If the list is very big this is not very optimised. How can I get rid of object_list?. Here is my view:
class Home(ListView): #TemplateView
context_object_name = 'content_list'
template_name = 'website/index.html'
paginate_by = CONTENT_PAGINATE_BY
def get_queryset(self):
cc_id = self.kwargs.get('cc_id')
if cc_id != None:
qs = Content.objects.filter(category=cc_id)
else:
qs = Content.objects.all()
return qs.order_by('-created_on')
def get_context_data(self, **kwargs):
context = super(Home, self).get_context_data(**kwargs)
context['content_category_list'] = ContentCategory.objects.all()
print(context)
return context
I'm pretty sure they're both reference to the same list in memory.
From the docs:
Well, if you’re dealing with a model object, this is already done for you. When you are dealing with an object or queryset, Django is able to populate the context using the lower cased version of the model class’ name. This is provided in addition to the default object_list entry, but contains exactly the same data, i.e. publisher_list.
Aside from that, even if they weren't referencing the same data, you're forgetting that querysets are executed lazily so if you never use the other list then it is never executed.
This is by design. It's not another interaction to the database, but a second reference.
I'm running into a very strange issue where one form is initializing with the data from another form entirely. Here is the first view:
class UpdateProfileView(FormMixin, DetailView):
form_class = UpdateProfileForm
model = Profile
template_name = 'profile/update.html'
def get_context_data(self, **kwargs):
self.object = self.get_object()
context = super(UpdateProfileView, self).get_context_data(**kwargs)
...
self.initial['description'] = profile.about
context['form'] = self.get_form()
return context
...
This is the form that will return the correct data. As soon as it is loaded, however, the following form will return the initialized data from the previous one, even from different sessions, browsers, and locations:
class BountyUpdateForm(forms.ModelForm):
class Meta:
model = Bounty
fields = ("description", "banner")
class UpdateBountyView(UpdateView):
form_class = BountyUpdateForm
model = Bounty
template_name = 'bounty/update.html'
...
def get_context_data(self, **kwargs):
context = super(UpdateBountyView, self).get_context_data(**kwargs)
description = context['form']['description']
value = description.value()
# Value equals what was initialized by the previous form.
I'm really curious why these two forms are interacting in this way. Both form fields are called 'description', but that doesn't explain why the initial data from one would be crossing over to the other. Restarting the server seems to temporarily get the second form to show the correct values, but as soon as the first one is loaded, the second follows suit.
Any help would be greatly appreciated!
After some more searching, I was able to determine that my second view was having self.initial set to the same values as the first form by the time dispatch was being run. I couldn't determine why, but found these related questions:
Same problem, but no accepted answer:
Django(trunk) and class based generic views: one form's initial data appearing in another one's
Different problem, but good answer:
Setting initial formfield value from context data in Django class based view
My workaround was overriding get_initial() on my first form, instead of setting self.initial['description'] directly.
class UpdateProfileView(FormMixin, DetailView):
form_class = UpdateProfileForm
model = Profile
template_name = 'profile/update.html'
def get_initial(self):
return {
'description': self.object.about
}
def get_context_data(self, **kwargs):
...
# Removed the following line #
# self.initial['description'] = profile.about
...
context['form'] = self.get_form()
return context
Hope this helps anyone else who runs into this same problem. I wish I knew more about Django class-based views to be able to understand why this happens to begin with. However, I was unable to determine where self.initial was being set, beyond the empty dict in FormMixin().