As you know we can define a ModelForm and then replace add and change forms by setting form attribute in modelAdmin class. for example:
class FooAdminForm(django.forms.ModelForm):
class Meta:
model = Foo
def __init__(self, *args, **kwargs):
super(FooAdminForm, self).__init__(self, *args, **kwargs)
class FooAdmin(admin.ModelAdmin):
form = FooAdminForm
in a simple view we can initialize a form object and pass extra arguments to init function of the form. something like this:
def my_view(request):
form = FooAdminForm(p1='aaa', p2='bbb')
and then in the init function we can access these parameter.
self.p1 = kwargs.pop('p1')
self.p2 = kwargs.pop('p2')
but how can I pass arguments to modelAdmin form? I can only pass form class to modelAdmin form attribute, and I can't initialize it like in views.
I found a solution on stackoverflow here:
Use a form with a custom __init__ in Django Admin
but it adds a dynamic attribute to form object, which I think is a little hackish. Is there a better or formal way for doing this?
I was looking around for a solution to this, and found a kinda-elegant solution in Django's issue tracker:
def get_form(self, request, obj=None, **kwargs):
Form = super().get_form(request, obj=None, **kwargs)
return functools.partial(Form, user=request.user)
https://code.djangoproject.com/ticket/27240#comment:9
So if you're using a custom form, you can simply pass it to super().get_form() using kwargs
def get_form(self, request, obj=None, **kwargs):
kwargs['form'] = FooAdminForm
Form = super().get_form(request, obj=None, **kwargs)
return functools.partial(Form, user=request.user)
Because ModelAdmin.get_form does not quite behave the same as FormView.get_form, you need to be a bit sneaky:
https://docs.djangoproject.com/en/3.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_form
class FooAdmin(admin.ModelAdmin):
form = FooAdminForm
def get_form(self, request, obj=None, **kwargs):
form_class = super().get_form(request, obj, **kwargs)
class Form(form_class):
def __init__(self, *args, **kwargs):
kwargs.update(foo='bar')
super().__init__(self, *args, **kwargs)
return Form
Related
I have Form with a ModelChoiceField, which is being used as the form_class in a FormView.
The choice field has to be populated with information bound to the request object.
Let's summarize:
class MyFormView(FormView):
# I need to pass `request.user` and a value
# derived from `request.GET['pk']` to the form
form_class = MyForm
class MyForm(Form):
choices = ModelChoiceField(queryset=MyChoice.objects.none())
def __init__(self, user, number, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['choices'] = MyChoice.objects(number=number, owner=user)
What would I need to do to pass that data to the form when its instance is created?
I tried overriding get_form but I am unsure this is the proper way of doing this:
def get_form(self, form_class):
user = self.request.user
number = SomeModel.objects.get(self.GET['pk']).number
return form_class(user, number, **self.get_form_kwargs())
Overriding get_form would work, but a better approach would be to override get_form_kwargs, so that you don't have to duplicate code from the get_form method.
class MyFormView(FormView):
form_class = MyForm
def get_form_kwargs(self):
kwargs = super(MyFormView, self).get_form_kwargs()
kwargs['user'] = self.request.user
kwargs['number'] = SomeModel.objects.get(self.GET['pk']).number
return kwargs
hi im trying to use the above forms - but i get
__init__() takes at least 2 arguments (1 given)
i get to the form that it should show but it never save me the new password
i also needed to change the:
def __init__(self, user, *args, **kwargs):
self.user = user
super(AdminPasswordChangeForm, self).__init__(*args, **kwargs)
to:
def __init__(self, *args, **kwargs):
super(AdminPasswordChangeForm, self).__init__(*args, **kwargs)
since it doesnt get a user arg.
any ideas why?
thx
============================ edit =============================================
class set(FormView):
model = User
form_class = AdminPasswordChangeForm
template_name = 'set.html'
def dispatch(self, request, *args, **kwargs):
return super(set, self).dispatch(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(set, self).get_form_kwargs()
kwargs['user_to_update'] = the user
return kwargs
the init:
def __init__(self, *args, **kwargs):
self.user = kwargs['user_to_update']
kwargs.pop('user_to_update')
super(AdminPasswordChangeForm, self).__init__(*args, **kwargs)
Use the existing form. Overide the view's get_form_kwargs method to pass the expected arguments to the form, instead of changing the __init__ method, which will break other things.
In order to save the password, you need to override the form_valid method and call form.save().
For create and update views, you don't always need to override form_valid, because the default behaviour is to save the form and redirect. For FormView, the default behaviour is simply to redirect, so you do have to override it to get it to do anything useful.
class SetPasswordView(FormView):
form_class = AdminPasswordChangeForm
template_name = 'set.html'
success_url = '/thanks/'
def get_form_kwargs(self):
kwargs = super(set, self).get_form_kwargs()
kwargs['user_to_update'] = the user
return kwargs
def form_valid(self, form):
form.save()
return super(SetPasswordView, self).form_valid(form)
I have a simple form which uses a queryset that is set dynamically:
class FooForm(forms.Form):
bar = forms.ModelChoiceField(queryset = Bar.objects.none())
def __init__(self, queryset=None, *args, **kwargs):
super(FooForm, self).__init__(*args, **kwargs)
self.fields['bar'].queryset = queryset
I'd like to use this as one of my forms in a FormWizard, but I can't figure out how I can get FormWizard to pass on the queryset. Is this possible at all?
I think you could override the "get_form" method for that particular wizard
class MyWizard(FormWizard):
def __init__(self, *args, **kwargs):
self.querysets = kwargs.pop('querysets', None)
super(self, MyWizard).__init__(*args, **kwargs)
def get_form(self, step, data=None, *args, **kwargs):
return self.form_list[step](data, prefix=self.prefix_for_step(step), initial=self.initial.get(step, None), queryset=self.querysets.get(step, None))
def done(self, *args, **kwargs): pass
In my implementation of ModelForm, I would like to perform different types of validation checks based on whether current user is superuser. How can I access the current request user?
If you're using Class Based Views (CBVs) then passing an extra argument in the form constructor (e.g. in get_forms_class) or in form_class will not work, as <form> object is not callable will be shown.
The solution for CBVs is to use get_form_kwargs(), e.g.:
views.py:
class MyUpdateView(UpdateView):
model = MyModel
form_class = MyForm
# Sending user object to the form, to verify which fields to display/remove (depending on group)
def get_form_kwargs(self):
kwargs = super(MyUpdateView, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs
forms.py:
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user') # To get request.user. Do not use kwargs.pop('user', None) due to potential security hole
super(MyForm, self).__init__(*args, **kwargs)
# If the user does not belong to a certain group, remove the field
if not self.user.groups.filter(name__iexact='mygroup').exists():
del self.fields['confidential']
you can pass the user object as an extra argument in the form constructor.
e.g.
f = MyForm(user=request.user)
and the constructor will look like:
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user',None)
super(MyForm, self).__init__(*args, **kwargs)
and then use user in the clean_XX forms as you wish
My small addition,
I had a requirement where one of the model choice fields of the form is dependent on the request.user, and it took a while to take my head around.
The idea is that
you need to have a __init__ method in the model form class,
and you access the request or other parameters from the arguments of the __init__ method,
then you need to call the super constructor to new up the form class
and then you set the queryset of the required field
code sample
class CsvUploadForm(forms.Form):
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(CsvUploadForm, self).__init__(*args, **kwargs)
self.fields['lists'].queryset = List.objects.filter(user=user)
lists = forms.ModelChoiceField(queryset=None, widget=forms.Select, required=True)
as you can see, the lists variable is dependent on the current user, which is available via request object, so we set the queryset of the field as null, and its assigned dynamically from the constructor later.
Take a look into the order of the statements in the above code
you can pass the user variable like this from the view file
form = CsvUploadForm(user=request.user)
or with other POST, FILE data like below
form = CsvUploadForm(request.POST, request.FILES, user=request.user)
You may reference the user object using the instance attribute within the instance it self.
Ex; self.instance.user
class StatusForm(ModelForm):
# def __init__(self, *args, **kwargs):
# self.user = kwargs.pop('user', None)
# super(StatusForm, self).__init__(*args, **kwargs)
class Meta:
model = Status
fields = [
'user',
'content',
'image'
]
def clean_content(self):
content = self.cleaned_data.get("content", None)
if len(content) > 240:
raise ValidationError(f"Hey {self.instance.user.username}, the content is too long")
return content
This worked for me, when I am not sending form in context explicitly in get_context_data:
views.py
class MyView(FormView):
model = MyModel
form_class = MyForm
def get_form_kwargs(self):
kwargs = super(MyView, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs
form.py
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(MyForm, self).__init__(*args, **kwargs)
if not self.user.groups.filter(name__iexact='t1_group').exists():
del self.fields['test_obj']
When sending form explicitly in get_context_data we can use and this is forms.Form :
views.py
class MyView(FormView):
model = MyModel
form_class = MyForm
def get_context_data(self, **kwargs):
context = super(MyView, self).get_context_data(**kwargs)
context['form'] = self.form_class(self.request.user)
return context
forms.py
class MyForm(forms.Form):
def __init__(self, user,*args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
if not user.groups.filter(name__iexact='t1_group').exists():
del self.fields['test_obj']
I'm trying to use validation with the request.user object to restrict updates to some rows for specific users within the django admin site. I get the impression I need to override the ModelAdmin change_view method to pass the request object to the form. I've looked at the change_view method in django.contrib.admin.options, but as someone very new to django, am having trouble understanding where in the change_view method I need to make these modifications. Any pointers in the right direction would be great.
class IssuesAdmin(admin.ModelAdmin):
def change_view(self, request, object_id, extra_context=None):
#modify lines to pass request to form
form = IssuesAdminForm
class IssuesAdminForm(forms.ModelForm):
class Meta:
model = Issues
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(IssuesAdminForm, self).__init__(*args, **kwargs)
def clean_product(self):
if self.request.user.name=='someone'
return self.cleaned_data["product"]
else:
raise forms.ValidationError("Nope!")
class IssuesAdmin(admin.ModelAdmin):
def change_view(self, request, object_id, extra_context=None): #remember to edit also add_view()... etc
self.form.request = request
form = IssuesAdminForm
class IssuesAdminForm(forms.ModelForm):
class Meta:
model = Issues
def __init__(self, *args, **kwargs):
self.request = # do what you need ;)
super(IssuesAdminForm, self).__init__(*args, **kwargs)
def clean_product(self):
if self.request.user.name=='someone'
return self.cleaned_data["product"]
else:
raise forms.ValidationError("Nope!")