Django: Change the DOM id at form field level - django

I understand that, by default, Django auto-populates id for each form field upon rendering with the format id_for_%s. One can modify the format by providing the auto_id argument with a different format as its value to the Form constructor.
That's not exactly what I am looking for, however. What I want to accomplish is changing the id of just one of the many fields in my form. Also, the solution should not break the use of form = MyForm(request.POST).
PS. MyForm is a model form, so each id is derived from its corresponding Model field.
Thanks for helping out.

The forms framework appears to generate labels here:
def _id_for_label(self):
"""
Wrapper around the field widget's `id_for_label` class method.
Useful, for example, for focusing on this field regardless of whether
it has a single widget or a MutiWidget.
"""
widget = self.field.widget
id_ = widget.attrs.get('id') or self.auto_id
return widget.id_for_label(id_)
id_for_label = property(_id_for_label)
Which means you can just supply your field widget with an "id" key to set it to whatever you'd like.
foo = forms.CharField(widget=forms.TextInput(attrs={'id': 'foobar'}))
Or override init and set the attrs after form initialization.
I don't see how this could break a form as django's forms framework isn't ever aware of HTML ids (that data is not passed to the server...)

Related

How to get an auto modified field in django model?

Say I have a model User, which has a credits field (IntegerField). When a user registers, I will set the credits field to 0, and I will update the credits for certain events.
I don't want the user know there is a field like this in the db table.
What attribute should I set to the field?
To accomplish the defaulting to 0 part, you can simply use the default argument of the model field.
For the part where you don't want your users to know about the field, you have a couple choices.
Solution 1: Field.editable
Defining your field as follows will cause the field to never show up in a model form.
credits = models.IntegerField(default=0, editable=False)
Downsides
You won't be able to edit the field's value in the admin
Form validation will never take this field into account (e.g., def clean_credits(self): won't run)
Solution 2: ModelForm.exclude|fields
Creating a ModelForm for the model is something you're going to be doing. You can define an exclude attribute on the form's Meta class, and add "credits" to the list. See the docs linked above. You can instead define fields on the Meta class, and omit "credits". The latter of the two options is considered a better practice, particularly when pertaining to security, and is known as a whitelist.
Downsides
You have to remember to define exclude or fields on every exposed form
Updating the "secret" field
The proper way to handle specifying a "secret" field's value when the field isn't in the form is:
# Inside your view's post method (or FormView.form_valid, if you're using generic views)
instance = form.save(commit=False) # Does everything except INSERT into the database
instance.credits = <however many credits you feel like giving the user>
instance.save()
If you didn't do that, and instead just saved the form as-is, the value specified by default would be set to the instance's credits field.
You'll want to use an IntegerField with default=0: credits = models.IntegerField(default=0). Just take care not to show this field to the user in any forms or when displaying the user.
E.g., if you had a ModelForm for User, do not include credits in the fields field of Meta

How to render django form differently based on what user selects?

I have a model and a form like this:
class MyModel(models.Model):
param = models.CharField()
param1 = models.CharField()
param2 = models.CharField()
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('param', 'param1', 'param2')
Then I have one drop down menu with different values and based on what value is selected I'm hiding and showing fields of MyForm. Now I have to take one step further and render param2 as a CheckboxInput widget if user selects a certain value from a drop down but in other cases it should be standard text field. So how would I do that?
I know this post is almost a year old, but it took me multiple hours to even find a post related to this topic (this is the only one I found, which came up as related when submitting my own question), so I felt the need to share my solution.
I wanted to have a form that would show and require a text field if an option from a dropdown menu matched a value stored in another model. I had a foreignKey relation between two models and I passed an instance of Model1 into the ModelForm for Model2. If a value chosen for a variable in Model2 matched a variable already set in Model1, I wanted to show and require a textfield. It was basically a "choose Other and then enter your own description" scenario.
I did not want the page to reload (I was trying to have this work in both mobile and desktop browsers with the least delay/reloads and using the same code for both), so I could not use the mentioned multiple forms loading in a view option. I started trying to do it with AJAX as suggested above when I realized I was over thinking the problem.
The answer was using JS and clean methods in the form. I added a non-required field (field1) that was not in Model2 to my Model2Form. I then hid this using jQuery and only displayed it (using jQuery) if the value of another field (field2) matched the value of the variable from Model1. To make that work, I did decide to have a hidden < span > in my template with the pk of the variable so I could easily grab it with jQuery. This jQuery worked perfectly for hiding and showing the field correctly so the user could choose the "other" value and then decided to choose a different one instead (and go back and forth endlessly).
I then used a clean method in my Model2Form for field1 that raised a ValidationError if no value was entered when the value in field2 matched my Model1 variable. I accessed that variable by using "self.other = Model1.variable" in my __ init __ method and then referencing that in the clean_field1 method.
I would have liked to have been able to accomplish this without having to hide and show a field with JS, but I think the only solutions for doing so with views or ajax caused delays/reloads that I did not want. Also, I liked the general simplicity of the method I used, rather than having to figure out how to pass partial forms back and forth through the HTTPRequest.
Update:
In my situation, I was creating entries for lost and found items and if the location where the item was found was not a provided option, then I wanted to show a textbox for the user to enter the location. I created a location object that was set as the "other" location and then displayed the textbox when that object was selected as the "found" location.
In forms.py, I added an extra CharField and use a clean method to check if the field is required and then throw a ValidationError if it wasn't filled in:
class Model2Form(forms.ModelForm):
def __init__(self, Model1, *args, **kwargs):
self.other = Model1.otherLocation
super(Model2Form, self).__init__(*args, **kwargs)
...
otherLocation = forms.CharField(
label="Location Description",
max_length=255,
required=False
)
def clean_otherLocation(self):
if self.cleaned_data['locationFound'] == self.other and not self.cleaned_data['otherLocation']:
raise ValidationError("Must describe the location.")
return self.cleaned_data['otherLocation']
Then in my JavaScript, I checked if the value of the "found" location was the "other" location (the value of which I had in a hidden span on my html page). I then used .show() and .hide() on the textbox's parent element as necessary:
$("#id_locationFound").change( function(){
if ($("#id_locationFound").val() == $("#otherLocation").attr("value")){ //if matches "other" location, display textbox; otherwise, hide textbox
$("#id_otherLocation").parent().show();
}else
$("#id_otherLocation").parent().hide();
});
Your best guess would be to trigger a "POST" request when you select something from your drop down menu.
The Value of that "POST" has to correspond your values you use to determine which field you would like to output.
Now you will actually need two forms:
class MyBaseForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('param', 'param1', 'param2')
class MyDropDownForm(MyBaseForm):
class Meta:
widgets = {
'param2': Select(attrs={...}),
}
So as you can see the DropDownForm has been derived from MyBaseForm to make sure it will have all the same properties. But we have modified the widget of one of the fields.
Now you can update your view. Please note, this is untested Python + Pseudocode
views.py
def myFormView(request):
if request.method == 'POST': # If the form has been submitted...
form = MyBaseForm(request.POST)
#submit button has not been pressed, so the dropdown has triggered the submission.
#Hence we won't safe the form, but reload it
if 'my_real_submitbotton' not in form.data:
if 'param1' == "Dropdown":
form = MyDropDownForm(request.POST)
else:
#do your normal form saving procedure
else:
form = ContactForm() # An unbound form
return render(request, 'yourTemplate.html', {
'form': form,
})
This mechanism does the following:
When the form is submitted it checks if you have pressed the "submit" button or have used a dropdown onChange to trigger a submission. My solution doesn't contain the javascript code you need to trigger the submission with an onChange. I just like to provide a way to solve it.
To use the 'my_real_submitbutton' in form.data construct you will be required to name your submit button:
<input type="submit" name="my_real_submitbutton" value="Submit" />
Of course you can choose any string as Name. :-)
In case of a submit by your dropdown field you must check which value has been selected in this drop down menu. If this value satisfies the condition you want to return a Dropdown Menu you create an instance of DropDownForm(request.POST) otherwise you can leave everything as it is and rerender your template.
On the downside this will refresh your page.
On the upside it will keep all the already entered field values. So no harm done here.
If you would like to avoid the page refresh you can keep my proposed idea but you need to render the new form via AJAX.

Django formset validation: automatically fix form validation errors

In an my model, I've the following
--- models.py ---
class A(models.Model):
my_Bs = models.ManyToManyField('B', through='AlinksB')
...
class B(models.Model):
...
class AlinksB(models.Model):
my_A = models.ForeignKey(A)
my_B = models.models.ForeignKey(B)
order = models.IntegerField()
So is the corresponding admin (A admin view has an inline to link B instances, and I prepared the required to custom this inline's formset and forms):
--- admin.py ---
class AlinksBInlineForm(forms.ModelForm):
class Meta:
model = AlinksB
class AlinksBInlineFormset(forms.models.BaseInlineFormSet): # there also is a BaseModelFormset
form = AlinksBInlineForm
class AlinksBInline(admin.TabularInline):
formset = AlinksBInlineFormset
model = AlinksB
class AAdmin(admin.ModelAdmin):
form = AForm
inlines = (AlinksBInline,)
...
class BAdmin(admin.ModelAdmin):
...
Now to custom the forms validation, nothing difficult: just override the "clean" method of the form object. If you want many different forms in the formset, I think you just have to change some manually in the "init" method of the formset. But what about programatically validating all the forms when we clean the formset, and that only under some conditions.
In my case: how to automatically set the "order" field (in the inline of A admin view) with an autoincrement if all the orders (inline rows to remove excluded) are empty ?!
I just spent a lot of time Googling about trying to perform automatic form cleaning during a formset validation in Django Framework. After a few days a couldn't figure a solution so I started looking right into Django's source code to see how work fields, widgets, forms and formsets.
Here is what I understood:
-All the data POSTed by the user when he submits the formset it stored in the "data" attribute of the formset. This attribute is very ugly and cannot be directly used.
- The form is just a wrapper for fields (it calls all the fields' clean methods and fill error buffers, and only a few more)
-The form fields have a widget. This widget allow getting back the field's raw value from the "data" attribute of the formset
form.add_prefix('field name') # returns the 'field prefix', the key of formset.data used to retrieve the field's raw value
form.fields['field name'].widget.value_from_datadict(form.data, form.files, 'field prefix') # returns the raw value
-The form fields also have a method to transform the raw value into a right python value (in my case: order is an integer, or None if the field has been left empty)
form.fields['field name'].to_python(raw_value) # returns a value with the right type
-You can change the value of one of the fields from the formset with the following code
form.data.__setitem__('field prefix', value) # code to update an iterable knowing the key to change
-Once you have modified the fields value, you can call the "full_clean" method of the forms to retry cleaning them (this will remove the previous errors).
-Once you have validated again the forms, you can retry validating the formset with its "full_clean" method too. But take care to avoid infinite loops
-The forms clean data can only be used has a read-only data, to add more error messages in the form or the formset
An other solution would be to manually change the "form.clean_data" attribute, and clean the formset.errors and all the form.errors
Hope it could help somebody in the same situation as me !
Ricola3D

Instanting ModelForm with where modelfields are overwritten

I have a ModelForm field that is based on the following Model:
class Phrase(models.Model):
subject = models.ForeignKey(Entity) # Entity is unique on a per Entity.name basis
object = models.ForeignKey(Entity) # Entity is unique on a per Entity.name basis
The modelform (PhraseForm) has a field 'subject' that is a CharField. I want users to be able to enter a string. When the modelform is saved, and the string does not match an existing Entity, a new Entity is created.
This is why I had to overwrite the "subject" field of the Modelform, as I cannot use the automatically generated "subject" field of the Modelform (I hope I'm making myself clear here).
Now, all tests run fine when creating a new Phrase through the modelform. But, when modifying a Phrase:
p = Phrase.objects.latest()
pf = PhraseForm({'subject': 'anewsubject'}, instance=p).
pf.is_valid() returns False. The error I get is that "object" cannot be None. This makes sense, as indeed, the object field was not filled in.
What would be the best way to handle this? I could of course check if an instance is provided in the init() function of the PhraseForm, and then assign the missing field values from the instance passed. This doesn't feel as if it's the right way though, so, is there a less cumbersome way of making sure the instance's data is passed on through the ModelForm?
Now that I'm typing this, I guess there isn't, as the underlying model fields are being overwritten, meaning the form field values need to be filled in again in order for everything to work fine. Which makes me rephrase my question: is the way I've handled allowing users to enter free text and linking this to either a new or existing Entity the correct way of doing this?
Thanks in advance!
Why are you modifying using the form.
p = Phrase.objects.latest()
p.subject = Entity.objects.get_or_create(name='anewsubject')[0]
docs for get_or_create
If you are actually using the form it should work fine:
def mod_phrase(request, phrase_id=None):
phrase = get_object_or_404(Phrase, pk=phrase_id)
if request.method == 'POST':
form = PhraseForm(request.POST, instance=phrase)
if form.is_valid():
form.save()
return HttpResponse("Success")
else:
form = PhraseForm(instance=phrase)
context = { 'form': form }
return render_to_response('modify-phrase.html', context,
context_instance=RequestContext(request))
Setting the instance for the ModelForm sets initial data, and also lets the form know which object the form is working with. The way you are trying to use the form, you are passing an invalid data dictionary (lacks object), which the form is correctly telling you isn't valid. When you set the data to request.POST in the example above, the request.POST includes the initial data which allows the form to validate.

How to make a "workflow" form

For my project I need many "workflow" forms. I explain myself:
The user selects a value in the first field, validates the form and new fields appear depending on the first field value. Then, depending on the others fields, new fields can appear...
How can I implement that in a generic way ?
I think the solution you are looking for is django form wizard
Basically you define separate forms for different pages and customize the next ones based on input in previous screens, at the end, you get all form's data together.
Specifically look at the process step advanced option on the form wizard.
FormWizard.process_step()
"""
Hook for modifying the wizard's internal state, given a fully validated Form object. The Form is guaranteed to have clean, valid data.
This method should not modify any of that data. Rather, it might want to set self.extra_context or dynamically alter self.form_list, based on previously submitted forms.
Note that this method is called every time a page is rendered for all submitted steps.
The function signature:
"""
def process_step(self, request, form, step):
# ...
If you need to only modify the dropdown values based on other dropdowns within the same form, you should have a look at the implemented dajaxproject
I think it depends on the scale of the problem.
You could write some generic JavaScript that shows and hides the form fields (then in the form itself you apply these css classes). This would work well for a relatively small number showing and hiding fields.
If you want to go further than that you will need to think about developing dynamic forms in Django. I would suggest you don't modify the ['field'] in the class like Ghislain suggested. There is a good post here about dynamic forms and it shows you a few approaches.
I would imagine that a good solution might be combining the dynamic forms in the post above with the django FormWizard. The FormWizard will take you through various different Forms and then allow you to save the overall data at the end.
It had a few gotchas though as you can't easily go back a step without loosing the data of the step your on. Also displaying all the forms will require a bit of a customization of the FormWizard. Some of the API isn't documented or considered public (so be wary of it changing in future versions of Django) but if you look at the source you can extend and override parts of the form wizard fairly easily to do what you need.
Finally a simpler FormWizard approach would be to have say 5 static forms and then customize the form selection in the wizard and change what forms are next and only show the relevant forms. This again would work well but it depends how much the forms change on previous choices.
Hope that helps, ask any questions if have any!
It sounds like you want an AJAXy type solution. Checkout the Taconite plugin for jQuery. I use this for populating pulldowns, etc. on forms. Works very nicely.
As for being "generic" ... you might have standard methods on your container classes that return lists of children and then have a template fragmen t that knows how to format that in some 'standard' way.
Ok, I've found a solution that does not use ajax at all and seems nice enough to me :
Create as many forms as needed and make them subclass each other. Put an Integer Hidden Field into the first one :
class Form1(forms.Form):
_nextstep = forms.IntegerField(initial = 0, widget = forms.HiddenInput())
foo11 = forms.IntegerField(label = u'First field of the first form')
foo12 = forms.IntegerField(label = u'Second field of the first form')
class Form2(Form1):
foo21 = forms.CharField(label = u'First field of the second form')
class Form3(Form2):
foo31 = forms.ChoiceField([],
label=u'A choice field which choices will be completed\
depending on the previous forms')
foo32 = forms.IntegerField(label = u'A last one')
# You can alter your fields depending on the data.
# Example follows for the foo31 choice field
def __init__(self, *args, **kwargs):
if self.data and self.data.has_key('foo12'):
self.fields['foo31'].choices = ['make','a','nice','list',
'and you can','use your models']
Ok, that was for the forms now here is the view :
def myview(request):
errors = []
# define the forms used :
steps = [Form1,Form2,Form3]
if request.method != 'POST':
# The first call will use the first form :
form = steps[0]()
else:
step = 0
if request.POST.has_key('_nextstep'):
step = int(request.POST['_nextstep'])
# Fetch the form class corresponding to this step
# and instantiate the form
klass = steps[step]
form = klass(request.POST)
if form.is_valid():
# If the form is valid, increment the step
# and use the new class to create the form
# that will be displayed
data = form.cleaned_data
data['_nextstep'] = min(step + 1, len(steps) - 1)
klass = steps[data['_nextstep']]
form = klass(data)
else:
errors.append(form.errors)
return render_to_response(
'template.html',
{'form':form,'errors':errors},
context_instance = RequestContext(request))
The only problem I saw is that if you use {{form}} in your template, it calls form.errors and so automagically validates the new form (Form2 for example) with the data of the previous one (Form1). So what I do is iterate over the items in the form and only use {{item.id}}, {{item.label}} and {{item}}. As I've already fetched the errors of the previous form in the view and passed this to the template, I add a div to display them on top of the page.