Reputation: 4008
I've a form that has 3 sections:
1) tamanios (sizes), 2) cantidades (quantities), 3) archivos subidos (Uploaded Image).
I need to separate 1 & 2 in one page, and 3 in a different page, and save form data to data base.
As far as I've investigated this is possible using formtools. But using formtools I've had to split my model into 2 models: a) TamaniosCantidades, b) ArchivosSubidos, so they can be rendered in different "steps".
Nevertheless, I'd prefer to have only 1 model. But it's ok if the solution contains some ForeingKey to connect both splitted models.
What I need to do in order to save the form as a model object in data base?
I can't think of on how to write the 'done' method in my Wizard Class.
Original model:
class TamaniosCantidades(models.Model):
TAMANIOS = (('2x2', '2" x 2"',), ('3x3', '3" x 3"',),
('4x4', '4" x 4"',), ('5x5', '5" x 5"',))
CANTIDADES = (('50', '50',), ('100', '100',),
('150', '150',))
tamanios = models.CharField(max_length=10, choices=TAMANIOS)
cantidades = models.CharField(max_length=10, choices=CANTIDADES)
imagenes = models.FileField(upload_to='imagenes/')
uploaded_at = models.DateTimeField(auto_now_add=True)
Modified models (splited result):
class TamaniosCantidades(models.Model):
TAMANIOS = (('2x2', '2" x 2"',), ('3x3', '3" x 3"',),
('4x4', '4" x 4"',), ('5x5', '5" x 5"',))
CANTIDADES = (('50', '50',), ('100', '100',),
('150', '150',))
tamanios = models.CharField(max_length=10, choices=TAMANIOS)
cantidades = models.CharField(max_length=10, choices=CANTIDADES)
# imagenes = models.FileField(upload_to='imagenes/')
# uploaded_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.tamanios
class ArchivosSubidos(models.Model):
# imagenes = models.FileField(upload_to='imagenes/') #commented because I was having problems uploading files with formtools
imagenes = models.CharField(max_length=100) #using charfield to test splitting
uploaded_at = models.DateTimeField(auto_now_add=True)
views.py
class ContactWizard(SessionWizardView):
template_name = "main_app/contact_form.html"
file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, 'imagenes'))
def done(self, form_list, **kwargs):
form_data = process_form_data(form_list)
return HttpResponseRedirect('/')
# return HttpResponseRedirect('/done.html')
# return render('main_app/done.html', {'form_data':form_data})
def process_form_data(form_list):
form_data = [form.cleaned_data for form in form_list]
return form_data
urls.py
app_name = 'main_app'
urlpatterns = [
path('', views.index),
path('productos/', views.productos),
# path('productos/die-cut-stickers', views.die_cut, name='die-cut-stickers'),
path('contact/', ContactWizard.as_view([TamaniosCantidadesForm, ArchivosSubidosForm]))
]
UPDATE 1: stacktrace
2x2 #printed from print(kwargs.get('tamanios', None)) in def save
Internal Server Error: /step-three/
Traceback (most recent call last):
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/db/backends/utils.py", line 85, in _execute
return self.cursor.execute(sql, params)
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/db/backends/sqlite3/base.py", line 296, in execute
return Database.Cursor.execute(self, query, params)
sqlite3.IntegrityError: NOT NULL constraint failed: main_app_tamanioscantidades.tamanios
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/core/handlers/base.py", line 126, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/core/handlers/base.py", line 124, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/views/generic/base.py", line 68, in view
return self.dispatch(request, *args, **kwargs)
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/views/generic/base.py", line 88, in dispatch
return handler(request, *args, **kwargs)
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/views/generic/edit.py", line 172, in post
return super().post(request, *args, **kwargs)
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/views/generic/edit.py", line 142, in post
return self.form_valid(form)
File "/home/ogonzales/Escritorio/web_proyects/gallito/main_app/views.py", line 195, in form_valid
return super(StepThreeView, self).form_valid(form)
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/views/generic/edit.py", line 125, in form_valid
self.object = form.save()
File "/home/ogonzales/Escritorio/web_proyects/gallito/main_app/forms.py", line 55, in save
instance.save()
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/db/models/base.py", line 718, in save
force_update=force_update, update_fields=update_fields)
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/db/models/base.py", line 748, in save_base
updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/db/models/base.py", line 812, in _save_table
forced_update)
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/db/models/base.py", line 861, in _do_update
return filtered._update(values) > 0
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/db/models/query.py", line 712, in _update
return query.get_compiler(self.db).execute_sql(CURSOR)
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/db/models/sql/compiler.py", line 1383, in execute_sql
cursor = super().execute_sql(result_type)
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/db/models/sql/compiler.py", line 1065, in execute_sql
cursor.execute(sql, params)
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/db/backends/utils.py", line 100, in execute
return super().execute(sql, params)
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/db/backends/utils.py", line 68, in execute
return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/db/backends/utils.py", line 77, in _execute_with_wrappers
return executor(sql, params, many, context)
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/db/backends/utils.py", line 85, in _execute
return self.cursor.execute(sql, params)
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/db/utils.py", line 89, in __exit__
raise dj_exc_value.with_traceback(traceback) from exc_value
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/db/backends/utils.py", line 85, in _execute
return self.cursor.execute(sql, params)
File "/home/ogonzales/Escritorio/projects_envs/gallito_env/lib/python3.6/site-packages/django/db/backends/sqlite3/base.py", line 296, in execute
return Database.Cursor.execute(self, query, params)
django.db.utils.IntegrityError: NOT NULL constraint failed: main_app_tamanioscantidades.tamanios
[17/Nov/2018 03:53:44] "POST /step-three/ HTTP/1.1" 500 178292
Upvotes: 0
Views: 683
Reputation: 51988
Using formtools may not be a good option(IMHO: it gives less control over your form), instead you can use Django Form and Modelform for multistep forms, and use django session to store the data in each step. In final step, you can pop those session data and store it in model. For example:
# Lets declare our choices in a global scope rather than inside models
TAMANIOS = (('2x2', '2" x 2"',), ('3x3', '3" x 3"',),
('4x4', '4" x 4"',), ('5x5', '5" x 5"',))
CANTIDADES = (('50', '50',), ('100', '100',),
('150', '150',))
# Declare Forms
class StepOneForm(forms.Form):
tamanios = forms.ChoiceField(choices=TAMANIOS)
class StepTwoForm(forms.Form):
cantidades = forms.ChoiceField(choices=CANTIDADES)
# Third step is attached to the model directly, we will do some tweaks inside the save method of this form
class StepThreeForm(forms.ModelForm):
class Meta:
model = TamaniosCantidades
fields = ('imagenes',)
# Views
# First Two steps are basic FormViews, because it will be used just to store data in session
class StepOneView(FormView):
form_class = StepOneForm
template_name = 'step_one.html'
success_url = '/step-two/'
def get_initials(self):
# pre-populate form if someone goes back and forth between forms
initial = super(StepOneView, self).get_initial()
initial['tamanios'] = self.request.session.get('tamanios', None)
return initial
def form_valid(self, form):
# In form_valid method we can access the form data in dict format
# and will store it in django session
self.request.session['tamanios'] = form.cleaned_data.get('tamanios')
return HttpResponseRedirect(self.get_success_url())
class StepTwoView(FormView):
form_class = StepTwoForm
template_name = 'step_two.html'
success_url = '/step-three/'
def get_initials(self):
# pre-populate form if someone goes back and forth between forms
initial = super(StepOneView, self).get_initial()
initial['cantidades'] = self.request.session.get('cantidades', None)
return initial
def form_valid(self, form):
# same as StepOneView
self.request.session['cantidades'] = form.cleaned_data.get('cantidades')
return HttpResponseRedirect(self.get_success_url())
# here we are going to use CreateView to save the Third step ModelForm
class StepThreeView(CreateView):
form_class = StepThreeForm
template_name = 'step_three.html'
success_url = '/thank-you/'
def form_valid(self, form):
form.instance.tamanios = self.request.session.get('tamanios') # get tamanios from session
form.instance.cantidades = self.request.session.get('cantidades') # get cantidades from session
del self.request.session['cantidades'] # delete cantidades value from session
del self.request.session['tamanios'] # delete tamanios value from session
self.request.session.modified = True
return super(StepThreeView, self).form_valid(form)
# template
# step_one.html, step_two.html, step_three.html can be all the same as following code
<form method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit">
</form>
# urls
path('step-one/', StepOneView.as_view()),
path('step-two/', StepTwoView.as_view()),
path('step-three/', StepThreeView.as_view()),
path('thank-you/', TemplateView.as_view(template_name="thank-you.html")),
In this approach, you don't need to split the model, and store the data in last step. FYI: its untested code, but I hope it will put you in right direction for implementation.
Upvotes: 2