Django ListView - Form to filter and sort - django

My Goal
A site that list all my Updates (model) in a table
Dont display all models at once (pagination - maybe 10 per page)
Filter and sort the list
My thoughts
I can use ListView to get a set of all my Updates
Use paginate_by = 10
Use a form to set order_by or filter in my QuerySet
My Problem
I am not sure how to add an form to modify my QuerySet with filter and sortings. My Idea was to modify the Query in get_queryset with additional filter and order_by.
My View
class MyView(ListView):
model = Update
template_name = "updates/update.html"
paginate_by = 10
def get_queryset(self):
return Update.objects.filter(
~Q(state=Update.STATE_REJECTED),
~Q(state=Update.STATE_CANCELED),
~Q(state=Update.STATE_FINISHED),
).order_by(
'planned_release_date'
)
My Idea
Something like this. I know it's not working like this ... just to illustrate
class MyView(ListView):
model = Update
template_name = "updates/update.html"
paginate_by = 10
def post(self, request, *args, **kwargs):
new_context = Update.objects.filter(
request.POST.get("filter"),
).order_by(
request.POST.get("sorting"),
)
def get_queryset(self):
return Update.objects.filter(
~Q(state=Update.STATE_REJECTED),
~Q(state=Update.STATE_CANCELED),
~Q(state=Update.STATE_FINISHED),
).order_by(
'planned_release_date'
)

You don't need post. Pass the filter value and order_by in the url for example:
.../update/list/?filter=filter-val&orderby=order-val
and get the filter and orderby in the get_queryset like:
class MyView(ListView):
model = Update
template_name = "updates/update.html"
paginate_by = 10
def get_queryset(self):
filter_val = self.request.GET.get('filter', 'give-default-value')
order = self.request.GET.get('orderby', 'give-default-value')
new_context = Update.objects.filter(
state=filter_val,
).order_by(order)
return new_context
def get_context_data(self, **kwargs):
context = super(MyView, self).get_context_data(**kwargs)
context['filter'] = self.request.GET.get('filter', 'give-default-value')
context['orderby'] = self.request.GET.get('orderby', 'give-default-value')
return context
Make sure you give proper default value to filter and orderby
Example form (you can modify this to your need):
<form method="get" action="{% url 'update-list' %}">
<p>Filter: <input type="text" value={{filter}} name="filter"/></p>
<p>order_by: <input type="text" value={{orderby}} name="orderby"/></p>
<p><input type="submit" name="submit" value="submit"/></p>
</form>

I am wondering why nobody mentioned here this cool library: django-filter https://github.com/carltongibson/django-filter
you can define your logic for filtering very clean and get fast working forms etc.
demo here: https://stackoverflow.com/a/46492378/953553

I posted this elsewhere but I think this adds to the selected answer.
I think you would be better off doing this via get_context_data. Manually create your HTML form and use GET to retrieve this data. An example from something I wrote is below. When you submit the form, you can use the get data to pass back via the context data. This example isn't tailored to your request, but it should help other users.
def get_context_data(self, **kwargs):
context = super(Search, self).get_context_data(**kwargs)
filter_set = Gauges.objects.all()
if self.request.GET.get('gauge_id'):
gauge_id = self.request.GET.get('gauge_id')
filter_set = filter_set.filter(gauge_id=gauge_id)
if self.request.GET.get('type'):
type = self.request.GET.get('type')
filter_set = filter_set.filter(type=type)
if self.request.GET.get('location'):
location = self.request.GET.get('location')
filter_set = filter_set.filter(location=location)
if self.request.GET.get('calibrator'):
calibrator = self.request.GET.get('calibrator')
filter_set = filter_set.filter(calibrator=calibrator)
if self.request.GET.get('next_cal_date'):
next_cal_date = self.request.GET.get('next_cal_date')
filter_set = filter_set.filter(next_cal_date__lte=next_cal_date)
context['gauges'] = filter_set
context['title'] = "Gauges "
context['types'] = Gauge_Types.objects.all()
context['locations'] = Locations.objects.all()
context['calibrators'] = Calibrator.objects.all()
# And so on for more models
return context

This is how we do it, that way you get validation/type conversion as well:
class UnitList(PermissionRequiredMixin, ListView):
""" Class based view to show a list of all buildings for a specific user """
model = Unit
ordering = ['building', 'unit']
paginate_by = 100
# Access
permission_required = ['core.manager_perm']
raise_exception = True # If true, give access denied message rather than redirecting to login
def get_queryset(self):
try:
units = self.model.objects.filter(building__company=self.request.user.profile.company)
except Profile.DoesNotExist:
units = self.model.objects.none()
form = UnitSearchForm(self.request.GET)
if form.is_valid():
filters = {}
address = form.cleaned_data['address']
neighborhood = form.cleaned_data['neighborhood']
beds = form.cleaned_data['beds']
amenity = form.cleaned_data['amenity']
if address:
filters['building__street_index__istartswith'] = compute_street_address_index(address)
if neighborhood:
filters['building__neighborhood__icontains'] = neighborhood
if beds:
filters['beds'] = beds
if amenity:
filters['unit_amenities__name__iexact'] = amenity
units = units.filter(**filters)
return units.select_related('building').order_by(*self.ordering)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = UnitSearchForm(self.request.GET)
return context

Related

access to pk in a template form

I want to display all elements of a form including the pk and I have not found a satisfying method to do so:
class SomeForm(forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["number"] = forms.IntegerField(required = True)
self.fields["id"] = forms.IntegerField(disabled = True)
self.fields["data"] = forms.CharField(required = False)
class Meta:
model = SomeModel
fields = ["id", "number", "data"]
So now I just call the form in the view with:
class SomeView(TemplateView):
template_name = "app/sometemplate.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
obj = MODEL.objects.get(pk = context["pk"])
MODEL_data = {}
MODEL_data["number"] = obj.number
MODEL_data["id"] = obj.pk
MODEL_data["data"] = obj.data
context["form"] = SomeForm(initial = MODEL_data)
return context
and now in the templatetags I have a filter get_pk:
#register.filter(name='get_pk')
def get_pk(obj):
return obj.initial["id"]
and I get the pk in the template with {{ form|get_pk }} and I feel like this is not smart. I tried stuff like {{ form.instance.id }} like suggested here. I still feel there must be an easy way to achieve this?
I don't know why you need form in template view? You just need to get the context no more..
There are many comments in shared code like:
1-Disabled id field in initial form
2-Using different model name in form and get context data
3-Filtering context by using context["pk"] before assessing obj
Find the revised code,
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["obj"] = SomeModel.objects.get(pk = the desired value)
return context
Note: if you want user to insert pk then you can use input field inside form with get method and get its value inside get context data in this way:
context["pk"] = self.request.GET.get("name of input field ")
In template html you can display fields directly:
{{obj.id}},{{obj.name}}, {{obj.data}}

DoesNotExist - matching query does not exist

I have this django class-based view where i am trying to overwrite the get_queryset function because i want get to the inserted values from the frontend to search in my database after the subject with that name and then get the id. but when i call the view it gives me a "Subject matching query does not exist." because the subject_val is None. That makes sense because the user has not submitted the values jet.. so how do i get it to wait until a user have choosen "submit
class AttendanceList(LoginRequiredMixin, ListView):
model = AttendanceLog
template_name = "./attendancecode/showattendance.html"
def get_queryset(self):
class_val = self.request.GET.get('class')
subject_val = self.request.GET.get('subject')
sub = Subject.objects.get(name=subject_val).id
new_context = get_statstic(class_val, sub)
return new_context
def get_context_data(self, **kwargs):
context = super(AttendanceList, self).get_context_data(**kwargs)
context['class'] = self.request.GET.get('class')
context['subject'] = self.request.GET.get('subject')
return context
You can check if the values are not None, in case they are, you need to return another queryset of AttendanceLogs (for example AttendanceLog.objects.all() or AttendanceLog.objects.none()):
class AttendanceList(LoginRequiredMixin, ListView):
model = AttendanceLog
template_name = "./attendancecode/showattendance.html"
def get_queryset(self):
class_val = self.request.GET.get('class')
subject_val = self.request.GET.get('subject')
if class_val is not None and subject_val is not None:
sub = Subject.objects.get(name=subject_val).id
return get_statstic(class_val, sub)
# return another queryset:
return AttendanceLog.objects.none()
# …

How to return a query set in get_queryset() in Django

I'm currently trying to convert my FBV codes to CBV. get_context_data is working well by returning contexts that I put in. However, get_queryset() returns NOTHING for some reason. To double-check, I tried to print search_stores right before returning it and it printed the queryset that is supposed to be printed. However, when I printed it on Django template, by typing {{ search_stores }}, it shows nothing. Am I using get_queryset in a wrong way?
class SearchListView(ListView):
model = Store
template_name = 'boutique/search.html'
# paginate_by = 5
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['search_text'] = self.request.GET.get('search_text')
context['sorter'] = self.request.GET.get('sorter')
if not context['sorter']:
context['sorter'] = 'popularity'
return context
def get_queryset(self):
search_text = self.request.GET.get('search_text')
sorter = self.request.GET.get('sorter')
if search_text:
search_stores = Store.objects.filter(Q(businessName__icontains=search_text) | Q(mKey__icontains=search_text))
if sorter == 'businessName':
search_stores = search_stores.order_by(sorter)
else:
search_stores = search_stores.order_by(sorter).reverse()
else:
search_stores = ''
for store in search_stores:
store.mKey = store.mKey.split(' ')
print(search_stores)
return search_stores
Your queryset is accessible via the context_object_name.
By default it's object_list if you don't provide context_object_name
You can access the queryset in templates with object_list
If you want to change the name, change the context_object_name:
class SearchListView(ListView):
model = Store
template_name = 'boutique/search.html'
context_object_name = 'search_stores'
search_stores will be the variable accessible to loop through in templates

Django: Search form in Class Based ListView

I am trying to realize a Class Based ListView which displays a selection of a table set. If the site is requested the first time, the dataset should be displayed. I would prefer a POST submission, but GET is also fine.
That is a problem, which was easy to handle with function based views, however with class based views I have a hard time to get my head around.
My problem is that I get a various number of error, which are caused by my limited understanding of the classed based views. I have read various documentations and I understand views for direct query requests, but as soon as I would like to add a form to the query statement, I run into different error. For the code below, I receive an ValueError: Cannot use None as a query value.
What would be the best practise work flow for a class based ListView depending on form entries (otherwise selecting the whole database)?
This is my sample code:
models.py
class Profile(models.Model):
name = models.CharField(_('Name'), max_length=255)
def __unicode__(self):
return '%name' % {'name': self.name}
#staticmethod
def get_queryset(params):
date_created = params.get('date_created')
keyword = params.get('keyword')
qset = Q(pk__gt = 0)
if keyword:
qset &= Q(title__icontains = keyword)
if date_created:
qset &= Q(date_created__gte = date_created)
return qset
forms.py
class ProfileSearchForm(forms.Form):
name = forms.CharField(required=False)
views.py
class ProfileList(ListView):
model = Profile
form_class = ProfileSearchForm
context_object_name = 'profiles'
template_name = 'pages/profile/list_profiles.html'
profiles = []
def post(self, request, *args, **kwargs):
self.show_results = False
self.object_list = self.get_queryset()
form = form_class(self.request.POST or None)
if form.is_valid():
self.show_results = True
self.profiles = Profile.objects.filter(name__icontains=form.cleaned_data['name'])
else:
self.profiles = Profile.objects.all()
return self.render_to_response(self.get_context_data(object_list=self.object_list, form=form))
def get_context_data(self, **kwargs):
context = super(ProfileList, self).get_context_data(**kwargs)
if not self.profiles:
self.profiles = Profile.objects.all()
context.update({
'profiles': self.profiles
})
return context
Below I added the FBV which does the job. How can I translate this functionality into a CBV?
It seems to be so simple in function based views, but not in class based views.
def list_profiles(request):
form_class = ProfileSearchForm
model = Profile
template_name = 'pages/profile/list_profiles.html'
paginate_by = 10
form = form_class(request.POST or None)
if form.is_valid():
profile_list = model.objects.filter(name__icontains=form.cleaned_data['name'])
else:
profile_list = model.objects.all()
paginator = Paginator(profile_list, 10) # Show 10 contacts per page
page = request.GET.get('page')
try:
profiles = paginator.page(page)
except PageNotAnInteger:
profiles = paginator.page(1)
except EmptyPage:
profiles = paginator.page(paginator.num_pages)
return render_to_response(template_name,
{'form': form, 'profiles': suppliers,},
context_instance=RequestContext(request))
I think your goal is trying to filter queryset based on form submission, if so, by using GET :
class ProfileSearchView(ListView)
template_name = '/your/template.html'
model = Person
def get_queryset(self):
name = self.kwargs.get('name', '')
object_list = self.model.objects.all()
if name:
object_list = object_list.filter(name__icontains=name)
return object_list
Then all you need to do is write a get method to render template and context.
Maybe not the best approach. By using the code above, you no need define a Django form.
Here's how it works : Class based views separates its way to render template, to process form and so on. Like, get handles GET response, post handles POST response, get_queryset and get_object is self explanatory, and so on. The easy way to know what's method available, fire up a shell and type :
from django.views.generic import ListView if you want to know about ListView
and then type dir(ListView). There you can see all the method defined and go visit the source code to understand it. The get_queryset method used to get a queryset. Why not just define it like this, it works too :
class FooView(ListView):
template_name = 'foo.html'
queryset = Photo.objects.all() # or anything
We can do it like above, but we can't do dynamic filtering by using that approach. By using get_queryset we can do dynamic filtering, using any data/value/information we have, it means we also can use name parameter that is sent by GET, and it's available on kwargs, or in this case, on self.kwargs["some_key"] where some_key is any parameter you specified
Well, I think that leaving validation to form is nice idea. Maybe not worth it in this particular case, because it is very simple form - but for sure with more complicated one (and maybe yours will grow also), so I would do something like:
class ProfileList(ListView):
model = Profile
form_class = ProfileSearchForm
context_object_name = 'profiles'
template_name = 'pages/profile/list_profiles.html'
profiles = []
def get_queryset(self):
form = self.form_class(self.request.GET)
if form.is_valid():
return Profile.objects.filter(name__icontains=form.cleaned_data['name'])
return Profile.objects.all()
This is similar to #jasisz 's approach, but simpler.
class ProfileList(ListView):
template_name = 'your_template.html'
model = Profile
def get_queryset(self):
query = self.request.GET.get('q')
if query:
object_list = self.model.objects.filter(name__icontains=query)
else:
object_list = self.model.objects.none()
return object_list
Then all you have to do on the html template is:
<form method='GET'>
<input type='text' name='q' value='{{ request.GET.q }}'>
<input class="button" type='submit' value="Search Profile">
</form>
This has been explained nicely on the generic views topic here Dynamic filtering.
You can do filtering through GET, I don't think you can use POST method for this as ListView is not inherited from edit mixings.
What you can do is:
urls.py
urlpatterns = patterns('',
(r'^search/(\w+)/$', ProfileSearchListView.as_view()),
)
views.py
class ProfileSearchListView(ListView):
model = Profile
context_object_name = 'profiles'
template_name = 'pages/profile/list_profiles.html'
profiles = []
def get_queryset(self):
if len(self.args) > 0:
return Profile.objects.filter(name__icontains=self.args[0])
else:
return Profile.objects.filter()
I think that the error you are getting is because your form doesn't require the name field. So, although the form is valid, the cleaned_data for your name field is empty.
These could be the problematic lines:
if form.is_valid():
self.show_results = True
self.profiles = Profile.objects.filter(name__icontains=form.cleaned_data['name'])
If I were you, I would try changing the line:
self.profiles = Profile.objects.filter(name__icontains=form.cleaned_data['name'])
to this:
self.profiles = Profile.objects.none()
If you stop receiving errors (and your template receives an empty object_list), the problem you have is what I said before: name field not required.
Let us know if this doesn't work!
Search on all fields in model
class SearchListView(ItemsListView):
# Display a Model List page filtered by the search query.
def get_queryset(self):
fields = [m.name for m in super(SearchListView, self).model._meta.fields]
result = super(SearchListView, self).get_queryset()
query = self.request.GET.get('q')
if query:
result = result.filter(
reduce(lambda x, y: x | Q(**{"{}__icontains".format(y): query}), fields, Q())
)
return result
def get_queryset(self):
query_name = self.request.GET.get('query', '')
object_list = Product.objects.filter(
Q(title__icontains=query_name)
)
return object_list
<form action="" method="GET">
{% csrf_token %}
<input type="text" name="query" placeholder="Search keyword">
<i class="ti-search"></i>
</form>

formset_factory and updating a field to show only filtered items

On my scorecard entry form, i only want the user to select from the shortlisted players for that match. If there was one field, I am succesfully able to rewrite using.
form.fields['player'].queryset = PlayerShortlist.objects.filter(team=userteam, fixture=fixture_id)
but when i apply it on formset_factory, i am unable to get result.
my forms.py
class TossForm(forms.Form):
toss_won_by = forms.BooleanField()
bat_first = forms.BooleanField()
class InningsForm(forms.Form):
player = forms.ModelChoiceField(
PlayerShortlist.objects.all()
)
status = forms.ChoiceField(choices=OUT_CHOICES, initial='DNB')
score = forms.IntegerField(initial=0)
balls_faced = forms.IntegerField(initial=0)
my views.py
#login_required
def scorecard(request, team_id, fixture_id):
template = get_template('cricket/scorecard.html')
tossform = TossForm()
#inningform = InningsForm()
InningsForms = formset_factory(InningsForm, extra=11)
inningsforms = InningsForm()
inningsforms.fields['player'].queryset = PlayerShortlist.objects.filter(team=Team.objects.get(id=1), fixture=fixture_id)
page_vars = Context({
'loggedinuser': request.user,
'tossform': tossform,
'inningsforms': inningsforms,
})
crsfcontext = RequestContext(request, page_vars)
output = template.render(crsfcontext)
return HttpResponse(output)
it gives me errors.
'InningsFormFormSet' object has no attribute 'fields'
thanks
//yousuf
okey, i looked around, and it seems formfield_callback can be used for what i intend it for but when i use it lin my views.py like
def update_field(field, **kwargs):
if field.name == 'players':
field.queryset = PlayerShortlist.objects.filter(team=Team.objects.get(id=team_id), fixture=fixture_id)
InningsFormset = formset_factory(InningsForm, extra=11, formfield_callback)
it gives me
formset_factory() got an unexpected keyword argument 'formfield_callback'
Remember: a formset wraps around a list of forms. So this:
inningsforms.fields['player'].queryset = PlayerShortlist.objects.filter(team=Team.objects.get(id=1), fixture=fixture_id)
Should rather be:
qs = PlayerShortlist.objects.filter(team=Team.objects.get(id=1), fixture=fixture_id)
# force execution of the queryset once and for all
list(qs)
for form in inningsforms.forms:
form.fields['player'].queryset = qs
Also, formfield_callback is an argument of modelformset_factory (and modelform_factory), not of formset_factory. See how it is used:
def modelformset_factory(model, form=ModelForm, formfield_callback=None,
formset=BaseModelFormSet,
extra=1, can_delete=False, can_order=False,
max_num=None, fields=None, exclude=None):
"""
Returns a FormSet class for the given Django model class.
"""
form = modelform_factory(model, form=form, fields=fields, exclude=exclude,
formfield_callback=formfield_callback)
FormSet = formset_factory(form, formset, extra=extra, max_num=max_num,
can_order=can_order, can_delete=can_delete)
FormSet.model = model
return FormSet
See, formfield_callback is proxied to modelform_factory by modelformset_factory.