What Is Can Order In Django Formsets Actually For? - django

I've spent a fair amount of time the last day or so exploring the can order field with Django Formsets but I can't for the life of me actually figure out what it's for. Apparently it doesn't update the database...and it does put an order number field with the form....but beyond that what is it for?
I can't find any useful documentation on how to actually use this field. Do I have to write Javascript in order to get this field to do anything actually meaningful?
I get the delete option...and you have to add code in to delete the record...So I guess I'm to take it the same is required for the can_order field?
Sorry if this is a silly question but I've spent more time trying to figure this out than what would be reasonable by now.

All it does is add an integer ORDER field to each form in the formset so you can manually order them after instantiating the formset. Just imagine your forms have a title with this imaginary formset data. You can use ordered_forms to iterate through each form, inspect its cleaned_data, and change it's ordering before returning the form in your context.
formset = ArticleFormSet(data)
if formset.is_valid():
for form in formset.ordered_forms:
if form.cleaned_data['title'] = 'My form title'
form.cleaned_data['ORDER'] = 2
form.save()

The can_order adds a field to the formset, just like the can_delete does. You do not NEED JavaScript, but you can use it, for example if you use JavaScript to drag and drop the forms in the formset. You can sort them and then you can change the name attribute to reflect the new order by accessing the specific form by using it's id in your JavaScript.
The can_order IS like the can_delete, in that the can_order` will simply add the following to each of the forms in your formset, where N = 0, 1, 2, 3, 4...
<input type="number" name="form-N-ORDER" id="id_form-N-ORDER">
What can you do with this without JavaScript? Add an order field to your model, and then you can get the forms order in your post, and set the order field of your model to equal that.

Related

Specifying specific form format in Django

I am using materializecss to give my django site some material elements. I have put together a form (the 'old' way using html) but now realised I need to use a django form instead. The problem is, these forms don't play well with materialises built in column system (they use classes to determine rows and column spacing). Here is an example of the layout I set up so far. However when defining the form through form.py, it spits out one input per layer.
My question is: what can I do to either a) get django to work with the html-defined form or b) make a 'form template' to give the input fields the appropriate classes?
If you want to see the code I can post some but I'm quite a new coder so it's messy.
Thanks!
There are three ways I can think of off the top of my head.
If you want full control over the HTML form, in a Django template or HTML form, simply map the names of your fields to match the underlying field names in the Django form. This way, when POSTed back to your view, Django will automatically link up the POSTed fields with the Django form fields.
For example, if you have a field username in your Django form (or Django model if using ModelForm), you could have an element <input type="text" name="username" maxlength="40"> (that you can style any way you need) on your HTML form that Django will happily parse into your Django form field, assuming your view is plumbed correctly. There is an example of this method in the Django documentation.
Another way is to customize the Django form field widgets in your Django form definition. The Django documentation talks a little bit about how to do this. This is great for one offs, but is probably not the best approach if you expect to reuse widgets.
The final approach would be to subclass Django form field widgets to automatically provide whatever attributes you need. For example, we use Bootstrap and have subclassed nearly all of the widgets we use to take advantage of Bootstrap classes.
class BootstrapTextInput(forms.TextInput):
def __init__(self, attrs=None):
final_attrs = {'class': 'form-control'}
if attrs is not None:
final_attrs.update(attrs)
super().__init__(attrs=final_attrs)
Then it's simply a matter of letting the Django form know which widget to use for your form field.
class UsernameForm(forms.ModelForm):
class Meta:
model = auth.get_user_model()
fields = ['username']
widgets = {'username': BootstrapTextInput()}
Hope this helps. Cheers!

Best way to get dynamic fields in Django

I have a Django application that allows people to create a record. A record can contain one or more people and the form by default contains three fields to capture a person's name. One for first name, one for middle initial and one for last name. When a person is creating a record they can add additional people by clicking a plus button. The plus button adds another set of three text boxes. They can do this for as many people as they want to add.
Once they click the plus button a minus button shows up next to each row so they can remove those fields if they decide to.
What is the best way to name the text fields so that I can get all the text fields and iterate through them in the back end of the application?
I thought if I named them all the same I would get an array of names when I do:
request.POST.getlist('firstname')
However, that is not the case. Instead I get the value from the last input field with that name.
Any suggestions are appreciated.
I suggest you take a look at formsets instead of reinventing this one.
https://docs.djangoproject.com/en/dev/topics/forms/formsets/
from django.forms.formsets import formset_factory
FormSet = formset_factory(MyForm, extra=2)
formset = FormSet()
print formset.forms # you will see it is a collection of MyForm objects
You can dynamically add / remove formsets by modifying a the "management form" and adding the next form (you can copy {{ formset.empty_form }} and modify the IDs to be the N-th form) and there are many examples online of how to do so:
How would you make a dynamic formset in Django?
Dynamically adding a form to a Django formset with Ajax

django inlineformset_factory, view changed fields on save

I am creating some custom validation, that on save checks if certain fields values have changed.
On POST, is there a way in the view that I can check what fields have changed for that formset?
I am currently looping through the formset and am able to view individual field values, so I could compare them to a query. It just seems to be a bit more than necessary considering the formset doesn't go through the save process if nothing has changed.
Any help is appreciated.
Add a method to your formset:
def has_changed()
return all([form.has_changed() for form in self.forms])
Similarly, the form also documents the changed_fields, which you can concatenate in your formset.
I don't think formset.save() blindly overwrites all entries into the db. It checks the changed forms, which I think is available in formset.changed_forms and it updates only those rows in the db.

Limiting available choices in a Django formset

I have a formset which has a field "Teams" which should be limited to the teams the current user belongs to.
def edit_scrapbook(request):
u=request.user
ScrapbookAjaxForm = modelformset_factory(Scrapbook, fields=
('description','status','team'))
choices=False
for t in u.team_set.all():
if choices:
choices=choices,(t.id,t.name)
else:
choices=choices,(t.id,t.name)
if request.method == 'POST':
formset = ScrapbookAjaxForm(request.POST,
queryset=Scrapbook.objects.filter(owner=u))
if formset.is_valid():
instances=formset.save(commit=False)
for i in instances:
i.owner=request.user
i.save()
formset.save_m2m()
return HttpResponseRedirect(reverse('scrapbooks.views.index'))
else:
formset = ScrapbookAjaxForm(queryset=Scrapbook.objects.filter(owner=u))
for form in forms:
for field in form:
if field.label == 'Team':
field.choices=choices
c=RequestContext(request)
return render_to_response('scrapbooks/ajax_edit.html',
{'fs':formset},context_instance=c)
This does not seem to affect the choices in the form at all. This is quite ugly and probably the result of looking at this problem for way too long. I have also tried using a custom formset but I can't seem to get the custom formset to accept the parameter.
How do I limit the choices for the Team field on my subselect in a formset based on the teams the user is in?
From django model documentation:
Finally, note that choices can be any
iterable object -- not necessarily a
list or tuple. This lets you construct
choices dynamically. But if you find
yourself hacking choices to be
dynamic, you're probably better off
using a proper database table with a
ForeignKey. choices is meant for
static data that doesn't change much,
if ever.
I would use then the same idea: in the form, you use a ForeignKey for the team and then, you can limit that list with some query.
Some further suggestion:
Use a ForeignKey for the team
Define your own ModelChoiceField, with a query that will limit its content, basing on a parameter given in its initialization.
Override the default field type, to use your own ModelChoiceField. Note that you should pass the filter for the team in the initialization of your ModelChoiceField.
Not sure if this is the cause of the issue, but there's a big problem with the way you build up the choices tuple.
After four teams, choices will look like this:
((((False, (1, u'Team 1')), (2L, u'Team 2')), (3, u'Team 3')), (4, u'Team 4'))
which obviously isn't valid for setting a choice field. A much better way of doing it would be to use a list comprehension in place of the whole loop:
choices = [(t.id,t.name) for t in u.team_set.all()]

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.