(Django) How to clean an unbound form? - python

I have a problem. I looked at the web for a solution but did not find one. If my problem already have an answer, please give me the links.
The Problem
Here is the problem:
I create a form inside one of my view with some initial values:
form = MyForm(initial=initial)
However, I do not have a full control over these initial values, thus I need to check is the form is valid. However, because the form in unbound .is_valid() always return False.
So, I tried to create an bound form from the first step:
form = MyForm(initial)
However, I do not initialize all the field, simply because I do not know their name and how many they are, thus I cannot initialize all of them.
The problem is that some of the field I do not initialize are required. Thus, the .is_valid() always return False because I do not provide required field(s).
What I know is that all of the required field have a default value.
When I created the form with initial values (i.e. MyForm(initial=initial)), the defaults value are provided. So I wish I could do something like:
form = MyForm(initial=initial)
form = MyForm(form)
However, that obviously doesn't work.
I see two potential solution to my problem:
Validate an unbound form
Get a list with all the fields and their default values (if one) (I do not know this list in advance)
I do not know how to make 2, however, I tried this for 1:
form = MyForm(initial=initial)
form.clean()
form.clean() calls a custom function where I stated (I had no problem with a bound form):
cleaned_date = super().clean()
That returns the following errors:
AttributeError at XXX
'MyForm' object has no attribute 'cleaned_data'
And well, that is kind of logical, the form is unbound, thus the data are not cleaned yet.
Any piece of advice (or complete solution) will be greatly appreciated.
Thank you for having reading me.
Wrong Solution (that works, but too wordly/ugly)
There is one solution to this problem but I am sure this is not the could one (it is way too ugly).
I first create a unbound field:
form = MyForm()
Then I read the initial value of the fields inside the string representation of the form.
for field in form:
index_name = str(field.find("name"
name = field[index_name+len('name="':]
name = name[:name.find('"')]
index = str(field).find("value")
if index >= 0: # if their is a default value
value = field[index+len('value="'):]
value = value[:value.find('"')]
initial[name] = value
Then I just need to remake the form, bound this time:
form = MyForm(initial)
However, this solution is overwhelming and I am sure there is a better way to go.

Ideally you would get the default values and do an initial.update(defaults) and hand it to the form.
If the defaults are not available to you, you can try to remove fields from the form that are not in initial.
form = MyForm(data=initial)
field_keys = list(form.fields.keys())
for field_name in field_keys:
if field_name not in initial:
form.fields.pop(field_name)
form.is_valid() # <- only validates fields in initial

Maybe you can initialize your form with your initial values with no full control, and run form.is_valid() and use the form.cleaned_data to initial another form? But Why would you have to do validating on the unbound form? I think this scenario is rare.

Related

Handling a filter form along with other get parameters in Django

I have a django form with 2 required fields and some optional fields which filters the data presented in a view.
This view uses some GET parameters apart from the ones for the filter form and I'm initialising my filter from like form = MyFilterForm(request.GET or None) [see (1) in the code below].
I'm finding when my view is first loaded and there are no GET parameters this works fine as request.GET is falsey so the form doesn't get bound (and therefore we use the initial values for the required fields). If the filter form gets used then request.GET gets populated with the form parameters and all works well again. However if one of my other GET parameters (namely one used to sort the resulting data table) gets passed without the filter form getting used then request.GET is truthy but doesn't have any of the parameters that correspond to the form and so the form gets bound and errors as invalid because my required fields have not been given a value.
What should happen here is that data table should be sorted and the form should continue using the initial ('default') values, just like when it is first loaded.
This is a little tricky to explain and might be clearer with some code...
I've googled extensively as this feels like a problem other people must have too but haven't had any luck finding anything.
I'm using django-tables2 to present the data in a table for the user but this question is more around the form, the only thing you need to know about the table is that it allows a user to sort the data presented in the table by clicking a table heading - this then adds a GET parameter to the request with the column to sort by I.E ?sort=start_date.
class MyFilterForm(forms.Form):
date_from = DateTimeField(required=True)
date_to = DateTimeField(required=True)
user = ModelChoiceField(required=False, queryset=User.objects.all())
def __init__(self, *args, **kwargs):
super(MyFilterForm, self).__init__(*args, **kwargs)
# dates in correct timezone and at start/end of day
# initial values set in __init__ so that they aren't fixed at import time
week_start, week_end = get_start_end_week(timezone.now())
self.fields['date_from'].initial = week_start
self.fields['date_to'].initial = week_end
And in my view
import django-tables2 as tables
#login_required
def view_with_filter_form_and_table(request):
form = MyFilterForm(request.GET or None) # (1) The form gets bound when the table is sorted here as request.GET is truthy
if form.is_bound and form.is_valid():
date_from = self.cleaned_data['date_from']
date_to = self.cleaned_data['date_to']
else:
# use defaults if not bound or not valid
date_from = form.fields['date_from'].initial
date_to = form.fields['date_to'].initial
user = form.cleaned_data.get('user') if form.is_bound else None
query = Action.objects.all()
if date_from:
query = query.filter(date__gte=date_from)
if date_to:
query = query.filter(date__lte=date_to)
if user:
query = query.filter(user=user)
table = MyTable(query)
tables.RequestConfig(request, paginate=False).configure(table)
return render(request, 'my_form_and_table.html', {'form': form, 'table': table})
What I've considered
Checking that all the required fields are in request.GET before binding to the form. Apart from being a bit of a code smell because I'll need to instantiate an unbound form to iterate over all the fields and check if the required ones are in request.GET to even know if I should instantiate a bound form. The problem here is that if the form is edited and one of the required fields is set to empty by a user I still want validation errors to be shown, aka This field is required..
I've currently worked around the issue by adding the below code under the code labeled (1) but would love it if there was a cleaner solution.
for name, field in form.fields.items():
if field.required and name not in request.GET:
form = MyFilterForm(None) # unbound form
break

How to set a default value for a Django Form Field, that would be saved even in the absence of user initiated changes?

When looking for this feature, one is flooded under answers pointing toward the Form initial member.
Yet, by its design, this approach would not save anything to database if the user does not change at least one value in the form, because the has_changed method returns False when only initial values are submitted.
Then, if one were to override has_changed to always return true, the behaviour would be to try to save forms for which no value (nor initial nor user input) is found.
Is it possible to have a real default value in Django: a value that the user can change if he wants, but that would still save the form to DB when the form is only submitted with default values ?
Here is the solution I am currently using to replace the meaning of initial to achieve the behaviour described in the question. I override the had_changed method of the Form to be:
def has_changed(self):
for name, field in self.fields.items():
prefixed_name = self.add_prefix(name)
data_value = field.widget.value_from_datadict(self.data, self.files, prefixed_name)
if data_value and not issubclass(field.__class__, forms.models.InlineForeignKeyField):
return True
return False
The second check (issubclass(...)) is required in case the Form is used in an InlineFormset: when editing the parent model, the foreign_key field is automatically populated for all forms displayed (even the ones that have no default values), so it prevents saving the ones that are left blank (neither defaults nor user input).
Now, on a personal and probably polemic note, I must say I hope I missed something obvious. My requirements seem quite basic, yet the solution here is nothing short of a brittle hack...
I don't think there is a premade solution for you. You'll have to do one of two things:
When the form is submitted, examine the value of the field in question. If it is equal to the default value, then ignore the result of has_changed and save it. (Be aware that this could result in duplicate items being saved, depending on your schema.)
When the form is submitted, search for an existing record with those field values. If no such record exists, save it. Otherwise do nothing. (If these records contain a last-updated timestamp, you might update that value, depending on your application.)

Django: Check if user have added a new instance in the form?

I have a form, which i am rendering. It also allow user to add a new instance if it does not exist in the list.
lets say the field is name from category modal.
name is an drop down list, and if user does not find his value in the list, he writes the name in the next input box.
When i validate it, it fails. because its not an instance of category.
Then I got it by using self.data and i compare if its an Integer, or not?
If its an integer, than its simply an existing element, if not, the user might have chosen a new User, and thus by getting it from the self.data, i can create the
category
object and save it and replace the original value with the ID so that validation does not fail.
Problem.
I know using self.data and using it for a db query can be dangerous. As user is allowed to input anything in it, and it might empty my database with that query, (i have heard it like that). Is this really possible? if yes, how to avoid that.
Hope someone can give me an idea.
Suppose your form has a name field for existing category names and a new_name field for the input box. In your form's clean() method, you'll want to check to see if self.cleaned_data has a value for new_name and act accordingly. You might do:
self.cleaned_data['name'] = self.cleaned_data['new_name']
for example.
Unless you override other field or form methods, the data in these fields are 'safe' as far as malicious input is concerned. If you raise a ValidationError in the form's clean() method, is_valid() will return false. So, as long as you:
form = YourForm(...)
if form.is_valid():
form.save()
The save() will not be called with invalid data.

Model save update only specific fields

I'm trying to write a webservice which performs inserts or updates.
The request is a post with headers,value which contains table name, column name and the value to be set for each column, I'm parsing the request headers and forming a parameter dict
def handel_request(request): if request.method == "POST":
param_dict = formParmDict(request)
##if insert param_dict["Model"] is {'pk':1,'field1':100,'field2':200}
##if update param_dict["Model"] is {'pk':1,'field1':100}
Model(**param_dict["Model"]).save() ## if update then sets field2 to null
return HttpResponse()
else:
return HttpResponseBadRequest()
This works fine while the .save() performs an insert.
In case of update ie if param_dict["Model"] contains {pk:1, field1:somevalue} to be updated then it sets the rest of the fields other than the ones specified in param_dict["Model"] to null. why is that? am I doing something wrong? isn't save suppose to update only the fields specified?
This is not how you're supposed to update.
Model(**param_dict["Model"]).save()
You're creating a new instance with the same id. Instead, you should get the instance, and then update it appropriately.
m = Model.objects.get(id=param_dict['id'])
m.field = param_dict['some_field']
m.save()
Or, you can use the Manager update method:
Model.objects.filter(id=param_dict['id']).update(**param_dict['Model'])
There's also the get_or_create method if you're not sure whether or not the record already exists.
You can try using a REST framework, like tasty-pie or django-rest-framework, which might alleviate some problems you're having.
Edit:
A brief summary about how save works in django. This is what I meant about whether or not an INSERT or an UPDATE is happening. Unless your post_data dict contains empty values for all the fields, read the documentation on how save works for a more thorough understanding of how django works.
So, what is happening in your case is this:
dict = {'id': 1, 'field1': 'my_value'}
m = Model(**dict)
m.id # 1
m.field1 # my_value
m.field2 # None (because you haven't set it, it defaults to None
m.save() # UPDATEs the existing instance with id 1 with ALL of the values of `m`
So, you're saving an instance that contains None values. That's why I'm suggesting you do a get, so that all the correct values are filled, before saving to the database.
Maybe you shoul use some function like this:
def insert_or_update(param_dict):
pk = param_dict.get('pk', None)
if pk:
Model.objects.filter(pk=pk).update(**param_dict)
else:
Model(**param_dict)
Model.save()

Clearing Django form fields on form validation error?

I have a Django form that allows a user to change their password. I find it confusing on form error for the fields to have the *'ed out data still in them.
I've tried several methods for removing form.data, but I keep getting a This QueryDict instance is immutable exception message.
Is there a proper way to clear individual form fields or the entire form data set from clean()?
Regarding the immutable QueryDict error, your problem is almost certainly that you have created your form instance like this:
form = MyForm(request.POST)
This means that form.data is the actual QueryDict created from the POST vars. Since the request itself is immutable, you get an error when you try to change anything in it. In this case, saying
form.data['field'] = None
is exactly the same thing as
request.POST['field'] = None
To get yourself a form that you can modify, you want to construct it like this:
form = MyForm(request.POST.copy())
Someone showed me how to do this. This method is working for me:
post_vars = {}
post_vars.update(request.POST)
form = MyForm(post_vars, auto_id='my-form-%s')
form.data['fieldname'] = ''
form.data['fieldname2'] = ''
If you need extra validation using more than one field from a form, override the .clean() method. But if it's just for one field, you can create a clean_field_name() method.
http://docs.djangoproject.com/en/1.1/ref/forms/validation/#ref-forms-validation
I just created the form again.
Just try:
form = AwesomeForm()
and then render it.
Django has a widget that does that:
https://docs.djangoproject.com/en/1.8/ref/forms/widgets/#passwordinput
now if you are not working with passwords you can do something like this:
class NonceInput(Input):
"""
Hidden Input that resets its value after invalid data is entered
"""
input_type = 'hidden'
is_hidden = True
def render(self, name, value, attrs=None):
return super(NonceInput, self).render(name, None, attrs)
Of course you can make any django widget forget its value just by overriding its render method (instead of value I passed None in super call.)
Can't you just delete the password data from the form's cleaned_data during validation?
See the Django docs for custom validation (especially the second block of code).
I guess you need to use JavaScript to hide or remove the text from the container.

Categories

Resources