Skip to content

Commit

Permalink
[#1795] Add backend support for multiple file upload
Browse files Browse the repository at this point in the history
  • Loading branch information
pi-sigma committed Nov 23, 2023
1 parent 116e0ab commit 02d41ae
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 65 deletions.
34 changes: 20 additions & 14 deletions src/open_inwoner/cms/cases/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
ZaakTypeConfig,
ZaakTypeInformatieObjectTypeConfig,
)
from open_inwoner.utils.forms import MultipleFileField


class CaseUploadForm(forms.Form):
Expand All @@ -17,7 +18,7 @@ class CaseUploadForm(forms.Form):
empty_label=None,
label=_("Bestand type"),
)
file = forms.FileField(label=_("Bestand"), required=True)
files = MultipleFileField(label=_("Bestand"))

def __init__(self, case, **kwargs):
self.oz_config = OpenZaakConfig.get_solo()
Expand All @@ -31,7 +32,7 @@ def __init__(self, case, **kwargs):
except (AttributeError, ObjectDoesNotExist):
pass

self.fields["file"].help_text = help_text
self.fields["files"].help_text = help_text

if case:
self.fields[
Expand All @@ -46,24 +47,29 @@ def __init__(self, case, **kwargs):
self.fields["type"].initial = list(choices)[0][0].value
self.fields["type"].widget = forms.HiddenInput()

def clean_file(self):
file = self.cleaned_data["file"]
def clean_files(self):
files = self.files.getlist("file")

max_allowed_size = 1024**2 * self.oz_config.max_upload_size
allowed_extensions = sorted(self.oz_config.allowed_file_extensions)
filename, file_extension = os.path.splitext(file.name)

if file.size > max_allowed_size:
raise ValidationError(
f"Een aangeleverd bestand dient maximaal {self.oz_config.max_upload_size} MB te zijn, uw bestand is te groot."
)
cleaned_files = []
for file in files:
filename, file_extension = os.path.splitext(file.name)

if file_extension.lower().replace(".", "") not in allowed_extensions:
raise ValidationError(
f"Het type bestand dat u hebt geüpload is ongeldig. Geldige bestandstypen zijn: {', '.join(allowed_extensions)}"
)
if file.size > max_allowed_size:
raise ValidationError(
f"Een aangeleverd bestand dient maximaal {self.oz_config.max_upload_size} MB te zijn, uw bestand is te groot."
)

if file_extension.lower().replace(".", "") not in allowed_extensions:
raise ValidationError(
f"Het type bestand dat u hebt geüpload is ongeldig. Geldige bestandstypen zijn: {', '.join(allowed_extensions)}"
)

cleaned_files.append(file)

return file
return cleaned_files


class CaseContactForm(forms.Form):
Expand Down
4 changes: 3 additions & 1 deletion src/open_inwoner/cms/cases/tests/test_htmx.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,9 @@ def test_cases(self, m):
upload_form = page.locator("#document-upload")
expect(upload_form).to_be_visible()

file_input = upload_form.get_by_text(_("Sleep of selecteer bestand"))
file_input = upload_form.get_by_label(
_("Sleep of selecteer bestand"), exact=True
)
file_input.set_input_files(
files=[
{
Expand Down
83 changes: 42 additions & 41 deletions src/open_inwoner/cms/cases/views/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,70 +480,71 @@ class CaseDocumentUploadFormView(CaseAccessMixin, LogMixin, FormView):
form_class = CaseUploadForm

def post(self, request, *args, **kwargs):
form = self.get_form()
form_class = self.get_form_class()
form = self.get_form(form_class)

if form.is_valid() and not getattr(self.case, "einddatum", None):
return self.handle_document_upload(request, form)
return self.form_invalid(form)

def handle_document_upload(self, request, form):
cleaned_data = form.cleaned_data
files = cleaned_data["files"]

file = cleaned_data["file"]
title = file.name
document_type = cleaned_data["type"]
source_organization = self.case.bronorganisatie

created_document = upload_document(
request.user,
file,
title,
document_type.informatieobjecttype_url,
source_organization,
)
if created_document:
created_documents = []

for file in files:
title = file.name
document_type = cleaned_data["type"]
source_organization = self.case.bronorganisatie

created_document = upload_document(
request.user,
file,
title,
document_type.informatieobjecttype_url,
source_organization,
)
created_relationship = connect_case_with_document(
self.case.url, created_document["url"]
)
if created_relationship:
self.log_user_action(
request.user,
_("Document was uploaded for {case}: {filename}").format(
case=self.case.identificatie,
filename=file.name,
),
)

# TODO implement `created_documents` properly when uploading of multiple
# docs is implemented
created_documents = [created_document]
success_message = (
_(
"Wij hebben **{num_uploaded} bestand(en)** succesvol geüpload:"
).format(num_uploaded=len(created_documents))
+ "\n\n"
+ "\n".join(f"- {doc['titel']}" for doc in created_documents)
)
# failed uploading the document or connecting it to the zaak
if not created_document or not created_relationship:
messages.add_message(
request,
messages.SUCCESS,
success_message,
extra_tags="as_markdown local_message",
messages.ERROR,
_("An error occured while uploading file {filename}").format(
filename=file.name
),
)

return HttpResponseClientRedirect(
reverse(
"cases:case_detail", kwargs={"object_id": str(self.case.uuid)}
)
)
else:
self.log_user_action(
request.user,
_("Document was uploaded for {case}: {filename}").format(
case=self.case.identificatie,
filename=file.name,
),
)
created_documents.append(created_document)

# fail uploading the document or connecting it to the zaak
success_message = (
_("Wij hebben **{num_uploaded} bestand(en)** succesvol geüpload:").format(
num_uploaded=len(created_documents)
)
+ "\n\n"
+ "\n".join(f"- {doc['titel']}" for doc in created_documents)
)
messages.add_message(
request,
messages.ERROR,
_("An error occured while uploading file {filename}").format(
filename=file.name
),
messages.SUCCESS,
success_message,
extra_tags="as_markdown local_message",
)

return HttpResponseClientRedirect(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
<div class="form__control file-input">
{% render_card direction="vertical" %}
{% icon icon="upload" icon_position="before" outlined=True %}
<input class="file-input__input" id="{{ field.auto_id }}" name="file" type="file"{% if field.field.required %} required{% endif %}>
<label class="button button--primary" aria-label="_('Sleep of selecteer bestand')" for="id_file">
<input class="file-input__input" id="file-upload" name="file" type="file"{% if field.field.required %} required{% endif %} {% if multiple %}multiple{% endif %}>
<label class="button button--primary" aria-label="_('Sleep of selecteer bestand')" for="file-upload">
{% trans 'Sleep of selecteer bestand' %}
</label>
{% endrender_card %}
Expand Down
5 changes: 3 additions & 2 deletions src/open_inwoner/components/templatetags/form_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ def input(field, **kwargs):


@register.inclusion_tag("components/Form/FileInput.html")
def file_input(file, text="", **kwargs):
def file_input(files, text="", **kwargs):
"""
Displaying a file upload interface.
Expand All @@ -288,7 +288,8 @@ def file_input(file, text="", **kwargs):
+ field: Field | The field that needs to be rendered.
- extra_classes: string| classes which should be added to the top-level container
"""
return {**kwargs, "field": file, "text": text}
multiple = files.field.widget.attrs.get("multiple", False)
return {**kwargs, "field": files, "text": text, "multiple": multiple}


@register.inclusion_tag("components/Form/DateField.html")
Expand Down
4 changes: 2 additions & 2 deletions src/open_inwoner/openzaak/tests/test_case_detail.py
Original file line number Diff line number Diff line change
Expand Up @@ -1004,7 +1004,7 @@ def test_upload_file_flow_fails_with_invalid_file_extension(self, m):
)
form = response.forms["document-upload"]
form["type"] = zaak_type_iotc.id
form["file"] = Upload("upload.xml", b"data", "application/xml")
form["files"] = Upload("upload.xml", b"data", "application/xml")
form_response = form.submit()

self.assertEqual(
Expand Down Expand Up @@ -1044,7 +1044,7 @@ def test_upload_with_larger_file_size_fails(self, m):
form = response.forms["document-upload"]

form["type"] = zaak_type_iotc.id
form["file"] = Upload("upload.txt", b"data", "text/plain")
form["files"] = Upload("upload.txt", b"data", "text/plain")
form_response = form.submit()

self.config.refresh_from_db()
Expand Down
6 changes: 3 additions & 3 deletions src/open_inwoner/templates/pages/cases/document_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{# if local upload is enabled #}
{% render_form id="document-upload" form=form method="POST" hxencoding="multipart/form-data" hxpost=hxpost_document_action hxtarget="#form_upload" submit_text=_("Bestand uploaden") extra_classes="case-detail-form" %}
{% csrf_token %}
{% input form.type no_label=True no_help=True class="label input" id="id_type" extra_classes="file-type__select" %}
{% file_input form.file %}
{% form_actions primary_text=_("Upload documenten") enctype="multipart/form-data" %}
{% input form.type no_label=False no_help=True class="label input" id="id_type" extra_classes="file-type__select" %}
{% file_input form.files %}
{% form_actions primary_text=_("Upload documenten") primary_icon="arrow_forward" enctype="multipart/form-data" %}
{% endrender_form %}
18 changes: 18 additions & 0 deletions src/open_inwoner/utils/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,24 @@ def get_context(self, name, value, attrs):
return context


class MultipleFileInput(forms.ClearableFileInput):
allow_multiple_selected = True


class MultipleFileField(forms.FileField):
def __init__(self, *args, **kwargs):
kwargs.setdefault("widget", MultipleFileInput())
super().__init__(*args, **kwargs)

def clean(self, data, initial=None):
single_file_clean = super().clean
if isinstance(data, (list, tuple)):
result = [single_file_clean(d, initial) for d in data]
else:
result = single_file_clean(data, initial)
return result


class LimitedUploadFileField(forms.FileField):
upload_error_messages = {
"min_size": _(
Expand Down

0 comments on commit 02d41ae

Please sign in to comment.