django wizard, using form and formset in the same step - django

I have a scenario which I'm trying to plan to start coding and I'm thinking to use django wizard.
My plan is to build a django wizard with two steps, the first simple but the second a bit more complicated. The second step will contain a form that will reshape based on value selected from the first step, which I can see myself doing. I already explored all the existing functionality and I think it can be done easily.
The challenge I'm facing though, is that in the second step itself. I have a form and a formset, the formset is one to many to form (Article -> Images) so when reaching the second step the user will be able to upload one or more images to the same article.
I tried to search everywhere on google mailing lists and stackoverflow for passing a formset to the django wizard class but it seems like you can not pass two forms in the same step.
NewItemWizard.as_view([
('category', CategorySelectionForm),
('article', ArticleForm)
])
as seen, in the example code above, I would like to be able to pass both ArticleForm and ImageFormset to the second step. Is there a way to do this out of the box?
Based on what I'm reading, I believe using a function like get_context_data could help, but it will be very hacky.
def get_context_data(self, form, **kwargs):
context = super(NewItemWizard, self).get_context_data(form=form, **kwargs)
if self.steps.current == 'article':
context.update({
'image_formset': ImageFormset()
})
return context
Anyone can advise for a better approach?
Cheers,

There is no straight API to do it yet, but there is a clean solution like the one mentioned here:
https://code.djangoproject.com/ticket/18830

Related

Django Rest Framework pre-populate form with specific queryset

Basically in a popup (bootstrap) I would like to have all specified pre-populated fields from my model.
I found this code (https://groups.google.com/forum/#!searchin/django-rest-framework/HTMLFormRenderer/django-rest-framework/s24WFvnWMxw/hhmaD6Qw0AMJ)
class CreatePerformanceForm(forms.ModelForm):
model = Performance
fields = ('field1', 'field2')
class PerformanceCreateView(ListCreateAPIView):
serializer_class = PerformanceCreateSerializer
model = Performance
template_name = 'core/perform.html'
def get(self, request, format=None):
data = {'
form': CreatePerformanceForm()
}
return Response(data)
My question is the same.
Is there a way to create the form directly from the serializer so I don't have to create a Django form?
I looked at HTMLFormRenderer, but the DRF doc is quiet poor about this issue.
Thanks,
D
See this issue. Important part:
There are some improvements that could be made there [to HTMLFormRenderer], notably supporting error messaging against fields, and rendering the serializer directly into html without creating a Django form in order to do so [...]
So basically, HTMLFormRenderer also uses Django forms. Also, you are right, the documentation doesn't provide too much support for it. Even more, it seems that this renderer might soon change. See here. Quote:
Note that the template used by the HTMLFormRenderer class, and the context submitted to it may be subject to change. If you need to use this renderer class it is advised that you either make a local copy of the class and templates, or follow the release note on REST framework upgrades closely.
I know this doesn't help much, but for now there is no better way than the way you did it.

Make Django Admin remember my parameters after posting

I have a problems thats been bugging me for a while. I want to use dates in django admin to view entries between certain dates.
To do this I have customized my changelist.html for this model and put a form in there. When posted I override the queryset method like this
def queryset(self, request):
qs = super(ModelAdmin, self).queryset(request)
if request.POST.has_key('date1'):
return qs.filter(startdate__gte=request.POST['date1']).filter(startdate__lte=request.POST['date2'])
return qs
This works great but its only one little problem. The parameters are forgotten if I for example choose to sort the result in any way.
If I instead of this type in the url straight into the browser so it looks like this
http//localhost/admin/some/model/?startdate__gte=2010-01-01&startdate__lte=2010-12-30
I can sort however I want to afterwards because they´ll stick just like this
http//localhost/admin/some/model/?o=5&ot=asc&startdate__lte=2010-12-30&startdate__gte=2010-01-01
Do I need to use a filterspec to solve this?
Thanks heaps!
There is a change request over at the Django project asking for this functionality.
It's waiting for someone to write tests for the proposed patch before it's committed, so you could either do that or you could download the proposed patch (near the bottom of the page) and use it.
https://code.djangoproject.com/ticket/6903

Building a formset dynamically

I initially wrote code to build a form dynamically, based on data from the DB, similar to what I described in my previous SO post.
As SO user Daniel Roseman points out, he would use a formset for this, and now I've come to the realization that he must be completely right. :)
My approach works, basically, but I can't seem to get validation across the entire form to be working properly (I believe it's possible, but it's getting quite complex, and there has to be a smarter way of doing it => Formsets!).
So now my question is: How can I build a formset dynamically? Not in an AJAX way, I want each form's label to be populated with an FK value (team) from the DB.
As I have a need for passing parameters to the form, I've used this technique from a previous SO post.
With the former approach, my view code is (form code in previous link):
def render_form(request):
teams = Team.objects.filter(game=game)
form_collection = []
for team in teams:
f = SuggestionForm(request.POST or None, team=team, user=request.user)
form_collection.append(f)
Now I want to do something like:
def render_form(request):
teams = Team.objects.filter(game=game)
from django.utils.functional import curry
from django.forms.formsets import formset_factory
formset = formset_factory(SuggestionForm)
for team in teams:
formset.form.append(staticmethod(curry(SuggestionForm, request.POST or None, team=team, user=request.user)))
But the append bit doesn't work. What's the proper way of doing this?
Thanks!
Thanks for the recognition of my unvarying rightness...
Probably what you need here is a model formset, which will automatically build itself from a queryset you pass in:
from django.forms.models import modelformset_factory
def render_form(request):
teams = Team.objects.filter(game=game)
formset = modelformset_factory(form=SuggestionForm, queryset=teams)
As for the dynamic parameter, which I'm guessing is user, I've previously used the closure solution, but the curry method should still work:
formset.form = staticmethod(curry(SuggestionForm, user=request.user))
Edit after comment Thanks for the clarification. I think I understand what you're trying to do. I wonder if an inline formset might work better? If you started off with a Game object pre-populated with eight related Team objects, an inline formset would give you eight pre-existing forms.
my_game = Game.objects.create(params=whatever)
for i in range(1, 9):
team = Team.objects.create(game=my_game, name="team_%s" % i
formset = inlinemodelformset_factory(Game, Team, form=SuggestionForm)
fs = formset(instance=my_game)
Does that work?

Django FormWizard Dynamically Alter form_list

I'm having some issues with the form wizard, that maybe someone can shed some light on. According docstring in the method process_step: I can "dynamically alter self.form_list". So, based on my project needs, I'm appending forms to the form_list. The forms I'm appending contain questions and answers: http://dpaste.com/hold/152201/
The issue is that when 2 people hit the formwizard at the same time, they start to see each others questions and answers. I guess I don't understand how process_step suggests I can dynamically alter the form_list, when by doing so I'm modifying the form list of another user.
Is the form_list a shared object among visitors hitting the formwizard url defined in urls.py? I've seen this issue under apache2/prefork/worker/mod_wsgi, and while running the app with runserver.
How do you use FormWizard? If you're putting it in urls.py like docs says then it could be cached, i had that issue couple of times. Just put it in a view like:
def my_view(request):
return FormWizard(request)
UPDATE: Example from real
def registration_wizard(request, template_name=None):
rw = RegistrationWizard([RegistrationForm, 0])
#hack formwizard to replace default template
if template_name:
rw.get_template = lambda x: template_name
return rw(request)
here RegistrationWizard is a FormWizard subclass with dynamic form_list, [RegistrationForm, 0] is needed because if there's only one form at creation time, wizard won't get to form_list function. Template thing is pretty self-explanatory

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.