Django: Access request object from admin's form.clean() - django

My question is very similar to this one: How do I access the request object or any other variable in a form's clean() method?
Except, I have the same problem with admin form. So I can't see a way to init the form myself, therefore - to pass a request to it.
Thanks beforehand.

Indeed, there is a way to solve your issue!
You will need to subclass form provided by ModelAdmin.get_form() and override it:
class BusinessDocumentCommentForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
# Voila, now you can access request anywhere in your form methods by using self.request!
super(BusinessDocumentCommentForm, self).__init__(*args, **kwargs)
if self.request.GET.get('document_pk', False):
#Do something
def clean(self):
# Do something with self.request
# etc.
class Meta:
model = BusinessDocumentComment
class BusinessDocumentCommentAdmin(admin.ModelAdmin):
form = BusinessDocumentCommentForm
def get_form(self, request, obj=None, **kwargs):
AdminForm = super(BusinessDocumentCommentAdmin, self).get_form(request, obj, **kwargs)
class AdminFormWithRequest(AdminForm):
def __new__(cls, *args, **kwargs):
kwargs['request'] = request
return AdminForm(*args, **kwargs)
return AdminFormWithRequest

There are a number of hooks in the ModelAdmin class to allow you to do things this - look at the code in django.contrib.admin.options.
Two methods that might help you are ModelAdmin.save_form and ModelAdmin.save_model, both of which are passed the request object. So you can override these methods in your Admin subclass and do any extra processing you need.
Edited after comment
You're quite right that this won't let you validate the form dependent on the user's privileges. Unfortunately the form instantiation is buried deep within the add_view and change_view methods of ModelAdmin.
There aren't many possibilities without duplicating a lot of existing code. You could override the *_view methods; or you could try and override the modelform_factory function to return a new class with the request object baked in already; or you could try fiddling with the form class __new__ method to do the same thing, but that's tricky because of the form metaclass.

This solution works for me. You can use self.request anywhere in the form to use it, including def clean(self)
class MyModelAdmin(admin.ModelAdmin):
form = MyForm
def get_form(self, request, *args, **kwargs):
form = super(MyModelAdmin, self).get_form(request, *args, **kwargs)
form.request = request
return form

Related

Django overriding detail view get method

This is my post detail view and it works perfectly.
class PostDetailView(DetailView):
model = Post
context_object_name = 'post'
template_name = 'posts/detail.html'
def get_queryset(self, *args, **kwargs):
request = self.request
pk = self.kwargs.get('pk')
queryset = Post.objects.filter(pk=pk)
return queryset
def get_context_data(self, **kwargs):
context = super(PostDetailView, self).get_context_data(**kwargs)
content['comments'] = Comment.objects.all()
return context
However, when I add get method to the view it does not work anymore.
def get(self, request, *args, **kwargs):
# how to return here so that it works exactly like before
After adding get method get_queryset and get_context_data do not gets called automatically and the context is empty in the template. So what would be the get method so that it works exactly like before?
EDIT
My target is to do something like this
if request.is_ajax():
html = render_to_string('comments/detail.html') # ajax reply with html data
return HttpResponse(html)
return render 'posts/detail.html'
So where do I put this code and still want to keep call all methods such as get_queryset and get_context_data to be called automatically?
The idea of views like a DetailView, ListView, etc. is that it implements the boilerplate logic for you. So it has defined a function def get(self, request, *args, **kwargs) that is used to render the logic. You can usually tweak a few things by specifying the model, queryset, etc. without reimplementing the entire view.
For a DetailView [Django-doc], the logic is implemented in the BaseDetailView you can inspect the source code [GitHub]:
class BaseDetailView(SingleObjectMixin, View):
"""A base view for displaying a single object."""
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
One general piece of advice I want to share:
Before overriding any attribute, one must have deep knowledge of what is the significance of that attribute (callable or not callable). This advice applies to any language or framework. Suppose when someone overrides the get in Django, all the methods that are being called from get will not be invoked unless one invokes that from overridden get. So you should see the source of get and observe that methods are called from that.

Unable to figure out how ModelForm is rendered using generic UpdateView

I'm trying to figure out how the ModelForm is instantiated when I'm using generic UpdateView.
I've gone through the django source code and looked into UpdateView and relevant Form classes but I can't see any line of code where we are explicitly passing instance to the object of ModelForm class.
For example, say we have PostForm as a ModelForm then we would have written :
form = PostForm(instance=Post.object.get(pk=pk))
to render the form from the models object.
I can't see similar code in the django source code and can't figure out how the generic ModelForm is getting populated in case of UpdateView
i.e. how the self.instance attribute of my form is getting instantiated when I submit data after POSTing the form.
The instance attribute of ModelForm is instantiated in get_form_kwargs() defined in ModelFormMixin
For detailed explanation see below :
The UpdateView view inherits SingleObjectTemplateResponseMixin, BaseUpdateView
BaseUpdateView further inherits ModelFormMixin and ProcessFormView
It also defines the get and post methods that are called via dispatch
These get and post methods sets the object attribute as the current model object. Below is the code snippet from django docs :
class BaseUpdateView(ModelFormMixin, ProcessFormView):
"""
Base view for updating an existing object.
Using this base class requires subclassing to provide a response mixin.
"""
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super(BaseUpdateView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super(BaseUpdateView, self).post(request, *args, **kwargs)
The get and post methods also call the parent's get and post method i.e. get and post defined in ProcessFormView
During GET request
The get method defined in ProcessFormView calls the get_context_data() overridden under FormMixin which further invokes get_form() to return an instance of the form to be used in the view.
def get_context_data(self, **kwargs):
"""
Insert the form into the context dict.
"""
if 'form' not in kwargs:
kwargs['form'] = self.get_form()
return super(FormMixin, self).get_context_data(**kwargs)
get_form() calls get_form_kwargs() which lies in ModelFormMixin as well as FormMixin but since the ModelFormMixin inherits from FormMixin, the method defined in ModelFormMixin overrides the one defined in FormMixin. This get_form_kwargs() method first calls the super/parent's method and then sets the instance attribute of the form to the current model object i.e self.object (or simply object).
Code snippet from the docs below :
def get_form_kwargs(self): #defined in ModelFormMixin class
"""
Returns the keyword arguments for instantiating the form.
"""
kwargs = super(ModelFormMixin, self).get_form_kwargs()
if hasattr(self, 'object'):
kwargs.update({'instance': self.object})
return kwargs
The form is then rendered using the model object's attributes
During POST request :
As mentioned earlier (see first code snippet), just like get(), post() method also sets the object attribute to the current model object i.e. self.object=self.get_object(). ( get_object() is inherited from SingleObjectMixin class )
It then calls post method of ProcessFormViewi.e. parent class which creates the instance of form using get_form() method. (Just like get_context_method was doing in case of get request)
get_form() calls the get_form_kwargs which further sets the instance attribute of the form to the self.object instantiated in first post method call.
Code snippet below :
class ProcessFormView(View):
"""
A mixin that renders a form on GET and processes it on POST.
"""
def get(self, request, *args, **kwargs):
"""
Handles GET requests and instantiates a blank version of the form.
"""
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
# PUT is a valid HTTP verb for creating (with a known URL) or editing an
# object, note that browsers only support POST for now.
def put(self, *args, **kwargs):
return self.post(*args, **kwargs)
Next, the form is validated against basic constraints and this is done by calling form.is_valid() method which is inherited from BaseForm class.
This is a very important step because at this point, the instance object's attributes are updated to the data POSTed in the form.
This all is achieved via following stack of calls :
form.is_valid() calls -> errors property -> which calls full_clean() -> _clean_fields() -> _clean_form() -> _post_clean()
_post_clean() constructs the instance from POST data by calling construct_instance_method
To understand these functions better read the BaseForm class for is_valid() here and BaseModelForm class for _post_clean() here
I think you might be looking for the method
FormMixin.get_form_kwargs().
Here is the source from the Github repo:
def get_form_kwargs(self):
...
if self.request.method in ('POST', 'PUT'):
kwargs.update({
'data': self.request.POST,
'files': self.request.FILES,
})
return kwargs
As you can see, if the request is POST, the data from POST and FILES
are returned from this method, which in turn is used to instanciate the form, as you can see in this second snippet below from the same source:
def get_form(self, form_class=None):
...
return form_class(**self.get_form_kwargs())

Mixin for changing the behaviour of a get method in Django class based view

I'm attempting to write a mixin for setting a translation language based on the language set in the user Profile model.
When a get request comes in, the mixin should set a language to user language, get response from the view that adds the mixin, and then set the language back to what it was before. I wrote the following mixin, which is invoked, but it's get method is not invoked. What am I doing wrong?
class SetUserLanguageMixin(object):
def get(self, request):
current_language = translation.get_language()
try:
translation.activate(request.user.profile.language)
response = super(SetUserLanguageMixin, self).get(request)
finally:
translation.activate(current_language)
return response
class SomeView(LoggingMixin, APIView, SetUserLanguageMixin):
def get(self, request):
...
return Response(data, status=status.HTTP_200_OK)
If your SomeView overrides get(), then your mixin's get() method will not be called unless you call super(). You could try overriding dispatch in your mixin instead.
Note that your view will be more robust if the overridden get/dispatch method accepts args and kwargs:
def dispatch(self, request, *args, **kwargs):
...
response = super(SetUserLanguageMixin, self).dispatch(request, *args, **kwargs)
...

can't pass 'user' parameter to ChangePasswordForm in django

Good day SO!
I'm learning Django (1.8) with class-based-views. Django itself provides an authentication module with the possibility to change the user's password. While using the Django's PasswordChangeForm (which extends Django's SetPasswordForm), I stumble upon the following error:
init() missing 1 required positional argument: 'user'
When I take a look at SetPasswordForm class, I can see it requires an user-object as parameter.
def __init__(self, user, *args, **kwargs):
self.user = user
super(SetPasswordForm, self).__init__(*args, **kwargs)
What did I initially do?
First off, in my view I simply assigned the Django's PasswordChangeForm:
class ChangePassword(LoginRequiredMixin, FormView):
template_name = 'users/reset_password.html'
form_class = PasswordChangeForm
Which led to the error of course, because no user-object has been provided.
So what have I attempted to solve this issue?
Attempt one: Custom form which inherits from PasswordChangeForm and adds the init method.
Since the PasswordChangeForm does not have an init method, I crated a new form class called MyPasswordChangeForm, which inherits from PasswordChangeForm and adds the init:
class MyPasswordChangeForm(PasswordChangeForm):
def __init__(self, request, *args, **kwargs):
super(MyPasswordChangeForm, self).__init__(request.user, *args, **kwargs)
Expected result: MyPasswordChangeForm->inherit from PasswordChangeForm and add init->super init->perform init in SetPasswordForm
Actual result: super is calling the LoginRequiredMixin:
init() missing 1 required positional argument: 'request'
stack-tr l:80 return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs)
Attempt 'two': minor changes
Changing super->MyPasswordChangeFrom to super->PasswordChangeForm
Attempt three: using a mixin, but had same result as above unfortunately.
Attempt four: not done this yet, but would be the final option? But there must be a way to use the django's forms as much as possible.
So my question is...
Can somebody give a hint or small explanation on how I can pass the (authenticated) user-object to the Django's SetPasswordForm via Django's PasswordChangeForm, so I can use as much as possible of the currently existing forms.
Thanks in advance!
request isn't sent by default to the FormView upon initialization. You have to sneak it in there somehow.
Based on your attempt #1, a good way to do this is overriding the method get_form_kwargs() in your FormView, and add request as a key to the dict it's super already provides. Then, use the kwargs in MyPasswordChangeForm's __init__ to get request.
Esentially, you'd do something like:
class ChangePassword(LoginRequiredMixin, FormView):
template_name = 'users/reset_password.html'
form_class = PasswordChangeForm
def get_form_kwargs(self, **kwargs):
data = super(ChangePassword, self).get_form_kwargs(**kwargs)
data['request'] = self.request
return data
And then, in your Form's init:
def __init__(self, *args, **kwargs):
request = kwargs.pop("request") # it's best you pop request, so that you don't get any complains for a parent that checks what kwargs it gets
super(MyPasswordChangeForm, self).__init__(request.user, *args, **kwargs)

Object ownership validation in Django UpdateView

EDIT:
The better solution for me was just using a permissions system, especially since I needed other types of controlled access to objects. I now use Django-guardian to help with object level permissions like this.
Original:
I'm expanding a bit on the standard django book guide by letting users upload stories, as well as having author, publisher, etc. I'm attempting to only let authors (creators) of a story use the updateview, with other users being redirected away.
Modifying get_object in the UpdateStory view set it off, but the traceback goes through my StoryForm init for some reason. The error is 'HttpResponseRedirect' object has no attribute '_meta'
views.py
class UpdateStory(LoginRequiredMixin, UpdateView):
model = Story
template_name = 'stories/story_update.html'
form_class = StoryForm
def get_object(self, queryset=None):
obj = super(UpdateStory, self).get_object()
if not obj.author == self.request.user:
return redirect(obj)
return obj
forms.py
class StoryForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(StoryForm,self).__init__(*args, **kwargs)
I'm still new, so it might be obvious, but I've been looking for a couple hours and I'm stumped.
The best approach would be to use another mixin, something like this:
class AuthorRequiredMixin(object):
def dispatch(self, request, *args, **kwargs):
if self.object.author != self.request.user:
return HttpResponseForbidden()
return super(AuthorRequiredMixin, self).dispatch(request, *args, **kwargs)
Of course you can return another HttpResponse, but keep in mind what is the proper use here.
http://ccbv.co.uk/projects/Django/1.5/django.views.generic.edit/UpdateView/
Go through the above link to understand how UpdateView works. get_object is supposed to return the model instance, It is not supposed to return HttpResponseRedirect object, that's why you are getting that error.
Try doing the check in dispatch method like the following.
def dispatch(self, request, *args, **kwargs):
""" Making sure that only authors can update stories """
obj = self.get_object()
if obj.author != self.request.user:
return redirect(obj)
return super(UpdateStory, self).dispatch(request, *args, **kwargs)
PS: I guess it is not recommended to override dispatch. But as you
have to do the check on both get and post methods, overriding dispatch
will be easier.
This specific issue is considered in Django anti-patterns.
We're encouraged to filter the QuerySet to only retrieve objects where the user is the author, as opposed to UserPassesTestMixin.
In OP's case it would actually be quite similar to what they have there
from django.contrib.auth.mixins import LoginRequiredMixin
class UpdateStory(LoginRequiredMixin, UpdateView):
model = Story
# …
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(
author=self.request.user
)