Hidden field in Django form not in cleaned_data - python

I have this form:
class CollaboratorForm(forms.Form):
user = forms.CharField(label="Username",max_length=100)
canvas = forms.IntegerField(widget=forms.HiddenInput)
....
def clean_user(self):
user = self.cleaned_data['user']
canvas = self.cleaned_data['canvas']
In the view I'm simply calling
if form.is_valid():
I get the error:
KeyError at /canvas/1/add-collaborator/
'canvas'
According to firebug the value is posting, it's just doesn't seem to be making it to my clean function. Am I doing it wrong?
EDIT: Post data
canvas 1
csrfmiddlewaretoken 2cb73be791b32ca9a41566082c804312
user username
EDIT2: I would also be willing to take an answer that could tell me how to send the primary key to the clean_user function, where the primary key is the /1/ in the example url above. The function in the view that is called is:
def canvas_add_collaborator(request, pk):
So I would want to send the pk to the clean_user function which would solve my problem by not needing the hidden field.

You need to change the method name to clean(), not clean_user(). 'canvas' is not in the cleaned_data if you are just validating the user field.

I solved my problem (probably not the best way, but works) using this:
class CollaboratorForm(forms.Form):
....
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('canvas', None)
super(CollaboratorForm, self).__init__(*args, **kwargs)
Then in my view:
def canvas_add_collaborator(request, pk):
....
form.canvas = pk
Maybe not the most elegant solution, but it works for now. Feedback welcome.

I found that the order in the declaration of fields matters, so if you want to access cleaned_data['canvas'] in the clean_user method, you must declare canvas first in your fields. I have tested this in Model forms

Related

Django: tie two redirects to a single Update button depending on where the user came from

So, i have a rather usual "update item" page that is a class-based view which inherits UpdateView. (in views.py it looks like "class ItemUpdateView(UpdateView) and it has method get_success_url(self) defined which contains the redirect url where user will be taken after clicking "Update" button.
My problem is that in my application, there are two different pages that could lead me to this "Update item" page, and depending on the page that user comes from - i want to take the user back to either pageA or pageB upon the successful update of the item.
I wasn't able to find the best-practices of how to handle this anywhere on the web, so - would really appreciate the help.
My guess is that I need to create an additional parameter that will be a part of the url and will contain A or B depending on the pageA or pageB that user came from, i.e. the url itself would be something like '/itemUpdate/int:pk/sourcepage' => '/itemUpdate/45/A'. Does that sound like a correct aproach or is there a better way?
There is a better way that you can check Meta dictionary in request:
write in your views file:
class ItemUpdateView(UpdateView):
previous_url = ''
form_class = UpdateItem
def get(self, request, *args, **kwargs):
self.previous_url = request.META.get('HTTP_REFERER')
print(self.previous_url)
return super().get(request, *args, **kwargs)
def get_initial(self):
initial = super().get_initial()
initial['success_url'] = self.previous_url
return initial
def form_valid(self, form):
self.success_url = form.cleaned_data['success_url']
print(self.success_url)
return super().form_valid(form)
# also you can use get_success_url instead of form_valid()
# def get_success_url(self):
# return super().get_form().cleaned_data['success_url']
and then write a hidden field in your form and name it success_url
class UpdateItem(forms.ModelForm):
success_url = forms.URLField(widget=forms.HiddenInput)
class Meta:
model=Item
fields=['itemName','quantity']
Note you can not use instance in order to get success_url field, because this field belong to form nor your model instance !
refer to documentions

Django: return messages in form.clean()

Suppose I have a CreateView that uses a ModelForm to add a new calendar event. Sometimes, when adding a new calendar event, another calendar event has to be added before the new event to be able to successfully validate the new event (which happens in the ModelForm.clean() method). I think the only place this other event can be added is in the clean method, just before the validation which validates the new event. Am I right that this is the only place I can do this? I want to let the user know that this happened using Django messages, but of course I don't have access to the request object in the ModelForm. That makes me think I have to add this event in another place. Which method of CreateView is suited for this purpose?
I'm sorry in advance for my English and if my question isn't worded good enough. Thanks in advance.
If you don't use ModelForm, but a custom form, then you have more control over actions. Then you can pass the request to the form and use it during your clean process.
Here is an example of one of my forms where I needed access to request to save form content:
class RatifyQuestionForm(forms.Form):
...
...
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
....
....
def save(self):
data = self.cleaned_data
# create approval record
ratification = Ratification.objects.create(
assessment=data['assessment'],
alternative=data['alternative'],
author=request.user)
In a similar way you could access self.request in the clean method.
You instantiate the form in your view as so:
if request.method == 'POST':
form = RatifyQuestionForm(request.POST, request=request)
If you really want to have access to request object within your form, you can override the get_form_kwargs method within the CreateView (This will give access to self.request within in your form's clean method):
class YourViewName(CreateView):
def get_form_kwargs(self):
kwargs = super(YourViewName, self).get_form_kwargs()
kwargs.update({'request': self.request})
return kwargs
But, for your scenario it is better to do the validations within the form_valid method in your view. If the validation failed, you can set the message and then call form_invalid() to re-render the form with your error message.
class YourViewName(CreateView):
def form_valid(self, form):
if not your_check_goes_here():
messages.error(self.request, _("Form is Invalid"))
return self.form_invalid(form)
return super(YourViewName, self).form_valid(form)

MultipleChoiceField - invalid_choice error - Select a valid choice. SomeChoice is not one of the available choices

I am creating a form in django, hoping to allow users to delete some of their devices. When I click on the submit button of my form, I keep getting the message: Select a valid choice. <Some choice> is not one of the available choices. Here is my code. Thanks a lot :)
forms.py
class DeleteDeviceForm(forms.Form):
devices = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple)
views.py
def delete_device(request):
if request.method == 'POST':
deletedeviceform = DeleteDeviceForm(request.POST)
if deletedeviceform.is_valid():
devicelist = request.POST.getlist('devices')
#will put other stuff there to process the data later, just want to access list now
return HttpResponseRedirect('/accounts/loggedin', {"devicelist": devicelist, })
else: #if not a POST request
userid = request.user.profile.pk
devices = Device.objects.filter(user_id=userid)
deletedeviceform = DeleteDeviceForm()
deletedeviceform.fields['devices'].choices = [(x.id, x) for x in devices]
return render(request, 'userprofile/delete_device.html', {"full_name": request.user.username, "deletedeviceform": deletedeviceform,})
Note that: I don't have a model for this form
Thanks to #Daniel Roseman, I was able to figure it out.
Here is how I changed my code :
forms.py
class DeleteDeviceForm(forms.Form):
devices = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple,label="Select the devices you want to delete:")
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(DeleteDeviceForm, self).__init__(*args, **kwargs)
self.fields['devices'].choices = [(x.id, x) for x in Device.objects.filter(user_id=user)]
views.py
changed only one line to :
deletedeviceform = DeleteDeviceForm(request.POST, user=request.user.profile.pk)
You've set the list of valid choices on the GET request only. On the POST, there are no choices, so the field can never be valid.
That code should go in the form's __init__ method, so it is run every time the form is instantiated.
You can use very simple way in which you just have to update model not form. Use django-multiselectfield
pip install django-multiselectfield
Check here for reference
https://pypi.org/project/django-multiselectfield/

django ModelForm save() method issue

I have a model form:
class SnippetForm(ModelForm):
class Meta:
model = Snippet
exclude = ['author', 'slug']
and I want to be able to edit a particular instance by using this:
def edit_snippet(request, snippet_id):
#look up for that snippet
snippet = get_object_or_404(Snippet, pk=snippet_id)
if request.user.id != snippet.author.id:
return HttpResponseForbidden()
if request.method == 'POST':
form = SnippetForm(data=request.POST, instance=snippet)
if form.is_valid():
form.save()
return HttpResponseRedirect(snippet.get_absolute_url())
else:
form = SnippetForm(instance=snippet)
return render_to_response(SNIPPET_EDIT_TEMPLATE,
{'form':form, 'add':False, 'user':request.user},
RequestContext(request))
Notice that at the line
form = SnippetForm(data=request.POST, instance=snippet)
, I created a form that use the data supplied from the user, and bound it with the instance found using the primary key (received from the url). According to django documentation, when I call save() the existing instance should be updated with POSTED data. Instead, what I see is a new object is created and saved into the database. What went wrong? Thanks a lot.
[Edit] This is really embarrassed. The code indeed has nothing wrong with it. The only thing that messed up the whole thing was the action I put in the template (as I use a same template for add and edit a snippet)....Thanks a lot for your help, really appreciate that.
I don't see why it would happen. What version of django is it?
In any case, you can manually force update passing the corresponding argument.
form = SnippetForm(data=request.POST, instance=snippet, force_update=True)

Django: How to set initial values for a field in an inline model formset?

I have what I think should be a simple problem. I have an inline model formset, and I'd like to make a select field have a default selected value of the currently logged in user. In the view, I'm using Django's Authentication middleware, so getting the user is a simple matter of accessing request.user.
What I haven't been able to figure out, though, is how to set that user as the default selected value in a select box (ModelChoiceField) containing a list of users. Can anyone help me with this?
This does the trick. It works by setting the initial values of all "extra" forms.
formset = MyFormset(instance=myinstance)
user = request.user
for form in formset.forms:
if 'user' not in form.initial:
form.initial['user'] = user.pk
I'm not sure how to handle this in inline formsets, but the following approach will work for normal Forms and ModelForms:
You can't set this as part of the model definition, but you can set it during the form initialization:
def __init__(self, logged_in_user, *args, **kwargs):
super(self.__class__, self).__init__(*args, **kwargs)
self.fields['my_user_field'].initial = logged_in_user
...
form = MyForm(request.user)
I'm using Rune Kaagaard's idea above, except I noticed that formsets provide an extra_forms property: django.forms.formsets code
#property
def extra_forms(self):
"""Return a list of all the extra forms in this formset."""
return self.forms[self.initial_form_count():]
So, sticking with the example above:
formset = MyFormset(instance=myinstance)
user = request.user
for form in formset.extra_forms:
form.initial['user'] = user.pk
Saves having to test any initial forms, just provide default for extra forms.

Categories