mirror of
https://github.com/inventree/InvenTree.git
synced 2025-05-03 05:48:47 +00:00
* Squashed commit of the following: commit f5cf7b2e7872fc19633321713965763d1890b495 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:36:57 2024 +0100 fixed reqs commit 9d845bee98befa4e53c2ac3c783bd704369e3ad2 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:32:35 2024 +0100 disable autofix/format commit aff5f271484c3500df7ddde043767c008ce4af21 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:28:50 2024 +0100 adjust checks commit 47271cf1efa848ec8374a0d83b5646d06fffa6e7 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:28:22 2024 +0100 reorder order of operations commit e1bf178b40b3f0d2d59ba92209156c43095959d2 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:01:09 2024 +0100 adapted ruff settings to better fit code base commit ad7d88a6f4f15c9552522131c4e207256fc2bbf6 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 19:59:45 2024 +0100 auto fixed docstring commit a2e54a760e17932dbbc2de0dec23906107f2cda9 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 19:46:35 2024 +0100 fix getattr useage commit cb80c73bc6c0be7f5d2ed3cc9b2ac03fdefd5c41 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 19:25:09 2024 +0100 fix requirements file commit b7780bbd21a32007f3b0ce495b519bf59bb19bf5 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:42:28 2024 +0100 fix removed sections commit 71f1681f55c15f62c16c1d7f30a745adc496db97 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:41:21 2024 +0100 fix djlint syntax commit a0bcf1bccef8a8ffd482f38e2063bc9066e1d759 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:35:28 2024 +0100 remove flake8 from code base commit 22475b31cc06919785be046e007915e43f356793 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:34:56 2024 +0100 remove flake8 from code base commit 0413350f14773ac6161473e0cfb069713c13c691 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:24:39 2024 +0100 moved ruff section commit d90c48a0bf98befdfacbbb093ee56cdb28afb40d Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:24:24 2024 +0100 move djlint config to pyproject commit c5ce55d5119bf2e35e429986f62f875c86178ae1 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:20:39 2024 +0100 added isort again commit 42a41d23afc280d4ee6f0e640148abc6f460f05a Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:19:02 2024 +0100 move config section commit 85692331816348cb1145570340d1f6488a8265cc Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:17:52 2024 +0100 fix codespell error commit 2897c6704d1311a800ce5aa47878d96d6980b377 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 17:29:21 2024 +0100 replaced flake8 with ruff mostly for speed improvements * enable autoformat * added autofixes * switched to single quotes everywhere * switched to ruff for import sorting * fix wrong url response * switched to pathlib for lookup * fixed lookup * Squashed commit of the following: commit d3b795824b5d6d1c0eda67150b45b5cd672b3f6b Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 22:56:17 2024 +0100 fixed source path commit 0bac0c19b88897a19d5c995e4ff50427718b827e Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 22:47:53 2024 +0100 fixed req commit 9f61f01d9cc01f1fb7123102f3658c890469b8ce Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 22:45:18 2024 +0100 added missing toml req commit 91b71ed24a6761b629768d0ad8829fec2819a966 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:49:50 2024 +0100 moved isort config commit 12460b04196b12d0272d40552402476d5492fea5 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:43:22 2024 +0100 remove flake8 section from setup.cfg commit f5cf7b2e7872fc19633321713965763d1890b495 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:36:57 2024 +0100 fixed reqs commit 9d845bee98befa4e53c2ac3c783bd704369e3ad2 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:32:35 2024 +0100 disable autofix/format commit aff5f271484c3500df7ddde043767c008ce4af21 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:28:50 2024 +0100 adjust checks commit 47271cf1efa848ec8374a0d83b5646d06fffa6e7 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:28:22 2024 +0100 reorder order of operations commit e1bf178b40b3f0d2d59ba92209156c43095959d2 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 20:01:09 2024 +0100 adapted ruff settings to better fit code base commit ad7d88a6f4f15c9552522131c4e207256fc2bbf6 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 19:59:45 2024 +0100 auto fixed docstring commit a2e54a760e17932dbbc2de0dec23906107f2cda9 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 19:46:35 2024 +0100 fix getattr useage commit cb80c73bc6c0be7f5d2ed3cc9b2ac03fdefd5c41 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 19:25:09 2024 +0100 fix requirements file commit b7780bbd21a32007f3b0ce495b519bf59bb19bf5 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:42:28 2024 +0100 fix removed sections commit 71f1681f55c15f62c16c1d7f30a745adc496db97 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:41:21 2024 +0100 fix djlint syntax commit a0bcf1bccef8a8ffd482f38e2063bc9066e1d759 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:35:28 2024 +0100 remove flake8 from code base commit 22475b31cc06919785be046e007915e43f356793 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:34:56 2024 +0100 remove flake8 from code base commit 0413350f14773ac6161473e0cfb069713c13c691 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:24:39 2024 +0100 moved ruff section commit d90c48a0bf98befdfacbbb093ee56cdb28afb40d Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:24:24 2024 +0100 move djlint config to pyproject commit c5ce55d5119bf2e35e429986f62f875c86178ae1 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:20:39 2024 +0100 added isort again commit 42a41d23afc280d4ee6f0e640148abc6f460f05a Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:19:02 2024 +0100 move config section commit 85692331816348cb1145570340d1f6488a8265cc Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 18:17:52 2024 +0100 fix codespell error commit 2897c6704d1311a800ce5aa47878d96d6980b377 Author: Matthias Mair <code@mjmair.com> Date: Sun Jan 7 17:29:21 2024 +0100 replaced flake8 with ruff mostly for speed improvements * fix coverage souce format --------- Co-authored-by: Oliver Walters <oliver.henry.walters@gmail.com>
512 lines
17 KiB
Python
512 lines
17 KiB
Python
"""Django views for interacting with common models."""
|
|
|
|
from django.conf import settings
|
|
from django.core.files.storage import FileSystemStorage
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from crispy_forms.helper import FormHelper
|
|
from formtools.wizard.views import SessionWizardView
|
|
|
|
from InvenTree.views import AjaxView
|
|
|
|
from . import forms
|
|
from .files import FileManager
|
|
|
|
|
|
class MultiStepFormView(SessionWizardView):
|
|
"""Setup basic methods of multi-step form.
|
|
|
|
form_list: list of forms
|
|
form_steps_description: description for each form
|
|
"""
|
|
|
|
form_steps_template = []
|
|
form_steps_description = []
|
|
file_manager = None
|
|
media_folder = ''
|
|
file_storage = FileSystemStorage(settings.MEDIA_ROOT)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
"""Override init method to set media folder."""
|
|
super().__init__(**kwargs)
|
|
|
|
self.process_media_folder()
|
|
|
|
def process_media_folder(self):
|
|
"""Process media folder."""
|
|
if self.media_folder:
|
|
media_folder_abs = settings.MEDIA_ROOT.joinpath(self.media_folder)
|
|
if not media_folder_abs.exists():
|
|
media_folder_abs.mkdir(parents=True, exist_ok=True)
|
|
self.file_storage = FileSystemStorage(location=media_folder_abs)
|
|
|
|
def get_template_names(self):
|
|
"""Select template."""
|
|
try:
|
|
# Get template
|
|
template = self.form_steps_template[self.steps.index]
|
|
except IndexError:
|
|
return self.template_name
|
|
|
|
return template
|
|
|
|
def get_context_data(self, **kwargs):
|
|
"""Update context data."""
|
|
# Retrieve current context
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
# Get form description
|
|
try:
|
|
description = self.form_steps_description[self.steps.index]
|
|
except IndexError:
|
|
description = ''
|
|
# Add description to form steps
|
|
context.update({'description': description})
|
|
|
|
return context
|
|
|
|
|
|
class FileManagementFormView(MultiStepFormView):
|
|
"""File management form wizard.
|
|
|
|
Perform the following steps:
|
|
1. Upload tabular data file
|
|
2. Match headers to InvenTree fields
|
|
3. Edit row data and match InvenTree items
|
|
"""
|
|
|
|
name = None
|
|
form_list = [
|
|
('upload', forms.UploadFileForm),
|
|
('fields', forms.MatchFieldForm),
|
|
('items', forms.MatchItemForm),
|
|
]
|
|
form_steps_description = [_('Upload File'), _('Match Fields'), _('Match Items')]
|
|
media_folder = 'file_upload/'
|
|
extra_context_data = {}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
"""Initialize the FormView."""
|
|
# Perform all checks and inits for MultiStepFormView
|
|
super().__init__(self, *args, **kwargs)
|
|
|
|
# Check for file manager class
|
|
if not hasattr(self, 'file_manager_class') and not issubclass(
|
|
self.file_manager_class, FileManager
|
|
):
|
|
raise NotImplementedError(
|
|
'A subclass of a file manager class needs to be set!'
|
|
)
|
|
|
|
def get_context_data(self, form=None, **kwargs):
|
|
"""Handle context data."""
|
|
if form is None:
|
|
form = self.get_form()
|
|
|
|
context = super().get_context_data(form=form, **kwargs)
|
|
|
|
if self.steps.current in ('fields', 'items'):
|
|
# Get columns and row data
|
|
self.columns = self.file_manager.columns()
|
|
self.rows = self.file_manager.rows()
|
|
# Check for stored data
|
|
stored_data = self.storage.get_step_data(self.steps.current)
|
|
if stored_data:
|
|
self.get_form_table_data(stored_data)
|
|
elif self.steps.current == 'items':
|
|
# Set form table data
|
|
self.set_form_table_data(form=form)
|
|
|
|
# Update context
|
|
context.update({'rows': self.rows})
|
|
context.update({'columns': self.columns})
|
|
|
|
# Load extra context data
|
|
for key, items in self.extra_context_data.items():
|
|
context.update({key: items})
|
|
|
|
return context
|
|
|
|
def get_file_manager(self, step=None, form=None):
|
|
"""Get FileManager instance from uploaded file."""
|
|
if self.file_manager:
|
|
return
|
|
|
|
if step is not None:
|
|
# Retrieve stored files from upload step
|
|
upload_files = self.storage.get_step_files('upload')
|
|
if upload_files:
|
|
# Get file
|
|
file = upload_files.get('upload-file', None)
|
|
if file:
|
|
self.file_manager = self.file_manager_class(
|
|
file=file, name=self.name
|
|
)
|
|
|
|
def get_form_kwargs(self, step=None):
|
|
"""Update kwargs to dynamically build forms."""
|
|
# Always retrieve FileManager instance from uploaded file
|
|
self.get_file_manager(step)
|
|
|
|
if step == 'upload':
|
|
# Dynamically build upload form
|
|
if self.name:
|
|
kwargs = {'name': self.name}
|
|
return kwargs
|
|
elif step == 'fields':
|
|
# Dynamically build match field form
|
|
kwargs = {'file_manager': self.file_manager}
|
|
return kwargs
|
|
elif step == 'items':
|
|
# Dynamically build match item form
|
|
kwargs = {}
|
|
kwargs['file_manager'] = self.file_manager
|
|
|
|
# Get data from fields step
|
|
data = self.storage.get_step_data('fields')
|
|
|
|
# Process to update columns and rows
|
|
self.rows = self.file_manager.rows()
|
|
self.columns = self.file_manager.columns()
|
|
self.get_form_table_data(data)
|
|
self.set_form_table_data()
|
|
self.get_field_selection()
|
|
|
|
kwargs['row_data'] = self.rows
|
|
|
|
return kwargs
|
|
|
|
return super().get_form_kwargs()
|
|
|
|
def get_form(self, step=None, data=None, files=None):
|
|
"""Add crispy-form helper to form."""
|
|
form = super().get_form(step=step, data=data, files=files)
|
|
|
|
form.helper = FormHelper()
|
|
form.helper.form_show_labels = False
|
|
|
|
return form
|
|
|
|
def get_form_table_data(self, form_data):
|
|
"""Extract table cell data from form data and fields. These data are used to maintain state between sessions.
|
|
|
|
Table data keys are as follows:
|
|
|
|
col_name_<idx> - Column name at idx as provided in the uploaded file
|
|
col_guess_<idx> - Column guess at idx as selected
|
|
row_<x>_col<y> - Cell data as provided in the uploaded file
|
|
"""
|
|
# Map the columns
|
|
self.column_names = {}
|
|
self.column_selections = {}
|
|
|
|
self.row_data = {}
|
|
|
|
for item, value in form_data.items():
|
|
# Column names as passed as col_name_<idx> where idx is an integer
|
|
|
|
# Extract the column names
|
|
if item.startswith('col_name_'):
|
|
try:
|
|
col_id = int(item.replace('col_name_', ''))
|
|
except ValueError:
|
|
continue
|
|
|
|
self.column_names[col_id] = value
|
|
|
|
# Extract the column selections (in the 'select fields' view)
|
|
if item.startswith('fields-'):
|
|
try:
|
|
col_name = item.replace('fields-', '')
|
|
except ValueError:
|
|
continue
|
|
|
|
for idx, name in self.column_names.items():
|
|
if name == col_name:
|
|
self.column_selections[idx] = value
|
|
break
|
|
|
|
# Extract the row data
|
|
if item.startswith('row_'):
|
|
# Item should be of the format row_<r>_col_<c>
|
|
s = item.split('_')
|
|
|
|
if len(s) < 4:
|
|
continue
|
|
|
|
# Ignore row/col IDs which are not correct numeric values
|
|
try:
|
|
row_id = int(s[1])
|
|
col_id = int(s[3])
|
|
except ValueError:
|
|
continue
|
|
|
|
if row_id not in self.row_data:
|
|
self.row_data[row_id] = {}
|
|
|
|
self.row_data[row_id][col_id] = value
|
|
|
|
def set_form_table_data(self, form=None):
|
|
"""Set the form table data."""
|
|
if self.column_names:
|
|
# Re-construct the column data
|
|
self.columns = []
|
|
|
|
for idx, value in self.column_names.items():
|
|
header = {'name': value, 'guess': self.column_selections.get(idx, '')}
|
|
self.columns.append(header)
|
|
|
|
if self.row_data:
|
|
# Re-construct the row data
|
|
self.rows = []
|
|
|
|
# Update the row data
|
|
for row_idx, row_key in enumerate(sorted(self.row_data.keys())):
|
|
row_data = self.row_data[row_key]
|
|
|
|
data = []
|
|
|
|
for idx, item in row_data.items():
|
|
column_data = {
|
|
'name': self.column_names[idx],
|
|
'guess': self.column_selections[idx],
|
|
}
|
|
|
|
cell_data = {'cell': item, 'idx': idx, 'column': column_data}
|
|
data.append(cell_data)
|
|
|
|
row = {'index': row_idx, 'data': data, 'errors': {}}
|
|
|
|
self.rows.append(row)
|
|
|
|
# In the item selection step: update row data with mapping to form fields
|
|
if form and self.steps.current == 'items':
|
|
# Find field keys
|
|
field_keys = []
|
|
for field in form.fields:
|
|
field_key = field.split('-')[0]
|
|
if field_key not in field_keys:
|
|
field_keys.append(field_key)
|
|
|
|
# Populate rows
|
|
for row in self.rows:
|
|
for field_key in field_keys:
|
|
# Map row data to field
|
|
row[field_key] = field_key + '-' + str(row['index'])
|
|
|
|
def get_column_index(self, name):
|
|
"""Return the index of the column with the given name.
|
|
|
|
It named column is not found, return -1
|
|
"""
|
|
try:
|
|
idx = list(self.column_selections.values()).index(name)
|
|
except ValueError:
|
|
idx = -1
|
|
|
|
return idx
|
|
|
|
def get_field_selection(self):
|
|
"""Once data columns have been selected, attempt to pre-select the proper data from the database. This function is called once the field selection has been validated. The pre-fill data are then passed through to the part selection form.
|
|
|
|
This method is very specific to the type of data found in the file,
|
|
therefore overwrite it in the subclass.
|
|
"""
|
|
pass
|
|
|
|
def get_clean_items(self):
|
|
"""Returns dict with all cleaned values."""
|
|
items = {}
|
|
|
|
for form_key, form_value in self.get_all_cleaned_data().items():
|
|
# Split key from row value
|
|
try:
|
|
(field, idx) = form_key.split('-')
|
|
except ValueError:
|
|
continue
|
|
|
|
try:
|
|
if idx not in items:
|
|
# Insert into items
|
|
items.update({idx: {self.form_field_map[field]: form_value}})
|
|
else:
|
|
# Update items
|
|
items[idx][self.form_field_map[field]] = form_value
|
|
except KeyError:
|
|
pass
|
|
|
|
return items
|
|
|
|
def check_field_selection(self, form):
|
|
"""Check field matching."""
|
|
# Are there any missing columns?
|
|
missing_columns = []
|
|
|
|
# Check that all required fields are present
|
|
for col in self.file_manager.REQUIRED_HEADERS:
|
|
if col not in self.column_selections.values():
|
|
missing_columns.append(col)
|
|
|
|
# Check that at least one of the part match field is present
|
|
part_match_found = False
|
|
for col in self.file_manager.ITEM_MATCH_HEADERS:
|
|
if col in self.column_selections.values():
|
|
part_match_found = True
|
|
break
|
|
|
|
# If not, notify user
|
|
if not part_match_found:
|
|
for col in self.file_manager.ITEM_MATCH_HEADERS:
|
|
missing_columns.append(col)
|
|
|
|
# Track any duplicate column selections
|
|
duplicates = []
|
|
|
|
for col in self.column_names:
|
|
if col in self.column_selections:
|
|
guess = self.column_selections[col]
|
|
else:
|
|
guess = None
|
|
|
|
if guess:
|
|
n = list(self.column_selections.values()).count(
|
|
self.column_selections[col]
|
|
)
|
|
if n > 1 and self.column_selections[col] not in duplicates:
|
|
duplicates.append(self.column_selections[col])
|
|
|
|
# Store extra context data
|
|
self.extra_context_data = {
|
|
'missing_columns': missing_columns,
|
|
'duplicates': duplicates,
|
|
}
|
|
|
|
# Data validation
|
|
valid = not missing_columns and not duplicates
|
|
|
|
return valid
|
|
|
|
def validate(self, step, form):
|
|
"""Validate forms."""
|
|
valid = True
|
|
|
|
# Get form table data
|
|
self.get_form_table_data(form.data)
|
|
|
|
if step == 'fields':
|
|
# Validate user form data
|
|
valid = self.check_field_selection(form)
|
|
|
|
if not valid:
|
|
form.add_error(None, _('Fields matching failed'))
|
|
|
|
elif step == 'items':
|
|
pass
|
|
|
|
return valid
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
"""Perform validations before posting data."""
|
|
wizard_goto_step = self.request.POST.get('wizard_goto_step', None)
|
|
|
|
form = self.get_form(data=self.request.POST, files=self.request.FILES)
|
|
|
|
form_valid = self.validate(self.steps.current, form)
|
|
|
|
if not form_valid and not wizard_goto_step:
|
|
# Re-render same step
|
|
return self.render(form)
|
|
|
|
return super().post(*args, **kwargs)
|
|
|
|
|
|
class FileManagementAjaxView(AjaxView):
|
|
"""Use a FileManagementFormView as base for a AjaxView Inherit this class before inheriting the base FileManagementFormView.
|
|
|
|
ajax_form_steps_template: templates for rendering ajax
|
|
validate: function to validate the current form -> normally point to the same function in the base FileManagementFormView
|
|
"""
|
|
|
|
def post(self, request):
|
|
"""Handle wizard step call.
|
|
|
|
Possible actions:
|
|
- Step back -> render previous step
|
|
- Invalid form -> render error
|
|
- Valid form and not done -> render next step
|
|
- Valid form and done -> render final step
|
|
"""
|
|
# check if back-step button was selected
|
|
wizard_back = self.request.POST.get('act-btn_back', None)
|
|
if wizard_back:
|
|
back_step_index = self.get_step_index() - 1
|
|
self.storage.current_step = list(self.get_form_list().keys())[
|
|
back_step_index
|
|
]
|
|
return self.renderJsonResponse(request, data={'form_valid': None})
|
|
|
|
# validate form
|
|
form = self.get_form(data=self.request.POST, files=self.request.FILES)
|
|
form_valid = self.validate(self.steps.current, form)
|
|
|
|
# check if valid
|
|
if not form_valid:
|
|
return self.renderJsonResponse(request, data={'form_valid': None})
|
|
|
|
# store the cleaned data and files.
|
|
self.storage.set_step_data(self.steps.current, self.process_step(form))
|
|
self.storage.set_step_files(self.steps.current, self.process_step_files(form))
|
|
|
|
# check if the current step is the last step
|
|
if self.steps.current == self.steps.last:
|
|
# call done - to process data, returned response is not used
|
|
self.render_done(form)
|
|
data = {'form_valid': True, 'success': _('Parts imported')}
|
|
return self.renderJsonResponse(request, data=data)
|
|
self.storage.current_step = self.steps.next
|
|
|
|
return self.renderJsonResponse(request, data={'form_valid': None})
|
|
|
|
def get(self, request):
|
|
"""Reset storage if flag is set, proceed to render JsonResponse."""
|
|
if 'reset' in request.GET:
|
|
# reset form
|
|
self.storage.reset()
|
|
self.storage.current_step = self.steps.first
|
|
return self.renderJsonResponse(request)
|
|
|
|
def renderJsonResponse(self, request, form=None, data=None, context=None):
|
|
"""Always set the right templates before rendering."""
|
|
# Set default - see B006
|
|
if data is None:
|
|
data = {}
|
|
|
|
self.setTemplate()
|
|
return super().renderJsonResponse(
|
|
request, form=form, data=data, context=context
|
|
)
|
|
|
|
def get_data(self) -> dict:
|
|
"""Get extra context data."""
|
|
data = super().get_data()
|
|
data['hideErrorMessage'] = '1' # hide the error
|
|
buttons = (
|
|
[{'name': 'back', 'title': _('Previous Step')}]
|
|
if self.get_step_index() > 0
|
|
else []
|
|
)
|
|
data['buttons'] = buttons # set buttons
|
|
return data
|
|
|
|
def setTemplate(self):
|
|
"""Set template name and title."""
|
|
self.ajax_template_name = self.ajax_form_steps_template[self.get_step_index()]
|
|
self.ajax_form_title = self.form_steps_description[self.get_step_index()]
|
|
|
|
def validate(self, obj, form, **kwargs):
|
|
"""Generic validate action.
|
|
|
|
This is the point to process provided userinput.
|
|
"""
|
|
raise NotImplementedError('This function needs to be overridden!')
|