Amélioration UX
This commit is contained in:
parent
a3e13429eb
commit
3001685937
@ -40,6 +40,54 @@ import logging
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class GroupFormMixin:
|
||||||
|
|
||||||
|
template_name = 'agenda_culturel/forms/div_group.html'
|
||||||
|
|
||||||
|
class FieldGroup:
|
||||||
|
|
||||||
|
def __init__(self, id, label, display_label=False, maskable=False, default_masked=True):
|
||||||
|
self.id = id
|
||||||
|
self.label = label
|
||||||
|
self.display_label = display_label
|
||||||
|
self.maskable = maskable
|
||||||
|
self.default_masked = default_masked
|
||||||
|
|
||||||
|
def toggle_field_name(self):
|
||||||
|
return 'group_' + self.id
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.groups = []
|
||||||
|
|
||||||
|
def add_group(self, *args, **kwargs):
|
||||||
|
self.groups.append(GroupFormMixin.FieldGroup(*args, **kwargs))
|
||||||
|
if self.groups[-1].maskable:
|
||||||
|
self.fields[self.groups[-1].toggle_field_name()] = BooleanField(required=False)
|
||||||
|
self.fields[self.groups[-1].toggle_field_name()].toggle_group = True
|
||||||
|
|
||||||
|
def get_fields_in_group(self, g):
|
||||||
|
return [f for f in self.visible_fields() if not hasattr(f.field, "toggle_group") and hasattr(f.field, "group_id") and f.field.group_id == g.id]
|
||||||
|
|
||||||
|
def get_no_group_fields(self):
|
||||||
|
return [f for f in self.visible_fields() if not hasattr(f.field, "toggle_group") and (not hasattr(f.field, "group_id") or f.field.group_id == None)]
|
||||||
|
|
||||||
|
def fields_by_group(self):
|
||||||
|
return [(g, self.get_fields_in_group(g)) for g in self.groups] + [(None, self.get_no_group_fields())]
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
result = super().clean()
|
||||||
|
|
||||||
|
if result:
|
||||||
|
data = dict(self.data)
|
||||||
|
# for each masked group, we remove data
|
||||||
|
for g in self.groups:
|
||||||
|
if g.maskable and not g.toggle_field_name() in data:
|
||||||
|
fields = self.get_fields_in_group(g)
|
||||||
|
for f in fields:
|
||||||
|
self.cleaned_data[f.name] = None
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
class TagForm(ModelForm):
|
class TagForm(ModelForm):
|
||||||
required_css_class = 'required'
|
required_css_class = 'required'
|
||||||
@ -68,16 +116,12 @@ class TagRenameForm(Form):
|
|||||||
name = kwargs.pop("name", None)
|
name = kwargs.pop("name", None)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if not (force or (not len(args) == 0 and 'force' in args[0])):
|
if not (force or (not len(args) == 0 and 'force' in args[0])):
|
||||||
logger.warning('on delete ' + str(args))
|
|
||||||
del self.fields["force"]
|
del self.fields["force"]
|
||||||
else:
|
|
||||||
logger.warning('on delete pas')
|
|
||||||
if not name is None and self.fields["name"].initial is None:
|
if not name is None and self.fields["name"].initial is None:
|
||||||
self.fields["name"].initial = name
|
self.fields["name"].initial = name
|
||||||
|
|
||||||
|
|
||||||
def is_force(self):
|
def is_force(self):
|
||||||
logger.warning(self.cleaned_data)
|
|
||||||
return "force" in self.fields and self.cleaned_data["force"] == True
|
return "force" in self.fields and self.cleaned_data["force"] == True
|
||||||
|
|
||||||
class URLSubmissionForm(Form):
|
class URLSubmissionForm(Form):
|
||||||
@ -88,14 +132,12 @@ class URLSubmissionForm(Form):
|
|||||||
label=_("Category"),
|
label=_("Category"),
|
||||||
queryset=Category.objects.all().order_by("name"),
|
queryset=Category.objects.all().order_by("name"),
|
||||||
initial=None,
|
initial=None,
|
||||||
help_text=_('Optional. If you don''t specify a category, we''ll find it for you.'),
|
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
tags = MultipleChoiceField(
|
tags = MultipleChoiceField(
|
||||||
label=_("Tags"),
|
label=_("Tags"),
|
||||||
initial=None,
|
initial=None,
|
||||||
choices=[],
|
choices=[],
|
||||||
help_text=_('Optional. If you offer labels, they''ll make it easier for your girlfriends to find your event.'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -133,7 +175,7 @@ class CategorisationRuleImportForm(ModelForm):
|
|||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
class EventForm(ModelForm):
|
class EventForm(GroupFormMixin, ModelForm):
|
||||||
required_css_class = 'required'
|
required_css_class = 'required'
|
||||||
|
|
||||||
old_local_image = CharField(widget=HiddenInput(), required=False)
|
old_local_image = CharField(widget=HiddenInput(), required=False)
|
||||||
@ -143,7 +185,6 @@ class EventForm(ModelForm):
|
|||||||
label=_("Tags"),
|
label=_("Tags"),
|
||||||
initial=None,
|
initial=None,
|
||||||
choices=[],
|
choices=[],
|
||||||
help_text=_('Optional. If you offer labels, they''ll make it easier for your girlfriends to find your event.'),
|
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -192,6 +233,45 @@ class EventForm(ModelForm):
|
|||||||
self.fields['category'].initial = Category.get_default_category()
|
self.fields['category'].initial = Category.get_default_category()
|
||||||
self.fields['tags'].choices = Tag.get_tag_groups(all=True)
|
self.fields['tags'].choices = Tag.get_tag_groups(all=True)
|
||||||
|
|
||||||
|
# set groups
|
||||||
|
self.add_group('main', _('Main fields'))
|
||||||
|
self.fields['title'].group_id = 'main'
|
||||||
|
|
||||||
|
self.add_group('start', _('Start of event'))
|
||||||
|
self.fields['start_day'].group_id = 'start'
|
||||||
|
self.fields['start_time'].group_id = 'start'
|
||||||
|
|
||||||
|
self.add_group('end', _('End of event'))
|
||||||
|
self.fields['end_day'].group_id = 'end'
|
||||||
|
self.fields['end_time'].group_id = 'end'
|
||||||
|
|
||||||
|
self.add_group('recurrences', _('This is a recurring event'), maskable=True, default_masked=True)
|
||||||
|
self.fields['recurrences'].group_id = 'recurrences'
|
||||||
|
|
||||||
|
self.add_group('details', _('Details'))
|
||||||
|
self.fields['description'].group_id = 'details'
|
||||||
|
if is_authenticated:
|
||||||
|
self.fields['organisers'].group_id = 'details'
|
||||||
|
|
||||||
|
self.add_group('location', _('Location'))
|
||||||
|
self.fields['location'].group_id = 'location'
|
||||||
|
self.fields['exact_location'].group_id = 'location'
|
||||||
|
|
||||||
|
self.add_group('illustration', _('Illustration'))
|
||||||
|
self.fields['local_image'].group_id = 'illustration'
|
||||||
|
self.fields['image_alt'].group_id = 'illustration'
|
||||||
|
|
||||||
|
|
||||||
|
if is_authenticated:
|
||||||
|
self.add_group('meta-admin', _('Meta information'))
|
||||||
|
self.fields['category'].group_id = 'meta-admin'
|
||||||
|
self.fields['tags'].group_id = 'meta-admin'
|
||||||
|
self.fields['status'].group_id = 'meta-admin'
|
||||||
|
else:
|
||||||
|
self.add_group('meta', _('Meta information'))
|
||||||
|
self.fields['category'].group_id = 'meta'
|
||||||
|
self.fields['tags'].group_id = 'meta'
|
||||||
|
|
||||||
def is_clone_from_url(self):
|
def is_clone_from_url(self):
|
||||||
return self.cloning
|
return self.cloning
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -212,8 +212,8 @@ class Tag(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Étiquette")
|
verbose_name = _("Tag")
|
||||||
verbose_name_plural = _("Étiquettes")
|
verbose_name_plural = _("Tags")
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=['name']),
|
models.Index(fields=['name']),
|
||||||
]
|
]
|
||||||
@ -555,7 +555,7 @@ class Event(models.Model):
|
|||||||
recurrence_dtend = models.DateTimeField(editable=False, blank=True, null=True)
|
recurrence_dtend = models.DateTimeField(editable=False, blank=True, null=True)
|
||||||
|
|
||||||
title = models.CharField(
|
title = models.CharField(
|
||||||
verbose_name=_("Title"), help_text=_("Short title"), max_length=512
|
verbose_name=_("Title"), max_length=512
|
||||||
)
|
)
|
||||||
|
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
@ -565,32 +565,27 @@ class Event(models.Model):
|
|||||||
category = models.ForeignKey(
|
category = models.ForeignKey(
|
||||||
Category,
|
Category,
|
||||||
verbose_name=_("Category"),
|
verbose_name=_("Category"),
|
||||||
help_text=_("Category of the event"),
|
|
||||||
null=True,
|
null=True,
|
||||||
default=None,
|
default=None,
|
||||||
on_delete=models.SET_DEFAULT,
|
on_delete=models.SET_DEFAULT,
|
||||||
)
|
)
|
||||||
|
|
||||||
start_day = models.DateField(
|
start_day = models.DateField(
|
||||||
verbose_name=_("Day of the event"), help_text=_("Day of the event")
|
verbose_name=_("Start day")
|
||||||
)
|
)
|
||||||
start_time = models.TimeField(
|
start_time = models.TimeField(
|
||||||
verbose_name=_("Starting time"),
|
verbose_name=_("Start time"),
|
||||||
help_text=_("Starting time"),
|
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
end_day = models.DateField(
|
end_day = models.DateField(
|
||||||
verbose_name=_("End day of the event"),
|
verbose_name=_("End day"),
|
||||||
help_text=_(
|
|
||||||
"End day of the event, only required if different from the start day."
|
|
||||||
),
|
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
end_time = models.TimeField(
|
end_time = models.TimeField(
|
||||||
verbose_name=_("Final time"), help_text=_("Final time"), blank=True, null=True
|
verbose_name=_("End time"), blank=True, null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
recurrences = recurrence.fields.RecurrenceField(
|
recurrences = recurrence.fields.RecurrenceField(
|
||||||
@ -600,7 +595,6 @@ class Event(models.Model):
|
|||||||
exact_location = models.ForeignKey(
|
exact_location = models.ForeignKey(
|
||||||
Place,
|
Place,
|
||||||
verbose_name=_("Location"),
|
verbose_name=_("Location"),
|
||||||
help_text=_("Address of the event"),
|
|
||||||
null=True,
|
null=True,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
blank=True,
|
blank=True,
|
||||||
@ -616,6 +610,13 @@ class Event(models.Model):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
description = models.TextField(
|
||||||
|
verbose_name=_("Description"),
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
organisers = models.ManyToManyField(Organisation,
|
organisers = models.ManyToManyField(Organisation,
|
||||||
related_name='organised_events',
|
related_name='organised_events',
|
||||||
verbose_name=_("Organisers"),
|
verbose_name=_("Organisers"),
|
||||||
@ -625,24 +626,16 @@ class Event(models.Model):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
description = models.TextField(
|
|
||||||
verbose_name=_("Description"),
|
|
||||||
help_text=_("General description of the event"),
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
local_image = models.ImageField(
|
local_image = models.ImageField(
|
||||||
verbose_name=_("Illustration (local image)"),
|
verbose_name=_("Illustration"),
|
||||||
help_text=_("Illustration image stored in the agenda server"),
|
|
||||||
max_length=1024,
|
max_length=1024,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
image = models.URLField(
|
image = models.URLField(
|
||||||
verbose_name=_("Illustration"),
|
verbose_name=_("Illustration (URL)"),
|
||||||
help_text=_("URL of the illustration image"),
|
help_text=_("External URL of the illustration image"),
|
||||||
max_length=1024,
|
max_length=1024,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
@ -680,7 +673,6 @@ class Event(models.Model):
|
|||||||
tags = ArrayField(
|
tags = ArrayField(
|
||||||
models.CharField(max_length=64),
|
models.CharField(max_length=64),
|
||||||
verbose_name=_("Tags"),
|
verbose_name=_("Tags"),
|
||||||
help_text=_("A list of tags that describe the event."),
|
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
@ -43,6 +43,10 @@ const update_datetimes = (event) => {
|
|||||||
|
|
||||||
start_day.oldvalue = start_day.value;
|
start_day.oldvalue = start_day.value;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
new_date = new Date(start_day.value);
|
||||||
|
end_day.value = formatDate(new_date);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (end_day.value && end_time.value && start_day.value) {
|
if (end_day.value && end_time.value && start_day.value) {
|
||||||
|
@ -972,7 +972,7 @@ aside nav.paragraph li a, aside .no-breakline li a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* mise en forme pour les récurrences */
|
/* mise en forme pour les récurrences */
|
||||||
article form p .recurrence-widget {
|
article form div .recurrence-widget {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
|
||||||
@ -1452,3 +1452,47 @@ label.required::after {
|
|||||||
content: ' *';
|
content: ' *';
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contenu #event_form {
|
||||||
|
div#group_start, div#group_end {
|
||||||
|
grid-column: auto;
|
||||||
|
.body_group {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 10px;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* group form */
|
||||||
|
@media only screen and (min-width: 992px) {
|
||||||
|
.contenu #event_form form {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
grid-gap: 10px;
|
||||||
|
|
||||||
|
>div {
|
||||||
|
grid-column: 1 / 3;
|
||||||
|
}
|
||||||
|
div#group_meta, div#group_meta-admin {
|
||||||
|
.body_group {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
grid-gap: 10px;
|
||||||
|
div {
|
||||||
|
grid-column: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div#group_meta {
|
||||||
|
.body_group {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.maskable_group .body_group.closed {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
@ -48,7 +48,7 @@ Duplication de {% else %}
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
{% load static_content_extra %}
|
{% load static_content_extra %}
|
||||||
|
|
||||||
<article>
|
<article id="event_form">
|
||||||
<header>
|
<header>
|
||||||
{% if object %}
|
{% if object %}
|
||||||
<h1>{% if form.is_clone_from_url %}
|
<h1>{% if form.is_clone_from_url %}
|
||||||
@ -78,8 +78,8 @@ Duplication de {% else %}
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<form method="post" action="{{ urlparam }}" enctype="multipart/form-data">{% csrf_token %}
|
<form method="post" action="{{ urlparam }}" enctype="multipart/form-data">{% csrf_token %}
|
||||||
{{ form.media }}
|
{{ form.media }}
|
||||||
{{ form.as_p }}
|
{{ form }}
|
||||||
<div class="grid buttons">
|
<div class="grid buttons stick-bottom">
|
||||||
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{{ object.get_absolute_url }}{% endif %}" role="button" class="secondary">Annuler</a>
|
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{{ object.get_absolute_url }}{% endif %}" role="button" class="secondary">Annuler</a>
|
||||||
<input type="submit" value="Enregistrer">
|
<input type="submit" value="Enregistrer">
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
|
||||||
|
{{ errors }}
|
||||||
|
{% if errors and not fields %}
|
||||||
|
<p>{% for field in hidden_fields %}{{ field }}{% endfor %}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% for group, fields in form.fields_by_group %}
|
||||||
|
<div {% if group.maskable %}class="maskable_group"{% endif %} id="group_{{ group.id }}">
|
||||||
|
{% if group.maskable %}
|
||||||
|
<input class="toggle_body" type="checkbox" id="maskable_group_{{ group.id }}" name="group_{{ group.id }}"><label for="maskable_group_{{ group.id }}">{{ group.label }}</label>
|
||||||
|
{% endif %}
|
||||||
|
<div class="error_group">
|
||||||
|
{% for field in fields %}
|
||||||
|
<div id="error_{{ field.auto_id }}">
|
||||||
|
{{ field.errors }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="body_group">
|
||||||
|
{% for field in fields %}
|
||||||
|
<div id="div_{{ field.auto_id }}">
|
||||||
|
<div{% with classes=field.css_classes %}{% if classes %} class="{{ classes }}"{% endif %}{% endwith %}>
|
||||||
|
{% if field.label %}{{ field.label_tag }}{% endif %}
|
||||||
|
{{ field }}
|
||||||
|
{% if field.help_text %}
|
||||||
|
<span class="helptext"{% if field.auto_id %} id="{{ field.auto_id }}_helptext"{% endif %}>{{ field.help_text|safe }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% if not errors %}
|
||||||
|
{% for field in hidden_fields %}{{ field }}{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
<script>
|
||||||
|
const maskables = document.querySelectorAll('.maskable_group');
|
||||||
|
maskables.forEach(function (item) {
|
||||||
|
item.querySelector('.body_group').classList.add('closed');
|
||||||
|
console.log('item ' + item);
|
||||||
|
|
||||||
|
item.querySelector('.toggle_body').addEventListener('change', (event) => {
|
||||||
|
if (event.currentTarget.checked) {
|
||||||
|
item.querySelector('.body_group').classList.remove('closed');
|
||||||
|
} else {
|
||||||
|
item.querySelector('.body_group').classList.add('closed');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
window.addEventListener('load', function(event) {
|
||||||
|
if (item.querySelector('.toggle_body').checked) {
|
||||||
|
item.querySelector('.body_group').classList.remove('closed');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
item.querySelector('.body_group').classList.add('closed');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
Loading…
Reference in New Issue
Block a user