So basically here is what I am trying to do:
I would like to have a way to be able to make some of the fields readonly depending on the user.
So far it's easy but here comes the problem, I need to make some select input read only and well...they don't work well with read only. The thing is the user is still able to play with it even though the new value is not submitted. I want the select field to be disabled but if I do that, the value is not sent via the POST and there is a problem with the form valid method.
So after reading around, I understand that the only way is to disable all the fields except the one that is selected by default?
How is it possible
Here is what I have so far:
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(TeamForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.pk is None:
self.fields['division'].initial = 1
user_role = self.user.memberaccount.get_role()
if user_role != 'admin':
And here is whhere I want to disable the choices....
Thanks a lot for your help,
Ara
I see two possible choices here:
create a custom widget which will show the disabled select alongside a hidden input containing the value to submit https://docs.djangoproject.com/en/dev/ref/forms/widgets/#customizing-widget-instances
set the field as required=False and create a clean_division() method inside your form to populate the field if it hasn't been submitted: https://docs.djangoproject.com/en/dev/ref/forms/validation/#cleaning-a-specific-field-attribute
Related
The use case: It's kind of like a signup sheet. The form has 3 fields, each of which should only be editable if they were blank when the form was requested. Once the form is submitted, these fields for this specific instance of the model shouldn't be editable.
The question: How do I do this? I was thinking of using Javascript to set the fields to editable if they already have something in there, but I'm almost certain there's an easier way.
i think the easiest way is to use javascript since it works directly within the browser.
just get the id of your fields and set disabled to true
var myInput = document.getElementById("myFieldId")
if (myInput.value.length != 0) {
myInput.disabled = true
}
You can do a check in the init function of your form and then update the field.
class YourForm(forms.Form):
somefield = forms.Charfield()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.somefield is not "":
self.fields['somefield'].disabled = True
Validation should always be done on the server-side and not rely on the client.
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
Imagine we're developing a message system, and each Message has a foreign key for sender.
We're using ModelForms, and there is a MessageForm that infers its fields from Message.
Of course, we don't want the user to be able to spoof sender by posting a different sender ID.
Therefore, we must exclude sender from ModelForm and fill it from session on post.
Where and how should I assign arbitrary data to ModelForm fields?
In my example, I probably want to access session so we need to access to request as well.
Does this mean the code has to be in the view, right after the form has been created?
How do we assign a form field from code and make sure it overrides POST data?
(Of course, the example is pretty fictional and is here just to illustrate the problem.)
You can just exclude it as you have and then when processing the form in the view do:
obj = form.save(commit=False)
obj.sender = request.user
obj.save()
Alternatively, you can do it in the save method of the form; for this you need to save the request object momentarily in the form. Something like the following:
class MyForm(forms.ModelForm):
def __init__(self, request, *args, **kwargs):
self._request = request
super(MyForm, self).__init__(*args, **kwargs)
def save(self, commit=False):
obj = super(MyForm, self).save(commit=False)
obj.sender = self._request.user
if commit:
obj.save()
return obj
I prefer the second way myself as it helps encapsulate the logic regarding that model and it's data in one neat place.
Just exclude the sender field from the ModelForm and, when you instantiate the object in the view on the POST endpoint (just before saving), make sure you populate the sender field with the appropriate session or user ID.
When you exclude the field, there is no way to add the field to the post data[1], so a user can't spoof it.
[1] With JavaScript, a &sender=someSenderId could be added in theory, but in your view, you don't need to look for a sender field. It won't be serialized into the ModelForm object.
I know how to get it in views.py....
request.META['REMOTE_ADDR']
However, how do I get it in models.py when one of my forms is being validateD?
You can pass the request object to the form/model code that is being called: this will then provide access to request.META['REMOTE_ADDR']. Alternatively, just pass that in.
Ona possible way, but i am not sure if it is the best or not...
define your own clean method,
class someForm(forms.Form):
afield = CharField()
def clean(self, **kwargs):
cleaned_data = self.cleaned_data
afield = cleaned_data.get('afield')
if 'ip' in kwargs:
ip = kwargs['ip']
# ip check block, you migth use your cleaned data in here
return cleaned_data
some_info = {'afield':123} #you will wish to use post or gt form data instead, but tihs iis for example
form = someForm(some_info)
if form.is_valid():
data = form.clean({'ip':request.META['REMOTE_ADDR']}) # you pass a dict with kwargs, which wwill be used in custom clean method
If you are validating at form level or at model level, both instances know nothing about the HTTP request (where the client IP info is stored).
I can think of two options:
Validate at the view level where you can insert errors into the form error list.
You can put the user IP (may be encrypted) in a hidden field at your form.
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.