The problem I've run into here is how to display some additional text in a form,
specifically the values of the previous step. The point is to show the user a message such as "you have chosen {{ value_from_step_0 }} as your starter, now choose a main course."
I can get that value from step 0 as follows:
class MenuWizard(SessionWizardView):
def get_form_kwargs(self, step=None):
kwargs = {}
if step == '1':
starter = self.get_cleaned_data_for_step('0')['starter']
kwargs.update({'starter': starter})
class MainCourseForm(forms.Form):
def __init__(self, *args, **kwargs):
starter = kwargs.pop('starter')
print "{0} chosen".format(starter) # This works
super(DomainForm, self).__init__(*args, **kwargs)
# What next?
If I don't pop the 'starter' value then the super call fails. But, how may I get that value into the relevant template? Presumably I need to do something further within init or override something else, but what?
First, note that 1.7 is unsupported, and the form wizards were removed from Django in 1.8 and are now in the third-party formtools library.
This isn't really a job for the form, but the view. SessionWizardView is a standard class-based view, and like most class-based views it allows you to override get_context_data to add elements to the template contents. See the documentation, which has an example of using self.steps.current to check the current step name.
Related
How do I add an arbitrary hidden input to a specific model's view and populate it with a value? I would like to get it later from request.GET.get('my_input_name') on submit (e.g. "Save") . It's not a field that exist in the model, it's just a random value. Also: I am not trying to make some existing field hidden with widgets. If possible, without introducing a custom template.
My view is a model-based class in admin.py ( I have no forms.py ) with custom get_form and formfield_for_foreignkey methods.
I looked at several solutions related to Dynamic fields, they are not working in Django 2.2:
Dynamical Form fields in __init__in Django admin
Dynamic fields in Django Admin
Add dynamic field to django admin model form
I don't really care about it being a dynamic field, or widget or whatever, as long as I can have it saved and retrieved from the request on GET / POST submit. It's also somewhat strange that I don't find any discussions (or documentation) about dynamic fields for Django 2.2 or higher.
My code
model.py
class MyModel(SuperModel):
...model fields definitions...
admin.py
from .models import MyModel
class MyModelAdmin(SuperAdmin):
def get_form(self, request, obj=None, **kwargs):
...some code...
def formfield_for_foreignkey(self, db_field, request, **kwargs):
...some code...
admin.site.register(MyModel, MyModelAdmin)
Thanks.
UPDATE
Never figured out how to implement what I wanted, so solved it with an ugly but working "flag-based" workaround using request.session. It does not answer the question so I do not post it as an answer.
I store in the session the value upon arrival to the entity edit form from the list (in get_form), use it in subsequent POST submits (formfield_for_foreignkey) and erase it when it's not needed anymore (get_queryset). This way I have one out of two states defined, i.e. everything is filtered down to the "parent id" passed from the "list" view to "edit" view, or not, and this state persists through multiple actions in "edit" view. Relevant code:
admin.py
class MyModelAdmin(SuperAdmin)
def get_form(self, request, obj=None, **kwargs):
......
if "parent_model_value_inline_request" in request.GET:
request.session['parent_model_value'] = request.GET['parent_model_value_inline_request']
......
def formfield_for_foreignkey(self, db_field, request, **kwargs):
......
if db_field.name == "parent_model_dropdown_name":
# Make sure we are dealing with a GET method
if request.method == 'GET' or request.method == 'POST': # GET - arrived from list, POST - submitting "save" or "save and and add another"
parent_model_value = None
# if the GET method contains the variable we are interested in that was passed by the action button
# "add entity":
if "parent_model_value_inline_request" in request.GET: # coming from parent with "add child" button
parent_model_value = request.GET.get('parent_model_value_inline_request')
elif 'parent_model_value' in request.session:
if request.session['parent_model_value'] != None:
parent_model_value = request.session['parent_model_value']
if parent_model_value:
...do conditional stuff...
def get_queryset(self, request):
.........
request.session['parent_model_value'] = None
........
UPDATE Jan 24 2020.
No luck so far (tried a custom init gain in a different way), so I implemented it by writing needed info in session and erasing it later.
I'm confused on whether I can set initial data to a field with crispy forms. I have one field in which I generate a number and want to set that to a specific field. If I do anything like:
def __init__(self, *args, **kwargs):
self.fields['medical_record_number'].initial = 'whatever you want'
I get a error stating that name 'self' is not defined.
I am assuming there is something different I have to do while using crispy forms to accomplish the same thing?
Thanks,
Tom
Nothing to do with crispy-forms, this is regular Django forms code. You have forgotten to call parent constructor, if you are using python 2.X do:
def __init__(self, *args, **kwargs):
super(YourFormClassName, self).__init__(*args, **kwargs)
self.fields['medical_record_number'].initial = 'whatever you want'
Beware that maybe you prefer to set initial in your Django form field:
whatever = forms.CharField(initial="whatever you want")
I'm using the FormWizard functionality in Django 1.4.3.
I have successfully created a 4-step form. In the first 3 steps of the form it correctly takes information from the user, validates it, etc. In Step #4, it right now just shows a "Confirm" button. Nothing else. When you hit "Confirm" on step #4, does something useful with it in the done() function. So far it all works fine.
However, I would like to make it so that in step 4 (The confirmation step), it shows the user the data they have entered in the previous steps for their review. I'm trying to figure out the most painless way to make this happen. So far I am creating an entry in the context called formList which contains a list of forms that have already been completed.
class my4StepWizard(SessionWizardView):
def get_template_names(self):
return [myWizardTemplates[self.steps.current]]
def get_context_data(self, form, **kwargs):
context = super(my4StepWizard, self).get_context_data(form=form, **kwargs)
formList = [self.get_form_list()[i[0]] for i in myWizardForms[:self.steps.step0]]
context.update(
{
'formList': formList,
}
)
return context
def done(self, form_list, **kwargs):
# Do something here.
return HttpResponseRedirect('/doneWizard')
Form #1 has an input field called myField.
So in my template for step #4, I would like to do {{ formList.1.clean_myField }}. However, when I do that, I get the following error:
Exception Value:
'my4StepWizard' object has no attribute 'cleaned_data'
It seems that the forms I am putting into formList are unbounded. So they don't contain the user's data. Is there a fix I can use to get the data itself? I would really like to use the context to pass the data as I'm doing above.
Try this:
def get_context_data(self, form, **kwargs):
previous_data = {}
current_step = self.steps.current # 0 for first form, 1 for the second form..
if current_step == '3': # assuming no step is skipped, this will be the last form
for count in range(3):
previous_data[unicode(count)] = self.get_cleaned_data_for_step(unicode(count))
context = super(my4StepWizard, self).get_context_data(form=form, **kwargs)
context.update({'previous_cleaned_data':previous_data})
return context
previous_data is a dictionary and its keys are the steps for the wizard (0 indexed). The item for each key is the cleaned_data for the form in the step which is the same as the key.
I was looking at the documentation and I am not quite sure how to use different templates for each step...
I looked into the source code and it seems that the template name is hardcoded:
class WizardView(TemplateView):
"""
The WizardView is used to create multi-page forms and handles all the
storage and validation stuff. The wizard is based on Django's generic
class based views.
"""
storage_name = None
form_list = None
initial_dict = None
instance_dict = None
condition_dict = None
template_name = 'formtools/wizard/wizard_form.html'
...........
The docs say something about mixins, but I am not sure how to use them since I just started with django...
Thanks
UPDATE:
I looked further into the source code and realized that there is a method get_template_names.
I tried:
class AddWizard(SessionWizardView):
def get_template_names(self, step):
if step == 0:
return 'business/add1.html'
return 'business/add2.html'
def done(self, form_list, **kwargs):
return render_to_response('business/done.html', {
'form_data': [form.cleaned_data for form in form_list],
})
But got an error:
get_template_names() takes exactly 2 arguments (1 given)
get_template_names doesn't accept arguments. You can't just define a new argument for a function to accept and hope the framework will pass it in! (for your future troubleshooting)
Judging by the WizardView source, it looks like you can access the currently active step via self.steps.current which you could use in your get_template_names view to return a path containing the step.
class AddWizard(SessionWizardView):
def get_template_names(self):
return ['step_{0}_template.html'.format(self.steps.current)]
I'm not sure if current is a string or integer or what - but one look at the view and you should find a useful "can't find template named X" error.
Is it possible to build a two-stage form for creating an object in Django admin?
When an admin user visits /admin/my-app/article/add/, I'd like to display some options. Then, the app would display the creation page with pre-calculated fields based on the selections made.
You could overwrite the add_view method on the ModelAdmin (Source) of myapp.article. It is responsible for rendering the modelform and adding objects to the database.
While adding your functionality, you likely want to keep the original code intact instead of copying/modifying it.
Draft
def add_view(self, request, **kwargs):
if Stage1:
do_my_stuff()
return response
else:
return super(ModelAdmin, self).add_view(self, request, **kwargs)
Now you need to distinguish between the two stages. A parameter in the GET query string could do that. To pass initial data to a form in the admin, you only need to include the fieldname value pairs as parameters in the querystring.
Draft 2
def add_view(self, request, **kwargs):
if 'stage2' not in request.GET:
if request.method == 'POST':
# calculate values
parameters = 'field1=foo&field2=bar'
return redirect('myapp_article_add' + '?stage2=1&' + parameters)
else:
# prepare form for selections
return response
else:
return super(ModelAdmin, self).add_view(self, request, **kwargs)
You probably need to look at the code to return a correct response in your first stage. There are a couple of template variables add_view sets, but hopefully this is a good start to look further.