Amélioration UX
This commit is contained in:
parent
a3e13429eb
commit
3001685937
@ -40,6 +40,54 @@ import logging
|
||||
|
||||
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):
|
||||
required_css_class = 'required'
|
||||
@ -68,16 +116,12 @@ class TagRenameForm(Form):
|
||||
name = kwargs.pop("name", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
if not (force or (not len(args) == 0 and 'force' in args[0])):
|
||||
logger.warning('on delete ' + str(args))
|
||||
del self.fields["force"]
|
||||
else:
|
||||
logger.warning('on delete pas')
|
||||
if not name is None and self.fields["name"].initial is None:
|
||||
self.fields["name"].initial = name
|
||||
|
||||
|
||||
def is_force(self):
|
||||
logger.warning(self.cleaned_data)
|
||||
return "force" in self.fields and self.cleaned_data["force"] == True
|
||||
|
||||
class URLSubmissionForm(Form):
|
||||
@ -88,14 +132,12 @@ class URLSubmissionForm(Form):
|
||||
label=_("Category"),
|
||||
queryset=Category.objects.all().order_by("name"),
|
||||
initial=None,
|
||||
help_text=_('Optional. If you don''t specify a category, we''ll find it for you.'),
|
||||
required=False,
|
||||
)
|
||||
tags = MultipleChoiceField(
|
||||
label=_("Tags"),
|
||||
initial=None,
|
||||
choices=[],
|
||||
help_text=_('Optional. If you offer labels, they''ll make it easier for your girlfriends to find your event.'),
|
||||
required=False
|
||||
)
|
||||
|
||||
@ -133,7 +175,7 @@ class CategorisationRuleImportForm(ModelForm):
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class EventForm(ModelForm):
|
||||
class EventForm(GroupFormMixin, ModelForm):
|
||||
required_css_class = 'required'
|
||||
|
||||
old_local_image = CharField(widget=HiddenInput(), required=False)
|
||||
@ -143,7 +185,6 @@ class EventForm(ModelForm):
|
||||
label=_("Tags"),
|
||||
initial=None,
|
||||
choices=[],
|
||||
help_text=_('Optional. If you offer labels, they''ll make it easier for your girlfriends to find your event.'),
|
||||
required=False
|
||||
)
|
||||
|
||||
@ -192,6 +233,45 @@ class EventForm(ModelForm):
|
||||
self.fields['category'].initial = Category.get_default_category()
|
||||
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):
|
||||
return self.cloning
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -212,8 +212,8 @@ class Tag(models.Model):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Étiquette")
|
||||
verbose_name_plural = _("Étiquettes")
|
||||
verbose_name = _("Tag")
|
||||
verbose_name_plural = _("Tags")
|
||||
indexes = [
|
||||
models.Index(fields=['name']),
|
||||
]
|
||||
@ -555,7 +555,7 @@ class Event(models.Model):
|
||||
recurrence_dtend = models.DateTimeField(editable=False, blank=True, null=True)
|
||||
|
||||
title = models.CharField(
|
||||
verbose_name=_("Title"), help_text=_("Short title"), max_length=512
|
||||
verbose_name=_("Title"), max_length=512
|
||||
)
|
||||
|
||||
status = models.CharField(
|
||||
@ -565,32 +565,27 @@ class Event(models.Model):
|
||||
category = models.ForeignKey(
|
||||
Category,
|
||||
verbose_name=_("Category"),
|
||||
help_text=_("Category of the event"),
|
||||
null=True,
|
||||
default=None,
|
||||
on_delete=models.SET_DEFAULT,
|
||||
)
|
||||
|
||||
start_day = models.DateField(
|
||||
verbose_name=_("Day of the event"), help_text=_("Day of the event")
|
||||
verbose_name=_("Start day")
|
||||
)
|
||||
start_time = models.TimeField(
|
||||
verbose_name=_("Starting time"),
|
||||
help_text=_("Starting time"),
|
||||
verbose_name=_("Start time"),
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
|
||||
end_day = models.DateField(
|
||||
verbose_name=_("End day of the event"),
|
||||
help_text=_(
|
||||
"End day of the event, only required if different from the start day."
|
||||
),
|
||||
verbose_name=_("End day"),
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
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(
|
||||
@ -600,7 +595,6 @@ class Event(models.Model):
|
||||
exact_location = models.ForeignKey(
|
||||
Place,
|
||||
verbose_name=_("Location"),
|
||||
help_text=_("Address of the event"),
|
||||
null=True,
|
||||
on_delete=models.SET_NULL,
|
||||
blank=True,
|
||||
@ -616,6 +610,13 @@ class Event(models.Model):
|
||||
blank=True
|
||||
)
|
||||
|
||||
description = models.TextField(
|
||||
verbose_name=_("Description"),
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
|
||||
|
||||
organisers = models.ManyToManyField(Organisation,
|
||||
related_name='organised_events',
|
||||
verbose_name=_("Organisers"),
|
||||
@ -625,24 +626,16 @@ class Event(models.Model):
|
||||
blank=True
|
||||
)
|
||||
|
||||
description = models.TextField(
|
||||
verbose_name=_("Description"),
|
||||
help_text=_("General description of the event"),
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
|
||||
local_image = models.ImageField(
|
||||
verbose_name=_("Illustration (local image)"),
|
||||
help_text=_("Illustration image stored in the agenda server"),
|
||||
verbose_name=_("Illustration"),
|
||||
max_length=1024,
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
|
||||
image = models.URLField(
|
||||
verbose_name=_("Illustration"),
|
||||
help_text=_("URL of the illustration image"),
|
||||
verbose_name=_("Illustration (URL)"),
|
||||
help_text=_("External URL of the illustration image"),
|
||||
max_length=1024,
|
||||
blank=True,
|
||||
null=True,
|
||||
@ -680,7 +673,6 @@ class Event(models.Model):
|
||||
tags = ArrayField(
|
||||
models.CharField(max_length=64),
|
||||
verbose_name=_("Tags"),
|
||||
help_text=_("A list of tags that describe the event."),
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
|
@ -43,6 +43,10 @@ const update_datetimes = (event) => {
|
||||
|
||||
start_day.oldvalue = start_day.value;
|
||||
}
|
||||
else {
|
||||
new_date = new Date(start_day.value);
|
||||
end_day.value = formatDate(new_date);
|
||||
}
|
||||
}
|
||||
else {
|
||||
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 */
|
||||
article form p .recurrence-widget {
|
||||
article form div .recurrence-widget {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
|
||||
@ -1451,4 +1451,48 @@ form.messages div, form.moderation-events {
|
||||
label.required::after {
|
||||
content: ' *';
|
||||
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 %}
|
||||
{% load static_content_extra %}
|
||||
|
||||
<article>
|
||||
<article id="event_form">
|
||||
<header>
|
||||
{% if object %}
|
||||
<h1>{% if form.is_clone_from_url %}
|
||||
@ -78,8 +78,8 @@ Duplication de {% else %}
|
||||
{% endif %}
|
||||
<form method="post" action="{{ urlparam }}" enctype="multipart/form-data">{% csrf_token %}
|
||||
{{ form.media }}
|
||||
{{ form.as_p }}
|
||||
<div class="grid buttons">
|
||||
{{ form }}
|
||||
<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>
|
||||
<input type="submit" value="Enregistrer">
|
||||
</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