class based templateview context is not rendering - django

I have a template view that is supposed to take in a user submitted search. I planning to use the get method to do a query in the get_context_data so that I can show some results on the HTML. Unfortunately, my get_context_data does not work while the get method and dispatch in my templateview works fine. The get_context_data does not run at all when the user submits the search.
class PollSearchView(TemplateView):
template_name = 'polls/polls_search.html'
def get(self, request, **kwargs):
self.request.session["search"] = request.GET.get("search")
return render(request, 'polls/polls_search.html')
def dispatch(self, *args, **kwargs):
dispatch = super(PollSearchView, self).dispatch(*args, **kwargs)
#exit if no search
if self.request.GET.get("search") == None:
pass
return redirect('/')
return dispatch
def get_context_data(self, **kwargs):
context = super(PollSearchView, self).get_context_data(**kwargs)
search = self.request.session.get("search")
context["test"] = search
return context
I have another class that is redirecting to the class above based a user input through a form.
class HomeView(TemplateView):
template_name = "home.html"
def get_context_data(self, *args, **kwargs):
context = super(HomeView, self).get_context_data(*args, **kwargs)
context["form"] = SearchForm()
return context
I think the form works completely fine, why the get_context_data does not take in any information baffles me, and I seek alternative ways to render the context based on my results from get. Any guidance on why this does not work and how to go about doing this will be great.
Thanks all

Instead of rendering the form using context["form"] = SearchForm(), include a formclass in the first templateview to render the form for the user.
class HomeView(TemplateView, FormView):
template_name = "home.html"
title = 'Your Dashboard'
form_class = SearchForm
on the second templateview, do a self.request.GET to collect the user input inside get_context_data method.
class PollSearchView(TemplateView):
template_name = 'polls/polls_search.html'
def get_context_data(self, **kwargs):
context = super(PollSearchView, self).get_context_data(**kwargs)
print self.request.GET
return context
This will enable the get_context_data to get the user input.

Related

How to Validate and Compare Data against the Database in DetailView - Django

I have a ChallengeDetailView which shows the details of a challenge like a blog post. I also have a form field on the same page and it supposed to get an answer of a challenge, and compare it to the database. So far here is what I did;
class ChallengeDetailView(DetailView):
model = Challenge
def get_context_data(self, **kwargs):
context = super(ChallengeDetailView, self).get_context_data(**kwargs)
context['form'] = FlagForm
return context
from django import forms
class FlagForm(forms.Form):
flag = forms.CharField(label='Challenge Flag',required=True)
class FlagFormView(FormView):
form_class = FlagForm
success_url = reverse_lazy('challenge-detail')
I try to implement a simple logic like the following;
def get_flag(self, request):
if self.request.method == 'POST':
form = FlagForm(request.POST)
if form.is_valid():
flag = form.cleaned_data.get('flag')
if flag == Challenge.flag:
return messages.success("You found the flag!")
else:
return FlagForm()
I tried to include this in form_valid() method but couldn't make it work in the ChallengeDetailView. I'm open to any kind of suggestions. I'm coming from a Symfony background and pretty new to Django.
Finally I was able to figure it out.
I created a regular form in forms.py
from django import forms
class FlagForm(forms.Form):
flag = forms.CharField()
In views.py I have a detail view and form view with SingleObjectMixin to work on the current post.
class ChallengeDisplay(DetailView):
model = Challenge
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = FlagForm()
return context
Form view with SingleObjectMixin;
class ChallengeFormEntry(SingleObjectMixin, FormView):
template_name = 'ctf/challenge_detail.html'
form_class = FlagForm
model = Challenge
def post(self, request, *args, **kwargs):
if request.method == 'POST':
form = FlagForm(request.POST)
if form.is_valid():
flag = form.cleaned_data.get('flag')
if self.get_object().flag == flag:
messages.success(request, 'good job!')
else: messages.warning(request, 'Try again!')
form = FlagForm()
return super().post(request, *args, **kwargs)
def get_success_url(self):
return reverse('challenge-detail', kwargs={'pk': self.get_object().pk, 'slug':self.get_object().slug})

How to redirect in a Generic View

I am using a generic view to render my blog post item:
class PostUpdateView(UpdateView, LoginRequiredMixin):
model = Post
# etc
I have a model method on the Post model that results in a boolean True or False:
#property
def can_edit(self):
return self.displays_set.count() == 0
If can_edit is False for the Post object, how can I refactor the view to redirect from my UpdateView to a different DetailView?
Override the dispatch method, and check obj.can_edit there. That way the object will be checked for get and post requests.
class PostUpdateView(LoginRequiredMixin, UpdateView):
model = Post
def dispatch(self, *args, **kwargs):
obj = self.get_object()
if not obj.can_edit:
return redirect('/readonly-view/')
return super().dispatch(*args, **kwargs)
With this solution, get_object() is called twice so there is a duplicate SQL query. However this is probably worth it to keep the code simple.
I would say that override dispatch method is best solution,
but if you want to avoid extra database hit, then you need to override get and post methods
def get(self, request, *args, **kwargs):
self.object = self.get_object()
if not obj.can_edit:
return redirect('/readonly-view/')
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs):
self.object = self.get_object()
if not obj.can_edit:
return redirect('/readonly-view/')
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)

Passing additional context into CBV

I'm having trouble passing some additional context into a CBV. When I pass 'userprofile' as context, it prevents any other context from successfully being passed into the view.
My view started as this:
class OrderDetail(LoginRequiredMixin, DetailView):
model = Order
def dispatch(self, request, *args, **kwargs):
try:
user_checkout = UserCheckout.objects.get(user=self.request.user)
except:
user_checkout = None
if user_checkout:
obj = self.get_object()
if obj.user == user_checkout and user_checkout is not None: #checks to see if the user on the order instance ties to the user of the current request
return super(OrderDetail, self).dispatch(request, *args, **kwargs)
else:
raise Http404
else:
raise Http404
I then tried adding this
def get_context_data(self, *args, **kwargs):
context = super(OrderDetail, self).get_context_data(*args, **kwargs)
userprofile = UserProfile.objects.get(user=self.request.user)
context["userprofile"] = userprofile
I don't get any errors. It's just that when the page loads, none of the values that should appear (based on context) show up.
Thanks!
I think you need to add return context in your get_context_data method:
def get_context_data(self, *args, **kwargs):
context = super(OrderDetail, self).get_context_data(*args, **kwargs)
userprofile = UserProfile.objects.get(user=self.request.user)
context["userprofile"] = userprofile
return context
Also, as your userprofile has a relation(FK or OneToOne) with User model, you can simply access them template(without passing it in context) like this:
// If OneToOne
{{ user.userprofile }}
// If FK
{{ user.userprofile_set.first }} // using reverse relationship to fetch userprofiles
For more details, please check documentations on OneToOne, FK, Reverse Relationship.

Get URL pk in django forms.py

I have this URL
path('private/productores/<pk>', views.Productor_Private.as_view()),
Views.py
class Productor_Private(generic.DetailView):
model = Productor
template_name = 'firstpage/productor_private.html'
def get(self, request, pk):
form = RepartoForm()
return render(request, self.template_name, {'form': form})
def post(self, request, pk):
form = RepartoForm(request.POST)
if form.is_valid():
return render(request, self.template_name, args)
I want to retrieve the pk from the URL to use it as a filter inside the forms.py, to do something like this:
class RepartoForm(forms.Form):
productos = forms.ModelMultipleChoiceField(queryset=Producto.objects.filter(productor=pk))
So in other words, I need to check what the current user's "productor" id is in order to only retrieve the "productos" that belong to this "productor"
You will need to "patch" the form constructor, and manually set the queryset in the __init__ function:
class RepartoForm(forms.Form):
productos = forms.ModelMultipleChoiceField(queryset=Producto.objects.all())
def __init__(self, *args, productor_pk=None, **kwargs):
super(forms.Form, self).__init__(*args, **kwargs)
if productor_pk is not None:
self.fields['productos'].queryset = Producto.objects.filter(
productor=productor_pk
)
Or for older versions of Python that does not implement more advanced parameter unpacking, one can implement it like:
class RepartoForm(forms.Form):
productos = forms.ModelMultipleChoiceField(queryset=Producto.objects.all())
def __init__(self, *args, **kwargs):
productor_pk = kwargs.pop('productor_pk', None)
super(forms.Form, self).__init__(*args, **kwargs)
if productor_pk is not None:
self.fields['productos'].queryset = Producto.objects.filter(
productor=productor_pk
)
In case no product_pk is given, the queryset are all the Productos (in case you do not want that, you can alter the form, and for example by default use an empty QuerySet like Producto.objects.none()).
Then in the view, you can construct the form with a named productor_pk parameter:
class Productor_Private(generic.DetailView):
model = Productor
template_name = 'firstpage/productor_private.html'
def get(self, request, pk):
form = RepartoForm(productor_pk=pk)
return render(request, self.template_name, {'form': form})
def post(self, request, pk):
form = RepartoForm(request.POST, productor_pk=pk)
if form.is_valid():
return render(request, self.template_name, args)
Note: you also need to cover the case where the form is invalid: right now post will return None for that, but you should return a HTTP response for all codepaths.

Django last page pagination redirect

My problem is similar to this problem. The only difference is I use GCBV for my pagination. My view file is as follows:
class ChatListView(ListView):
model = Chat
form_class = NewMessageForm
template_name = 'chat.html'
paginate_by = 5
queryset = model.objects.all() ###not needed
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
form.save()
return redirect('chat') <--- here
return render(request, self.template_name, {'form': form})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = NewMessageForm() # edited: self.form_class
return context
What I want the post method to redirect to the last page of the pagination. In the link, it was achieved by return HttpResponseRedirect('/forum/topic/%s/?page=%s' % (topic.slug, posts.num_pages)). But for my GCBV, I don't know how to get the .num_pages via an object. A little help please.
You could call get_context_data, which will paginate the queryset and include paginator in the context. You can then access the number of pages with paginator.num_pages.
from django.urls import reverse
class ChatListView(ListView):
...
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
form.save()
self.object_list = self.get_queryset() # get_context_data expects self.object_list to be set
context = self.get_context_data()
paginator = context['paginator']
num_pages = paginator.num_pages
return redirect(reverse('chat') + '?page=%s' % paginator.num_pages)