Django DetailView get one record from QuerySet - django

Please help me get one record from QuerySet object.
views.py:
from django.contrib.postgres.aggregates import ArrayAgg
from django.http import JsonResponse,
from django.views.generic.list import ListView
from django.views.generic.detail import DetailView
from movies.models import Book
class BooksMixin(ListView):
model = Book
http_method_names = ['get']
def get_queryset(self):
books = Book.objects.values().annotate(authors=ArrayAgg('authors__name',distinct=True)
return books
def render_to_response(self, context, **response_kwargs):
return JsonResponse(context)
class BooksDetail(BooksMixin, DetailView):
def get_context_data(self, *, object_list=None, **kwargs):
queryset = self.get_queryset()
context = queryset.get(id=self.kwargs['pk'])
return context
urls.py:
from django.urls import path
from books import views
urlpatterns = [
path('books/<uuid:pk>/', views.BooksDetail.as_view()),
]
This works, but I want to get one record from the queryset inside the BooksDetail class more elegantly, without using the kwargs['pk'] filter. I tried to use construct like
class BooksDetail(BooksMixin, DetailView):
def get_context_data(self, *, object_list=None, **kwargs):
queryset = self.get_queryset()
context = super(BooksDetail, self).get_context_data(**kwargs)
context['bla'] = queryset
return context
but got the error:
'BooksDetail' object has no attribute 'object'

Related

Django CreateView: 'str' object is not callable

I'm trying to use django's CreateView, but I get the following error:
File "/Users/PycharmProjects/gusta/venv/lib/python3.8/site-packages/django/views/generic/edit.py", line 33, in get_form
return form_class(**self.get_form_kwargs())
TypeError: 'str' object is not callable
views.py:
from django.views.generic import ListView, CreateView, DeleteView, DetailView, UpdateView
from .models import Employee, Event, detailEvent, Department, Poste
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse, reverse_lazy
# ----------------------------------------------------- Views for postes.
class PostelistView(LoginRequiredMixin, ListView):
template_name = "postes/post_list.html"
context_object_name = "Poste"
def get_queryset(self):
# initial queryset of leads for the entire organisation
queryset = Poste.objects.all()
return queryset
class PosteCreateView(LoginRequiredMixin, CreateView):
template_name = "postes/post_create.html"
form_class = "Poste"
def get_success_url(self):
return reverse_lazy("employee:post_list")
def form_valid(self, form):
instance = form.save(commit=False)
instance.save()
self.Poste = instance
return super(PosteCreateView, self).form_valid(form)
my models.py
from django.utils.translation import ugettext_lazy as _
class Poste(models.Model):
intitule = models.TextField(_("Name"), max_length=250, default='')
Why is this happening?
In PosteCreateView, you set
form_class = "Poste"
This isn't a class, its a string. Django expects it to be a class and will later try to instantiate an object. You can see this in the error message, which gives you the line where the exception occurs:
return form_class(**self.get_form_kwargs())
To solve it, set form_class to the actual class:
form_class = Poste

The view awesomeinventory.supplier.views.supplierCreateView didn't return an HttpResponse object. It returned None instead in Create view

My code is:
views.py
class supplierListView(LoginRequiredMixin, ListView):
template_name = "supplier/Supplier_list.html"
def get_queryset(self):
organisation = self.request.user.userprofile.company
return Supplier.objects.filter(organisation=organisation)
class supplierCreateView(LoginRequiredMixin, CreateView):
template_name = "supplier/supplier_create.html"
form_class = SupplierModelForm
def get_success_url(self):
return reverse("supplier:supplier_list")
def form_valid(self, form):
supplier = form.save(commit=False)
supplier.organisation = self.request.user.userprofile.company
supplier.supplier_created_by = self.request.user
supplier.save()
my urls:
from awesomeinventory.supplier.views import (
supplierListView,
supplierDetailView,
supplierCreateView,
supplierContactListView,
supplierContactCreateView,
supplierContactDetailView,
)
app_name = "supplier"
urlpatterns = [
path("supplier_list/", view=supplierListView.as_view(), name="supplier_list"),
path("supplier_create/", view=supplierCreateView.as_view(), name="supplier_create"),
path("<int:pk>/detail/", view=supplierDetailView.as_view(), name="supplier_detail"),
path("<int:pk>/update/", view=supplierDetailView.as_view(), name="supplier_update"),
path("<int:pk>/delete/", view=supplierDetailView.as_view(), name="supplier_delete"),
path("supplierContact_list/", view=supplierContactListView.as_view(), name="supplierContact_list"),
path("<int:suppk>/supplierContact_create/", view=supplierContactCreateView.as_view(), name="supplierContact_create"), # int is supplier_id
path("<int:pk>/Contact/detail/", view=supplierContactDetailView.as_view(), name="supplierContact_detail"),
]
I m able to go to supplier:supplier_list page and it works well.
But when I want to create a supplier with supplierCreateView, supplier is create but it seems to have an issue with get_success_url as I have error
The view awesomeinventory.supplier.views.supplierCreateView didn't return an HttpResponse object. It returned None instead
The method form_valid is supposed to return the response or redirect the user. In your implementation you only save the object and return nothing essentially returning None which gives you an error. Instead of using form.save(commit=False) you can simply modify the instance wrapped by the form and leave all the processing to the super classes form_valid:
class supplierCreateView(LoginRequiredMixin, CreateView):
template_name = "supplier/supplier_create.html"
form_class = SupplierModelForm
def get_success_url(self):
return reverse("supplier:supplier_list")
def form_valid(self, form):
form.instance.organisation = self.request.user.userprofile.company
form.instance.supplier_created_by = self.request.user
return super().form_valid(form)
Note: A class name should ideally be in PascalCase so SupplierCreateView instead of supplierCreateView
This will work for you.
from django.urls import reverse_lazy
class supplierCreateView(LoginRequiredMixin, CreateView):
template_name = "supplier/supplier_create.html"
form_class = SupplierModelForm
def get_success_url(self):
# pay attention I'm using reverse_lazy instead of reverse
return reverse_lazy("supplier:supplier_list")
You can read more about reverse_lazy here.

Can I use pk from another model in RetrieveAPIView. Is this a bad practice?

I am working on an RetrieveAPIView api in which I want to pass the id of college and it will return all students who are currently studying there, but I am using Modelserializer of student model. code is in college app. I am in trouble is this a bad practice?
my urls.py:
from django.urls import path
from student.views import StudentView
path('studentdetails/<int:college_id>/', views.StudentView.as_view())
my serializer.py
from student.models import Student
class StudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = ('id', 'name', 'rollno')
my views.py
from rest_framework.generics import RetrieveAPIView
from rest_framework import serializers
from student.serializer import StudentSerializer
from student.models import Student
from .models import College
class StudentView(RetrieveAPIView):
queryset = Student.objects.all()
serializer_class = StudentSerializer
def get(self, request, *args, **kwargs):
try:
college_id=College.objects.get(id=self.kwargs.get('college_id'))
data=myfunction(id=college_id)
response_data=self.get_serializer(data, many=True)
return Response({"data": response_data.data})
except College.DoesNotExist:
raise serializers.ValidationError(_("College Does Not exists"))
RetrieveAPIView Used for read-only endpoints to represent a single model instance.
so try to user ListAPIView
Try this
Assuming that you have college foreign key in student model
from rest_framework import generics
class StudentView(generics.ListAPIView):
queryset = Student.objects.all()
serializer_class = StudentSerializer
def get(self, request, *args, **kwargs):
try:
college_id=College.objects.get(id=self.kwargs.get('college_id'))
students = self.queryset.filter(college=college_id)
response_data=self.get_serializer(students, many=True)
return Response(
{
"data": response_data.data
}
)
except College.DoesNotExist:
raise serializers.ValidationError(_("College Does Not exists"))
hope it helps
Use ListAPIView instead of RetrieveAPIView and use filtering. Change get_queryset method or add own filter.
https://www.django-rest-framework.org/api-guide/filtering/

when a database content deletes ,how can i redirect to same page using url argument passing method?

i want to delete a database content.bt after deletion utl goes to http://127.0.0.1:8004/login/delete_detail/6/ ..how can i redirect to success.html ie in the same page
class DeleteView(generic.TemplateView):
template_name = 'success.html'
success_url='/login/success'
def get_context_data(self, *args, **kwargs):
context = super(DeleteView,self).get_context_data(**kwargs)
did = self.kwargs['did']
q_obj = Quest.objects.filter(id=did)
q_obj.delete()
You should override get_success_url method. For example:
def get_success_url(self):
return reverse_lazy('delete-success')
Also, try to use named urls in your success_url
success_url = reverse_lazy('delete-success')
You can use get_success_url method:
from django.urls import reverse_lazy
class DeleteView(generic.TemplateView):
template_name = 'success.html'
success_url='/login/success'
def get_context_data(self, *args, **kwargs):
context = super(DeleteView,self).get_context_data(**kwargs)
did = self.kwargs['did']
q_obj = Quest.objects.filter(id=did)
q_obj.delete()
def get_success_url(self, **kwargs):
return reverse_lazy('delete_detail', kwargs = {'pk': self.kwargs['did']})
Also instead of TemplateView you can use DeleteView class:
class QuestDelete(DeleteView):
model = Quest
pk_url_kwarg = 'did'
def get_success_url(self, **kwargs):
return reverse_lazy('delete_detail', kwargs = {'pk': self.kwargs['did']})
To use url's name you need to add name argument to the url pattern inside urls.py file like this:
urlpatterns = [
path('delete_detail', views.delete_detail, name='delete_detail'),
]

django Initialising Choices in Form from Class Based View

I've seen a lot of answers about how this is solved when not using Class Based Views. Is there some really obvious thing I'm missing about doing it with CBVs?
Basically I want to have a MultipleChoiceField in my form which has choices determined by what is happening in the view. e.g. I use the PK from the URL to do some backend requests and then those should be used to populate the choices.
# forms.py
from django.forms import Form, MultipleChoiceField, CharField
class EmailForm(Form):
users = MultipleChoiceField(required=False)
subject = CharField(max_length=100)
message = CharField()
def __init__(self, users=None, *args, **kwargs):
super(EmailForm, self).__init__(*args, **kwargs)
if users:
self.fields['users'].choices = users
#urls.py
from django.conf.urls import url, patterns
from .views import EmailView
# url patterns
urlpatterns = patterns('',
url( r'^(?P<pk>\d+)$', EmailView.as_view(), name="maindex" ),
)
#views.py
from django.views.generic import FormView, TemplateView
from .forms import EmailForm
class EmailView(FormView):
template_name = 'myapp/email.html'
form_class = EmailForm
success_ulr = '/thanks/'
def form_valid(self, form):
# Do stuff here
return super(EmailView, self).form_valid(form)
Basically it boils down to how/where to call the init function from the view. How do I do that? Or is there another way I've missed? I thought of overriding get_form_kwargs in the view, but couldn't make that do anything.
Thanks
The view:
from django.views.generic import FormView
class EmailView(FormView):
# ...
def get_form_kwargs(self):
kwargs = super(EmailView, self).get_form_kwargs()
# get users, note: you can access request using: self.request
kwargs['users'] = users
return kwargs
The form:
from django import forms import Form
class EmailForm(Form):
users = MultipleChoiceField(required=False)
# ...
def __init__(self, *args, **kwargs):
self.users = kwargs.pop('users', None)
super(EmailForm, self).__init__(*args, **kwargs)
self.fields['users'].choices = self.users
Basically, what I've done in a similar case is the following (Python 3.5, Django 1.8):
def get_form(self, *args, **kwargs):
form= super().get_form(*args, **kwargs)
form.fields['rank'].choices= <sequence of 2-tuples>
return form
where obviously rank is the field name. This way I use the default form.
Alright, the FormMixin calls get_form to get the form-class which looks like
def get_form(self, form_class):
"""
Returns an instance of the form to be used in this view.
"""
return form_class(**self.get_form_kwargs())
So you can either override get_form to instance your Form yourself
def get_form(self, form_class):
return EmailForm(files=self.request.FILES or None,
data=self.request.POST or None,
users=some_user_queryset)
or stay a bit more generic and override get_form_kwargs to something like
def get_form_kwargs(self):
form_kws = super(EmailView, self).get_form_kwargs()
form_kws["users"] = some_user_queryset
return form_kws
One way to do it is:
class EmailView(FormView):
# ...
def get(self, request, *args, **kwargs):
self.users = ...
return super(EmailView, self).get(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(EmailView, self).get_form_kwargs()
kwargs['users'] = self.users
return kwargs
This allows you to set the user choices in the view and to pass them to the form.
You can override get_form.
I needed to update the choices for ChoiceField based on logged in user.
Form:
class InteractionCreateForm(forms.Form):
device = forms.ChoiceField(choices=[(None, '----------')])
...
View:
class InteractionCreateView(FormView):
form_class = InteractionCreateForm
...
def get_form(self, form_class=None):
form_class = super().get_form(form_class=None)
form_class.fields['device'].choices = \
form_class.fields['device'].choices \
+ [(device.pk, device) for device in Device.objects.filter(owner=self.request.user.id)]
return form_class