How to Validate and Compare Data against the Database in DetailView - Django - 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})

Related

Filtering dropdown queryset based on URL parameter

I am building a CRM where I want each client to have multiple plans, and each plan to have multiple notes. When a user creates a new note, I want them to be able to select a relevant plan from a dropdown of plans belonging to the client. From what I can find, I should be able to get the contact_id from the kwargs, but my errors show nothing in kwargs. I know there should be a way to do this, but I can't seem to find it.
Variable Value
__class__ <class 'lynx.forms.SipNoteForm'>
args ()
kwargs {}
self <SipNoteForm bound=False, valid=Unknown, fields=(sip_plan;note;note_date;fiscal_year;quarter;class_hours;instructor;clients)>
Views.py
#login_required
def add_sip_note(request, contact_id):
form = SipNoteForm()
if request.method == 'POST':
form = SipNoteForm(request.POST)
if form.is_valid():
form = form.save(commit=False)
form.contact_id = contact_id
form.user_id = request.user.id
form.save()
return HttpResponseRedirect(reverse('lynx:client', args=(contact_id,)))
return render(request, 'lynx/add_sip_note.html', {'form': form})
Forms.py
class SipNoteForm(forms.ModelForm):
class Meta:
model = SipNote
exclude = ('created', 'modified', 'user', 'contact')
def __init__(self, *args, **kwargs):
super(SipNoteForm, self).__init__(*args, **kwargs)
self.fields['sip_plan'].queryset = SipPlan.objects.filter(contact_id=kwargs.get("contact_id"))
Urls.py
path('add-sip-note/<int:contact_id>/', views.add_sip_note, name='add_sip_note'),
You are trying to get the kwargs in __init__(self, *args, **kwargs) as
def __init__(self, *args, **kwargs):
contact_id = kwargs.pop('contact_id')
super(SipNoteForm, self).__init__(*args, **kwargs)
self.fields['sip_plan'].queryset = SipPlan.objects.filter(contact_id=contact_id)
But you are not passing contact_id kwargs to the form while posting. you should pass kwargs to the form you are going to get in __init__(self, *args, **kwargs) such as
#login_required
def add_sip_note(request, contact_id):
form = SipNoteForm()
if request.method == 'POST':
form = SipNoteForm(request.POST, contact_id=contact_id)

How to get object at CreatView

There is a form that is rendered by url
url(r'kredit/(?P<credit_slug>[-\.\w\d]+)/$', CreditDetail.as_view(), name='credit_detail'),
urls
url(r'kredit/(?P<credit_slug>[-\.\w\d]+)/$', CreditDetail.as_view(), name='credit_detail'),
url(r'kredit_request/$', CreditOnlineRequestView.as_view(), name='credit_request'),
The form is processed in the CreditOnlineRequestView(CreateView) view.
It is necessary to pull out the credit_slug from CreditDetail view in it (here the form was drawn)
​
views
class CreditDetail(FormView):
form_class = CreditPaymentForm
template_name = 'credits/credit_detail.html'
​
def get_initial(self):
initial = super(CreditDetail, self).get_initial()
initial['request'] = self.request
return initial
​
def get(self, *args, **kwargs):
request_form = CreditOnlineRequestForm(self.request.GET or None, prefix="request")
​
​
class CreditOnlineRequestView(CreateView):
form_class = CreditOnlineRequestForm
model = CreditOnlineRequest
template_name = 'credits/credit_listing.html'
prefix = 'request'
​
def form_valid(self, form, **kwargs):
credit_request = form.save(commit=False)
credit_request.credit = credit #???
return super(CreditOnlineRequestView, self).form_valid(form)
​
def form_invalid(self, form):
errors = dict([(k, v[0]) for k, v in form.errors.items()])
return errors
forms
class CreditOnlineRequestForm(forms.ModelForm):
class Meta:
model = CreditOnlineRequest
exclude = ['credit'] #this field must be define
​
def __init__(self, *args, **kwargs):
super(CreditOnlineRequestForm, self).__init__(*args, **kwargs)
#???
What are the options? I think, either through the cache, or through pulling out the previous page to do, but this is somehow not very humane, as for me. The best option, as for me, is to transfer the credit instance to a hidden form field in the CreditDetail view, but I don’t know how to do it yet.
The problem is that internally the form_valid function is doing the following:
def form_valid(self, form):
"""If the form is valid, save the associated model."""
self.object = form.save()
return super().form_valid(form)
So it does not matter what you're doing in your override that the super will try to save the form directly. You can solve your problem by doing:
def form_valid(self, form, **kwargs):
credit_request = form.save(commit=False)
credit_request.credit = credit
credit_request.save()
return HttpResponseRedirect(self.get_success_url())
urls
url(r'kredit_request/(?P<credit_slug>[-\.\w\d]+)/$', CreditOnlineRequestView.as_view(), name='credit_request'),
html
<form action="{% url 'credit_request' credit.slug %}" method="post">
view
class CreditOnlineRequestView(CreateView):
form_class = CreditOnlineRequestForm
model = CreditOnlineRequest
slug_url_kwarg = 'credit_slug'
prefix = 'request'
def post(self, request, *args, **kwargs):
form = self.get_form()
credit = Credit.objects.get(slug=kwargs.get('credit_slug'))
cache.set('credit_for_request', credit)
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
credit_request = form.save(commit=False)
credit = cache.get('credit_for_request')
cache.clear()
credit_request.credit = credit
credit_request.save()
return HttpResponseRedirect(reverse('credit_detail', kwargs={'credit_slug': credit.slug}))

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)

class based templateview context is not rendering

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.

Post request with DJango DetailView give error 'MyView' object has no attribute 'object'

I am trying to create an object with django DetailView. My code is like that.
class Detail(DetailView):
model = MyModel
template_name = 'mymodel_detail.html'
def get_context_data(self, **kwargs):
context = super(Detail, self).get_context_data(**kwargs)
context['form'] = DetailForm
return context
def post(self, request, *args, **kwargs):
form = DetailForm(request.POST, request.FILES)
if form.is_valid():
context['reply_form'] = DetailForm
self.object = super(Detail, self).get_object()
context['object'] = super(Detail, self).get_object()
return self.render_to_response(request=request, template=self.get_template_names(), context=context)
else:
context = context = super(Detail, self).get_context_data(**kwargs)
context['reply_form'] = form
self.object = super(Detail, self).get_object()
context['object'] = super(Detail, self).get_object()
return self.render_to_response(request=request, template=self.get_template_names(), context=context)
But here I am getting error that
'Detail' object has no attribute 'object'
I tried to assign object in context instance and with self as well. But nothing works.
What you are missing here is that you have to assign the object to the class or self before calling the get_context_data().
class Detail(DetailView):
model = MyModel
template_name = 'mymodel_detail.html'
def get_context_data(self, **kwargs):
context = super(Detail, self).get_context_data(**kwargs)
context['form'] = DetailForm
return context
def post(self, request, *args, **kwargs):
form = DetailForm(request.POST, request.FILES)
if form.is_valid():
# Write Your Logic here
self.object = self.get_object()
context = super(Detail, self).get_context_data(**kwargs)
context['form'] = DetailForm
return self.render_to_response(context=context)
else:
self.object = self.get_object()
context = super(Detail, self).get_context_data(**kwargs)
context['form'] = form
return self.render_to_response( context=context)
and in render_to_response() Just pass context. No other arguments.
Hope it will work for you.
This is how I implemented the code from Safrazs answer to make a reply option on my question model. I know this is an old question, but I hope that someone will find this useful.
class QuestionDetailView(generic.DetailView):
model = Question
template_name = 'forum/question.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = ReplyForm
return context
def post(self, request, *args, **kwargs):
form = ReplyForm(request.POST)
if form.is_valid():
reply = form.save(commit=False)
reply.creator = request.user
reply.question = self.get_object()
reply.save()
self.object = self.get_object()
context = context = super().get_context_data(**kwargs)
context['form'] = ReplyForm
return self.render_to_response(context=context)
else:
self.object = self.get_object()
context = super().get_context_data(**kwargs)
context['form'] = form
return self.render_to_response(context=context)
You are inheriting from the wrong generic view. You need to inherit from CreateView, like this:
class CreateModel(CreateView):
model = MyModel
template_name = 'mymodel_detail.html'
form_class = DetailForm
success_url = reverse('/thanks')
def form_valid(self, form):
# this method is called when the form
# is successfully validated
# if you need to do something with
# the database object, this is the place
# do not use it to redirect to a success page
# use the success_url for that
return super(CreateModel, self).form_valid(form)
You are calling super on the wrong class: they should be Detail, not MessageDetail. Also, you don't need the form code. Instead, use one of the generic editting views (CreateView, DeleteView, FormView, UpdateView). The DetailView is only for display purposes really. More detail on the generic views can be found at http://ccbv.co.uk/