Omar Gonzales
Omar Gonzales

Reputation: 4008

Django: split form in pages using formtools and save to data base

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

Answers (1)

ruddra
ruddra

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

Related Questions