How to use GET and queryset in classed based views? - django

i have two models with random farmers and animals (names / weight ) and i want to use the GET method, in my template, to apply a filter in my view on my models. For instance: show me all data with the name "xyz"
this was my approach:
models.py
class Farmer(models.Model):
first_name = models.CharField(max_length=40)
last_name = models.CharField(max_length=40)
city = models.CharField(max_length=40)
<...>
class Animal(models.Model):
name = models.CharField(max_length=40)
weight = models.DecimalField(max_digits=5, decimal_places=2)
species = models.ForeignKey('Species', on_delete=models.CASCADE)
farmer = models.ForeignKey('Farmer', related_name='farmername', on_delete=models.CASCADE)
<...>
so this is my template. when i submit a value it will show up in my browser adress
template.html
<...>
<form action="" method="GET">
name: <input type="text" name="q"><br>
<input type="submit" value="Search">
</form>
<...>
this was my approach to fit in the GET method and Q into my view
views.py
from django.views.generic import TemplateView
from django.views.generic import View
from django.shortcuts import render
from django.db.models import Q
<...>
class HomeView(TemplateView):
template_name = 'farmapi/search.html'
http_method_names = ['get']
def get(self, request):
query = request.GET.get("q")
def get_context_data(self, **kwargs):
context = super(HomeView, self).get_context_data(**kwargs)
context['farmers'] = Farmer.objects.filter(Q(first_name__icontains=query)|Q(last_name__icontains=query))
context['animals'] = Animal.objects.filter(Q(name__icontains=query))
return context
This is the Error Message i get
ValueError at /data/search/
The view farmapi.views.HomeView didn't return an HttpResponse object.
It returned None instead.
so as far as i understand i should use a if statement?
class HomeView(TemplateView):
template_name = 'farmapi/search.html'
http_method_names = ['get']
def get(self, request):
query = request.GET.get("q")
def get_context_data(self, **kwargs):
context = super(HomeView, self).get_context_data(**kwargs)
if query is not None and query != '':
context['farmers'] = Farmer.objects.filter(Q(first_name__icontains=query)|Q(last_name__icontains=query))
context['animals'] = Animal.objects.filter(Q(name__icontains=query))
return context
if query is None and query != '':
context['farmers'] = Farmer.objects.all()
context['animals'] = Animal.objects.all()
return context
Unfortunately this does not work, i received the same error message. i would appreciate it, if someone has a hint or maybe knows some nice django tutorials with the topic "query, request via templates and filters" - i searched for hours, but most stuff was query and python shell related.
Update
Solution
So instead of using classed based view i use now a function and it worked.
def search(request):
query = request.GET.get("q")
if query is not None and query != '': # <----
farmers = Farmer.objects.filter(Q(first_name__icontains=query)|Q(last_name__icontains=query))
animals = Animal.objects.filter(Q(name__icontains=query))
context = {'animals': animals,
'farmers': farmers}
return render(request, 'farmapi/search.html', context)
else: # <----
farmers = Farmer.objects.order_by("-id")[0:2]
animals = Animal.objects.order_by("-id")[0:2]
context = {'animals': animals,
'farmers': farmers}
return render(request, 'farmapi/search.html', context)
however i wished there was a class based way.

I would try the following adjustment
def get_context_data(self, **kwargs):
context = super(HomeView, self).get_context_data(**kwargs)
if query is not None and query != '': # <----
context['farmers'] = Farmer.objects.filter(Q(first_name__icontains=query)|Q(last_name__icontains=query))
context['animals'] = Animal.objects.filter(Q(name__icontains=query))
return context
else: # <----
context['farmers'] = Farmer.objects.all()
context['animals'] = Animal.objects.all()
return context
Your conditions for the if and else statements are a bit confusing, so I suspect that for some inputs neither condition triggered and None was therefore returned.

So instead of using classed based view i use now a function and it worked.
def search(request):
query = request.GET.get("q")
if query is not None and query != '': # <----
farmers = Farmer.objects.filter(Q(first_name__icontains=query)|Q(last_name__icontains=query))
animals = Animal.objects.filter(Q(name__icontains=query))
context = {'animals': animals,
'farmers': farmers}
return render(request, 'farmapi/search.html', context)
else: # <----
farmers = Farmer.objects.order_by("-id")[0:2]
animals = Animal.objects.order_by("-id")[0:2]
context = {'animals': animals,
'farmers': farmers}
return render(request, 'farmapi/search.html', context)
however i wished there was a class based way.
p.s i posted this so i can later close this Thread or is there a way to close my own question if i answered it?

Related

My view object 'has no attribute' {my attribute} in Django

Background
I'm trying to load a custom url (e.g. www.mysite.com/order-2523432) that will show a user details about their order.
Problem
I am trying to use the method order_id in my models.py in order to get the correct url. The problem is that I am getting the error:
'OrderDetailView' object has no attribute 'order_id'
Does anyone know what I can do to get order_id to work?
My views.py:
class OrderDetailView(DetailView):
model = Orders
template_name = "customer/orders.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
try:
context["orders"] = get_orders(self)
except RequestException as e:
logger.exception(e)
return context
My utils.py:
def get_orders(orders):
url = f"mysite.com/customer/{orders.order_id}"
method = "GET"
content_type = "application/json"
header = Sender(
credentials etc
).request_header
response = requests.request(
headers etc
)
response.raise_for_status()
return response.json()
My models.py:
class Orders(CustomModel):
table_name = models.CharField(max_length=256, unique=True)
#property
def order_id(self):
return f"order-{self.table_name}"
def get_absolute_url(self):
return reverse("order:edit", args=(self.id,))
you should use self.object or context['object'] or get_object() instead of passing self
please try this:
class OrderDetailView(DetailView):
model = Orders
template_name = "customer/orders.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
try:
context["orders"] = get_orders(context['object'])
except RequestException as e:
logger.exception(e)
return context

Generic DeleteView is returning django.db.models.query_utils.DeferredAttribute object at 0x04725628 - Django

Disclaimer: I'm just a novice trying to learn Django
Hello, I'm trying to refactor my code and modify all the views that I have created to be Class Based Views.
I have an issue loading a form with DeleteView that is showing the data and at the same time is disabled.
I have some success and the only thing that I cannot figure out how to do is to show the data instead of the message that appears now "<django.db.models.query_utils.DeferredAttribute object at 0x04725628>"
+models.py:
class Note(models.Model):
title = models.CharField(max_length=30)
image_url = models.URLField()
content = models.TextField()
owner = models.ForeignKey(Profile, default=8, on_delete=models.CASCADE)
def get_absolute_url(self):
return reverse(self.pk)
def __str__(self):
return f'{self.title}'
+forms.py
class NoteForm(forms.ModelForm):
class Meta:
model = Note
exclude = ('owner',)
class DeleteNoteForm(NoteForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for (_, field) in self.fields.items():
field.widget.attrs['readonly'] = True
field.widget.attrs['disabled'] = True
+views.py
class DeleteNoteView(DeleteView):
model = Note
template_name = 'note-delete.html'
form_class = DeleteNoteForm
success_url = reverse_lazy('home page')
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
data['form'] = self.form_class(instance=self.model)
return data
+urls.py
path('delete/<int:pk>/', views.DeleteNoteView.as_view(), name='delete note'),
+template
<!--note delete data form-->
<div class="form">
<form method="POST">
{{ form }}
{% csrf_token %}
<input type="submit" value="Delete"/>
</form>
</div>
<!--end note delete data form-->
If I use my view it works fine, but I want to modify it.
def delete_note(request, pk):
note = Note.objects.get(pk=pk)
if request.method=='GET':
note_form = DeleteNoteForm(instance=note)
context = {
'note_form': note_form
}
return render(request, 'note-delete.html', context)
else:
note.delete()
return redirect('home page')
Could someone tell me where I'm wrong and how I can fix it or at least provide me a link with information to understand why this is happening?
You are passing a reference to the model class in your DeleteNoteView, whereas you should use the object that is removed, so:
class DeleteNoteView(DeleteView):
model = Note
template_name = 'note-delete.html'
form_class = DeleteNoteForm
success_url = reverse_lazy('home page')
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
# user self.object instead of self.model &downarrow;
data['form'] = self.form_class(instance=self.object)
return data
I would also advise to filter the QuerySet such that it is impossible for another user (a user that is not the owner of a Note to remove that Note:
from django.contrib.auth.mixins import LoginRequiredMixin
class DeleteNoteView(LoginRequiredMixin, DeleteView):
model = Note
template_name = 'note-delete.html'
form_class = DeleteNoteForm
success_url = reverse_lazy('home page')
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(
owner=self.request.user
)
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
data['form'] = self.form_class(instance=self.object)
return data

How to display parameter from URL to template in class based views in Django

View below prints list of all posts done by certain user. User was a parameter taken from the URL and is working fine.
How do I extract **kwarg -> 'username' and display it on the template as a variable?
Things commented out in the code have been tried.
views.py
class AuthorPostIndexView(ListView):
model = Person
template_name ='authorpostindex.html'
context_object_name = 'author_post'
#1)
#date=request.GET.get('username','') -> wrong as varibales in classes
#is not possible?
#-> this works fine:
def get_queryset(self):
username = self.kwargs['username']
queryset = Person.objects.get(username=username).post.all()
return queryset, (username,'')
#-> attempts to extract username kwarg:
#2)
# def get_context_data(self, **kwargs):
# context = super(AuthorPostIndexView, self).get_context_data(**kwargs)
# context['username'] = self.username
# return context
#3)
# #property
# def username(self):
# return self.kwargs['username']
expected result
template.html
<h1>{{username}}</h1> -> username from the URL should be displayed
Error messages:
1)'request' is not defined (shell)
2) 'AuthorPostIndexView' object has no attribute 'username' (template)
3) Reverse for 'post_detail' with arguments '('',)' not found. 1 pattern(s) tried: ['post\/(?P[0-9]+)$'] (template)
ps. Still not working after advices:
Solutions looks similar however I think I have problem with getting the kwarg argument out of the method. In addition I lost confidence on what to put in the template.
Can I return two arguments in the function like that?
class AuthorPostIndexView(ListView):
model = Post
template_name ='authorpostindex.html'
context_object_name = 'author_post'
def get_queryset(self):
queryset = super().get_queryset()
username = self.kwargs['username']
return (queryset.filter(authors__username=username),username)
Or in one of the suggested solution I have added method to the view
def get_context_data(self, **kwargs):
context = super(AuthorPostIndexView, self).get_context_data(**kwargs)
context['username'] = self.kwargs['username']
return context
...and then what about template
authorpostindex.html
{{context}}
or
{{username}}
or
{{context.username}}
That is not working
try this
def get_context_data(self, **kwargs):
context = super(AuthorPostIndexView, self).get_context_data(**kwargs)
context['username'] = self.kwargs['username']
# or
context['username'] = self.request.GET.get('username', None)
return context
hope it helps

How to use django-el-pagination with filtering?

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

"Select a valid choice. <choice> is not one of the available choices" error when submitting ManyToMany ModelForm

I want to limit the choices of a ManyToManyField to those matching a ForeignKey. The form displays properly, but upon saving results in an error Select a valid choice. <choice> is not one of the available choices.
Before I was trying to limit the queryset by passing a parameter in the view to the form, and then using that parameter to filter the queryset.
Models:
class VenueEventTimeslot(models.Model):
venue = models.ForeignKey(Venue)
name = models.CharField(max_length=255)
class VenueEvent(models.Model):
venue = models.ForeignKey(Venue)
event_timeslots = models.ManyToManyField(VenueEventTimeslot)
class VenueEventForm(ModelForm):
event_timeslots = ModelMultipleChoiceField(queryset=None, widget=CheckboxSelectMultiple())
def __init__(self, *args, **kwargs): # limit timeslots to those of the venue only
venue_obj = kwargs.pop('venue_obj',None)
super(VenueEventForm, self).__init__(*args,**kwargs)
self.fields['event_timeslots'].queryset=VenueEventTimeslot.objects.filter(venue=venue_obj)
class Meta:
model = VenueEvent
fields = ['event_timeslots']
Views:
#login_required
def calendar(request, pk):
venue = Venue.objects.get(pk = pk)
if request.method == "POST":
form = VenueEventForm(request.POST)
if form.is_valid():
# form stuff
else:
form = VenueEventForm(venue_obj = venue)
context = {'venue':venue, 'form':form}
return render(request, ... , context)
However, if I pass the queryset from the view, it works perfectly.
Models:
class VenueEventTimeslot(models.Model):
# same as above
class VenueEvent(models.Model):
# same as above
class VenueEventForm(ModelForm):
class Meta:
model = VenueEvent
fields = ['date','client_name','event_timeslots']
widgets = {
'date': SelectDateWidget(),
'event_timeslots': CheckboxSelectMultiple(),
}
Views:
#login_required
def calendar(request, pk):
venue = Venue.objects.get(pk = pk)
if request.method == "POST":
form = VenueEventForm(request.POST)
if form.is_valid():
# form stuff
else:
form = VenueEventForm()
form.fields['event_timeslots'].queryset=VenueEventTimeslot.objects.filter(venue=venue)
context = {'venue':venue, 'form':form}
return render(request, ..., context)
Would anyone be able to shed some light on this?
I just solved a problem similar to this yesterday which is right here, How To Exclude A Value In A ModelMultipleChoiceField?, but I think the issue with your init function is the way it is formatted. Instead of venue=venue_obj, you need to change it to pk=venue_obj because it appear you are getting the pk of venue in the view instead of the venue attribute of VenueEvent , and I reformatted your form a bit to make it look cleaner.
forms.py
class VenueEventForm(ModelForm):
def __init__(self, *args, **kwargs): # limit timeslots to those of the venue only
venue_obj = kwargs.pop('venue_obj')
super(VenueEventForm, self).__init__(*args,**kwargs)
self.fields['event_timeslots'] = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple(), queryset=VenueEventTimeslot.objects.filter(pk=venue_obj))
class Meta:
model = VenueEvent
fields = ['event_timeslots']
views.py
#login_required
def calendar(request, pk):
venue = Venue.objects.get(pk = pk)
if request.method == "POST":
form = VenueEventForm(request.POST, venue_obj=venue)
if form.is_valid():
# form stuff
else:
print VenueEventForm.errors
else:
form = VenueEventForm(venue_obj=venue)
context = {'venue':venue, 'form':form}
return render(request, ... , context)