I have an abstract form and 2 forms using it. In the is_valid method of the abstract form, I check for a condition and that condition depends on the form calling the method.
The line is:
if eval(self.cleaned_data.get("validated_modif")):
According to the form, it should be replaced by one of the following lines:
if act.validated_attendance==0: #MinAttend form
if act.validated<2: #Act form
I have a code that works. But it is very (very) dirty, I am looking for a better idea.
forms.py:
class AbstractModif(forms.Form):
releve_mois_modif=forms.IntegerField(min_value=1, max_value=12)
class Meta:
#abstract form
abstract = True
#check if the searched act already exists in the db and has been validated
def is_valid(self, *args, **kwargs):
# run the parent validation first
valid=super(AbstractModif, self).is_valid()
# we're done now if not valid
if not valid:
return valid
#if the form is valid
Model=Act
fields={}
fields["releve_mois"]=self.cleaned_data.get("releve_mois_modif")
try:
act=Model.objects.get(**fields)
if Model!=Act:
act=act.act
#MinAttend form: check act.validated_attendance=0
#Act form: check act.validated<2
if eval(self.cleaned_data.get("validated_modif")):
self._errors['__all__']=ErrorList([u"The act you are looking for has not been validated yet!"])
return False
except Exception, e:
self._errors['__all__']=ErrorList([u"The act you are looking for doesn't exist in our database!"])
print "exception", e
return False
# form valid -> return True
return True
form_1.py:
class Modif(AbstractModif):
#fake field for the is_valid method
validated_modif=forms.CharField(widget=forms.HiddenInput(), initial="act.validated<2")
form_2.py
class Modif(AbstractModif):
#fake field for the is_valid method
validated_modif=forms.CharField(widget=forms.HiddenInput(), initial="act.validated_attendance==0")
form.html
<!-- hidden field for the is_valid method of the form -->
{{ modif.validated_modif }}
I use eval and the initial value of a hidden field to check the condition. Do you have a nicer solution?
Thank you!
This is an immensely bad idea. You're running eval on input received from the browser. So if I use the browser dev tools to modify the contents of the hidden field to os.system('rm -rf /'), what do you think would happen?
I can't see any need for this at all. You have two form subclasses; why don't you simply put the validation in a method in those subclasses?
class Form1(AbstractForm):
def validate_modif(self, act):
return act.validated < 2
class Form(AbstractForm):
def validate_modif(self, act):
return act.validated_attendance == 0
and you can simply call self.validate_modif(act) to perform the validation.
Note also that you should not be overriding is_valid(), but clean(). And your Meta class does nothing because this is a normal Form, not a ModelForm.
Related
So I have this flask app I'm making and I need some help with a variable access.
Most of the time, when you define a form in flask, you'll do the following :
class MyForm(Form):
my_field = StringField('I'm a field')
my_submit = SubmitField('Go!')
And when the time comes where you need the form, you'll declare an instance of that class with form = MyForm()
Up to here, it's all good, However :
If you want say, a SelectField (Dropdown) where the choices depend on the answers of a previous form, you need to be able to give the new form those choices. This is what I'm trying to achieve, but I can't get a variable to keep its contents.
Here is my Form code (Above the page code):
class DataMappingForm(Form):
dm_choices = #I need this array !
DMpatient_id = SelectField(u'Select Patient ID Column',
choices=dm_choices, validators=[Required()])
...
Here is my Page code :
#app.route('/upload', methods=['GET','POST'])
def upload():
uform = SomeOtherForm()
if uform.is_submitted() and uform.data['Usubmit']:
#Do stuff from previous form
# and declare array_of_choices
dmform = DataMappingForm() #Needs array_of_choices to work
...
What I've tried so far :
session['dm_choices'] gives me a working outside of request context error
global variables, get reset for some reason
overloading the __init__ of Form by adding the array but i can't access it in the parts above the __init__ function.
I should mention, this all needs to be on the same page.
Is there a way to pass this array_of_choices to my DataMappingForm class ?
EDIT This is what it looked like when I trid the __init__ overload:
class DataMappingForm(Form):
def __init__(self, dm_choices, *args, **kwargs):
self.dm_choices = dm_choices
Form.__init__(self, *args, **kwargs)
DMpatient_id = SelectField(u'Select Patient ID Column',
choices=dm_choices, validators=[Required()])
#I've tried putting it above or below, I get 'dm_choices is not defined'
I've Got it ! Thanks to #synonym for pointing me in the right direction with your last link.
All you need to do is declare a function in which the class is defined. You then pass the variable to the function, and it will be accessible within the class.
Finally, make the function return the form object.
Example :
def makeMyForm(myArray):
def class MyForm(Form):
my_select_field = SelectField(u'I'm a select field', choices=myArray)
my_submit = SubmitField(u'Go!')
return MyForm()
And to make the form, you use :
form = makeMyForm(theArrayYouWant)
And VoilĂ !
Note : As I've had the problem before, I'll mention that the Array is composed of tuples :
myArray = [('value','What you see'),('value2','What you see again')]
If you want to dynamically change the choices of a SelectField the following should work:
class DataMappingForm(Form):
def __init__(self, choices)
self.DMpatient_id.choices = choices
DMpatient_id = SelectField(u'Select Patient ID Column') #note that choices is absent
If you want fully dynamic fields you can create the class dynamically in a function. From the WTForms Documentation:
def my_view():
class F(MyBaseForm):
pass
F.username = StringField('username')
for name in iterate_some_model_dynamically():
setattr(F, name, StringField(name.title()))
form = F(request.POST, ...)
# do view stuff
In that case you can customize the form as much as you want. Of course in the case you only want to customize the choices the first approach should be enough.
I have this django field called is_private indicating whether the posting done by the user is private or not. If the posting is private then a certain field called private_room must be mentioned otherwise a field called public_room is required.
In the clean_private_room and clean_public_room fields I'm doing a check to see the value of is_private. If the room is private then in the clean_public_room method I simply return an empty string "" and the same for clean_private_room otherwise I continue with the validation.
The problem is that checking with self.cleaned_data.get('is_private') is returning different results in those two methods. I tried debugging the code and printed the self.cleaned_data value to the terminal and in one of the methods cleaned data contains one form field and in the other method contains my full posted values.
Here's a part of my code, please read the comments in it to see where I print and what gets printed. I don't know why it's behaving this way.
class RoomForm( forms.ModelForm ):
...
def clean_is_private( self ):
if not 'is_private' in self.cleaned_data:
raise forms.ValidationError("please select the type of room (private/public)")
return self.cleaned_data.get("is_private")
def clean_public_room( self ):
print "<clean_public_room>"
# !!!!!!!!!
# when printing this one I only get one form value which is: public_room
print self.cleaned_data
if self.cleaned_data.get("is_private"):
return ""
# otherwise....
if not self.cleaned_data.get("public_room"):
raise forms.ValidationError(
'you need to mention a public room'
)
return self.cleaned_data[ 'public_room' ]
def clean_private_room( self ):
print "<clean_private_room>"
# !!!!!!!!!
# when printing this one I get all form values: public_room, private_room, is_private
print self.cleaned_data
if not self.cleaned_data.get("is_private"):
return ""
# otherwise....
if not self.cleaned_data.get("private_room"):
raise forms.ValidationError(
'you need to mention a private room'
)
return self.cleaned_data[ 'private_room' ]
Form fields are cleaned in the order they defined in the form. So you just need to put is_private field before the public_room in the fields list.
I just want to know how can I set initial values to empty_form.
I do create the Inlines with initial values for extra forms without problem, but, when user clicks to Add button, the fields I expect it have the initial values show up empty, and I hope it have the same initial values than extra forms.
How could I make the empty_form to be filled with initial data?
Thanks in advance.
Django doesn't really provide a way to set initial values for empty forms. I've found a couple ways to work around this:
Set the field values dynamically in javascript.
Overwrite the empty_form property for your formset.
example:
formset = formset_factory(MyClass, **kwargs)
empty = formset.empty_form
# empty is a form instance, so you can do whatever you want to it
my_empty_form_init(empty_form)
formset.empty_form = empty_form
I had a similar problem and what finally worked for me was using Django Dynamic Formset. What DDF does is instead of using the empty form to create the new formset, it uses one of the extra_forms as a template. The default behavior is to clear all field values from the extra_form before inserting the HTML to the DOM, but you can use the keepFieldValues setting to specify the ones you want to keep.
In my case I wanted to keep all hidden field values:
$(function() {
$('#myForm_table tbody tr').formset({
keepFieldValues: 'input:hidden',
}
});
});
Of course you can bypass Django Dynamic Formsets and implement your own add/delete code with Javascript if you prefer.
Accepted answer didn't work for me, hopefully this will help someone in the future, this is my solution:
Create a new class based on BaseInlineFormSet
Override empty_form
Create a FormSet with inlineformset_factory(formset=YourBaseInlineFormSet)
Create a formset instance and pass parameters to initial on the formset instance
Add the field on the HTML as usual
I used BaseInlineFormSet, but probably will work with other types of FormSet
verification is the name of the field for my example.
forms.py
class YourBaseInlineFormSet(forms.BaseInlineFormSet):
#property
def empty_form(self): # This is almost the same as Django 3.1 code
form = self.form(
auto_id=self.auto_id,
prefix=self.add_prefix("__prefix__"),
empty_permitted=True,
use_required_attribute=False,
initial={"verification": self.initial_extra[0]["verification"]}, # This is the extra parameter
**self.get_form_kwargs(None),
)
self.add_fields(form, None)
return form
YourFormSet = forms.inlineformset_factory(
SomeObject,
SomeRelatedObject,
fields="__all__",
widgets={"verification": forms.HiddenInput},
formset=YourBaseInlineFormSet,
)
views.py
from .forms import YourFormSet
def your_view(request):
formset = YourFormSet(
data=request.POST or None,
instance=object,
queryset=object.related_objects.all()
initial=[{"verification": verification} for a in range(FormSet().total_form_count())],
)
return render(request, template, context={'formset': formset})
template.html
<div id="empty_form" style="display:none">
{{ formset.empty_form }}
</div>
Working on Django 3.1
There is at least one way to do this: Specify the default value on your model Field.
Of course, this may have side effects, depending on your implementation of the model.
As #jkk-jonah mentioned, BaseFormSet does not provide a way to set initial values in the empty_form. However, a small change can provide a simple solution.
The following provides a way to supply the FormSet instance with empty initial values without disrupting its base behavior.
from django.forms.formsets import BaseFormSet
class FormSetWithDefaultEmptyFormInitials(BaseFormSet):
"""This formset enables you to set the initial values in ``empty_form``.
Usage: ``formset_factory(..., formset=FormSetWithDefaultEmptyFormInitials)``
"""
def __init__(self, *args, **kwargs):
if 'empty_initial' in kwargs:
self._empty_initial = kwargs.pop('empty_initial')
super().__init__(*args, **kwargs)
def get_form_kwargs(self, index):
"""Augmented to return the empty initial data
when the index is ``None``,
which is the case when creating ``empty_form``.
"""
if index is None:
kwargs = self.form_kwargs.copy()
if self._empty_initial:
# Assign the initial value passed to the Form class.
kwargs['initial'] = self._empty_initial
else:
kwargs = super().get_form_kwargs(index)
return kwargs
Then to use this you'd do something like:
NonEmptyFormSet = formset_factory(
BringYourOwnForm,
min_num=1,
extra=1,
formset=FormSetWithDefaultEmptyFormInitials,
)
# Let's say your form has name and address fields...
empty_form_initial_values = {'name': 'default name', 'address': 'default address'}
formset = NonEmptyFormSet(empty_initial=empty_form_initial_values)
asset formset.empty_form.initial == empty_form_initial_values
In my implementation empty_form is used to provide a template for frontend javascript to add additional forms to the formset. Thus, this allows me to set the initial values for that all of the forms in that formset.
Note, this does not take the place of initial values to the minimum number of forms within the formset (e.g. formset_factory(min_num=2, ...)). Therefore, it is necessary to assign those through the standard initial keyword argument.
Tested with Django 3.2.
See also the standard implementation of get_form_kwargs.
This partially extends the answer given by #RobertPro. Or at least, I used their answer as the stepping stone to my own solution.
I am trying to follow the code listed on https://github.com/alex/django-ajax-validation/blob/master/ajax_validation/views.py
I have been able to understand a small chunk of it. I have added comments stating my understanding of what is happening.
I would really appreciate some assistance on questions I listed in comments next to the lines I couldn't quite follow.
def validate(request, *args, **kwargs):
# I thing it is some sort of initializations but I cannot really understand what's happening
form_class = kwargs.pop('form_class')
defaults = {
'data': request.POST
}
extra_args_func = kwargs.pop('callback', lambda request, *args, **kwargs: {})
kwargs = extra_args_func(request, *args, **kwargs)
defaults.update(kwargs)
form = form_class(**defaults)
if form.is_valid(): #straightforward, if there is no error then the form is valid
data = {
'valid': True,
}
else:
# if we're dealing with a FormSet then walk over .forms to populate errors and formfields
if isinstance(form, BaseFormSet): #I cannot really understand what is BaseFromSet
errors = {}
formfields = {}
for f in form.forms: # I am guessing that this is for when there are multiple form submitted for validation
for field in f.fields.keys(): # I think he is looping over all fields and checking for error. what does add_prefix () return? and what is formfields[]?
formfields[f.add_prefix(field)] = f[field]
for field, error in f.errors.iteritems():
errors[f.add_prefix(field)] = error
if form.non_form_errors():
errors['__all__'] = form.non_form_errors() # what is the '__all__'?
else:
errors = form.errors
formfields = dict([(fieldname, form[fieldname]) for fieldname in form.fields.keys()])
# if fields have been specified then restrict the error list
if request.POST.getlist('fields'): # I am having a hard time understanding what this if statement does.
fields = request.POST.getlist('fields') + ['__all__']
errors = dict([(key, val) for key, val in errors.iteritems() if key in fields])
final_errors = {} # here the author of this code totally lost me.
for key, val in errors.iteritems():
if '__all__' in key:
final_errors[key] = val
elif not isinstance(formfields[key].field, forms.FileField):
html_id = formfields[key].field.widget.attrs.get('id') or formfields[key].auto_id
html_id = formfields[key].field.widget.id_for_label(html_id)
final_errors[html_id] = val
data = {
'valid': False or not final_errors,
'errors': final_errors,
}
json_serializer = LazyEncoder() # Why does the result have to be returned in json?
return HttpResponse(json_serializer.encode(data), mimetype='application/json')
validate = require_POST(validate) # a decorator that requires a post to submit
LazyEncoder
class LazyEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, Promise):
return force_unicode(obj)
return obj
form_class = kwargs.pop('form_class')
This is simply pulling the keyword argument, form_class, that was passed in via the URL conf.
(r'^SOME/URL/$', 'ajax_validation.views.validate',
{'form_class': ContactForm}, # this keyword argument.
'contact_form_validate')
BaseFormSet is simply the formset class doing the work behind the scenes. When you don't know, search the source! grep -ri "baseformset" . It's an invaluable tool.
Take a look at at django.forms.formsets to see how formset_factory produces new "formset" classes based on the BaseFormSet, hence the factory part!
I am guessing that this is for when there are multiple form submitted for validation
Yes, that's exactly what a formset is for (dealing with multiple forms)
I think he is looping over all fields and checking for error. what does add_prefix () return? and what is formfields[]?
Yes, that would be looping through the field names.
add_prefix() is for prefixing form field names with a specific form. Because a formset repeats form elements multiple times, each field needs a unique prefix, such as 0-field1, 1-field1, etc.
formfields is just an empty dictionary defined a few lines above.
what is the 'all'?
__all__ is defined at the top of django.forms.forms
NON_FIELD_ERRORS = '__all__'
It's just what non field specific errors (such as constraints across 2 fields) are stored under in the errors dictionary as opposed to errors[fieldname].
I am having a hard time understanding what this if statement does.
The author has left a note:
# if fields have been specified then restrict the error list
if request.POST.getlist('fields'):
It's checking if you specified any specific fields to validate in your URLConf, this is not django but ajax_validation.
You can see that he's overwriting his errors dictionary based on only the fields specified, thus passing on the validation only for those fields.
errors = dict([(key, val) for key, val in errors.iteritems() if key in fields])
here the author of this code totally lost me.
The author has mapped a custom errors and fields dictionary to specific field names with prefixes, (as opposed to the usual FormSet with each form having its own errors dictionary, unaware of the formset itself) which he presumably uses in the AJAX response to validate all fields.
Normally, you can iterate over a formset and go through the errors on a form by form basis, but not so if you need to validate all of them through ajax.
The line pulling html_id should be straight forward most of the time, but it's there because form widgets CAN add interesting things to the end of the ID's based on whether or not the widget is a radio select for example.
From source comments :
# RadioSelect is represented by multiple <input type="radio"> fields,
# each of which has a distinct ID. The IDs are made distinct by a "_X"
# suffix, where X is the zero-based index of the radio field. Thus,
# the label for a RadioSelect should reference the first one ('_0').
Why does the result have to be returned in json?
Because it's an ajax request and javascript easily eats json.
2- could you go through these lines of code...
extra_args_func = kwargs.pop('callback', lambda request, *args, **kwargs: {})
Either return a keyword argument named 'callback' (which if passed in, is supposed to be a function that accepts request and return a dictionary), and if it wasn't, return a lambda function that only returns an empty dictionary.
I'm not sure what the specific use is for the extra context. You could use it to run arbitrary snippets of code without modifying or subclassing ajax_validation...
It might help you to run this code, and put a debugger breakpoint in somewhere so you can step through and examine the variables and methods. You can do this by simply putting this line where you want to break:
import pdb; pdb.set_trace()
and you will be dumped into the debugger in the console.
I have a form 'in the wild' that takes many different variables - which may or may not be populated.
try:
app_version = request.REQUEST["appVersion"]
except:
app_version = ''
try:
app_name = request.REQUEST["appName"]
except:
app_name = ''
try:
app_code_name = request.REQUEST["appCodeName"]
except:
app_code_name = ''
Is there a tighter way to accomplish this?
app_version = request.REQUEST.get("appVersion", "")
get(key, default) is a method implemented on Python dicts. If the key exists in the dictionary, its value is returned; if the key does not exist, the specified default value is returned. In Django, request objects are dictionary-like objects, so get is also defined for them in the same manner.
If these variables are intended to populate a form, then you can safely pass the request.POST object directly into the form constructor.
if request.method == 'POST':
form = MyForm(request.POST)
The form will automatically pass the correct values to the correct form fields and use defaults for keys that don't exist and will still create blank fields for missing keys (see addendum).
If you are trying to process a form, it is still better to create a form object as above, and read out the values from that object.
if request.method == 'POST':
form = MyForm(request.POST)
if form.is_valid():
# You may process these variables here
print form.appVersion
print form.appName
print form.appCodeName
Remember, validation code is best placed in the form class as well. That way, if form.is_valid() returns True, then you know you have a clean dataset to work with.
Note: Django docs recommend using request.POST or request.GET directly rather than the amalgamated variable request.REQUEST, as it is more explicit.
Addendum:
It is important to understand the difference between bound and unbound forms in this case. If you create an unbound form with form = MyForm(), then when the form is instantiated, it will fill in all fields with the initial property of each field (if it exists). For example, with this code:
from django import forms
class MyForm(forms.Form):
appVersion = forms.CharField(initial='1.0')
appName = forms.CharField()
appCodeName = forms.CharField()
the form will be initialized with appVersion having a value of '1.0'. However, if you bind a POST request to a form like this: form = MyForm(request.POST), then the initial properties are ignored. That means if the POST dict does not include an appVersion key, then that field will be left blank. As long as the field is not required, your form will still validate, and you can modify form.appVersion in the view after validation.
If you have many fields, a more compact version might be:
defaults = { 'field1' : 'val1', 'field2' : 'val2', ...}
defaults.update(request.POST)