I want to create a default value in admin "add" form based on request (user attributes), searching I found that I can create default value overriding init method of ModelForm, however I canĀ“t access to request here. Note: I tried self.request = kwargs.pop('request') and didn't work. Any Ideas, thaks.
Try overriding the render_change_form method on your model admin:
def render_change_form(self, request, context, *args, **kwargs):
# your code here, modifying the values of whatever fields you have
return super(YourModelAdmin, self).render_change_form(
request, context, args, kwargs)
Related
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())
I want to post to an endpoint from within a CBV and retrieve the response (in order to access the id of the created resource). I've had a look at How to programmatically call a Django Rest Framework view within another view? but cannot find how to send field values through with the request object. request.data is immutable, and passing kwargs through doesn't seem to be doing anything:
from app.views import OtherViewSet
class NewViews(APIView):
def post(self, request, *args, **kwargs):
# kwargs value here is {'field1':1, 'field2':2...}
view_func = OtherViewSet({'post':'create'})
response = view_func(self.request, *args, **kwargs).data
Although the kwargs contains the field values (set through several url named groups), the response always looks like:
{'field1': ['This field is required'], 'field2':['This field is required...
What am I doing wrong? Thanks all :)
Edit:
Once I've got the ID of the created resource, I want to retrieve the resource as rendered by a particular (custom) format (I don't want to return the response object above).
views.py
from app.views import OtherViewSet
class NewViews(APIView):
def post(self, request, *args, **kwargs):
view = OtherViewSet.as_view({'post':'create'})
response = view(request, *args, **kwargs)
return response
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)
In Django - Overriding get_form to customize admin forms based on request the problem is to select a different form based on the permissions of the user in the request object by hooking the get_form() method.
I would like to actually invoke a method on the object during iteration that uses the request context to output some information.
The documentation lists four ways to hook the form display.
But the function signatures don't include the request object. If they did, you could write something like (note that request is not in fact an argument):
class CustomAdmin(admin.ModelAdmin):
list_display = [ 'name', 'user_specific', ]
#
def user_specific(self, obj, request):
return obj.func1(request)
#
output.short_description = 'UserSpecific'
Overriding get_form() would not be thread safe if used to store the state... So what would be the best way?
In your case, I feel that maybe writing your own view is a better choice than hacking django's admin site.
But if you insist, you can override changelist_view and record the request.
class CustomAdmin(admin.ModelAdmin):
list_display = [ 'name', 'user_specific', ]
def changelist_view(self, request, extra_context=None):
self.request = request
return super(admin.ModelAdmin, self).changelist_view(self, request, extra_context)
def user_specific(self, obj):
return obj.func1(self.request)
output.short_description = 'UserSpecific'
I've looked at several questions here that looked similar, but none of them discussed the problem from the perspective of admin panel.
I need to check if user has permission to leave a field empty. I wanted to use request.user but I don't knot how to pass request from EntryAdmin to ModelForm. I wanted to do something like this:
class EntryAdminForm(ModelForm):
class Meta:
model = Entry
def clean_category(self):
if not self.request.user.has_perm('blog.can_leave_empty_category') and not bool(self.category):
raise ValidationError(u'You need to choose a Category!')
else:
return self.cleaned_data['category']
You could override the ModelAdmin.get_form, by adding the request as an attribute of the newly created form class (should be thread-safe).
Something along these lines:
class EntryAdmin(admin.ModelAdmin):
form = EntryAdminForm
def get_form(self, request, *args, **kwargs):
form = super(EntryAdmin, self).get_form(request, *args, **kwargs)
form.request = request
return form
Then the code in your question should work.