Django: return messages in form.clean() - python

Suppose I have a CreateView that uses a ModelForm to add a new calendar event. Sometimes, when adding a new calendar event, another calendar event has to be added before the new event to be able to successfully validate the new event (which happens in the ModelForm.clean() method). I think the only place this other event can be added is in the clean method, just before the validation which validates the new event. Am I right that this is the only place I can do this? I want to let the user know that this happened using Django messages, but of course I don't have access to the request object in the ModelForm. That makes me think I have to add this event in another place. Which method of CreateView is suited for this purpose?
I'm sorry in advance for my English and if my question isn't worded good enough. Thanks in advance.

If you don't use ModelForm, but a custom form, then you have more control over actions. Then you can pass the request to the form and use it during your clean process.
Here is an example of one of my forms where I needed access to request to save form content:
class RatifyQuestionForm(forms.Form):
...
...
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
....
....
def save(self):
data = self.cleaned_data
# create approval record
ratification = Ratification.objects.create(
assessment=data['assessment'],
alternative=data['alternative'],
author=request.user)
In a similar way you could access self.request in the clean method.
You instantiate the form in your view as so:
if request.method == 'POST':
form = RatifyQuestionForm(request.POST, request=request)

If you really want to have access to request object within your form, you can override the get_form_kwargs method within the CreateView (This will give access to self.request within in your form's clean method):
class YourViewName(CreateView):
def get_form_kwargs(self):
kwargs = super(YourViewName, self).get_form_kwargs()
kwargs.update({'request': self.request})
return kwargs
But, for your scenario it is better to do the validations within the form_valid method in your view. If the validation failed, you can set the message and then call form_invalid() to re-render the form with your error message.
class YourViewName(CreateView):
def form_valid(self, form):
if not your_check_goes_here():
messages.error(self.request, _("Form is Invalid"))
return self.form_invalid(form)
return super(YourViewName, self).form_valid(form)

Related

Django: tie two redirects to a single Update button depending on where the user came from

So, i have a rather usual "update item" page that is a class-based view which inherits UpdateView. (in views.py it looks like "class ItemUpdateView(UpdateView) and it has method get_success_url(self) defined which contains the redirect url where user will be taken after clicking "Update" button.
My problem is that in my application, there are two different pages that could lead me to this "Update item" page, and depending on the page that user comes from - i want to take the user back to either pageA or pageB upon the successful update of the item.
I wasn't able to find the best-practices of how to handle this anywhere on the web, so - would really appreciate the help.
My guess is that I need to create an additional parameter that will be a part of the url and will contain A or B depending on the pageA or pageB that user came from, i.e. the url itself would be something like '/itemUpdate/int:pk/sourcepage' => '/itemUpdate/45/A'. Does that sound like a correct aproach or is there a better way?
There is a better way that you can check Meta dictionary in request:
write in your views file:
class ItemUpdateView(UpdateView):
previous_url = ''
form_class = UpdateItem
def get(self, request, *args, **kwargs):
self.previous_url = request.META.get('HTTP_REFERER')
print(self.previous_url)
return super().get(request, *args, **kwargs)
def get_initial(self):
initial = super().get_initial()
initial['success_url'] = self.previous_url
return initial
def form_valid(self, form):
self.success_url = form.cleaned_data['success_url']
print(self.success_url)
return super().form_valid(form)
# also you can use get_success_url instead of form_valid()
# def get_success_url(self):
# return super().get_form().cleaned_data['success_url']
and then write a hidden field in your form and name it success_url
class UpdateItem(forms.ModelForm):
success_url = forms.URLField(widget=forms.HiddenInput)
class Meta:
model=Item
fields=['itemName','quantity']
Note you can not use instance in order to get success_url field, because this field belong to form nor your model instance !
refer to documentions

How can I reference a form when overriding a GET request in a class based view?

I overrode the GET request function handler in this class based view. In my else statement, I need to pass, as context data, the form that the class naturally creates (if I had not overridden the GET function). How can I do that?
I did not create a form at forms.py to create a form for the Post model. I let the create class based view handle the form creation for me. So, how can I get this form and pass as context data.
The only way I can think of doing this is creating a function based view and avoid using this class based view in this circumstance.
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ["title", "content", "payment_option", "price"]
def get(self, request):
card_set = BillingProfile.objects.get(user=request.user).card_set.all()
if not card_set.exists():
# The user does NOT have an inserted payment method.
return redirect("/billing/payment-method?next=/post/new/")
else:
# The user DOES have an inserted payment method.
form = "???"
return render(request, "posting/post_form.html", {"form":form})
You could use the method the class provides, which is self.get_form().
But this actually wouldn't be the right thing to do. What your should really do is to delegate to the default implementation of get and let it do what it normally would.
if not card_set.exists():
# The user does NOT have an inserted payment method.
return redirect("/billing/payment-method?next=/post/new/")
else:
return super().get(request)

How does class based view with multiple methods work with urls in django?

I have a created a view class "class Demo", which has 3 functions
def update_time()
def get_context()
def before_response()
urls.py : url(r'^demo/$', Demo.as_view(),name='demo_class'),
When i'll enter url /demo/ how will it determine which function to call from "class Demo" ?
Because Django’s URL resolver expects to send the request and associated arguments to a callable function, not a class, class-based views have an as_view() class method which serves as the callable entry point to your class. The as_view entry point creates an instance of your class and calls its dispatch() method. dispatch looks at the request to determine whether it is a GET, POST, etc, and relays the request to a matching method if one is defined, or raises HttpResponseNotAllowed if not.
just read the docs
Basically class based views are recommended when you need to handle both get and post requests at one point. For example in get method of class Register, you can render the registration form and in its post method, you can handle the form submission. If its a get request it will automatically invoke the get() method in the class, same for post request. Also you can write any common code in the dispatch() method which will be invoked for every request.eg:
class Register(View):
def dispatch(self, *args, **kwargs):
'''
common code here
'''
return super(Register, self).dispatch(*args, **kwargs)
def get(self, request):
registration_form = RegistrationForm()
return render(request, 'new.html', { 'form': registration_form })
def post(self, request):
registration_form = RegistrationForm(request.POST or None)
if registration_form.is_valid():
#save form
return HttpResponseRedirect(reverse('success-show'))
return render(request,new.html', { 'form': registration_form })
For references you can check this website.
You need to subclass a class based views, and depending on that it will have one or another method.
For example TemplateView renders a template you pass in the template_name attribute.
All class based views care about is that the attributes needed to work properly are setted. That is done via different methods. You can check the django's documentation for specifics.
For example, if you want to render a form in your template view, you will need to pass the form in the context, so you can override get_context_data() like:
def get_context_data(self, **kwargs):
context = super(DemoClass, self).get_context_data(**kwargs)
context['form'] = MyForm()
return context
There are different methods to handle different things, like querysets, objects, etc.
They are not complicated, but they are specific. If a class based view does not fit what you need, it may be easier to implement the functionality from a more general view (View, TemplateView) than forcing a more specific one to do things it is not intended for.
slightly change the url
add numbers one to three in url and put the condition in your view.
Ex.
url(r'^abc/(?P<newid>.*)$', 'class_demo'),
so your url will be like abc/1 or abc/2 or abc/3
view
def class_demo(requests, newid):
if newid==1:
update_time()
elif newid==2:
get_context()

Validating a form in a get request, how?

in my way of perfectionism, I'm here to ask more questions about the not-so-well-documented class-based views.
I spend like 5 hours learning about class-based views, lurking into the code and I got a question.
Maybe what I'm trying to do is stupid, and if so, just say that.
I will put a simple example:
class SearchFormView(FormView):
template_name = 'search/search.html'
form_class = SearchForm
def get(self, request, *args, **kwargs):
form = SearchForm(self.request.GET or None)
if form.is_valid():
self.mystuff = Stuff.objects.filter(title__icontains=form.cleaned_data['query'])[:10]
return super(SearchFormView, self).get(request, *args, **kwargs)
This is a perfect valid class (it is, right?).
You have a form, and you make a GET request with a query parameter.
Works like a charm.
But lets imagine... I validate the query input to prevent some type of attack and I see that the query is malicious so I put a validation error.
With the old functions, I have a form instance (empty) and I put data in it and validation errors if needed. I always return that instance, if empty (first request) or if it filled with errors (the case of the malicious query).
The problem is with class-based views. In my get method I work with an extra instance of SearchForm so if I put validation stuff would be there and if I call get on the father it will use the instance on "form_class" that would be empty.
So, I think that there should be a way where I use the same form always, I mean: I call the request method, I pick the form_class (not create a new form), pass the data, validate and the father will return that form with the validation stuff.
Im not sure if I explained this correctly. So in short, Im creating a copy of the form in the get but I return the father get who have another copy that will be empty, so my when I display the template, there will be no errors because the form sended is empty.
Any ideas? Thanks.
Your problem is that super(SearchFormView, self).get(request, *args, **kwargs) renders its own form and own context. It's only a 3 line view function, so you should really be overriding what you need to change its behavior.
def get(self, request, *args, **kwargs):
form = SearchForm(self.request.GET or None)
if form.is_valid():
self.mystuff = Stuff.objects.filter(title__icontains=form.cleaned_data['query'])[:10]
return self.render_to_response(self.get_context_data(form=form))
Update: alternate idea if you'd like to continue using the super call
def get(self, request, *args, **kwargs):
self.form = SearchForm(self.request.GET or None)
if self.form.is_valid():
self.mystuff = Stuff.objects.filter(title__icontains=form.cleaned_data['query'])[:10]
return super(SearchFormView, self).get(request, *args, **kwargs)
def get_form(self, form_class):
"""
Returns an instance of the form to be used in this view.
"""
return getattr(self, 'form', None) or form_class(**self.get_form_kwargs())
The problem appears to be the fact that Django class based views only populate the form kwargs if the HTTP method is POST or PUT:
class FormMixin(object):
def get_form_kwargs(self):
"""
Returns the keyword arguments for instanciating the form.
"""
kwargs = {'initial': self.get_initial()}
if self.request.method in ('POST', 'PUT'):
kwargs.update({
'data': self.request.POST,
'files': self.request.FILES,
})
return kwargs
I found this a bit peculiar also, since I have on occasion used a form in a GET request (eg. a "search" form), which needed to perform some basic validation. I just override the get_form_kwargs() method on such views, to also populate the kwargs['data'] item, even when the HTTP method is GET.

What's the cleanest way to add arbitrary data to ModelForm?

Imagine we're developing a message system, and each Message has a foreign key for sender.
We're using ModelForms, and there is a MessageForm that infers its fields from Message.
Of course, we don't want the user to be able to spoof sender by posting a different sender ID.
Therefore, we must exclude sender from ModelForm and fill it from session on post.
Where and how should I assign arbitrary data to ModelForm fields?
In my example, I probably want to access session so we need to access to request as well.
Does this mean the code has to be in the view, right after the form has been created?
How do we assign a form field from code and make sure it overrides POST data?
(Of course, the example is pretty fictional and is here just to illustrate the problem.)
You can just exclude it as you have and then when processing the form in the view do:
obj = form.save(commit=False)
obj.sender = request.user
obj.save()
Alternatively, you can do it in the save method of the form; for this you need to save the request object momentarily in the form. Something like the following:
class MyForm(forms.ModelForm):
def __init__(self, request, *args, **kwargs):
self._request = request
super(MyForm, self).__init__(*args, **kwargs)
def save(self, commit=False):
obj = super(MyForm, self).save(commit=False)
obj.sender = self._request.user
if commit:
obj.save()
return obj
I prefer the second way myself as it helps encapsulate the logic regarding that model and it's data in one neat place.
Just exclude the sender field from the ModelForm and, when you instantiate the object in the view on the POST endpoint (just before saving), make sure you populate the sender field with the appropriate session or user ID.
When you exclude the field, there is no way to add the field to the post data[1], so a user can't spoof it.
[1] With JavaScript, a &sender=someSenderId could be added in theory, but in your view, you don't need to look for a sender field. It won't be serialized into the ModelForm object.

Categories