Skip to content

Commit

Permalink
✨ #1795 - feat: implement drag-and-drop enabled file input.
Browse files Browse the repository at this point in the history
  • Loading branch information
svenvandescheur committed Nov 17, 2023
1 parent bc2b406 commit 8bcc307
Show file tree
Hide file tree
Showing 20 changed files with 330 additions and 418 deletions.
28 changes: 16 additions & 12 deletions src/open_inwoner/cms/cases/forms.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
import os

from django import forms
from django.core.exceptions import ValidationError
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.utils.translation import gettext_lazy as _

from open_inwoner.openzaak.models import (
OpenZaakConfig,
ZaakTypeConfig,
ZaakTypeInformatieObjectTypeConfig,
)
from open_inwoner.utils.validators import CharFieldValidator


class CaseUploadForm(forms.Form):
title = forms.CharField(
label=_("Titel bestand"),
max_length=255,
# empty_value=_("Titel bestand"),#TypeError: Object of type __proxy__ is not JSON serializable
required=False,
)
type = forms.ModelChoiceField(
ZaakTypeInformatieObjectTypeConfig.objects.none(),
empty_label=None,
Expand All @@ -26,8 +20,19 @@ class CaseUploadForm(forms.Form):
file = forms.FileField(label=_("Bestand"))

def __init__(self, case, **kwargs):
self.oz_config = OpenZaakConfig.get_solo()
super().__init__(**kwargs)

help_text = f"Grootte max. { self.oz_config.max_upload_size } MB, toegestane document formaten: { ', '.join(self.oz_config.allowed_file_extensions) }."

try:
ztc = ZaakTypeConfig.objects.filter_case_type(case.zaaktype).get()
help_text = ztc.description or help_text
except (AttributeError, ObjectDoesNotExist):
pass

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

if case:
self.fields[
"type"
Expand All @@ -44,14 +49,13 @@ def __init__(self, case, **kwargs):
def clean_file(self):
file = self.cleaned_data["file"]

config = OpenZaakConfig.get_solo()
max_allowed_size = 1024**2 * config.max_upload_size
allowed_extensions = sorted(config.allowed_file_extensions)
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 {config.max_upload_size} MB te zijn, uw bestand is te groot."
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:
Expand Down
3 changes: 2 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,8 @@ def test_cases(self, m):
upload_form = page.locator("#document-upload")
expect(upload_form).to_be_visible()

upload_form.get_by_label(_("Selecteer bestanden")).set_input_files(
file_input = upload_form.get_by_text(_("Sleep of selecteer bestand"))
file_input.set_input_files(
files=[
{
"name": "uploaded_test_file.txt",
Expand Down
2 changes: 1 addition & 1 deletion src/open_inwoner/cms/cases/views/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ def handle_document_upload(self, request, form):
cleaned_data = form.cleaned_data

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
{% endif %}
</a>
{% else %}
<button
<{{ tag }}
class="{{ classes }}"
type="{{type}}"
name="{{ name }}"
Expand All @@ -42,5 +42,5 @@
{% else %}
{% if not hide_text %}{{text}}{% endif %}
{% endif %}
</button>
</{{ tag }}>
{% endif %}
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
{% load i18n form_tags icon_tags %}

<div class="form__control {{ extra_classes|default:"upload" }}">
<div class="inputfile-group">
<label class="label">
<span class="input-file">
<p>{% icon icon="upload" icon_position="before" outlined=True %}</p>
{% load i18n button_tags card_tags form_tags icon_tags %}

{{ field|addclass:"inputfile" }}
{# appended HTML #}
{# <input type="file" name="file" class="inputfile" required="" id="id_file" data-max-size="13000">#}
<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">
{% button primary=True tag='label' text=text|default:_('Sleep of selecteer bestand') extra_attributes='for='|add:field.auto_id %}
{% endrender_card %}

<span class="label__label cloud" for="{{ field.auto_id }}">
{{ text }}
</span>
</span>
{% if field.help_text %}<p class="p p--muted p--small">{{ field.help_text }}</p>{% endif %}
{% if field.errors %}{% errors errors=field.errors %}{% endif %}

{% if field.errors %}
{% errors errors=field.errors %}
{% endif %}
</label>
<div class="file-list" aria-live="polite">
<h4 class="h4">{% trans 'Geselecteerd bestand'%}</h4>
<ul class="file-list__list" aria-live="polite" data-label-delete="{% trans 'Verwijderen' %}"></ul>
</div>
</div>
2 changes: 2 additions & 0 deletions src/open_inwoner/components/templatetags/button_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def button(text, **kwargs):
- icon: string | the icon that you want to display.
- icon_position: enum[before, after] | where the icon should be positioned to the text.
- icon_outlined: bool | if the outlined icons should be used.
- tag: string | The tag to use (if not a link, defaults to button)
- text_icon: string | An additional icon to show before the (current value) text.
- type: string | the type of button that should be used.
- title: string | The HTML title attribute if different than the text.
Expand Down Expand Up @@ -129,4 +130,5 @@ def get_classes():

kwargs["classes"] = get_classes()
kwargs["text"] = text
kwargs["tag"] = kwargs.get("tag", "button")
return {**kwargs}
2 changes: 1 addition & 1 deletion 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(file, text="", **kwargs):
"""
Displaying a file upload interface.
Expand Down
5 changes: 4 additions & 1 deletion src/open_inwoner/components/templatetags/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,7 @@ def get_product_url(context, product):
def as_attributes(attribute_dict):
if not attribute_dict:
return ""
return format_html_join(" ", '{}="{}"', attribute_dict.items())
try:
return format_html_join(" ", '{}="{}"', attribute_dict.items())
except AttributeError:
return attribute_dict
4 changes: 4 additions & 0 deletions src/open_inwoner/conf/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@
}
}

# Geospatial libraries
GEOS_LIBRARY_PATH = config("GEOS_LIBRARY_PATH", None)
GDAL_LIBRARY_PATH = config("GDAL_LIBRARY_PATH", None)

CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
Expand Down
23 changes: 23 additions & 0 deletions src/open_inwoner/js/components/abstract/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class Component {

this.setState(initialState)
this.bindEvents()
this.setupMutationObserver()
}

/**
Expand All @@ -28,6 +29,28 @@ export class Component {
*/
bindEvents() {}

/**
* Unbinds events from callbacks.
* Callbacks functions should be strict equal between `bindEvents()` and `unbindEvents`.
* Use this to call `removeEventListener()` for every `addEventListener`()` called.
*/
unbindEvents() {}

/**
* Sets up the mutation observer managing §this.unbindEvents()`.
*/
setupMutationObserver() {
this.mutationObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList' && !document.contains(this.node)) {
// Element has been removed from the DOM
this.unbindEvents()
this.mutationObserver.disconnect() // Stop observing once the element is removed
}
})
})
}

/**
* Mutates state, then re-renders.
* @param {Object} changes Object containing (only) changes to state.
Expand Down
Loading

0 comments on commit 8bcc307

Please sign in to comment.