How to use different forms in a view - django

I'm new in django and have a question about how to use the views in a situation:
I have a view which loads a track file.The track is broken down by different ways and I want to show every one, one by one, in a map. the user has to fill in a form with characteristics of the way. When one ends, I want to load the next until its finishes. I'm not sure about the structure that I have to use for this situation.
def acoplar_track(request, track_id):
track = get_object_or_404(Track, id=track_id)
x=Xarxa(track.zona.nom) #an object from a custom library
...
#split the track in different ways
...
newWays = x.getTrackWays(); # a list with the ways ids
for way in newWays:
emplenarWay(wId,x) #Function that have to show the way in a map and alow the user to fill the form.
return render_to_response('principal/inici.html', context_instance = RequestContext(request)) #final template
def carregar_way(request, x, way_id):
if request.method=='POST':
formulari = WayForm(request.POST, request.FILES)
if formulari.is_valid():
x.processData(formulari.descripcio, formulari.tipus)
# something for render de form again or come back to the loop of the previous function... NO IDEA!!
else:
formulari = WayForm()
mapFeatures = x.getMapFeatures(way_id)
return render(request,'principal/WayForm.html',
{'formulari':formulari, 'mapFeatures'=mapFeatures})
forms.py
CHOICES = (('1','Pista',),('2','Corriol',))
class WayForm(forms.Form):
descripcio = forms.Textarea()
tipus = forms.ChoiceField(
widget=forms.RadioSelect, choices=CHOICES)
It could be something like this?

Im assuming that you want to show a common form (the same fields each time) and a different image of the track someone is following. I'd start by defining the form class object. Then create two templates, one is the base where you cycle over the form fields for display purposes, the second is for your images and you extend from the base template. Then in your view, you are either displaying data to your templates (the base form and the specific track image) or you are processing the form data and moving the user to the next part of the track. See forms in views for great information on how to process forms in a view. Use your render_to_response and the ID of your track map to setup the next page to render.

Related

How to have dynamically added fields to specific model fields and not all

i am building an application that has a model with three fields Company,Name, position. in the same model i want to have company name as one field while the user can add name and positions for multiple candidates. the reason am trying to do that is because i didnt find any proper way to set automatically select the foreign key based on the company name entered since foreign key is a drop down list and couldnt figure out the way to make foreign key field equal to company name entered.
appreciate help and suggestions if any for the approach i have in mind.
You need two forms (or more usefully one form and one formset). Use form prefixes to make them distinguishable. Pass both to the template, say as selectform and candidate_formset and in the template, use
{{selectform}}
{{candidate_formset}}
The first is a company-select form. It might, for example, be
class CompanySelectForm(forms.ModelForm):
class Meta:
model = Candidate
fields = ['company']
The second is a form, or probably a formset, for entering one, or (via a formset) as many candidates as there are to be entered. It will look like
class CandidateForm(forms.ModelForm):
class Meta:
model = Candidate
fields = ['name','position']
Now, you use commit=False (docs) to create objects but not save them. First, process CandidateSelectForm, which will give you a Candidate object with a valid company instance, but not save it. Then process the formset of CandidateForm, again with commit=False, which will give you a list of candidate instances with no company, again unsaved. Finally for each candidate in this list, set the company field of every candidate to the one on the object retrieved by CandidateSelectForm and save it.
It will probably be easier to write a plain view function, than messing around with method overrides trying to get the class-based views to process two forms this way.
Edit - added on request.
The view could be modelled on this one in the Django doc. I've made the obvious changes in line with the earlier part of the answer, but it's probably full of errors and I'm not going to debug it further here
def get_name(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create form instances and populate with data from the request:
cs_form = CompanySelectForm(request.POST, prefix="cs")
cand_form = CandidateSelectForm( request.POST, prefix="cand")
# check whether it's valid:
if cs_form.is_valid() and cand_form.is_valid():
selector = cs_form.save(commit=False)
candidate = cand_form(commit=False)
candidate.company = selector.company
candidate.save()
return HttpResponseRedirect('/thanks/')
# if a GET (or any other method) we'll create a blank form
else:
cs_form = CompanySelectForm( prefix='cs')
cand_form = CandidateSelectForm( prefix='cand')
return render(request, 'name.html', {
"select_form": cs_form,
"candidate_form": cand_form,
})
Once you have got this working for a single candidate, you can progress to turning candidate_form into a formset, documented here which will let you enter any number of candidates to be attached to the one selected company.

Reusing a form in two views renders the fields in the wrong order if I remove a field in one of them and later come back to the first view

I have an odd problem with forms, reusing a form in two views get strange behaviors when rendering the form.
I have a form that I reuse in two views, one view use a form as it's the second one remove a field because I'm providing the value for that field in the url. them and later come back to the first view.
This is particularly annoying if you use django-crispy-forms to render your forms.
The code:
The first view:
def get_form(self, form_class):
return super(BaseSale, self).get_form(form_class)
The second view:
def get_form(self, form_class):
form = super(BaseSale, self).get_form(form_class)
del form.fields['price']
return form
Here is the user case:
First request, I render formA in view1
Second request, I render formA in view2, but remove a field that is not intended to be use.
Third request, I render formA in view1 again, the field I remove in view2 is there, but the order of the fields is wrong.
This has to be related to forms not being thread safe, but I'm being incapable to debug this.
Any ideas to fix this?
Not sure this will fix the problem, but you probably want to call super with current class. In your code example you call super(BaseSale, self) in both views. Have you forgot to fix this after copy/pasting?
Secondly, it is better to use different form classes. If you want to reuse some part of form then add a base form class and extend it as many times as you need.
Finally if you need a dynamic form then it is better to encapsulate the form building logic into the form. You can add/remove fields within the __init__ method.

django: use a queryset as modelform initial data

I'm making a settings interface which works by scanning for a settings folder in the installed applications, scanning for settings files, and finally scanning for ModelForms.
I'm at the last step now. The forms are properly found and loaded, but I now need to provide the initial data. The initial data is to be pulled from the database, and, as you can imagine, it must be limited to the authenticated user (via request.user.id).
Keep in mind, this is all done dynamically. None of the names for anything, nor their structure is known in advanced (I really don't want to maintain a boring settings interface).
Here is an example settings form. I just pick the model and which fields the user can edit (this is the extent to which I want to maintain a settings interface).
class Set_Personal_Info(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('nick_name', 'url')
I've looked at modelformset_factory which almost does what I want to do, but it only seems to work with results of two or more. (Here, obj is one of the settings forms)
Formset = modelformset_factory(obj.Meta.model, form=obj)
Formset(queryset=obj.Meta.model.objects.filter(id=request.user.id))
I can't filter the data, I have to get one, and only one result. Unfortunately I can't use get()
Formset = modelformset_factory(obj.Meta.model, form=obj)
Formset(queryset=obj.Meta.model.objects.get(id=request.user.id))
'User' object has no attribute 'ordered'
Providing the query result as initial data also doesn't work as it's not a list.
Formset = modelformset_factory(obj.Meta.model, form=obj)
Formset(initial=obj.Meta.model.objects.get(id=request.user.id))
'User' object does not support indexing
I have a feeling that the answer is right in front of me. How can I pull database from the database and shove it into the form as initial values?
I'm not really sure I understand what you're trying to do - if you're just interested in a single form, I don't know why you're getting involved in formsets at all.
To populate a modelform with initial data from the database, you just pass the instance argument:
my_form = Set_Personal_Info(instance=UserProfile.objects.get(id=request.user.id))
Don't forget to also pass the instance argument when you're instantiating the form on POST, so that Django updates the existing instance rather than creating a new one.
(Note you might want to think about giving better names to your objects. obj usually describes a model instance, rather than a form, for which form would be a better name. And form classes should follow PEP8, and probably include the word 'form' - so PersonalInfoForm would be a good name.)
Based on what I've understand ... if you want to generate a form with dynamic fields you can use this:
class MyModelForm(forms.ModelForm):
def __init__(self, dynamic_fields, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
self.fields = fields_for_model(self._meta.model, dynamic_fields, self._meta.exclude, self._meta.widgets)
class Meta:
model = MyModel
Where dynamic_fields is a tuple.
More on dynamic forms:
http://www.rossp.org/blog/2008/dec/15/modelforms/
http://jacobian.org/writing/dynamic-form-generation/
http://dougalmatthews.com/articles/2009/dec/16/nicer-dynamic-forms-django/
Also Daniel's approach is valid and clean ... Based on your different ids/types etc you can you use different Form objects
forms.py
class MyModelFormA(forms.ModelForm):
class Meta:
model = MyModel
fields = ('field_a','field_b','field_c')
class MyModelFormB(forms.ModelForm):
class Meta:
model = MyModel
fields = ('field_d','field_e','field_f')
views.py
if request.method == 'POST':
if id == 1:
form = MyModelFormA(data=request.POST)
elif id == 2:
form = MyModelFormB(data=request.POST)
else:
form = MyModelFormN(data=request.POST)
if form.is_valid():
form.save() else:
if id == 1:
form = MyModelFormA()
elif id == 2:
form = MyModelFormB()
else:
form = MyModelFormN()

Django : Formset as form field

one of the forms I need is a composite of simple fields (say "Department", "Building" and "RoomNumber"), and of dynamically generated pairs of fields (say "Name" and "Email"). Ideally, editing the contents of the simple fields and adding/removing dynamic field pairs would be done on a single form.
Code-wise, I'm wondering if trying to embed a Formset (of a form with the two dynamic fields) as a field in an ordinary form is a sensible approach or if there's another best practice to achieve what I'd like to accomplish.
Many thanks for any advice on these matters,
I'm not sure where the idea that you need to "embed a Formset as a field" comes from; this sounds like a case for the standard usage of formsets.
For example (making a whole host of assumptions about your models):
class OfficeForm(forms.Form):
department = forms.ModelChoiceField(...
room_number = forms.IntegerField(...
class StaffForm(forms.Form):
name = forms.CharField(max_length=...
email = forms.EmailField(...
from django.forms.formsets import formset_factory
StaffFormSet = formset_factory(StaffForm)
And then, for your view:
def add_office(request):
if request.method == 'POST':
form = OfficeForm(request.POST)
formset = StaffFormSet(request.POST)
if form.is_valid() && formset.is_valid():
# process form data
# redirect to success page
else:
form = OfficeForm()
formset = StaffFormSet()
# render the form template with `form` and `formset` in the context dict
Possible improvements:
Use the django-dynamic-formset jQuery plugin to get the probably-desired "add an arbitrary number of staff to an office" functionality without showing users a stack of blank forms every time.
Use model formsets instead (assuming the information you're collecting is backed by Django models), so you don't have to explicitly specify the field names or types.
Hope this helps.

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.