Django form with ModelMultipleChoiceField rendered empty - python

I have trouble setting up a form with a ModelMultipleChoiceField where the queryset depends on the user. My goal is to implement an export function.
My view looks like this:
class ExportView(FormView):
template_name = 'ExportTemplate.html'
def get(self, request, *args, **kwargs):
self.form_class = ExportForm(user = request.user)
return render(request, self.template_name, {'form': self.form_class})
def get_success_url(self):
return '/addrbook/'
def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
return super().form_valid(form)
form:
class ExportForm(forms.Form):
def __init__(self, user, *args, **kwargs):
usersContacts = ContactManager().getAllUsersContacts()
self.contactList = forms.ModelMultipleChoiceField(queryset = usersContacts[str(user)])
print(usersContacts[str(user)])
super(ExportForm, self).__init__(*args, **kwargs)
I verified that the queryset is not empty, it contains a list of model objects.
My template looks like this:
<form method="post">{% csrf_token %}
{{ form }}
<input type="submit">
</form>
the only thing that gets rendered is the submit button.
Another thing that left me completely unsure of python basics is that this code:
class ExportForm(forms.Form):
contactList = forms.ModelMultipleChoiceField(queryset = [])
def __init__(self, user, *args, **kwargs):
usersContacts = ContactManager().getAllUsersContacts()
self.contactList.queryset = usersContacts[str(user)]
print(usersContacts[str(user)])
super(ExportForm, self).__init__(*args, **kwargs)
returned the runtime error:
'ExportForm' object has no attribute 'contactList'
How is it possible? the contactList member is part of the ExportForm class definition and 'self' should point to an object of that class.
Could someone explain to me why the form field is rendered empty and/or point me to a better way to pass the user to the form?
Edit: here are the changes i made to the answer to get it working, although i now stumbled upon a different problem(the field expects a queryset, and not a list of model objects):
View:
class ExportView(FormView):
template_name = 'ExportTemplate.html'
form_class = ExportForm
def get_form_kwargs(self):
kwargs = super(ExportView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
form:
class ExportForm(forms.Form):
contactList = forms.ModelMultipleChoiceField(queryset = Contact.objects.none())
def __init__(self, *args, **kwargs):
user = kwargs['user']
kwargs.pop('user', None)
super(ExportForm, self).__init__(*args, **kwargs)
usersContacts = ContactManager().getAllUsersContacts()
self.fields['contactList'].queryset = usersContacts[str(user)]
print(self.fields['contactList'].queryset)

First of all, you should pass the user to the form every time you instantiate it, not just in the get method. The way to do this with FormView is to override get_form_kwargs.
def get_form_kwargs(self):
kwargs = super(ExportForm, self).get_form_kwargs()
kwargs[user] = self.request.user
return kwargs
You can then remove your get() method.
Then, in your form class, you should use the none() method instead of an empty list to get an empty queryset. In the __init__ method you can pop the user from kwargs, and then call super() before you edit the fields. You edit the contactList field via self.fields instead of self.contactList. Note that the recommended style for field names in Django is contact_list instead of contactList.
class ExportForm(forms.Form):
contactList = forms.ModelMultipleChoiceField(queryset=YourModel.objects.none())
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(ExportForm, self).__init__(*args, **kwargs)
usersContacts = ContactManager().getAllUsersContacts()
self.fields['contactList'].queryset = usersContacts[str(user)]
You haven't shown the ContactManager() code, but using str(user) as the dictionary key looks fragile. It would probably be better to use user.pk or user.username instead.

Related

In django, how can I access the request object in the form's clean method when using a CreateView?

I'm following the answer from this question, but the request object isn't actually being passed into it: How do I access the request object or any other variable in a form's clean() method?
I'm getting this error when I try to submit the form: 'NoneType' object has no attribute 'user'
Here is my form code:
class MyModelForm(ModelForm):
class Meta:
model = MyModel
fields=['question',]
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(MyModelForm, self).__init__(*args, **kwargs)
def clean(self, *args, **kwargs):
data = self.cleaned_data['question']
user = self.request.user
if user.author.number_of_questions < 1:
raise ValidationError("You don't have any questions left")
return data
Here is my CreateView:
class MyModelCreate(generic.CreateView):
model = MyModel
fields=['question',]
def form_valid(self, form):
# Code
return super(MyModelCreate, self).form_valid(form)
def get_success_url(self, **kwargs):
# obj = form.instance or self.object
return reverse_lazy('articles:thank-you')
def get_form_kwargs(self):
kw = super(MyModelCreate, self).get_form_kwargs()
kw['request'] = self.request # the trick!
return kw
def get_context_data(self, **kwargs):
#Code
return context
It's my understanding that the get_form_kwargs method in the CreateView is passing in self.request into the request kwarg of the form, which is then being accessed in the clean method of MyModelForm, but when I run this code, I get the error: 'NoneType' object has no attribute 'user'
I have no idea why the request object isn't getting passed to the form.
Just guessing but dont you need to call form.is_valid() in order to work : return super(MyModelCreate, self).form_valid(form), just guessing...

How do I pass arguments to django forms?

I want to filter form fields querysets based on the user selected. Therefore, I want to pass user as argument to the form in order to filter fields querysets in the form's __init__ method. When I pass any arguments to the form I get the following error.
class UserDetailView(LoginRequiredMixin, FormMixin, DetailView):
model = TbUser
form_class = TbPeopleEntranceRightForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
form = TbPeopleEntranceRightForm(user=self.object)
context['form'] = form
return context
__init__() got an unexpected keyword argument 'user'
how do I pass the argument correctly, and how I get it in the __init__ form method?
Update:
class TbPeopleEntranceRightForm(forms.ModelForm):
def __init__(self, user, **kwargs):
super().__init__(**kwargs)
print(user)
# Use `self.user` here or in some other methods.
__init__() missing 1 required positional argument: 'user'
don't do it in get_context_data, it's made for something else.
Use get_form_kwargs instead:
class UserDetailView(LoginRequiredMixin, FormMixin, DetailView):
model = TbUser
form_class = TbPeopleEntranceRightForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["user"] = self.object
return kwargs
And in your forms.py:
def __init__(self, user=None, *args, **kwargs):
self.user = user
super().__init__(*args, **kwargs)
You need to add the parameter to the form's __init__ method:
class TbPeopleEntranceRightForm(forms.ModelForm):
...
def __init__(self, user=None, **kwargs):
super().__init__(**kwargs)
self.user = user
# Use `self.user` here or in some other methods.
Also, the correct way to then pass the user argument to the form is to override get_form_kwargs in the view, like #MojixCoder showed.

Django FormSetView with custom validation

I'm using the ModelFormSetView class in django-extra-views to create a formset view of all WorkerStatus entries connected to a Worker. I'd also like to use custom validation on the formset, so I've defined my own formset_class and form_class in the view. Here's the view definition:
class WorkerStatusUpdateView(ModelFormSetView):
model = WorkerStatusEntry
formset_class = WorkerStatusFormSet
form_class = WorkerStatusForm
template_name = 'staff/workers/worker_status_update.tmpl'
can_delete = True
can_order = False
fields = ['status', 'start_date']
def dispatch(self, request, *args, **kwargs):
self.worker = Worker.objects.get(pk=self.kwargs['worker_pk'])
return super(WorkerStatusUpdateView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, *args, **kwargs):
ctx = super(WorkerStatusUpdateView, self).get_context_data(*args, **kwargs)
ctx['worker'] = self.worker
return ctx
def get_queryset(self, *args, **kwargs):
return self.worker.statuses.all()
...and here are the definitions of the form and formset respectively:
class WorkerStatusForm(forms.ModelForm):
class Meta:
model = WorkerStatusEntry
fields = ['status', 'start_date']
class WorkerStatusFormSet(BaseModelFormSet):
class Meta:
model = WorkerStatusEntry
def __init__(self, queryset, *args, **kwargs):
super(WorkerStatusFormSet, self).__init__(*args, **kwargs)
def clean(self):
print "Cleaning"
This results in a page where EVERY WorkerStatusEntry in the database is shown in the formset, regardless of get_queryset(). One thing you'll notice is that WorkerStatusFormSet.__init__ takes a queryset argument: I put that there because there was a queryset argument passed to it from the ModelFormSetView, but I don't know what to do with it.
Another thing to note: if I take formset_class = WorkerStatusFormSet out of the view definition, the correct queryset shows up in the formset. However I need to use my own formset class to validate across the whole formset. Unless there's another way?
The problem is your WorkerStatusFormSet.__init__ method. Looking at the code for BaseModelFormSet, the __init__ method already takes a queryset parameter. Since you aren't doing anything in your __init__ method except calling super(), the easiest fix is to remove it.
It's not a good idea to change the signature of the __init__ method as you have done for two reasons
def __init__(self, queryset, *args, **kwargs):
super(WorkerStatusFormSet, self).__init__(*args, **kwargs)
You have changed the order of the arguments. If you look at the code for BaseModelFormset, the first argument is data. That means that data might be incorrectly assigned to queryset if somebody calls WorkerStatusFormSet(data, ...)
You do not do anything with queryset or pass it to super(), so it is lost.

Passing arguments to a form obtained by 'getattr()'

I'm creating a view which loads differents forms according to an argument given using the getattr() function:
form = getattr(forms, service.form)
but in the form I need my username to filter my files, so I have this:
class MyForm(forms.Form):
filename = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple)
K = forms.CharField(label='K', max_length=1)
fullOut = forms.CharField(label='fullOut', max_length=1)
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.user = kwargs.pop('user', None)
self.fields['filename'].queryset = userFile.objects.filter(self.user)
The problem is that I don't know how to pass the 'request.user' in my getattr() funtion. I know that if it was static it should be something like:
form = MyForm(user=request.user)
But I have tried somethings like:
form = getattr(forms, service.form, user=request.user)
And it doesn't work.I'm trying this but any idea of how list user files in a form will be welcomed.
Thanks in advance.
This doesn't have anything to do with you using getattr, the problem is in your __init__ method. You need to pop user before calling super().
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super(MyForm, self).__init__(*args, **kwargs)
You should instantiate the form as you usually do:
form = MyForm(user=request.user)
It doesn't matter whether MyForm is declared in the same module:
class MyForm(forms.Form):
my_field = forms.CharField()
form = MyForm(user=request.user)
or if you get the form class dynamically using getattr
MyForm = getattr(forms, service.form)
form = MyForm(user=request.user)

django update view and passing context

I have a update view:
class GeneralUserUpdateView(UpdateView):
model = GeneralUser
form_class = GeneralUserChangeForm
template_name = "general_user_change.html"
def dispatch(self, *args, **kwargs):
return super(GeneralUserUpdateView, self).dispatch(*args, **kwargs)
def post(self, request, pk, username):
self.pk = pk
self.username = username
self.gnu = GeneralUser.objects.get(pk=self.pk)
#form = self.form_class(request.POST, request.FILES)
return super(GeneralUserUpdateView, self).post(request, pk)
def form_valid(self, form, *args, **kwargs):
self.gnu.username = form.cleaned_data['username']
self.gnu.email = form.cleaned_data['email']
self.gnu.first_name = form.cleaned_data['first_name']
self.gnu.last_name = form.cleaned_data['last_name']
self.gnu.address = form.cleaned_data['address']
self.gnu.save()
return redirect("user_profile", self.pk, self.username)
Here in this view I want to pass a context like:
context['picture'] = GeneralUser.objects.get(pk=self.pk)
I did trying get_context_data but I cant access pk in there..
Am I doing the update right?? How can I pass that context in there??
You shouldn't be overriding post at all. All of that logic should happen in get_context_data.
In fact, none of your overrides are needed. Everything that you do in form_valid will be done already by the standard form save. And overriding dispatch just to call the superclass is pointless.
Your view should look like this only, with no overridden methods at all:
class GeneralUserUpdateView(UpdateView):
model = GeneralUser
form_class = GeneralUserChangeForm
template_name = "general_user_change.html"
context_object_name = 'picture'
(although it seems a little odd that you want to refer to an instance of GeneralUser as "picture").
Edit to redirect to a specific URL, you can define get_success_url:
def get_success_url(self):
return reverse("user_profile", self.kwargs['pk'], self.kwargs['username'])

Categories

Resources