I have a Model form. The form contains a button "Add more Field". Clicking this button sends an AJAX call which should add a textfield to the form. Any number of "textfields" can be added.
I am not sure if Django-Formsets is the correct way to do it as I need to store the data of "extra added fields" in the same Model Form's Table in the database.
How can I achieve this ?
I did something similar to this recently, and my solution was to subclass the form dynamically, providing only the field needed, and rendering just that field:
from forms import SomeModelForm
from models import SomeModel
def view_name(request,pk,field):
SomeModelFieldForm(SomeModelForm):
class Meta(SomeModelForm.Meta):
fields = (field,)
inst = SomeModel.objects.get(pk=pk)
form = SomeModelFieldForm(instance=inst)
#the rest of your view goes here...
#...send only the form's field:
ctx = {'field': form[field]}
return render_to_response("template",ctx)
This takes advantage of your original form's specifics - i.e., if you have specially defined widgets, or other restrictions, or something. It then restricts the entire form to a single field. This is to allow the validation of a single field on an existing model.
However, you don't want to send the entire form, you only want to send the single field. form[field] is a django.forms.forms.BoundField that represents the specific field you pass in.
For example, if you were working with the User model from django.contrib.auth.models, and you created a form for a specifi user, in idle calling form["username"] would return:
<django.forms.forms.BoundField object at 0x01354750>
and calling print form["username"] would print:
<input id="id_username" type="text" name="username" value="name_of_user" maxlength="30" />
Related
Let's say I have an Contact object and I want to have two groups of contact Formsets in django(1.8) divided by fieldset tag in html template. I use modelformset_factory. Regardless I use one or two different factory functions, fields in these two formsets have same id in html. Since http.Request.body is dictionary, I lose information about one of the two formsets.
contacts_formset = modelformset_factory(
models.Contact,
form=forms.ContactDetailForm,
extra=2)
contacts_escalation_formset_new = contacts_formset(
queryset=models.Contact.objects.none())
contacts_other_formset_new = contacts_formset(
queryset=models.Contact.objects.none())
in HTML:
input id="id_form-0-name" maxlength="155" name="form-0-name" type="text"
input id="id_form-0-name" maxlength="155" name="form-0-name" type="text"
For simple django form, there is keyword "prefix=..." . But this factory function does not have this argument. How can I solve it?
The modelformset_factory class returns a FormSet class. This FormSet class has a optional prefix argument, similar to Form classes.
contacts_escalation_formset_new = contacts_formset(
prefix='escalation',
queryset=models.Contact.objects.none(),
)
contacts_other_formset_new = contacts_formset(
prefix='other'
queryset=models.Contact.objects.none(),
)
See the docs on using more than one formset in a view for another example.
I've come across How to create object from QueryDict in django? , which answers what I want to do. However I want to sanitize the data. What does the Brandon mean by "using a ModelForm" to sanitize posted data?
ModelForm are very helpful when you want to create just model instances. If you create a form that closely looks like a model then you should go for a model form instead. Here is an example.
Going by the example provided in the Django website.
In your forms.py
class ArticleForm(ModelForm):
class Meta:
model = Articels #You need to mention the model name for which you want to create the form
fields = ['content', 'headline'] #Fields you want your form to display
So in the form itself you can sanitize your data as well. There are 2 ways of doing that.
Way 1: Using the clean function provided by Django using which you can sanitize all your fields in one function.
class ArticleForm(ModelForm):
class Meta:
model = Articels #You need to mention the model name for which you want to create the form
fields = ['content', 'headline'] #Fields you want your form to display
def clean(self):
# Put your logic here to clean data
Way 2: Using clean_fieldname function using which you can clean your form data for each field separately.
class ArticleForm(ModelForm):
class Meta:
model = Articels #You need to mention the model name for which you want to create the form
fields = ['content', 'headline'] #Fields you want your form to display
def clean_content(self):
# Put your logic here to clean content
def clean_headline(self):
# Put your logic here to clean headline
Basically you would use clean and clean_fieldname methods to validate your form. This is done to raise any error in forms if a wrong input is submitted. Let's assume you want the article's content to have at least 10 characters. You would add this constraint to clean_content.
class ArticleForm(ModelForm):
class Meta:
model = Articels #You need to mention the model name for which you want to create the form
fields = ['content', 'headline'] #Fields you want your form to display
def clean_content(self):
# Get the value entered by user using cleaned_data dictionary
data_content = self.cleaned_data.get('content')
# Raise error if length of content is less than 10
if len(data_content) < 10:
raise forms.ValidationError("Content should be min. 10 characters long")
return data_content
So here's the flow:
Step 1: User open the page say /home/, and you show the user a form to add new article.
Step 2: User submits the form (content length is less than 10).
Step 3: You create an instance of the form using the POST data. Like this form = ArticleForm(request.POST).
Step 4: Now you call the is_valid method on the form to check if its valid.
Step 5: Now the clean_content comes in play. When you call is_valid, it will check if the content entered by user is min. 10 characters or not. If not it will raise an error.
This is how you can validate your form.
What he mean is that with ModelForm you can not only create model instance from QueryDict, but also do a bunch of validation on data types and it's requirements as for example if value's length correct, if it's required etc. Also you will pass only needed data from QueryDict to model instance and not whole request
So typical flow for this is:
form = ModelForm(request.POST)
if form.is_valid():
form.save()
return HttpResponse('blah-blah success message')
else:
form = ModelForm()
return HttpResponse('blah-blah error message')
And awesome Django docs for this: https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#django.forms.ModelForm
Django's ModelChoiceField is the default form field used for foreign keys when deriving a form from a model using ModelForm. Upon validation, the field will check that selected value does exist in the corresponding related table, and raise a ValidationError if it is not the case.
I'm creating a form for a Document model that has a type field, a foreign key to a Type model which does only contain a name attribute. Here is the code of models.py for clarity
class Type(models.Model):
name = models.CharField(max_length=32)
class Document(models.Model):
title = models.CharField(max_length=256)
type = models.ForeignKey(Type, related_name='related_documents')
Instead of a standard select control, I'm using selectize.js to provide auto-completion to the users. Moreover, selectize provides a "create" option, that allows to enter a value that does not exist yet into the select.
I would like to extend the ModelChoiceField in order to create a new Type object when the selected value does not exist (the new value will be assigned to name field, this should be an option of the field for reusability). If possible, I would like the object to not be inserted into DB until save() is called on the validated form (to prevent that multiple failed validation create multiple rows in db). What would be a good way to do so in Django? I tried to look into the documentation and the source code, tried to override ModelChoiceField and tried to build this behavior basted on a TextField but I'm not sure if there isn't a simpler way to do it.
I looked into the following questions but couldn't find the answer.
Django ModelChoiceField has no plus button
Django: override RelatedFieldWidgetWrapper
How to set initial value in a dynamic Django ModelChoiceField
I would like to keep the process of adding new types as simple as possible - i.e.: do not use a pop-up attached to a '+' button. User should be able to type the value, and the value gets created if it doesn't exist.
Thanks
This seems like it'd be easier without using a ModelForm. You could create a list of all of your Type objects then pass that to the template as a context variable. Then, you could use that list to construct a <select> element. Then use jquery and selectize to add the necessary attributes to the form.
#views.py
...
types = Type.objects.all()
...
#template.html
...
<form action="" method="POST">{% csrf_token %}
<input type='text' name='title'>
<select name='type' id='id_type'>
{% for type in types %}
<option value="{{type.id}}">{{type.name}}</option>
{% endfor %}
</select>
<input type='submit'>
</form>
<script type='text/javascript'>
$('#id_type').selectize({
create: true
});
</script>
...
Then when you get a form submission, you can process it in a simple view function:
if request.method == POST:
title_post = request.POST.get('title','')
type_post = request.POST.get('type',None)
if type_post is not None:
try:
#If an existing Type was selected, we'll have an ID to lookup
type = Type.objects.get(id=type_post)
except:
#If that lookup failed, we've got a user-inputted name to use
type = Type.objects.create(name=type_post)
new_doc = Document.objects.create(title=title_post, type=type)
I am trying to create a form in python / Flask that will add some dynamic slider inputs to a set of standard fields. I am struggling to get it to work properly, though.
Most of the web forms in my app are static, created through wtforms as in:
class CritiqueForm(Form):
rating = IntegerField('Rating')
comment = TextAreaField('Comments')
submit = SubmitField('Save Critique')
When I am explicit like that, I can get the expected results by using the CritiqueForm() in the view and passing the form object to render in the template.
However, I have a critique form that needs to dynamically include some sliders for rating criteria specific to a particular record. The number of sliders can vary form one record to the next, as will the text and IDs that come from the record's associated criteria.
When I looked for some ways to handle this, I found a possible solution from dezza (Dynamic forms from variable length elements: wtforms) by creating a class method in the form, which I could then call before instantiating the form I want to render. As in:
class CritiqueForm(Form):
rating = IntegerField('Rating')
comment = TextAreaField('Comments')
submit = SubmitField('Save Critique')
#classmethod
def append_slider(cls, name, label):
setattr(cls, name, IntegerField(label))
return cls
where 'append_slider' is always an IntegerField with a label I provide. This works enough to allow me to populate the criteria sliders in the view, as in:
#app.route('/critique/<url_id>/edit', methods=['GET', 'POST'])
def edit_critique(url_id):
from app.models import RecordModel
from app.models.forms import CritiqueForm
record = RecordModel.get_object_by_url_id(url_id)
if not record: abort(404)
# build editing form
ratings = list()
for i, criterium in enumerate(record.criteria):
CritiqueForm.append_slider('rating_' + str(i+1),criterium.name)
ratings.append('form.rating_' + str(i+1))
form = CritiqueForm(request.form)
# Process valid POST
if request.method=='POST' and form.validate():
# Process the submitted form and show updated read-only record
return render_template('critique.html')
# Display edit form
return render_template('edit_critique.html',
form=form,
ratings=ratings,
)
The ratings list is built to give the template an easy way to reference the dynamic fields:
{% for rating_field in ratings %}
{{ render_slider_field(rating_field, label_visible=True, default_value=0) }}
{% endfor %}
where render_slider_field is a macro to turn the IntegerField into a slider.
With form.rating—an integer field explicitly defined in CritiqueForm—there is no problem and the slider is generated with a label, as expected. With the dynamic integer fields, however, I cannot reference the label value in the integer field. The last part of the stack trace looks like:
File "/home/vagrant/msp/app/templates/edit_critique.html", line 41, in block "content"
{{ render_slider_field(rating_field, label_visible=True, default_value=0) }}
File "/home/vagrant/msp/app/templates/common/form_macros.html", line 49, in template
{% set label = kwargs.pop('label', field.label.text) %}
File "/home/vagrant/.virtualenvs/msp/lib/python2.7/site-packages/jinja2/environment.py", line 397, in getattr
return getattr(obj, attribute)
UndefinedError: 'str object' has no attribute 'label'
Through some debugging, I have confirmed that none of the expected field properties (e.g., name, short_name, id ...) are showing up. When the dust settles, I just want this:
CritiqueForm.append_slider('rating', 'Rating')
to be equivalent to this:
rating = IntegerField('Rating')
Is the setattr() technique inherently limiting in what information can be included in the form, or am I just initializing or referencing the field properties incorrectly?
EDIT:
Two changes allowed my immediate blockers to be removed.
1) I was improperly referencing the form field in the template. The field parameters (e.g., label) appeared where expected with this change:
{% for rating_field in ratings %}
{{ render_slider_field(form[rating_field], label_visible=True, default_value=0) }}
{% endfor %}
where I replace the string rating_field with form[rating_field].
2) To address the problem of dynamically changing a base class from the view, a new form class ThisForm() is created to extend my base CritiqueForm, and then the dynamic appending is done there:
class ThisForm(CritiqueForm):
pass
# build criteria form fields
ratings = list()
for i, criterium in enumerate(record.criteria):
setattr(ThisForm, 'rating_' + str(i+1), IntegerField(criterium.name))
ratings.append('rating_' + str(i+1))
form = ThisForm(request.form)
I don't know if this addresses the anticipated performance and data integrity problems noted in the comments, but it at least seems a step in the right direction.
setattr(obj, name, value) is the very exact equivalent of obj.name = value - both being syntactic sugar for obj.__setattr__(name, value) -, so your problem is not with "some limitation" of setattr() but first with how wtform.Form works. If you look at the source code, you can see there's much more to make fields and form work together than just having the fields declared as class attributes (metaclass magic involved...). IOW, you'll have to go thru the source code to find out how to dynamically add fields to a form.
Also, your code tries to set the new fields on the class itself. This is a big NO NO in a multiprocess / multithreaded / long-running process environnement with concurrent access - each request will modify the (shared at process level) form class, adding or overriding fields aphazardly. It might seem to work on a single-process single-threaded dev server with a single concurrent user but will break in production with the most unpredictable errors or (worse) wrong results.
So what you want to find out is really how to dynamically add fields to a form instance - or, as an alternative, how to dynamically build a new temporary form class (which is far from difficult really - remember that Python classes are objects too).
I have a model with a location field that is mapped to cities_light.city and I'm using an autocomplete field that allows users to type in their city and have it autocompleted to the correct/valid location model instance.
class Profile(models.Model):
location = models.ForeignKey(City, blank=True, null=True)
class ProfileForm(ModelForm):
class Meta:
model = Profile
fields = ('location')
widgets = {
'location': autocomplete_light.TextWidget(CityAutocomplete, autocomplete_js_attributes={'placeholder':'City, Country', 'minimum_characters':4})
}
The form field works exactly as advertised and a list of autocomplete options are shown. However when I save the form/model I get a validation error which seems to be caused by the field not being translated into the primary key of the City model instance.
Select a valid choice. That choice is not one of the available choices.
I'm guessing I need to extend the AutocompleteModelBase like the CityAutocomplete implemented below but I'm not sure how and I've been unable to find a working example.
class CityAutocomplete(autocomplete_light.AutocompleteModelBase):
search_fields = ('search_names',)
https://github.com/yourlabs/django-cities-light/blob/master/cities_light/contrib/autocompletes.py
Thank for any assistance and I'm sorry if my question is poorly formatted.
Your problem is not specific to django-autocomplete-light. What you're doing has no chance to work and here's why:
the form field for a ForeignKey like location is a ModelChoiceField by default,
ModelChoiceField accepts values which are pks of models in ModelChoiceField.queryset, which is TheModel.objects.all() by default,
the TextWidget widget is a TextInput,
a TextInput widget is just an <input type="text" />,
the value of a <input type="text" /> is directly sent to the server on form submission.
As a result, selecting a couple of cities like "Lisboa" and "Madrid" with a text widget will look like::
<input type="text" value="Lisboa, Madrid" name="location" />
Which means that the form will post {'location': 'Lisboa, Madrid'}. While this is good for a CharField, it won't work for a ModelMultipleChoiceField which would expect something like {'location': [3,5]} where 3 would be the pk of Lisboa and 5 the pk of Madrid.
In the same fashion, a ModelChoiceField would expect {'location': 3} which autocomplete_light.ChoiceWidget is able to do.
To fix this, use a ChoiceWidget instead of a TextWidget. I have clarified this in the tutorial I hope it is better now.