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.
Related
I'm trying to redirect users based on the referer in the request header. Basically, if the referer is say https://www.google.com, I would like to send them to a page, not on my website. Otherwise, continue processing as usual.
Here is what I have so far
class ArticleAccess(TemplateView, SomeMixin):
http_method_names = ['get']
template_name = 'template.html'
def dispatch(self, request, *args, **kwargs):
return super(ArticleAccess, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(ArticleAccess, self).get_context_data(**kwargs)
item = get_object_or_404(ClientItem.objects.using(self.get_site().name), id=kwargs['article_id'])
if self.request.META.get('HTTP_REFERER') == 'https://www.google.com/':
return redirect(item.item_url)
context['id'] = item.id
context['name'] = item.name
context['html'] = item.description
context['item_url'] = item.item_url
return context
This just stays on the same page instead of redirecting. I have also tried HttpResponseRedirect, but to no avail
alecxe is correct.. you'd have to redirect from a method that is expected to return an HttpResponse.
get_context_data is not expected to return an HttpResponse and isn't ever returned by the view. It's always used to get a data dict to populate say a template. No matter what you return from this method, it will never override the response.
Therefore wherever you write this override, it needs to be in a place that is expected to return a response, such as get, post, dispatch.
The problem now is to determine how to get your object outside of the get_context_data method.
For debugging, I recommend you to start by just using a plain redirect('some_view') without conditions on your dispatch method so you can check if redirection is hit as expected and only then, go for conditions and anything else. #Yuji-tomita-tomita is just right! :) Django-pdb and ipdb are very nice tools.
I have a view :
class SomeView(FormView):
form_class = SomeForm
template_name = 'app/template.html'
def get(self, request, *args, **kwargs):
form = self.form_class
return render(request, self.template_name, {form:'form'})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_Valid():
#do something here
return HttpResponseRedirect("Go somewhere from here after processing")
In the post I need to make an API call which takes data from this form. Instead of this though, I want to redirect user to another view, where he can view all the data from the form and say confirm
On submit of this confirm view I should actually make this API call.
How do I pass the form data to another view ? I can simply store the form data in a dict and pass it on from the URL, but that might expose some crucial data at the browser level.
Is there a safer way to do this ?
Thanks
Yes, there is a litle helper from django:
https://docs.djangoproject.com/en/1.5/ref/contrib/formtools/form-preview/
I am trying to process two forms in a Django class based view. The site contains a form called form (based on GET) for narrowing the list results of the ListView and the second form status_form (based on POST).
Both forms are required since the ListView returns a list of items. Form lets the user restrict the choices and status_forms lets the user flag incorrect items via a modal form (therefore it needs to be in the same template).
My trouble is that ListView does not come with the method post, however FormView does. My class List inherits from both classes, but when I execute the class I get the error message:
Attribute Error: 'List' object has no attribute 'status_form'
How should I change my implementation to allow the second form been processed via the post method?
class List(PaginationMixin, ListView, FormMixin):
model = ListModel
context_object_name = 'list_objects'
template_name = 'pages/list.html'
paginate_by = 10 #how may items per page
def get(self, request, *args, **kwargs):
self.form = ListSearchForm(self.request.GET or None,)
return super(List, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.status_form = StatusForm(self.request.POST or None)
if self.status_form.is_valid():
...
else:
return super(List, self).post(request, *args, **kwargs)
def get_queryset(self):
# define the queryset
...
# when done, pass to object_list
return object_list
def get_context_data(self, **kwargs):
context = super(List, self).get_context_data(**kwargs)
context.update(**kwargs)
context['form'] = self.form
context['status_form'] = self.status_form # Django is complaining that status_form is not existing, result since the post method is not executed
return context
# Django is complaining that status_form does not exist,
# result since the post method is not executed
context['status_form'] = self.status_form
Because you didn't define self.status_from in the first place.
You have defined it in get_context_data, and it's accessible from there.
You can access you object from get_context_data in your post method;
context = self.get_context_data(**kwargs)
status_form = context['status_form']
Also consider that you can define your status_form directly in post method itself without getting it from self or get_context_data.
Redesign you views to separate each Form processing in separate Views then tight them with each-other.
Views redesign:
In nutshell, let each view to do one job. You can create a View just for processing your status_form and name it like StatusFormProcessView then on your List view return it on its post method
class List(ListView);
def post(self, request, *args, **kwargs):
return StatusFormView.as_view()(request) # What ever you need be pass to you form processing view
This is just an example of it, need more work to be real.
For another example; On my website index page I have a search form. when user POST or GET the search form, The processing of searching doesn't exist in my IndexView, instead I handle the whole form stuff in separate view, If form should process on GET method, I'll override get() method, If form should process on POST, I'll override post() method to send search_form data to the view that is responsible for handling of processing the search_form.
Comments response
status_form = context['status_form']
shouldn't it be
context['status_form'] = status_form
after I created it ?
You want to get status_form from context, So you need to
status_form = context['status_form']
Anyway, your form data are available on self.request.POST
I have a template that I want to be able to both serve directly and embed in arbitrary other templates in my Django application. I tried to create a view class for it that looks like this:
class TemplateView(View):
def get(self, request):
context = self._create_context(request)
return render_to_response('template.html', context)
def get_string(self, request):
context = self._create_context(request)
return render_to_string('template.html', context)
def _create_context(self, request):
context = {}
# Complex context initialization logic...
return context
I've wired get to my Django URLs. However, I haven't been able to figure out how to instantiate TemplateView so that I can call get_string from other views.
There must be a better way to go about doing this. Ideas?
Update: I've seen some folks talking about making a request internally and using response.content, which would save me from having to write the get_string method. So, perhaps a better question is: How do I make a request to TemplateView from another view?
I'd follow in django's CBV pattern: it determines via dispatch what method to return. By default based on request.method. Why not based on any other argument passed to dispatch()?
So subclass dispatch and give it a way to determine whether or not to return get_string.
def dispatch(self, request, *args, **kwargs):
if 'as_string' in kwargs:
return self.get_string(request)
return super(TemplateView, self).dispatch(request, *args, **kwargs)
response = TemplateView.as_view()(request, as_string=True)
As part of a form validation in Django, I want to check when a user last made a post.
I've had a look at this, and this question on stackoverflow, which appear to address this issue, and initially used the accepted answer here. Essentially the process at the moment has been to add the request.user object during the
def form_valid(self, form):
method of the CreateView subclass. However, this doesn't allow the user variable to be used during form validation.
Since I want to access the user variable during validation, as the check on time since last post is logically validation, I need the user variable injected to the form earlier.
I've tried altering the get_initial() method:
def get_initial(self):
initial = super(ArticleCreateView, self).get_initial()
initial['user'] = self.request.user
return initial
This successfully sets the ['user'] key in the initial dictionary, but this never makes it through the cleaning process, which deals only with the stuff in the ['data'] dictionary, and removes anything in initial. I've deliberately excluded user from the form display, but this appears to also remove it from the cleaning process and validation.
Is the correct way to do this to override the init() method of the Form, and the get_initial() method of the view, putting 'user' into initial, and then the form pulling the extra information from initial into the form at init()?
I'm looking to pass the request.user to the form, and then I can access it within the form-wide clean() method to do the additional checks, which amount to validation.
def clean(self):
super(ArticleForm, self).clean()
**check whether user has posted recently, if so, raise ValidationError**
return self.cleaned_data
J
You can override get_form_kwargs method of the CreateView (ArticleCreateView in your case) to pass user to the form.
def get_form_kwargs(self):
kwargs = super(ArticleCreateView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
Override your form's init method
...
def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user")
Now you can use self.user in the clean method.
The way I've solved this at the moment is to keep validation within the form, and the rest of the business logic in the view. Consequently, I've done the following:
In the view:
def get_initial(self):
initial = super(MyCreateView, self).get_initial()
initial['user'] = self.request.user
return initial
In the form:
def __init__(self, *args, **kwargs):
self.user = kwargs['initial']['user']
super(MyObjectForm, self).__init__(self, *args, **kwargs)
def clean(self):
# Clean all the submitted data
super(MyObjectForm, self).clean()
# Validate that the user submitted > n time ago
profile = self.user.get_profile()
if datetime.now() - profile.lastpost < timedelta(hours=1)
raise forms.ValidationError("Need to wait longer than n=1 hours")
else:
self.cleaned_data['user'] = self.user
return self.cleaned_data
Which seems to work - are there any flaws I haven't seen with this solution?
i would probably override the form init method to take a user argument, and set this somewhere on the form.
class MyForm()
def __init__(self, user, *args, **kwargs):
super(MyForm, self)__init__(*args, **kwargs)
self.user = user
def clean_field(...)
if self.user ...
etc