Ajout des affichages de périodes remarquables
This commit is contained in:
parent
60b0255942
commit
9fa3a946b7
@ -44,6 +44,9 @@ class DayInCalendar:
|
||||
self.time_intervals = None
|
||||
self.id = d.strftime("%Y-%m-%d")
|
||||
|
||||
self.periods = []
|
||||
self.has_special_period_same_week = False
|
||||
|
||||
def is_in_past(self):
|
||||
return self.in_past
|
||||
|
||||
@ -77,6 +80,37 @@ class DayInCalendar:
|
||||
[(k, v) for k, v in self.events_by_category.items() if len(v) > 0]
|
||||
)
|
||||
|
||||
def add_special_period(self, period):
|
||||
self.periods.append(period)
|
||||
|
||||
def is_public_holiday(self):
|
||||
from .models import SpecialPeriod
|
||||
|
||||
return (
|
||||
len(
|
||||
[
|
||||
p
|
||||
for p in self.periods
|
||||
if p.periodtype == SpecialPeriod.PERIODTYPE.PUBLICHOLIDAYS
|
||||
]
|
||||
)
|
||||
> 0
|
||||
)
|
||||
|
||||
def is_school_vacation(self):
|
||||
from .models import SpecialPeriod
|
||||
|
||||
return (
|
||||
len(
|
||||
[
|
||||
p
|
||||
for p in self.periods
|
||||
if p.periodtype == SpecialPeriod.PERIODTYPE.SCHOOLVACATIONS
|
||||
]
|
||||
)
|
||||
> 0
|
||||
)
|
||||
|
||||
def add_event(self, event):
|
||||
if event.contains_date(self.date):
|
||||
if self.is_ancestor_uuid_event_from_other(event):
|
||||
@ -276,6 +310,9 @@ class CalendarList:
|
||||
# create a list of DayInCalendars
|
||||
self.create_calendar_days()
|
||||
|
||||
# get special periods
|
||||
self.load_special_periods()
|
||||
|
||||
# fill DayInCalendars with events
|
||||
self.fill_calendar_days()
|
||||
|
||||
@ -283,6 +320,13 @@ class CalendarList:
|
||||
for i, c in self.calendar_days.items():
|
||||
c.filter_events()
|
||||
|
||||
def load_special_periods(self):
|
||||
from .models import SpecialPeriod
|
||||
|
||||
self.special_periods = SpecialPeriod.objects.filter(
|
||||
Q(start_date__lte=self.lastdate) & Q(end_date__gte=self.firstdate)
|
||||
).order_by("-periodtype")
|
||||
|
||||
def get_calendar_days(self):
|
||||
if self.calendar_days is None:
|
||||
self.build_internal()
|
||||
@ -388,6 +432,42 @@ class CalendarList:
|
||||
):
|
||||
self.calendar_days[d.__str__()].add_event(e_rec)
|
||||
|
||||
for p in self.special_periods:
|
||||
first_date = max(p.start_date, self.firstdate)
|
||||
end_date = min(p.end_date, self.lastdate)
|
||||
current_date = first_date
|
||||
while current_date <= end_date:
|
||||
self.calendar_days[current_date.__str__()].add_special_period(p)
|
||||
current_date += timedelta(days=1)
|
||||
|
||||
start_week = first_date - timedelta(days=first_date.weekday())
|
||||
end_week = end_date + timedelta(end_date.weekday())
|
||||
c = start_week
|
||||
while c < p.start_date:
|
||||
if c.__str__() in self.calendar_days:
|
||||
self.calendar_days[c.__str__()].has_special_period_same_week = True
|
||||
c += timedelta(days=1)
|
||||
|
||||
c = p.end_date + timedelta(days=1)
|
||||
while c <= end_week:
|
||||
if c.__str__() in self.calendar_days:
|
||||
self.calendar_days[c.__str__()].has_special_period_same_week = True
|
||||
c += timedelta(days=1)
|
||||
|
||||
def has_school_vacation(self):
|
||||
from .models import SpecialPeriod
|
||||
|
||||
return (
|
||||
len(
|
||||
[
|
||||
p
|
||||
for p in self.special_periods
|
||||
if p.periodtype == SpecialPeriod.PERIODTYPE.SCHOOLVACATIONS
|
||||
]
|
||||
)
|
||||
> 0
|
||||
)
|
||||
|
||||
def create_calendar_days(self):
|
||||
# create daylist
|
||||
self.calendar_days = {}
|
||||
|
@ -20,6 +20,7 @@ from django.forms import (
|
||||
TextInput,
|
||||
URLField,
|
||||
ValidationError,
|
||||
FileField,
|
||||
formset_factory,
|
||||
)
|
||||
from django.utils.formats import localize
|
||||
@ -1003,3 +1004,12 @@ class SpecialPeriodForm(ModelForm):
|
||||
"start_date": TextInput(attrs={"type": "date"}),
|
||||
"end_date": TextInput(attrs={"type": "date"}),
|
||||
}
|
||||
|
||||
|
||||
class SpecialPeriodFileForm(Form):
|
||||
periodtype = ChoiceField(
|
||||
label=_("Period type"),
|
||||
required=True,
|
||||
choices=SpecialPeriod.PERIODTYPE.choices,
|
||||
)
|
||||
file = FileField(label=_("ICAL file"), required=True)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -8,6 +8,7 @@ import uuid
|
||||
from collections import defaultdict
|
||||
from datetime import date, time, timedelta
|
||||
from urllib.parse import urlparse
|
||||
import icalendar
|
||||
|
||||
import emoji
|
||||
import recurrence
|
||||
@ -3166,6 +3167,9 @@ class SpecialPeriod(models.Model):
|
||||
class Meta:
|
||||
verbose_name = _("Special period")
|
||||
verbose_name_plural = _("Special periods")
|
||||
indexes = [
|
||||
models.Index(fields=["start_date", "end_date"]),
|
||||
]
|
||||
|
||||
class PERIODTYPE(models.TextChoices):
|
||||
PUBLICHOLIDAYS = "public holidays", _("public holidays")
|
||||
@ -3193,10 +3197,63 @@ class SpecialPeriod(models.Model):
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
n = self.periodtype + ' "' + self.name + '" '
|
||||
n = self.periodtype + ' "' + self.name + '"'
|
||||
if self.start_date == self.end_date:
|
||||
return n + _(" on ") + str(self.start_date)
|
||||
else:
|
||||
return (
|
||||
n + _(" from ") + str(self.start_date) + _(" to ") + str(self.end_day)
|
||||
n + _(" from ") + str(self.start_date) + _(" to ") + str(self.end_date)
|
||||
)
|
||||
|
||||
def load_from_ical(uploaded_file, periodtype):
|
||||
# load file
|
||||
try:
|
||||
file_content = uploaded_file.read()
|
||||
calendar = icalendar.Calendar.from_ical(file_content)
|
||||
|
||||
nb_error = 0
|
||||
nb_overlap = 0
|
||||
periods = []
|
||||
periods_to_create = []
|
||||
|
||||
# extract events
|
||||
for event in calendar.walk("VEVENT"):
|
||||
try:
|
||||
name = event.decoded("SUMMARY").decode()
|
||||
r = event.decoded("DTSTART")
|
||||
if isinstance(r, datetime):
|
||||
start_date = r.date()
|
||||
elif isinstance(r, date):
|
||||
start_date = r
|
||||
r = event.decoded("DTEND")
|
||||
if isinstance(r, datetime):
|
||||
end_date = r.date()
|
||||
elif isinstance(r, date):
|
||||
end_date = r
|
||||
periods.append(
|
||||
SpecialPeriod(
|
||||
name=name,
|
||||
periodtype=periodtype,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
nb_error += 1
|
||||
|
||||
for p in periods:
|
||||
overlap_exists = SpecialPeriod.objects.filter(
|
||||
Q(periodtype=p.periodtype)
|
||||
& Q(start_date__lte=p.end_date)
|
||||
& Q(end_date__gte=p.start_date)
|
||||
).exists()
|
||||
|
||||
if overlap_exists:
|
||||
nb_overlap += 1
|
||||
else:
|
||||
periods_to_create.append(p)
|
||||
|
||||
SpecialPeriod.objects.bulk_create(periods_to_create)
|
||||
return len(periods_to_create), nb_overlap, nb_error, None
|
||||
except Exception as e:
|
||||
return 0, 0, 0, str(e)
|
||||
|
@ -2193,3 +2193,15 @@ dialog {
|
||||
@extend article;
|
||||
font-size: 110%;
|
||||
}
|
||||
.special_period {
|
||||
font-size: 75%;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
height: 1.5em;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
.special_period.holiday,
|
||||
.special_period.vacation {
|
||||
background: var(--primary);
|
||||
color: var(--primary-inverse);
|
||||
}
|
||||
|
@ -0,0 +1,19 @@
|
||||
{% extends "agenda_culturel/page.html" %}
|
||||
{% block title %}
|
||||
{% block og_title %}Importer des périodes remarquables depuis un fichier ical{% endblock %}
|
||||
{% endblock %}
|
||||
{% block fluid %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>Importer des périodes remarquables depuis un fichier ical</h1>
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<div class="grid buttons">
|
||||
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{% url 'list_specialperiods' %}{% endif %}"
|
||||
role="button"
|
||||
class="secondary">Annuler</a>
|
||||
<input type="submit" value="Enregistrer">
|
||||
</div>
|
||||
</form>
|
||||
{{ form.media }}
|
||||
{% endblock %}
|
@ -126,6 +126,13 @@
|
||||
</script>
|
||||
{% endif %}
|
||||
<header {% if day.is_today %}id="today"{% endif %}>
|
||||
{% if day.is_school_vacation or day.has_special_period_same_week %}
|
||||
<div class="special_period{% if day.is_school_vacation %} vacation{% endif %}">
|
||||
{% for p in day.periods %}
|
||||
{% if p.periodtype == "school vacations" %}{{ p.name }}{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<h3>
|
||||
<a href="{{ day.date | url_day:category }}?{{ filter.get_url }}"
|
||||
class="visible-link">
|
||||
@ -136,6 +143,13 @@
|
||||
{% endif %}
|
||||
</a>
|
||||
</h3>
|
||||
{% if day.is_public_holiday %}
|
||||
<div class="special_period holiday">
|
||||
{% for p in day.periods %}
|
||||
{% if p.periodtype == "public holidays" %}{{ p.name }}{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</header>
|
||||
{% if day.events %}
|
||||
<ul>
|
||||
|
@ -77,7 +77,14 @@
|
||||
onClick="toggleModal(event)">{% picto_from_name "calendar" %}</a>
|
||||
</h2>
|
||||
<h3>
|
||||
{% if cd.is_today or cd.is_tomorrow %}{{ cd.date|date:"l j F Y"|frdate }}{% endif %}
|
||||
{% if cd.is_today or cd.is_tomorrow %}
|
||||
{{ cd.date|date:"l j F Y"|frdate }}
|
||||
{% if cd.periods|length > 0 %}<br>{% endif %}
|
||||
{% endif %}
|
||||
{% for p in cd.periods %}
|
||||
{{ p.name }}
|
||||
{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
</h3>
|
||||
</hgroup>
|
||||
{% endwith %}
|
||||
@ -112,7 +119,16 @@
|
||||
Aujourd'hui <a href="#{{ cd.id }}" role="button" class="badge">{{ nb_events }} événement{{ nb_events|pluralize }} {% picto_from_name "calendar" %}</a>
|
||||
</h2>
|
||||
{% endwith %}
|
||||
<h3>{{ cd.date|date:"l j F Y"|frdate }}</h3>
|
||||
<h3>
|
||||
{{ cd.date|date:"l j F Y"|frdate }}
|
||||
{% if cd.periods|length > 0 %}
|
||||
<br>
|
||||
{% for p in cd.periods %}
|
||||
{{ p.name }}
|
||||
{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</h3>
|
||||
</hgroup>
|
||||
{% else %}
|
||||
{% if cd.is_tomorrow %}
|
||||
@ -120,12 +136,37 @@
|
||||
<h2>
|
||||
Demain <a href="#{{ cd.id }}" role="button" class="badge">{{ cd.events|length }} {% picto_from_name "calendar" %}</a>
|
||||
</h2>
|
||||
<h3>{{ cd.date|date:"l j F Y"|frdate }}</h3>
|
||||
<h3>
|
||||
{{ cd.date|date:"l j F Y"|frdate }}
|
||||
{% if cd.periods|length > 0 %}
|
||||
<br>
|
||||
{% for p in cd.periods %}
|
||||
{{ p.name }}
|
||||
{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</h3>
|
||||
</hgroup>
|
||||
{% else %}
|
||||
<h2>
|
||||
{{ cd.date|date:"l j F Y"|frdate |capfirst }} <a href="#{{ cd.id }}" role="button" class="badge">{{ cd.events|length }} {% picto_from_name "calendar" %}</a>
|
||||
</h2>
|
||||
{% if cd.periods|length > 0 %}
|
||||
<hgroup>
|
||||
<h2>
|
||||
{{ cd.date|date:"l j F Y"|frdate |capfirst }} <a href="#{{ cd.id }}" role="button" class="badge">{{ cd.events|length }} {% picto_from_name "calendar" %}</a>
|
||||
</h2>
|
||||
<h3>
|
||||
{% if cd.periods|length > 0 %}
|
||||
{% for p in cd.periods %}
|
||||
{{ p.name }}
|
||||
{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
</h3>
|
||||
</hgroup>
|
||||
{% else %}
|
||||
<h2>
|
||||
{{ cd.date|date:"l j F Y"|frdate |capfirst }} <a href="#{{ cd.id }}" role="button" class="badge">{{ cd.events|length }} {% picto_from_name "calendar" %}</a>
|
||||
</h2>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</header>
|
||||
|
@ -111,10 +111,24 @@
|
||||
</script>
|
||||
{% endif %}
|
||||
<header {% if day.is_today %}id="today"{% endif %}>
|
||||
{% if day.is_school_vacation or calendar.has_school_vacation %}
|
||||
<div class="special_period{% if day.is_school_vacation %} vacation{% endif %}">
|
||||
{% for p in day.periods %}
|
||||
{% if p.periodtype == "school vacations" %}{{ p.name }}{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<h2>
|
||||
<a class="visible-link"
|
||||
href="{{ day.date | url_day:category }}?{{ filter.get_url }}">{{ day.date | date:"l j" }}</a>
|
||||
</h2>
|
||||
{% if day.is_public_holiday %}
|
||||
<div class="special_period holiday">
|
||||
{% for p in day.periods %}
|
||||
{% if p.periodtype == "public holidays" %}{{ p.name }}{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</header>
|
||||
{% for ti in day.get_time_intervals %}
|
||||
{% if ti.events|length > 0 %}
|
||||
|
@ -21,6 +21,7 @@
|
||||
<header>
|
||||
<div class="slide-buttons">
|
||||
<a href="{% url 'add_specialperiod' %}" role="button">Ajouter {% picto_from_name "plus-circle" %}</a>
|
||||
<a href="{% url 'load_specialperiods_from_ical' %}" role="button">Importer (ical) {% picto_from_name "upload" %}</a>
|
||||
</div>
|
||||
<h1>Périodes spéciales</h1>
|
||||
</header>
|
||||
|
@ -110,6 +110,7 @@ from .views import (
|
||||
SpecialPeriodDeleteView,
|
||||
SpecialPeriodListView,
|
||||
SpecialPeriodUpdateView,
|
||||
load_specialperiods_from_ical,
|
||||
)
|
||||
|
||||
event_dict = {
|
||||
@ -502,6 +503,11 @@ urlpatterns = [
|
||||
SpecialPeriodDeleteView.as_view(),
|
||||
name="delete_specialperiod",
|
||||
),
|
||||
path(
|
||||
"specialperiods/load-from-ical",
|
||||
load_specialperiods_from_ical,
|
||||
name="load_specialperiods_from_ical",
|
||||
),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
|
@ -29,7 +29,7 @@ from django.utils.decorators import method_decorator
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.timezone import datetime
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _, ngettext
|
||||
from django.views.generic import DetailView, ListView
|
||||
from django.views.generic.edit import (
|
||||
CreateView,
|
||||
@ -86,6 +86,7 @@ from .forms import (
|
||||
URLSubmissionFormWithContact,
|
||||
UserProfileForm,
|
||||
SpecialPeriodForm,
|
||||
SpecialPeriodFileForm,
|
||||
)
|
||||
from .import_tasks.extractor import Extractor
|
||||
from .models import (
|
||||
@ -3169,3 +3170,56 @@ class SpecialPeriodUpdateView(
|
||||
success_message = _("The special period has been successfully updated.")
|
||||
success_url = reverse_lazy("list_specialperiods")
|
||||
form_class = SpecialPeriodForm
|
||||
|
||||
|
||||
def load_specialperiods_from_ical(request):
|
||||
if request.method == "POST":
|
||||
form = SpecialPeriodFileForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
nb_created, nb_overlap, nb_error, error = SpecialPeriod.load_from_ical(
|
||||
request.FILES["file"], request.POST["periodtype"]
|
||||
)
|
||||
|
||||
if nb_created > 0:
|
||||
messages.success(
|
||||
request,
|
||||
ngettext(
|
||||
"%(nb_created)d interval inserted.",
|
||||
"%(nb_created)d intervals inserted.",
|
||||
nb_created,
|
||||
)
|
||||
% {"nb_created": nb_created},
|
||||
)
|
||||
|
||||
if nb_overlap > 0:
|
||||
messages.success(
|
||||
request,
|
||||
ngettext(
|
||||
"%(nb_overlap)d insersion was not possible due to overlap.",
|
||||
"%(nb_overlap)d insersion were not possible due to overlap.",
|
||||
nb_overlap,
|
||||
)
|
||||
% {"nb_overlap": nb_overlap},
|
||||
)
|
||||
|
||||
if nb_error > 0:
|
||||
messages.success(
|
||||
request,
|
||||
ngettext(
|
||||
"%(nb_error)d error while reading ical file.",
|
||||
"%(nb_error)d error while reading ical file.",
|
||||
nb_error,
|
||||
)
|
||||
% {"nb_error": nb_error},
|
||||
)
|
||||
if error is not None:
|
||||
messages.success(
|
||||
request, _("Error during file reading: {}").format(error)
|
||||
)
|
||||
|
||||
return HttpResponseRedirect(reverse_lazy("list_specialperiods"))
|
||||
else:
|
||||
form = SpecialPeriodFileForm()
|
||||
return render(
|
||||
request, "agenda_culturel/load_specialperiods_from_ical.html", {"form": form}
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user