I have 2 models, one is a User models and other is a profile model. Now I want to make a single form the validates and saves the data during registration.
I have got both the forms in a single form tag and my view receives the data via request.POST, but how do I get the form validation to work?
Here is my view -
class IndexView(TemplateView):
template_name = 'index.html'
register_form = RegistrationForm(instance=User())
broker_profile_form = BrokerProfileForm(instance=BrokerProfile())
def get(self, request, *args, **kwargs):
user_type_form = UserTypeForm()
return render(request, self.template_name,
{
'login_form': self.register_form,
'broker_profile_form': self.broker_profile_form,
}
)
def post(self, request, *args, **kwargs):
print 'post data'
print request.POST
print self.register_form.is_valid()
for field in self.register_form:
print field.errors
First, read this answer to a similar question : https://stackoverflow.com/a/2374240/4789005 and use prefix
Second, when the view is hit with post you should get the form from the request. If not, you will never get the data from the request.
Class IndexView(TemplateView):
template_name = 'index.html'
def get(self, request, *args, **kwargs):
form1 = MyFormClass(prefix='some_prefix_1')
form2 = MyFormClass(prefix='some_prefix_2')
return render(request, self.template_name,
{
'form1': form1,
'form2': form2
}
)
def post(self, request, *args, **kwargs):
form1 = MyFormClass(request.POST, prefix='some_prefix_1')
for field in form1:
...
Related
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)
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.
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)
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.
I have following models:
class BankAccount(models.Model):
owner = models.ForeignKey(User)
class MoneyTransfer(models.Model):
sender = models.ForeignKey(BankAccount)
and url:
url(r'^accounts/(?P<pk>\w+)/send_transfer$', SendTransfer.as_view(), name='SendTransfer')
that means "I want to send money from Account with id=pk"
This is my view:
class SendTransfer(View):
form_class = SendTransferForm
template_name = 'dashboard/send_transfer.html'
def get(self, request, *args, **kwargs):
instance = BankAccount.objects.get(id=self.kwargs['pk'])
if instance.is_legal():
if instance.organization.owners.all().filter(user__id=self.request.user.id).count() == 0:
return None
else:
if instance.citizen.user.id != self.request.user.id:
return None
return render(self.request, self.template_name, self.get_context_data())
def post(self, request, *args, **kwargs):
sender = BankAccount.objects.get(id=kwargs['pk'])
form = self.form_class(sender, self.request.user, request.POST)
if form.is_valid():
MoneyTransfer.objects.create(sender=sender,
receiver=BankAccount.objects.get(id=self.request.POST['receiver']),
total=float(self.request.POST['total']),
when=timezone.localtime(timezone.now()),
comment=self.request.POST['comment'])
return redirect('AccountDetail', kwargs['pk'])
data = self.get_context_data()
data['form'] = form
return render(request, self.template_name, data)
def get_context_data(self):
account = BankAccount.objects.get(id=self.kwargs['pk'])
return {'form': SendTransferForm(account, self.request.user),
'user': self.request.user,
'account': account}
I think there's a lot of redudant code for CBV. What can I do for shorting it?
UPD
my current code:
class SendTransfer(SingleObjectMixin, FormView):
model = BankAccount
form_class = SendTransferForm
template_name = 'dashboard/send_transfer.html'
def dispatch(self, request, *args, **kwargs):
self.object = self.get_object()
return super(SendTransfer, self).dispatch(request, *args, **kwargs)
def get_object(self, queryset=None):
obj = super(SendTransfer, self).get_object(queryset)
if obj.is_legal():
if not obj.organization.owners.filter(user=self.request.user).exists():
raise Http404
else:
if obj.citizen.user != self.request.user:
raise Http404
return obj
def form_valid(self, form):
data = form.cleaned_data
MoneyTransfer.objects.create(sender=self.object,
receiver=data['receiver'], # ModelChoiceField in the form
total=data['total'], # FloatField in the form, etc.
when=timezone.localtime(timezone.now()),
comment=data['comment'])
return redirect('AccountDetail', self.object.pk)
last line of dispatch() method raises TypeError: init() takes at least 3 arguments (1 given)
CBVs are designed for code reuse. If you don't yet have another class that could benefit of code you posted, the actual amount of code is almost identical, be that a CBVs or a plain function.
But the more pythonic and Django-ish (from my biased POV) way would be to:
Inherit your class from the FormView instead of the View. That eases the form management a bit.
Add a SingleObjectMixin to get the object from url kwargs for free.
Move your object validation to the get_object() method. It's a good practive to raise 404 if your object doesn't validate.
Refactor out the get_context_data() as you already have all that data in your context (request, form and object)
Instead of relying on the self.request.POST, clean your data through the form.
class SendTransfer(SingleObjectMixin, FormView):
model = BankAccount
form_class = SendTransferForm
template_name = 'dashboard/send_transfer.html'
def dispatch(self, request, *args, **kwargs):
self.object = self.get_object()
return super(SendTransfer).dispatch(request, *args, **kwargs)
def get_object(self, queryset=None):
obj = super(SendTransfer, self).get_object(queryset)
if obj.is_legal():
if not obj.organization.owners.filter(user=self.request.user).exists():
raise Http404
else:
if obj.citizen.user != self.request.user:
raise Http404
return obj
def form_valid(self, form):
data = form.cleaned_data
MoneyTransfer.objects.create(sender=self.object,
receiver=data['receiver'], # ModelChoiceField in the form
total=data['total'], # FloatField in the form, etc.
when=timezone.localtime(timezone.now()),
comment=data['comment'])
return redirect('AccountDetail', self.object.pk)
Some of your code has gone thanks to the CBV magic, some just has moved to another methods. Take a look, I'd welcome your comments.