From 72da8a7445d4d6d401a919afcae9e0b4a17722ed Mon Sep 17 00:00:00 2001 From: Jean-Marie Favreau Date: Sat, 6 Jan 2024 23:08:59 +0100 Subject: [PATCH] =?UTF-8?q?Premi=C3=A8re=20version=20fonctionnelle=20qui?= =?UTF-8?q?=20g=C3=A8re=20les=20=C3=A9v=C3=A9nements=20r=C3=A9currents.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix #7 --- experimentations/get_ical_events.py | 30 ++++- src/agenda_culturel/calendar.py | 75 +++++++++++-- src/agenda_culturel/celery.py | 12 +- src/agenda_culturel/importation.py | 11 ++ .../migrations/0023_event_recurrences.py | 19 ++++ ..._dtend_event_dtstart_alter_event_status.py | 28 +++++ ...e_dtend_event_recurrence_dtend_and_more.py | 23 ++++ .../0026_alter_event_recurrences.py | 19 ++++ .../migrations/0027_set_dtstart_dtend.py | 23 ++++ src/agenda_culturel/models.py | 105 +++++++++++++++++- src/agenda_culturel/settings/base.py | 7 +- src/agenda_culturel/static/style.scss | 77 +++++++++++++ .../agenda_culturel/event_create_form.html | 30 ----- .../templates/agenda_culturel/event_form.html | 1 + .../templates/agenda_culturel/import.html | 16 +++ .../templates/agenda_culturel/page-day.html | 32 +++--- .../templates/agenda_culturel/page-event.html | 25 +++-- .../single-event/event-in-flat-list-inc.html | 8 ++ .../event-in-list-by-day-inc.html | 13 ++- .../single-event/event-single-inc.html | 18 ++- .../templatetags/event_extra.py | 1 - src/agenda_culturel/urls.py | 13 ++- src/agenda_culturel/views.py | 14 ++- src/requirements.txt | 2 +- 24 files changed, 510 insertions(+), 92 deletions(-) create mode 100644 src/agenda_culturel/migrations/0023_event_recurrences.py create mode 100644 src/agenda_culturel/migrations/0024_event_dtend_event_dtstart_alter_event_status.py create mode 100644 src/agenda_culturel/migrations/0025_rename_dtend_event_recurrence_dtend_and_more.py create mode 100644 src/agenda_culturel/migrations/0026_alter_event_recurrences.py create mode 100644 src/agenda_culturel/migrations/0027_set_dtstart_dtend.py delete mode 100644 src/agenda_culturel/templates/agenda_culturel/event_create_form.html diff --git a/experimentations/get_ical_events.py b/experimentations/get_ical_events.py index ff83786..6ef0242 100755 --- a/experimentations/get_ical_events.py +++ b/experimentations/get_ical_events.py @@ -10,10 +10,11 @@ from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options import icalendar +from icalendar import vDatetime from datetime import datetime, date import json from bs4 import BeautifulSoup - +import pickle class Downloader(ABC): @@ -77,7 +78,7 @@ class Extractor(ABC): def clear_events(self): self.events = [] - def add_event(self, title, category, start_day, location, description, tags, uuid, url_human=None, start_time=None, end_day=None, end_time=None, last_modified=None, published=False): + def add_event(self, title, category, start_day, location, description, tags, uuid, recurrences=None, url_human=None, start_time=None, end_day=None, end_time=None, last_modified=None, published=False): if title is None: print("ERROR: cannot import an event without name") return @@ -107,6 +108,9 @@ class Extractor(ABC): if last_modified is not None: event["last_modified"] = last_modified + if recurrences is not None: + event["recurrences"] = recurrences + self.events.append(event) def default_value_if_exists(self, default_values, key): @@ -191,11 +195,25 @@ class ICALExtractor(Extractor): last_modified = self.get_item_from_vevent(event, "LAST-MODIFIED", raw = True) - rrule = self.get_item_from_vevent(event, "RRULE", raw = True) - if rrule is not None: - print("Recurrent event not yet supported", rrule) + recurrence_entries = {} + for e in ["RRULE", "EXRULE", "EXDATE", "RDATE"]: + i = self.get_item_from_vevent(event, e, raw = True) + if i is not None: + recurrence_entries[e] = i - self.add_event(title, category, start_day, location, description, tags, uuid=event_url, url_human=url_human, start_time=start_time, end_day=end_day, end_time=end_time, last_modified=last_modified, published=published) + if start_day is not None and len(recurrence_entries) != 0: + recurrences = "" + + for k, r in recurrence_entries.items(): + if isinstance(r, list): + recurrences += "\n".join([k + ":" + e.to_ical().decode() for e in r]) + "\n" + else: + recurrences += k + ":" + r.to_ical().decode() + "\n" + else: + recurrences = None + + + self.add_event(title, category, start_day, location, description, tags, recurrences=recurrences, uuid=event_url, url_human=url_human, start_time=start_time, end_day=end_day, end_time=end_time, last_modified=last_modified, published=published) return self.get_structure() diff --git a/src/agenda_culturel/calendar.py b/src/agenda_culturel/calendar.py index 10031e0..a72f7c0 100644 --- a/src/agenda_culturel/calendar.py +++ b/src/agenda_culturel/calendar.py @@ -1,6 +1,8 @@ from datetime import datetime, timedelta, date, time import calendar from django.db.models import Q +from django.utils import timezone + import logging logger = logging.getLogger(__name__) @@ -16,7 +18,7 @@ def daterange(start, end, step=timedelta(1)): curr += step -class CalendarDay: +class DayInCalendar: midnight = time(23, 59, 59) def __init__(self, d, on_requested_interval = True): @@ -36,8 +38,40 @@ class CalendarDay: def is_today(self): return self.today + def is_generic_uuid_event_from_other(self, event): + for e in self.events: + if event.is_generic_by_uuid(e): + return True + return False + + def remove_event_with_generic_uuid_if_exists(self, event): + removed = False + for i, e in enumerate(self.events): + if e.is_generic_by_uuid(event): + # remove e from events_by_category + for k, v in self.events_by_category.items(): + if e in v: + self.events_by_category[k].remove(e) + # remove e from the events + del self.events[i] + removed = True + + if removed: + # remove empty events_by_category + self.events_by_category = dict([(k, v) for k, v in self.events_by_category.items() if len(v) > 0]) + + def add_event(self, event): if event.contains_date(self.date): + if self.is_generic_uuid_event_from_other(event): + # we do not add a generic event if a specific is already present + pass + else: + self.remove_event_with_generic_uuid_if_exists(event) + self._add_event_internal(event) + + + def _add_event_internal(self, event): self.events.append(event) if event.category is None: if not "" in self.events_by_category: @@ -49,7 +83,7 @@ class CalendarDay: self.events_by_category[event.category.name].append(event) def filter_events(self): - self.events.sort(key=lambda e: CalendarDay.midnight if e.start_time is None else e.start_time) + self.events.sort(key=lambda e: DayInCalendar.midnight if e.start_time is None else e.start_time) class CalendarList: @@ -70,13 +104,13 @@ class CalendarList: self.c_lastdate = lastdate + timedelta(days=6-lastdate.weekday()) - # create a list of CalendarDays + # create a list of DayInCalendars self.create_calendar_days() - # fill CalendarDays with events + # fill DayInCalendars with events self.fill_calendar_days() - # finally, sort each CalendarDay + # finally, sort each DayInCalendar for i, c in self.calendar_days.items(): c.filter_events() @@ -93,23 +127,35 @@ class CalendarList: qs = Event.objects.all() else: qs = self.filter.qs + startdatetime = datetime.combine(self.c_firstdate, time.min) + lastdatetime = datetime.combine(self.c_lastdate, time.max) self.events = qs.filter( - (Q(end_day__isnull=True) & Q(start_day__gte=self.c_firstdate) & Q(start_day__lte=self.c_lastdate)) | - (Q(end_day__isnull=False) & ~(Q(start_day__gt=self.c_lastdate) | Q(end_day__lt=self.c_firstdate))) + (Q(recurrence_dtend__isnull=True) & Q(recurrence_dtstart__lte=lastdatetime)) | + (Q(recurrence_dtend__isnull=False) & ~(Q(recurrence_dtstart__gt=lastdatetime) | Q(recurrence_dtend__lt=startdatetime))) ).order_by("start_day", "start_time") - for e in self.events: - for d in daterange(e.start_day, e.end_day): + firstdate = datetime.fromordinal(self.c_firstdate.toordinal()) + if firstdate.tzinfo is None or firstdate.tzinfo.utcoffset(firstdate) is None: + firstdate = timezone.make_aware(firstdate, timezone.get_default_timezone()) - if d.__str__() in self.calendar_days: - self.calendar_days[d.__str__()].add_event(e) + + lastdate = datetime.fromordinal(self.c_lastdate.toordinal()) + if lastdate.tzinfo is None or lastdate.tzinfo.utcoffset(lastdate) is None: + lastdate = timezone.make_aware(lastdate, timezone.get_default_timezone()) + + + for e in self.events: + for e_rec in e.get_recurrences_between(firstdate, lastdate): + for d in daterange(e_rec.start_day, e_rec.end_day): + if d.__str__() in self.calendar_days: + self.calendar_days[d.__str__()].add_event(e_rec) def create_calendar_days(self): # create daylist self.calendar_days = {} for d in daterange(self.c_firstdate, self.c_lastdate): - self.calendar_days[d.strftime("%Y-%m-%d")] = CalendarDay(d, d >= self.firstdate and d <= self.lastdate) + self.calendar_days[d.strftime("%Y-%m-%d")] = DayInCalendar(d, d >= self.firstdate and d <= self.lastdate) def is_single_week(self): @@ -162,3 +208,8 @@ class CalendarWeek(CalendarList): def previous_week(self): return self.firstdate + timedelta(days=-7) + +class CalendarDay(CalendarList): + + def __init__(self, date, filter): + super().__init__(date, date, filter, exact=True) diff --git a/src/agenda_culturel/celery.py b/src/agenda_culturel/celery.py index cb130c2..eaaa6ca 100644 --- a/src/agenda_culturel/celery.py +++ b/src/agenda_culturel/celery.py @@ -51,14 +51,14 @@ def import_events_from_json(self, json): importer = EventsImporter(self.request.id) - try: - success, error_message = importer.import_events(json) + # try: + success, error_message = importer.import_events(json) - # finally, close task - close_import_task(self.request.id, success, error_message, importer) - except Exception as e: + # finally, close task + close_import_task(self.request.id, success, error_message, importer) + """except Exception as e: logger.error(e) - close_import_task(self.request.id, False, e, importer) + close_import_task(self.request.id, False, e, importer)""" @app.task(bind=True) diff --git a/src/agenda_culturel/importation.py b/src/agenda_culturel/importation.py index fba2a9e..bf29e0d 100644 --- a/src/agenda_culturel/importation.py +++ b/src/agenda_culturel/importation.py @@ -3,6 +3,10 @@ import json from datetime import datetime from django.utils import timezone +import logging +logger = logging.getLogger(__name__) + + class EventsImporter: def __init__(self, celery_id): @@ -55,6 +59,7 @@ class EventsImporter: # get events for event in structure["events"]: # only process events if they are today or the days after + if self.event_takes_place_today_or_after(event): # set a default "last modified date" if "last_modified" not in event and self.date is not None: @@ -70,6 +75,10 @@ class EventsImporter: return (True, "") def event_takes_place_today_or_after(self, event): + # not optimal, but will work: import recurrent events even if they come from the past + if "recurrences" in event: + return True + if "start_day" not in event: return False @@ -97,10 +106,12 @@ class EventsImporter: def load_event(self, event): if self.is_valid_event_structure(event): + logger.warning("Valid event: {} {}".format(event["last_modified"], event["title"])) event_obj = Event.from_structure(event, self.url) self.event_objects.append(event_obj) return True else: + logger.warning("Not valid event: {}".format(event)) return False diff --git a/src/agenda_culturel/migrations/0023_event_recurrences.py b/src/agenda_culturel/migrations/0023_event_recurrences.py new file mode 100644 index 0000000..9f3a893 --- /dev/null +++ b/src/agenda_culturel/migrations/0023_event_recurrences.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.7 on 2024-01-02 10:13 + +from django.db import migrations +import recurrence.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('agenda_culturel', '0022_event_import_sources'), + ] + + operations = [ + migrations.AddField( + model_name='event', + name='recurrences', + field=recurrence.fields.RecurrenceField(blank=True, null=True), + ), + ] diff --git a/src/agenda_culturel/migrations/0024_event_dtend_event_dtstart_alter_event_status.py b/src/agenda_culturel/migrations/0024_event_dtend_event_dtstart_alter_event_status.py new file mode 100644 index 0000000..6979a02 --- /dev/null +++ b/src/agenda_culturel/migrations/0024_event_dtend_event_dtstart_alter_event_status.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.7 on 2024-01-04 18:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('agenda_culturel', '0023_event_recurrences'), + ] + + operations = [ + migrations.AddField( + model_name='event', + name='dtend', + field=models.DateTimeField(blank=True, editable=False, null=True), + ), + migrations.AddField( + model_name='event', + name='dtstart', + field=models.DateTimeField(blank=True, editable=False, null=True), + ), + migrations.AlterField( + model_name='event', + name='status', + field=models.CharField(choices=[('published', 'Published'), ('draft', 'Draft'), ('trash', 'Trash')], default='draft', max_length=20, verbose_name='Status'), + ), + ] diff --git a/src/agenda_culturel/migrations/0025_rename_dtend_event_recurrence_dtend_and_more.py b/src/agenda_culturel/migrations/0025_rename_dtend_event_recurrence_dtend_and_more.py new file mode 100644 index 0000000..aba5067 --- /dev/null +++ b/src/agenda_culturel/migrations/0025_rename_dtend_event_recurrence_dtend_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.7 on 2024-01-04 19:35 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('agenda_culturel', '0024_event_dtend_event_dtstart_alter_event_status'), + ] + + operations = [ + migrations.RenameField( + model_name='event', + old_name='dtend', + new_name='recurrence_dtend', + ), + migrations.RenameField( + model_name='event', + old_name='dtstart', + new_name='recurrence_dtstart', + ), + ] diff --git a/src/agenda_culturel/migrations/0026_alter_event_recurrences.py b/src/agenda_culturel/migrations/0026_alter_event_recurrences.py new file mode 100644 index 0000000..abc03aa --- /dev/null +++ b/src/agenda_culturel/migrations/0026_alter_event_recurrences.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.7 on 2024-01-05 15:23 + +from django.db import migrations +import recurrence.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('agenda_culturel', '0025_rename_dtend_event_recurrence_dtend_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='event', + name='recurrences', + field=recurrence.fields.RecurrenceField(blank=True, null=True, verbose_name='Recurrence'), + ), + ] diff --git a/src/agenda_culturel/migrations/0027_set_dtstart_dtend.py b/src/agenda_culturel/migrations/0027_set_dtstart_dtend.py new file mode 100644 index 0000000..cc64a4d --- /dev/null +++ b/src/agenda_culturel/migrations/0027_set_dtstart_dtend.py @@ -0,0 +1,23 @@ +from django.db import migrations + + +def forwards_func(apps, schema_editor): + Event = apps.get_model("agenda_culturel", "Event") + db_alias = schema_editor.connection.alias + events = Event.objects.filter(recurrence_dtstart__isnull=True) + for e in events: + e.update_recurrence_dtstartend() + Event.objects.bulk_update(events, ["recurrence_dtstart", "recurrence_dtend"]) + + + + +class Migration(migrations.Migration): + dependencies = [ + ('agenda_culturel', '0026_alter_event_recurrences'), + ] + + + operations = [ + migrations.RunPython(forwards_func), + ] \ No newline at end of file diff --git a/src/agenda_culturel/models.py b/src/agenda_culturel/models.py index 02b497a..55ccd51 100644 --- a/src/agenda_culturel/models.py +++ b/src/agenda_culturel/models.py @@ -12,7 +12,9 @@ from django.core.files import File from django.utils import timezone from django.contrib.postgres.search import TrigramSimilarity from django.db.models import Q - +import recurrence.fields +import recurrence +import copy from django.template.defaultfilters import date as _date from datetime import time, timedelta, date @@ -139,6 +141,9 @@ class Event(models.Model): imported_date = models.DateTimeField(blank=True, null=True) modified_date = models.DateTimeField(blank=True, null=True) + recurrence_dtstart = models.DateTimeField(editable=False, blank=True, null=True) + recurrence_dtend = models.DateTimeField(editable=False, blank=True, null=True) + title = models.CharField(verbose_name=_('Title'), help_text=_('Short title'), max_length=512) status = models.CharField(_("Status"), max_length=20, choices=STATUS.choices, default=STATUS.DRAFT) @@ -151,6 +156,8 @@ class Event(models.Model): 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.'), blank=True, null=True) end_time = models.TimeField(verbose_name=_('Final time'), help_text=_('Final time'), blank=True, null=True) + recurrences = recurrence.fields.RecurrenceField(verbose_name=_("Recurrence"), include_dtstart=False, blank=True, null=True) + location = models.CharField(verbose_name=_('Location'), help_text=_('Address of the event'), max_length=512) description = models.TextField(verbose_name=_('Description'), help_text=_('General description of the event'), blank=True, null=True) @@ -198,7 +205,10 @@ class Event(models.Model): return d >= self.start_day and d <= self.get_consolidated_end_day(intuitive) def get_absolute_url(self): - return reverse("view_event", kwargs={"pk": self.pk, "extra": slugify(self.title)}) + return reverse("view_event", kwargs={"year": self.start_day.year, + "month": self.start_day.month, + "day": self.start_day.day, + "pk": self.pk, "extra": slugify(self.title)}) def __str__(self): return _date(self.start_day) + ": " + self.title @@ -255,7 +265,7 @@ class Event(models.Model): def set_in_importation_process(self): self.in_importation_process = True - def update_dates(self): + def update_modification_dates(self): now = timezone.now() if not self.id: self.created_date = now @@ -265,8 +275,68 @@ class Event(models.Model): self.modified_date = now + def get_recurrence_at_date(self, year, month, day): + dtstart = timezone.make_aware(datetime(year, month, day, 0, 0), timezone.get_default_timezone()) + recurrences = self.get_recurrences_between(dtstart, dtstart) + if len(recurrences) == 0: + return self + else: + return recurrences[0] + + + # return a copy of the current object for each recurrence between first an last date (included) + def get_recurrences_between(self, firstdate, lastdate): + if self.recurrences is None: + return [self] + else: + result = [] + dtstart = timezone.make_aware(datetime.combine(self.start_day, time()), timezone.get_default_timezone()) + self.recurrences.dtstart = dtstart + for d in self.recurrences.between(firstdate, lastdate, inc=True, dtstart=dtstart): + + c = copy.deepcopy(self) + c.start_day = d.date() + if c.end_day is not None: + shift = d.date() - self.start_day + c.end_day += shift + result.append(c) + + return result + + + def update_recurrence_dtstartend(self): + sday = date.fromisoformat(self.start_day) if isinstance(self.start_day, str) else self.start_day + eday = date.fromisoformat(self.end_day) if isinstance(self.end_day, str) else self.end_day + stime = time.fromisoformat(self.start_time) if isinstance(self.start_time, str) else time() if self.start_time is None else self.start_time + etime = time.fromisoformat(self.end_time) if isinstance(self.end_time, str) else time() if self.end_time is None else self.end_time + + self.recurrence_dtstart = datetime.combine(sday, stime) + + if self.recurrences is None: + if self.end_day is None: + self.dtend = None + else: + self.recurrence_dtend = datetime.combine(eday, etime) + else: + if self.recurrences.rrules[0].until is None and self.recurrences.rrules[0].count is None: + self.recurrence_dtend = None + else: + self.recurrences.dtstart = datetime.combine(sday, time()) + occurrence = self.recurrences.occurrences() + try: + self.recurrence_dtend = occurrence[-1] + if self.recurrences.dtend is not None and self.recurrences.dtstart is not None: + self.recurrence_dtend += self.recurrences.dtend - self.recurrences.dtstart + except: + self.recurrence_dtend = self.recurrence_dtstart + + def prepare_save(self): - self.update_dates() + self.update_modification_dates() + + # TODO: update recurrences.dtstart et recurrences.dtend + + self.update_recurrence_dtstartend() # if the image is defined but not locally downloaded if self.image and not self.local_image: @@ -297,6 +367,8 @@ class Event(models.Model): def from_structure(event_structure, import_source = None): + if event_structure["title"].endswith("ole"): + logger.warning("on choope {}".format(event_structure)) if "category" in event_structure and event_structure["category"] is not None: event_structure["category"] = Category.objects.get(name=event_structure["category"]) @@ -316,6 +388,8 @@ class Event(models.Model): if "last_modified" in event_structure and event_structure["last_modified"] is not None: d = datetime.fromisoformat(event_structure["last_modified"]) + if d.year == 2024 and d.month > 2: + logger.warning("last modified {}".format(d)) if d.tzinfo is None or d.tzinfo.utcoffset(d) is None: d = timezone.make_aware(d, timezone.get_default_timezone()) event_structure["modified_date"] = d @@ -332,6 +406,14 @@ class Event(models.Model): if "description" in event_structure and event_structure["description"] is None: event_structure["description"] = "" + if "recurrences" in event_structure and event_structure["recurrences"] is not None: + event_structure["recurrences"] = recurrence.deserialize(event_structure["recurrences"]) + event_structure["recurrences"].exdates = [e.replace(hour=0, minute=0, second=0) for e in event_structure["recurrences"].exdates] + event_structure["recurrences"].rdates = [e.replace(hour=0, minute=0, second=0) for e in event_structure["recurrences"].rdates] + + else: + event_structure["recurrences"] = None + if import_source is not None: event_structure["import_sources"] = [import_source] @@ -358,6 +440,19 @@ class Event(models.Model): return None if self.uuids is None or len(self.uuids) == 0 else Event.objects.filter(uuids__contains=self.uuids) + def is_generic_uuid(uuid1, uuid2): + return uuid1 != "" and uuid2.startswith(uuid1) + + def is_generic_by_uuid(self, event): + if self.uuids is None or event.uuids is None: + return False + + for s_uuid in self.uuids: + for e_uuid in event.uuids: + if Event.is_generic_uuid(s_uuid, e_uuid): + return True + return False + def get_possibly_duplicated(self): if self.possibly_duplicated is None: return [] @@ -391,7 +486,7 @@ class Event(models.Model): def data_fields(): - return ["title", "location", "start_day", "start_time", "end_day", "end_time", "description", "image", "image_alt", "image_alt", "reference_urls"] + return ["title", "location", "start_day", "start_time", "end_day", "end_time", "description", "image", "image_alt", "image_alt", "reference_urls", "recurrences"] def same_event_by_data(self, other): for attr in Event.data_fields(): diff --git a/src/agenda_culturel/settings/base.py b/src/agenda_culturel/settings/base.py index b20cba7..add3569 100644 --- a/src/agenda_culturel/settings/base.py +++ b/src/agenda_culturel/settings/base.py @@ -46,6 +46,7 @@ INSTALLED_APPS = [ 'django_filters', 'compressor', 'ckeditor', + 'recurrence', ] MIDDLEWARE = [ @@ -199,4 +200,8 @@ if os_getenv("EMAIL_BACKEND"): # increase upload size for debug experiments DATA_UPLOAD_MAX_MEMORY_SIZE = 10 * 2621440 -DATA_UPLOAD_MAX_NUMBER_FIELDS = 10000 \ No newline at end of file +DATA_UPLOAD_MAX_NUMBER_FIELDS = 10000 + +# recurrence translation + +RECURRENCE_I18N_URL = "javascript-catalog" \ No newline at end of file diff --git a/src/agenda_culturel/static/style.scss b/src/agenda_culturel/static/style.scss index 901aa3b..44d15df 100644 --- a/src/agenda_culturel/static/style.scss +++ b/src/agenda_culturel/static/style.scss @@ -640,4 +640,81 @@ aside nav a.badge { .django-ckeditor-widget a[role="button"]:not([href]) { opacity: 1; pointer-events: all; +} + +/* mise en forme pour les récurrences */ +.container-fluid article form p .recurrence-widget { + + .header a, .add-button { + @extend [role="button"]; + margin-right: var(--nav-element-spacing-horizontal); + + span.plus { + margin-right: var(--nav-element-spacing-horizontal); + } + } + + li { + list-style: none; + } + + .freq, .count, .interval, .until { + width: 50%; + float: left; + } + .freq, .until { + padding-right: .2em; + } + .interval, .count { + padding-left: .2em; + } + [name="position"], [name="weekday"] { + width: 49%; + } + [name="position"] { + float: left; + } + [name="weekday"] { + float: right; + } + + .limit, .monthday { + clear: both; + } + [name="freq"], .date-selector { + margin-left: .4em; + width: 15em; + } + [name="interval"], [name="count"] { + width: 3em; + margin: 0 0.4em; + } + .control { + clear: both; + } + + table.grid { + td { + @extend [role="button"]; + background: transparent; + color: var(--secondary); + border-color: var(--secondary); + + margin-right: var(--nav-element-spacing-horizontal); + width: 5em; + + &.active { + @extend [role="button"]; + width: 5em; + margin-right: var(--nav-element-spacing-horizontal); + } + } + + } + + +} + +.container-fluid article form .hidden { + display: none; } \ No newline at end of file diff --git a/src/agenda_culturel/templates/agenda_culturel/event_create_form.html b/src/agenda_culturel/templates/agenda_culturel/event_create_form.html deleted file mode 100644 index 6849611..0000000 --- a/src/agenda_culturel/templates/agenda_culturel/event_create_form.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends "agenda_culturel/page.html" %} -{% load static %} - -{% block title %}Proposer un événement{% endblock %} - -{% block entete_header %} - - - - - - -{% endblock %} - - -{% block content %} - -

Proposer un événement

- -
-{% url 'add_event' as local_url %} -{% include "agenda_culturel/static_content.html" with name="add_event" url_path=local_url %} -
- -
{% csrf_token %} - {{ form.as_p }} - -
- -{% endblock %} \ No newline at end of file diff --git a/src/agenda_culturel/templates/agenda_culturel/event_form.html b/src/agenda_culturel/templates/agenda_culturel/event_form.html index 31b9401..a7a139a 100644 --- a/src/agenda_culturel/templates/agenda_culturel/event_form.html +++ b/src/agenda_culturel/templates/agenda_culturel/event_form.html @@ -21,6 +21,7 @@

Édition de l'événement {{ object.title }} ({{ object.start_day }})

{% csrf_token %} + {{ form.media }} {{ form.as_p }}
Annuler diff --git a/src/agenda_culturel/templates/agenda_culturel/import.html b/src/agenda_culturel/templates/agenda_culturel/import.html index 57f79a0..5dbcce5 100644 --- a/src/agenda_culturel/templates/agenda_culturel/import.html +++ b/src/agenda_culturel/templates/agenda_culturel/import.html @@ -2,6 +2,20 @@ {% block title %}Importer un événement{% endblock %} +{% load static %} + +{% block entete_header %} + + + + + + + + + +{% endblock %} + {% block content %} @@ -17,8 +31,10 @@

Ajout automatique

Si l'événement est déjà en ligne sur un autre site internet, on essaye de l'importer...

+
{% csrf_token %} + {{ form.media }} {{ form.as_p }} diff --git a/src/agenda_culturel/templates/agenda_culturel/page-day.html b/src/agenda_culturel/templates/agenda_culturel/page-day.html index bc9794e..2edefa0 100644 --- a/src/agenda_culturel/templates/agenda_culturel/page-day.html +++ b/src/agenda_culturel/templates/agenda_culturel/page-day.html @@ -70,22 +70,26 @@

En résumé

- {% regroup events by category as events_by_category %} - + {% endif %}
diff --git a/src/agenda_culturel/templates/agenda_culturel/page-event.html b/src/agenda_culturel/templates/agenda_culturel/page-event.html index 22f0b1e..2c5fdb6 100644 --- a/src/agenda_culturel/templates/agenda_culturel/page-event.html +++ b/src/agenda_culturel/templates/agenda_culturel/page-event.html @@ -13,13 +13,15 @@ {% block content %}
- {% include "agenda_culturel/single-event/event-single-inc.html" with event=event filter=filter %}
+ {% if event.recurrences %} + + {% endif %} +
{% if user.is_authenticated %}
{% include "agenda_culturel/edit-buttons-inc.html" with event=event %} diff --git a/src/agenda_culturel/templates/agenda_culturel/single-event/event-single-inc.html b/src/agenda_culturel/templates/agenda_culturel/single-event/event-single-inc.html index 11b7fd2..be5d355 100644 --- a/src/agenda_culturel/templates/agenda_culturel/single-event/event-single-inc.html +++ b/src/agenda_culturel/templates/agenda_culturel/single-event/event-single-inc.html @@ -9,6 +9,7 @@ {% include "agenda_culturel/ephemeris-inc.html" with event=event filter=filter %} {{ event.category | small_cat }}

{{ event|picto_status }} {{ event.title }}

+

{% picto_from_name "calendar" %} {% if event.end_day %}du{% else %}le{% endif %} {% include "agenda_culturel/date-times-inc.html" with event=event %} @@ -29,7 +30,8 @@