formatage black sur les src
This commit is contained in:
		@@ -1,6 +1,14 @@
 | 
			
		||||
from django.contrib import admin
 | 
			
		||||
from django import forms
 | 
			
		||||
from .models import Event, Category, StaticContent, DuplicatedEvents, BatchImportation, RecurrentImport, Place
 | 
			
		||||
from .models import (
 | 
			
		||||
    Event,
 | 
			
		||||
    Category,
 | 
			
		||||
    StaticContent,
 | 
			
		||||
    DuplicatedEvents,
 | 
			
		||||
    BatchImportation,
 | 
			
		||||
    RecurrentImport,
 | 
			
		||||
    Place,
 | 
			
		||||
)
 | 
			
		||||
from django_better_admin_arrayfield.admin.mixins import DynamicArrayMixin
 | 
			
		||||
from django_better_admin_arrayfield.forms.widgets import DynamicArrayWidget
 | 
			
		||||
from django_better_admin_arrayfield.models.fields import DynamicArrayField
 | 
			
		||||
@@ -16,12 +24,12 @@ admin.site.register(Place)
 | 
			
		||||
 | 
			
		||||
class URLWidget(DynamicArrayWidget):
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        kwargs['subwidget_form'] = forms.URLField()
 | 
			
		||||
        kwargs["subwidget_form"] = forms.URLField()
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin.register(Event)
 | 
			
		||||
class Eventdmin(admin.ModelAdmin, DynamicArrayMixin):
 | 
			
		||||
    
 | 
			
		||||
    formfield_overrides = {
 | 
			
		||||
        DynamicArrayField: {'urls': URLWidget},
 | 
			
		||||
    }
 | 
			
		||||
        DynamicArrayField: {"urls": URLWidget},
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ from django.utils import timezone
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -21,7 +22,7 @@ def daterange(start, end, step=timedelta(1)):
 | 
			
		||||
class DayInCalendar:
 | 
			
		||||
    midnight = time(23, 59, 59)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, d, on_requested_interval = True):
 | 
			
		||||
    def __init__(self, d, on_requested_interval=True):
 | 
			
		||||
        self.date = d
 | 
			
		||||
        now = date.today()
 | 
			
		||||
        self.week = d.isocalendar()[1]
 | 
			
		||||
@@ -35,6 +36,7 @@ class DayInCalendar:
 | 
			
		||||
 | 
			
		||||
    def is_in_past(self):
 | 
			
		||||
        return self.in_past
 | 
			
		||||
 | 
			
		||||
    def is_today(self):
 | 
			
		||||
        return self.today
 | 
			
		||||
 | 
			
		||||
@@ -58,8 +60,9 @@ class DayInCalendar:
 | 
			
		||||
 | 
			
		||||
        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])
 | 
			
		||||
 | 
			
		||||
            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):
 | 
			
		||||
@@ -70,24 +73,26 @@ class DayInCalendar:
 | 
			
		||||
                self.remove_event_with_ancestor_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:
 | 
			
		||||
                    self.events_by_category[""] = []
 | 
			
		||||
                self.events_by_category[""].append(event)
 | 
			
		||||
            else:
 | 
			
		||||
                if not event.category.name in self.events_by_category:
 | 
			
		||||
                    self.events_by_category[event.category.name] = []
 | 
			
		||||
                self.events_by_category[event.category.name].append(event)
 | 
			
		||||
        self.events.append(event)
 | 
			
		||||
        if event.category is None:
 | 
			
		||||
            if not "" in self.events_by_category:
 | 
			
		||||
                self.events_by_category[""] = []
 | 
			
		||||
            self.events_by_category[""].append(event)
 | 
			
		||||
        else:
 | 
			
		||||
            if not event.category.name in self.events_by_category:
 | 
			
		||||
                self.events_by_category[event.category.name] = []
 | 
			
		||||
            self.events_by_category[event.category.name].append(event)
 | 
			
		||||
 | 
			
		||||
    def filter_events(self):
 | 
			
		||||
        self.events.sort(key=lambda e: DayInCalendar.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:
 | 
			
		||||
 | 
			
		||||
    def __init__(self, firstdate, lastdate, filter=None, exact=False):
 | 
			
		||||
        self.firstdate = firstdate
 | 
			
		||||
        self.lastdate = lastdate
 | 
			
		||||
@@ -101,8 +106,7 @@ class CalendarList:
 | 
			
		||||
            # start the first day of the first week
 | 
			
		||||
            self.c_firstdate = firstdate + timedelta(days=-firstdate.weekday())
 | 
			
		||||
            # end the last day of the last week
 | 
			
		||||
            self.c_lastdate = lastdate + timedelta(days=6-lastdate.weekday())
 | 
			
		||||
 | 
			
		||||
            self.c_lastdate = lastdate + timedelta(days=6 - lastdate.weekday())
 | 
			
		||||
 | 
			
		||||
        # create a list of DayInCalendars
 | 
			
		||||
        self.create_calendar_days()
 | 
			
		||||
@@ -114,7 +118,6 @@ class CalendarList:
 | 
			
		||||
        for i, c in self.calendar_days.items():
 | 
			
		||||
            c.filter_events()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def today_in_calendar(self):
 | 
			
		||||
        return self.firstdate <= self.now and self.lastdate >= self.now
 | 
			
		||||
 | 
			
		||||
@@ -124,53 +127,56 @@ class CalendarList:
 | 
			
		||||
    def fill_calendar_days(self):
 | 
			
		||||
        if self.filter is None:
 | 
			
		||||
            from .models import Event
 | 
			
		||||
 | 
			
		||||
            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(recurrence_dtend__isnull=True) & Q(recurrence_dtstart__lte=lastdatetime)) |
 | 
			
		||||
            (Q(recurrence_dtend__isnull=False) & ~(Q(recurrence_dtstart__gt=lastdatetime) | Q(recurrence_dtend__lt=startdatetime)))
 | 
			
		||||
            (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_time")
 | 
			
		||||
 | 
			
		||||
        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())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        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")] = DayInCalendar(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):
 | 
			
		||||
        return hasattr(self, "week")
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    def is_full_month(self):
 | 
			
		||||
        return hasattr(self, "month")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def calendar_days_list(self):
 | 
			
		||||
        return list(self.calendar_days.values())
 | 
			
		||||
 | 
			
		||||
class CalendarMonth(CalendarList):
 | 
			
		||||
 | 
			
		||||
class CalendarMonth(CalendarList):
 | 
			
		||||
    def __init__(self, year, month, filter):
 | 
			
		||||
        self.year = year
 | 
			
		||||
        self.month = month
 | 
			
		||||
@@ -192,7 +198,6 @@ class CalendarMonth(CalendarList):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CalendarWeek(CalendarList):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, year, week, filter):
 | 
			
		||||
        self.year = year
 | 
			
		||||
        self.week = week
 | 
			
		||||
@@ -210,9 +215,8 @@ class CalendarWeek(CalendarList):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CalendarDay(CalendarList):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, date, filter=None):
 | 
			
		||||
        super().__init__(date, date, filter, exact=True)
 | 
			
		||||
 | 
			
		||||
    def get_events(self):
 | 
			
		||||
        return self.calendar_days_list()[0].events
 | 
			
		||||
        return self.calendar_days_list()[0].events
 | 
			
		||||
 
 | 
			
		||||
@@ -12,8 +12,6 @@ from .import_tasks.extractor_ical import *
 | 
			
		||||
from .import_tasks.custom_extractors import *
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Set the default Django settings module for the 'celery' program.
 | 
			
		||||
APP_ENV = os.getenv("APP_ENV", "dev")
 | 
			
		||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", f"agenda_culturel.settings.{APP_ENV}")
 | 
			
		||||
@@ -32,11 +30,14 @@ app.config_from_object("django.conf:settings", namespace="CELERY")
 | 
			
		||||
# Load task modules from all registered Django apps.
 | 
			
		||||
app.autodiscover_tasks()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def close_import_task(taskid, success, error_message, importer):
 | 
			
		||||
    from agenda_culturel.models import BatchImportation
 | 
			
		||||
 | 
			
		||||
    task = BatchImportation.objects.get(celery_id=taskid)
 | 
			
		||||
    task.status = BatchImportation.STATUS.SUCCESS if success else BatchImportation.STATUS.FAILED
 | 
			
		||||
    task.status = (
 | 
			
		||||
        BatchImportation.STATUS.SUCCESS if success else BatchImportation.STATUS.FAILED
 | 
			
		||||
    )
 | 
			
		||||
    task.nb_initial = importer.get_nb_events()
 | 
			
		||||
    task.nb_imported = importer.get_nb_imported_events()
 | 
			
		||||
    task.nb_updated = importer.get_nb_updated_events()
 | 
			
		||||
@@ -59,12 +60,11 @@ def import_events_from_json(self, json):
 | 
			
		||||
    # save batch importation
 | 
			
		||||
    importation.save()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    logger.info("Import events from json: {}".format(self.request.id))
 | 
			
		||||
 | 
			
		||||
    importer = DBImporterEvents(self.request.id)
 | 
			
		||||
 | 
			
		||||
    #try:
 | 
			
		||||
    # try:
 | 
			
		||||
    success, error_message = importer.import_events(json)
 | 
			
		||||
 | 
			
		||||
    # finally, close task
 | 
			
		||||
@@ -82,7 +82,7 @@ def run_recurrent_import(self, pk):
 | 
			
		||||
 | 
			
		||||
    logger.info("Run recurrent import: {}".format(self.request.id))
 | 
			
		||||
 | 
			
		||||
    # get the recurrent import 
 | 
			
		||||
    # get the recurrent import
 | 
			
		||||
    rimport = RecurrentImport.objects.get(pk=pk)
 | 
			
		||||
 | 
			
		||||
    # create a batch importation
 | 
			
		||||
@@ -94,7 +94,11 @@ def run_recurrent_import(self, pk):
 | 
			
		||||
    importer = DBImporterEvents(self.request.id)
 | 
			
		||||
 | 
			
		||||
    # prepare downloading and extracting processes
 | 
			
		||||
    downloader = SimpleDownloader() if rimport.downloader == RecurrentImport.DOWNLOADER.SIMPLE else ChromiumHeadlessDownloader()
 | 
			
		||||
    downloader = (
 | 
			
		||||
        SimpleDownloader()
 | 
			
		||||
        if rimport.downloader == RecurrentImport.DOWNLOADER.SIMPLE
 | 
			
		||||
        else ChromiumHeadlessDownloader()
 | 
			
		||||
    )
 | 
			
		||||
    if rimport.processor == RecurrentImport.PROCESSOR.ICAL:
 | 
			
		||||
        extractor = ICALExtractor()
 | 
			
		||||
    elif rimport.processor == RecurrentImport.PROCESSOR.ICALNOBUSY:
 | 
			
		||||
@@ -127,7 +131,12 @@ def run_recurrent_import(self, pk):
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        # get events from website
 | 
			
		||||
        events = u2e.process(url, browsable_url, default_values = {"category": category, "location": location, "tags": tags}, published = published)
 | 
			
		||||
        events = u2e.process(
 | 
			
		||||
            url,
 | 
			
		||||
            browsable_url,
 | 
			
		||||
            default_values={"category": category, "location": location, "tags": tags},
 | 
			
		||||
            published=published,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # convert it to json
 | 
			
		||||
        json_events = json.dumps(events, default=str)
 | 
			
		||||
@@ -145,15 +154,20 @@ def run_recurrent_import(self, pk):
 | 
			
		||||
@app.task(bind=True)
 | 
			
		||||
def daily_imports(self):
 | 
			
		||||
    from agenda_culturel.models import RecurrentImport
 | 
			
		||||
 | 
			
		||||
    logger.info("Imports quotidiens")
 | 
			
		||||
    imports = RecurrentImport.objects.filter(recurrence=RecurrentImport.RECURRENCE.DAILY)
 | 
			
		||||
    imports = RecurrentImport.objects.filter(
 | 
			
		||||
        recurrence=RecurrentImport.RECURRENCE.DAILY
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    for imp in imports:
 | 
			
		||||
        run_recurrent_import.delay(imp.pk)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@app.task(bind=True)
 | 
			
		||||
def run_all_recurrent_imports(self):
 | 
			
		||||
    from agenda_culturel.models import RecurrentImport
 | 
			
		||||
 | 
			
		||||
    logger.info("Imports complets")
 | 
			
		||||
    imports = RecurrentImport.objects.all()
 | 
			
		||||
 | 
			
		||||
@@ -164,12 +178,16 @@ def run_all_recurrent_imports(self):
 | 
			
		||||
@app.task(bind=True)
 | 
			
		||||
def weekly_imports(self):
 | 
			
		||||
    from agenda_culturel.models import RecurrentImport
 | 
			
		||||
 | 
			
		||||
    logger.info("Imports hebdomadaires")
 | 
			
		||||
    imports = RecurrentImport.objects.filter(recurrence=RecurrentImport.RECURRENCE.WEEKLY)
 | 
			
		||||
    imports = RecurrentImport.objects.filter(
 | 
			
		||||
        recurrence=RecurrentImport.RECURRENCE.WEEKLY
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    for imp in imports:
 | 
			
		||||
        run_recurrent_import.delay(imp.pk)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
app.conf.beat_schedule = {
 | 
			
		||||
    "daily_imports": {
 | 
			
		||||
        "task": "agenda_culturel.celery.daily_imports",
 | 
			
		||||
@@ -179,9 +197,8 @@ app.conf.beat_schedule = {
 | 
			
		||||
    "weekly_imports": {
 | 
			
		||||
        "task": "agenda_culturel.celery.weekly_imports",
 | 
			
		||||
        # Daily imports on Mondays at 2:22 a.m.
 | 
			
		||||
        "schedule": crontab(hour=2, minute=22, day_of_week='mon'),
 | 
			
		||||
        "schedule": crontab(hour=2, minute=22, day_of_week="mon"),
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
app.conf.timezone = "Europe/Paris"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,11 +4,11 @@ from datetime import datetime
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DBImporterEvents:
 | 
			
		||||
 | 
			
		||||
    def __init__(self, celery_id):
 | 
			
		||||
        self.celery_id = celery_id
 | 
			
		||||
        self.error_message = ""
 | 
			
		||||
@@ -60,7 +60,7 @@ class DBImporterEvents:
 | 
			
		||||
        # 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:
 | 
			
		||||
@@ -69,7 +69,7 @@ class DBImporterEvents:
 | 
			
		||||
                # conversion to Event, and return an error if it failed
 | 
			
		||||
                if not self.load_event(event):
 | 
			
		||||
                    return (False, self.error_message)
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        # finally save the loaded events in database
 | 
			
		||||
        self.save_imported()
 | 
			
		||||
 | 
			
		||||
@@ -92,27 +92,31 @@ class DBImporterEvents:
 | 
			
		||||
        return event["end_day"] >= self.today
 | 
			
		||||
 | 
			
		||||
    def save_imported(self):
 | 
			
		||||
        self.db_event_objects, self.nb_updated, self.nb_removed = Event.import_events(self.event_objects, remove_missing_from_source=self.url)
 | 
			
		||||
        self.db_event_objects, self.nb_updated, self.nb_removed = Event.import_events(
 | 
			
		||||
            self.event_objects, remove_missing_from_source=self.url
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    def is_valid_event_structure(self, event):
 | 
			
		||||
        if "title" not in event:
 | 
			
		||||
            self.error_message = "JSON is not correctly structured: one event without title"
 | 
			
		||||
            self.error_message = (
 | 
			
		||||
                "JSON is not correctly structured: one event without title"
 | 
			
		||||
            )
 | 
			
		||||
            return False
 | 
			
		||||
        if "start_day" not in event:
 | 
			
		||||
            self.error_message = "JSON is not correctly structured: one event without start_day"
 | 
			
		||||
            self.error_message = (
 | 
			
		||||
                "JSON is not correctly structured: one event without start_day"
 | 
			
		||||
            )
 | 
			
		||||
            return False
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def load_event(self, event):
 | 
			
		||||
        if self.is_valid_event_structure(event):
 | 
			
		||||
            logger.warning("Valid event: {} {}".format(event["last_modified"], event["title"]))
 | 
			
		||||
            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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,31 @@
 | 
			
		||||
from django.forms import ModelForm, ValidationError, TextInput, Form, URLField, MultipleHiddenInput, Textarea, CharField, ChoiceField, RadioSelect, MultipleChoiceField, BooleanField, HiddenInput, ModelChoiceField
 | 
			
		||||
from django.forms import (
 | 
			
		||||
    ModelForm,
 | 
			
		||||
    ValidationError,
 | 
			
		||||
    TextInput,
 | 
			
		||||
    Form,
 | 
			
		||||
    URLField,
 | 
			
		||||
    MultipleHiddenInput,
 | 
			
		||||
    Textarea,
 | 
			
		||||
    CharField,
 | 
			
		||||
    ChoiceField,
 | 
			
		||||
    RadioSelect,
 | 
			
		||||
    MultipleChoiceField,
 | 
			
		||||
    BooleanField,
 | 
			
		||||
    HiddenInput,
 | 
			
		||||
    ModelChoiceField,
 | 
			
		||||
)
 | 
			
		||||
from datetime import date
 | 
			
		||||
from django_better_admin_arrayfield.forms.widgets import DynamicArrayWidget
 | 
			
		||||
 | 
			
		||||
from .models import Event, BatchImportation, RecurrentImport, CategorisationRule, ModerationAnswer, ModerationQuestion, Place
 | 
			
		||||
from .models import (
 | 
			
		||||
    Event,
 | 
			
		||||
    BatchImportation,
 | 
			
		||||
    RecurrentImport,
 | 
			
		||||
    CategorisationRule,
 | 
			
		||||
    ModerationAnswer,
 | 
			
		||||
    ModerationQuestion,
 | 
			
		||||
    Place,
 | 
			
		||||
)
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from string import ascii_uppercase as auc
 | 
			
		||||
from .templatetags.utils_extra import int_to_abc
 | 
			
		||||
@@ -12,6 +35,7 @@ from django.utils.formats import localize
 | 
			
		||||
from .templatetags.event_extra import event_field_verbose_name, field_to_html
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -22,45 +46,63 @@ class EventSubmissionForm(Form):
 | 
			
		||||
class DynamicArrayWidgetURLs(DynamicArrayWidget):
 | 
			
		||||
    template_name = "agenda_culturel/widgets/widget-urls.html"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DynamicArrayWidgetTags(DynamicArrayWidget):
 | 
			
		||||
    template_name = "agenda_culturel/widgets/widget-tags.html"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RecurrentImportForm(ModelForm):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = RecurrentImport
 | 
			
		||||
        fields = '__all__'
 | 
			
		||||
        fields = "__all__"
 | 
			
		||||
        widgets = {
 | 
			
		||||
            'defaultTags': DynamicArrayWidgetTags(),
 | 
			
		||||
            "defaultTags": DynamicArrayWidgetTags(),
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CategorisationRuleImportForm(ModelForm):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = CategorisationRule
 | 
			
		||||
        fields = '__all__'
 | 
			
		||||
        fields = "__all__"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EventForm(ModelForm):
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = Event
 | 
			
		||||
        exclude = ["possibly_duplicated", "imported_date", "modified_date", "moderated_date"]
 | 
			
		||||
        exclude = [
 | 
			
		||||
            "possibly_duplicated",
 | 
			
		||||
            "imported_date",
 | 
			
		||||
            "modified_date",
 | 
			
		||||
            "moderated_date",
 | 
			
		||||
        ]
 | 
			
		||||
        widgets = {
 | 
			
		||||
            'start_day': TextInput(attrs={'type': 'date', 'onchange': 'update_datetimes(event);', "onfocus": "this.oldvalue = this.value;"}),
 | 
			
		||||
            'start_time': TextInput(attrs={'type': 'time', 'onchange': 'update_datetimes(event);', "onfocus": "this.oldvalue = this.value;"}),
 | 
			
		||||
            'end_day': TextInput(attrs={'type': 'date'}),
 | 
			
		||||
            'end_time': TextInput(attrs={'type': 'time'}),
 | 
			
		||||
            'uuids': MultipleHiddenInput(),
 | 
			
		||||
            'import_sources': MultipleHiddenInput(),
 | 
			
		||||
            'reference_urls': DynamicArrayWidgetURLs(),
 | 
			
		||||
            'tags': DynamicArrayWidgetTags(),
 | 
			
		||||
            "start_day": TextInput(
 | 
			
		||||
                attrs={
 | 
			
		||||
                    "type": "date",
 | 
			
		||||
                    "onchange": "update_datetimes(event);",
 | 
			
		||||
                    "onfocus": "this.oldvalue = this.value;",
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            "start_time": TextInput(
 | 
			
		||||
                attrs={
 | 
			
		||||
                    "type": "time",
 | 
			
		||||
                    "onchange": "update_datetimes(event);",
 | 
			
		||||
                    "onfocus": "this.oldvalue = this.value;",
 | 
			
		||||
                }
 | 
			
		||||
            ),
 | 
			
		||||
            "end_day": TextInput(attrs={"type": "date"}),
 | 
			
		||||
            "end_time": TextInput(attrs={"type": "time"}),
 | 
			
		||||
            "uuids": MultipleHiddenInput(),
 | 
			
		||||
            "import_sources": MultipleHiddenInput(),
 | 
			
		||||
            "reference_urls": DynamicArrayWidgetURLs(),
 | 
			
		||||
            "tags": DynamicArrayWidgetTags(),
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        is_authenticated = kwargs.pop('is_authenticated', False)
 | 
			
		||||
        is_authenticated = kwargs.pop("is_authenticated", False)
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        if not is_authenticated:
 | 
			
		||||
            del self.fields['status']
 | 
			
		||||
 | 
			
		||||
            del self.fields["status"]
 | 
			
		||||
 | 
			
		||||
    def clean_end_day(self):
 | 
			
		||||
        start_day = self.cleaned_data.get("start_day")
 | 
			
		||||
@@ -82,40 +124,71 @@ class EventForm(ModelForm):
 | 
			
		||||
            # both start and end time are defined
 | 
			
		||||
            if start_time is not None and end_time is not None:
 | 
			
		||||
                if start_time > end_time:
 | 
			
		||||
                    raise ValidationError(_("The end time cannot be earlier than the start time."))
 | 
			
		||||
                    raise ValidationError(
 | 
			
		||||
                        _("The end time cannot be earlier than the start time.")
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
        return end_time
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BatchImportationForm(Form):
 | 
			
		||||
    json = CharField(label="JSON", widget=Textarea(attrs={"rows":"10"}), help_text=_("JSON in the format expected for the import."), required=True)
 | 
			
		||||
    json = CharField(
 | 
			
		||||
        label="JSON",
 | 
			
		||||
        widget=Textarea(attrs={"rows": "10"}),
 | 
			
		||||
        help_text=_("JSON in the format expected for the import."),
 | 
			
		||||
        required=True,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FixDuplicates(Form):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    action = ChoiceField()
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        nb_events = kwargs.pop('nb_events', None)
 | 
			
		||||
        nb_events = kwargs.pop("nb_events", None)
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
        if nb_events == 2:
 | 
			
		||||
            choices = [("NotDuplicates", "Ces événements sont différents")]
 | 
			
		||||
            choices += [("SelectA", "Ces événements sont identiques, on garde A et on met B à la corbeille")]
 | 
			
		||||
            choices += [("SelectB", "Ces événements sont identiques, on garde B et on met A à la corbeille")]
 | 
			
		||||
            choices += [("Merge", "Ces événements sont identiques, on fusionne à la main")]
 | 
			
		||||
            choices += [
 | 
			
		||||
                (
 | 
			
		||||
                    "SelectA",
 | 
			
		||||
                    "Ces événements sont identiques, on garde A et on met B à la corbeille",
 | 
			
		||||
                )
 | 
			
		||||
            ]
 | 
			
		||||
            choices += [
 | 
			
		||||
                (
 | 
			
		||||
                    "SelectB",
 | 
			
		||||
                    "Ces événements sont identiques, on garde B et on met A à la corbeille",
 | 
			
		||||
                )
 | 
			
		||||
            ]
 | 
			
		||||
            choices += [
 | 
			
		||||
                ("Merge", "Ces événements sont identiques, on fusionne à la main")
 | 
			
		||||
            ]
 | 
			
		||||
        else:
 | 
			
		||||
            choices = [("NotDuplicates", "Ces événements sont tous différents")]
 | 
			
		||||
            for i in auc[0:nb_events]:
 | 
			
		||||
                choices += [("Remove" + i, "L'événement " + i + " n'est pas identique aux autres, on le rend indépendant")]
 | 
			
		||||
                choices += [
 | 
			
		||||
                    (
 | 
			
		||||
                        "Remove" + i,
 | 
			
		||||
                        "L'événement "
 | 
			
		||||
                        + i
 | 
			
		||||
                        + " n'est pas identique aux autres, on le rend indépendant",
 | 
			
		||||
                    )
 | 
			
		||||
                ]
 | 
			
		||||
            for i in auc[0:nb_events]:
 | 
			
		||||
                choices += [("Select" + i, "Ces événements sont identiques, on garde " + i + " et on met les autres à la corbeille")]
 | 
			
		||||
            choices += [("Merge", "Ces événements sont identiques, on fusionne à la main")]
 | 
			
		||||
                choices += [
 | 
			
		||||
                    (
 | 
			
		||||
                        "Select" + i,
 | 
			
		||||
                        "Ces événements sont identiques, on garde "
 | 
			
		||||
                        + i
 | 
			
		||||
                        + " et on met les autres à la corbeille",
 | 
			
		||||
                    )
 | 
			
		||||
                ]
 | 
			
		||||
            choices += [
 | 
			
		||||
                ("Merge", "Ces événements sont identiques, on fusionne à la main")
 | 
			
		||||
            ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        self.fields['action'].choices = choices
 | 
			
		||||
        self.fields["action"].choices = choices
 | 
			
		||||
 | 
			
		||||
    def is_action_no_duplicates(self):
 | 
			
		||||
        return self.cleaned_data["action"] == "NotDuplicates"
 | 
			
		||||
@@ -145,26 +218,28 @@ class FixDuplicates(Form):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SelectEventInList(Form):
 | 
			
		||||
 | 
			
		||||
    event = ChoiceField()
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        events = kwargs.pop('events', None)
 | 
			
		||||
        events = kwargs.pop("events", None)
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
        self.fields['event'].choices = [(e.pk, str(e.start_day) + " " + e.title + ", " + e.location) for e in events]
 | 
			
		||||
        self.fields["event"].choices = [
 | 
			
		||||
            (e.pk, str(e.start_day) + " " + e.title + ", " + e.location) for e in events
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MergeDuplicates(Form):
 | 
			
		||||
 | 
			
		||||
    checkboxes_fields = ["reference_urls", "description"]
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        self.duplicates = kwargs.pop('duplicates', None)
 | 
			
		||||
        self.duplicates = kwargs.pop("duplicates", None)
 | 
			
		||||
        nb_events = self.duplicates.nb_duplicated()
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
        choices = [("event" + i, "Valeur de l'évenement " + i) for i in auc[0:nb_events]]
 | 
			
		||||
        choices = [
 | 
			
		||||
            ("event" + i, "Valeur de l'évenement " + i) for i in auc[0:nb_events]
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        for f in self.duplicates.get_items_comparison():
 | 
			
		||||
            if not f["similar"]:
 | 
			
		||||
@@ -172,42 +247,61 @@ class MergeDuplicates(Form):
 | 
			
		||||
                    self.fields[f["key"]] = MultipleChoiceField(choices=choices)
 | 
			
		||||
                    self.fields[f["key"]].initial = choices[0][0]
 | 
			
		||||
                else:
 | 
			
		||||
                    self.fields[f["key"]] = ChoiceField(widget=RadioSelect, choices=choices)
 | 
			
		||||
                    self.fields[f["key"]] = ChoiceField(
 | 
			
		||||
                        widget=RadioSelect, choices=choices
 | 
			
		||||
                    )
 | 
			
		||||
                    self.fields[f["key"]].initial = choices[0][0]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def as_grid(self):
 | 
			
		||||
        result = '<div class="grid">'
 | 
			
		||||
        for i, e in enumerate(self.duplicates.get_duplicated()):
 | 
			
		||||
            result += '<div class="grid entete-badge">'
 | 
			
		||||
            result += '<div class="badge-large">' + int_to_abc(i) + '</div>'
 | 
			
		||||
            result += '<ul>'
 | 
			
		||||
            result += '<li><a href="' + e.get_absolute_url() + '">' + e.title + '</a></li>'
 | 
			
		||||
            result += '<li>Création : ' + localize(localtime(e.created_date)) + '</li>'
 | 
			
		||||
            result += '<li>Dernière modification : ' + localize(localtime(e.modified_date)) + '</li>'
 | 
			
		||||
            result += '<div class="badge-large">' + int_to_abc(i) + "</div>"
 | 
			
		||||
            result += "<ul>"
 | 
			
		||||
            result += (
 | 
			
		||||
                '<li><a href="' + e.get_absolute_url() + '">' + e.title + "</a></li>"
 | 
			
		||||
            )
 | 
			
		||||
            result += (
 | 
			
		||||
                "<li>Création : " + localize(localtime(e.created_date)) + "</li>"
 | 
			
		||||
            )
 | 
			
		||||
            result += (
 | 
			
		||||
                "<li>Dernière modification : "
 | 
			
		||||
                + localize(localtime(e.modified_date))
 | 
			
		||||
                + "</li>"
 | 
			
		||||
            )
 | 
			
		||||
            if e.imported_date:
 | 
			
		||||
                result += '<li>Dernière importation : ' + localize(localtime(e.imported_date)) + '</li>'
 | 
			
		||||
            result += '</ul>'
 | 
			
		||||
            result += '</div>'
 | 
			
		||||
        result += '</div>'
 | 
			
		||||
                result += (
 | 
			
		||||
                    "<li>Dernière importation : "
 | 
			
		||||
                    + localize(localtime(e.imported_date))
 | 
			
		||||
                    + "</li>"
 | 
			
		||||
                )
 | 
			
		||||
            result += "</ul>"
 | 
			
		||||
            result += "</div>"
 | 
			
		||||
        result += "</div>"
 | 
			
		||||
 | 
			
		||||
        for e in self.duplicates.get_items_comparison():
 | 
			
		||||
            key = e["key"]
 | 
			
		||||
            result += "<h3>" + event_field_verbose_name(e["key"]) + "</h3>"
 | 
			
		||||
            if e["similar"]:
 | 
			
		||||
                result += '<div class="comparison-item">Identique :' + str(field_to_html(e["values"], e["key"])) + '</div>'
 | 
			
		||||
                result += (
 | 
			
		||||
                    '<div class="comparison-item">Identique :'
 | 
			
		||||
                    + str(field_to_html(e["values"], e["key"]))
 | 
			
		||||
                    + "</div>"
 | 
			
		||||
                )
 | 
			
		||||
            else:
 | 
			
		||||
                result += '<fieldset>'
 | 
			
		||||
                result += "<fieldset>"
 | 
			
		||||
                result += '<div class="grid comparison-item">'
 | 
			
		||||
                if hasattr(self, "cleaned_data"):
 | 
			
		||||
                    checked = self.cleaned_data.get(key)
 | 
			
		||||
                else:
 | 
			
		||||
                    checked = self.fields[key].initial
 | 
			
		||||
 | 
			
		||||
                for i, (v, radio) in enumerate(zip(e["values"], self.fields[e["key"]].choices)):
 | 
			
		||||
                for i, (v, radio) in enumerate(
 | 
			
		||||
                    zip(e["values"], self.fields[e["key"]].choices)
 | 
			
		||||
                ):
 | 
			
		||||
                    result += '<div class="duplicated">'
 | 
			
		||||
                    id = 'id_' + key + '_' + str(i)
 | 
			
		||||
                    value = 'event' + auc[i]
 | 
			
		||||
                    id = "id_" + key + "_" + str(i)
 | 
			
		||||
                    value = "event" + auc[i]
 | 
			
		||||
 | 
			
		||||
                    result += '<input id="' + id + '" name="' + key + '"'
 | 
			
		||||
                    if key in MergeDuplicates.checkboxes_fields:
 | 
			
		||||
@@ -219,13 +313,18 @@ class MergeDuplicates(Form):
 | 
			
		||||
                        if checked == value:
 | 
			
		||||
                            result += " checked"
 | 
			
		||||
                    result += ' value="' + value + '"'
 | 
			
		||||
                    result += '>'
 | 
			
		||||
                    result += '<div class="badge-small">' + int_to_abc(i) + '</div>' + str(field_to_html(v, e["key"])) + '</div>'
 | 
			
		||||
                    result += ">"
 | 
			
		||||
                    result += (
 | 
			
		||||
                        '<div class="badge-small">'
 | 
			
		||||
                        + int_to_abc(i)
 | 
			
		||||
                        + "</div>"
 | 
			
		||||
                        + str(field_to_html(v, e["key"]))
 | 
			
		||||
                        + "</div>"
 | 
			
		||||
                    )
 | 
			
		||||
                result += "</div></fieldset>"
 | 
			
		||||
 | 
			
		||||
        return mark_safe(result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def get_selected_events_id(self, key):
 | 
			
		||||
        value = self.cleaned_data.get(key)
 | 
			
		||||
        if not key in self.fields:
 | 
			
		||||
@@ -240,20 +339,20 @@ class MergeDuplicates(Form):
 | 
			
		||||
class ModerationQuestionForm(ModelForm):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = ModerationQuestion
 | 
			
		||||
        fields = '__all__'
 | 
			
		||||
        fields = "__all__"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ModerationAnswerForm(ModelForm):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = ModerationAnswer
 | 
			
		||||
        exclude = ['question']
 | 
			
		||||
        exclude = ["question"]
 | 
			
		||||
        widgets = {
 | 
			
		||||
            'adds_tags': DynamicArrayWidgetTags(),
 | 
			
		||||
            'removes_tags': DynamicArrayWidgetTags()
 | 
			
		||||
            "adds_tags": DynamicArrayWidgetTags(),
 | 
			
		||||
            "removes_tags": DynamicArrayWidgetTags(),
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ModerateForm(ModelForm):
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = Event
 | 
			
		||||
        fields = []
 | 
			
		||||
@@ -265,75 +364,104 @@ class ModerateForm(ModelForm):
 | 
			
		||||
        mas = ModerationAnswer.objects.all()
 | 
			
		||||
 | 
			
		||||
        for q in mqs:
 | 
			
		||||
            self.fields[q.complete_id()] = ChoiceField(widget=RadioSelect, label=q.question, choices=[(a.pk, a.html_description()) for a in mas if a.question == q], required=True)
 | 
			
		||||
            self.fields[q.complete_id()] = ChoiceField(
 | 
			
		||||
                widget=RadioSelect,
 | 
			
		||||
                label=q.question,
 | 
			
		||||
                choices=[(a.pk, a.html_description()) for a in mas if a.question == q],
 | 
			
		||||
                required=True,
 | 
			
		||||
            )
 | 
			
		||||
            for a in mas:
 | 
			
		||||
                if a.question == q and a.valid_event(self.instance):
 | 
			
		||||
                    self.fields[q.complete_id()].initial = a.pk
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
class CategorisationForm(Form):
 | 
			
		||||
 | 
			
		||||
class CategorisationForm(Form):
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        if "events" in kwargs:
 | 
			
		||||
            events = kwargs.pop('events', None)
 | 
			
		||||
            events = kwargs.pop("events", None)
 | 
			
		||||
        else:
 | 
			
		||||
            events = []
 | 
			
		||||
            for f in args[0]:
 | 
			
		||||
                logger.warning('fff: ' + f)
 | 
			
		||||
                if '_' not in f:
 | 
			
		||||
                    if f + '_cat' in args[0]:
 | 
			
		||||
                        events.append((Event.objects.get(pk=int(f)), args[0][f + '_cat']))
 | 
			
		||||
                logger.warning("fff: " + f)
 | 
			
		||||
                if "_" not in f:
 | 
			
		||||
                    if f + "_cat" in args[0]:
 | 
			
		||||
                        events.append(
 | 
			
		||||
                            (Event.objects.get(pk=int(f)), args[0][f + "_cat"])
 | 
			
		||||
                        )
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
        for e, c in events:
 | 
			
		||||
            self.fields[str(e.pk)] = BooleanField(initial=False, label=_('Apply category {} to the event {}').format(c, e.title), required=False)
 | 
			
		||||
            self.fields[str(e.pk)] = BooleanField(
 | 
			
		||||
                initial=False,
 | 
			
		||||
                label=_("Apply category {} to the event {}").format(c, e.title),
 | 
			
		||||
                required=False,
 | 
			
		||||
            )
 | 
			
		||||
            self.fields[str(e.pk) + "_cat"] = CharField(initial=c, widget=HiddenInput())
 | 
			
		||||
 | 
			
		||||
    def get_validated(self):
 | 
			
		||||
        return [(e, self.cleaned_data.get(e + '_cat')) for e in self.fields if '_' not in e and self.cleaned_data.get(e)]
 | 
			
		||||
        return [
 | 
			
		||||
            (e, self.cleaned_data.get(e + "_cat"))
 | 
			
		||||
            for e in self.fields
 | 
			
		||||
            if "_" not in e and self.cleaned_data.get(e)
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EventAddPlaceForm(Form):
 | 
			
		||||
 | 
			
		||||
    place = ModelChoiceField(label=_("Place"), queryset=Place.objects.all().order_by("name"), empty_label=_("Create a missing place"), required=False)
 | 
			
		||||
    place = ModelChoiceField(
 | 
			
		||||
        label=_("Place"),
 | 
			
		||||
        queryset=Place.objects.all().order_by("name"),
 | 
			
		||||
        empty_label=_("Create a missing place"),
 | 
			
		||||
        required=False,
 | 
			
		||||
    )
 | 
			
		||||
    add_alias = BooleanField(initial=True, required=False)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        self.instance = kwargs.pop('instance', False)
 | 
			
		||||
        self.instance = kwargs.pop("instance", False)
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        if self.instance.location:
 | 
			
		||||
            self.fields["add_alias"].label = _("Add \"{}\" to the aliases of the place").format(self.instance.location)
 | 
			
		||||
            self.fields["add_alias"].label = _(
 | 
			
		||||
                'Add "{}" to the aliases of the place'
 | 
			
		||||
            ).format(self.instance.location)
 | 
			
		||||
        else:
 | 
			
		||||
            self.fields.pop("add_alias")
 | 
			
		||||
 | 
			
		||||
    def modified_event(self):
 | 
			
		||||
        return self.cleaned_data.get('place')
 | 
			
		||||
        return self.cleaned_data.get("place")
 | 
			
		||||
 | 
			
		||||
    def save(self):
 | 
			
		||||
        if self.cleaned_data.get("place"):
 | 
			
		||||
            place = self.cleaned_data.get("place")
 | 
			
		||||
            self.instance.exact_location = place
 | 
			
		||||
            self.instance.save()
 | 
			
		||||
            if self.cleaned_data.get('add_alias'):
 | 
			
		||||
            if self.cleaned_data.get("add_alias"):
 | 
			
		||||
                place.aliases.append(self.instance.location)
 | 
			
		||||
                place.save()
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return self.instance
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PlaceForm(ModelForm):
 | 
			
		||||
    apply_to_all = BooleanField(initial=True, label=_('On saving, use aliases to detect all matching events with missing place'), required=False)
 | 
			
		||||
    apply_to_all = BooleanField(
 | 
			
		||||
        initial=True,
 | 
			
		||||
        label=_(
 | 
			
		||||
            "On saving, use aliases to detect all matching events with missing place"
 | 
			
		||||
        ),
 | 
			
		||||
        required=False,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = Place
 | 
			
		||||
        fields = '__all__'
 | 
			
		||||
        widgets = {
 | 
			
		||||
            'location': TextInput()
 | 
			
		||||
        }
 | 
			
		||||
    
 | 
			
		||||
    def as_grid(self):
 | 
			
		||||
        return mark_safe('<div class="grid"><div>' + super().as_p() + '</div><div><div class="map-widget">' +
 | 
			
		||||
            '<div id="map_location" style="width: 100%; aspect-ratio: 16/9"></div><p>Cliquez pour ajuster la position GPS</p></div></div></div>')
 | 
			
		||||
        fields = "__all__"
 | 
			
		||||
        widgets = {"location": TextInput()}
 | 
			
		||||
 | 
			
		||||
    def as_grid(self):
 | 
			
		||||
        return mark_safe(
 | 
			
		||||
            '<div class="grid"><div>'
 | 
			
		||||
            + super().as_p()
 | 
			
		||||
            + '</div><div><div class="map-widget">'
 | 
			
		||||
            + '<div id="map_location" style="width: 100%; aspect-ratio: 16/9"></div><p>Cliquez pour ajuster la position GPS</p></div></div></div>'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def apply(self):
 | 
			
		||||
        return self.cleaned_data.get("apply_to_all") 
 | 
			
		||||
        return self.cleaned_data.get("apply_to_all")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,7 @@
 | 
			
		||||
from os.path import dirname, basename, isfile, join
 | 
			
		||||
import glob
 | 
			
		||||
 | 
			
		||||
modules = glob.glob(join(dirname(__file__), "*.py"))
 | 
			
		||||
__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]
 | 
			
		||||
__all__ = [
 | 
			
		||||
    basename(f)[:-3] for f in modules if isfile(f) and not f.endswith("__init__.py")
 | 
			
		||||
]
 | 
			
		||||
 
 | 
			
		||||
@@ -8,25 +8,30 @@ from datetime import timedelta
 | 
			
		||||
# URL: https://lacomediedeclermont.com/saison23-24/wp-admin/admin-ajax.php?action=load_dates_existantes
 | 
			
		||||
# URL pour les humains: https://lacomediedeclermont.com/saison23-24/
 | 
			
		||||
class CExtractor(TwoStepsExtractor):
 | 
			
		||||
 | 
			
		||||
    nom_lieu = "La Comédie de Clermont"
 | 
			
		||||
 | 
			
		||||
    def category_comedie2agenda(self, category):
 | 
			
		||||
        mapping = { "Théâtre": "Théâtre", "Danse": "Danse", "Rencontre": "Autre", "Sortie de résidence": "Autre", "PopCorn Live": "Autre"}
 | 
			
		||||
        mapping = {
 | 
			
		||||
            "Théâtre": "Théâtre",
 | 
			
		||||
            "Danse": "Danse",
 | 
			
		||||
            "Rencontre": "Autre",
 | 
			
		||||
            "Sortie de résidence": "Autre",
 | 
			
		||||
            "PopCorn Live": "Autre",
 | 
			
		||||
        }
 | 
			
		||||
        if category in mapping:
 | 
			
		||||
            return mapping[category]
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def build_event_url_list(self, content):
 | 
			
		||||
        dates = json5.loads(content)["data"][0]
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        url = self.url.split("?")[0]
 | 
			
		||||
        for d in list(set(dates)):
 | 
			
		||||
            if not self.only_future or self.now <= datetime.date.fromisoformat(d):
 | 
			
		||||
                events = self.downloader.get_content(url, post={'action': "load_evenements_jour", "jour": d})
 | 
			
		||||
                events = self.downloader.get_content(
 | 
			
		||||
                    url, post={"action": "load_evenements_jour", "jour": d}
 | 
			
		||||
                )
 | 
			
		||||
                if events:
 | 
			
		||||
                    events = json5.loads(events)
 | 
			
		||||
                    if "data" in events:
 | 
			
		||||
@@ -34,27 +39,43 @@ class CExtractor(TwoStepsExtractor):
 | 
			
		||||
                        soup = BeautifulSoup(events, "html.parser")
 | 
			
		||||
                        events = soup.select("div.unedatedev")
 | 
			
		||||
                        for e in events:
 | 
			
		||||
                            e_url = e.select('a')[0]["href"] + "#" + d # a "fake" url specific for each day of this show
 | 
			
		||||
                            e_url = (
 | 
			
		||||
                                e.select("a")[0]["href"] + "#" + d
 | 
			
		||||
                            )  # a "fake" url specific for each day of this show
 | 
			
		||||
                            self.add_event_url(e_url)
 | 
			
		||||
                            self.add_event_start_day(e_url, d)
 | 
			
		||||
                            t = str(e.select('div#datecal')[0]).split(' ')[-1].split('<')[0]
 | 
			
		||||
                            t = (
 | 
			
		||||
                                str(e.select("div#datecal")[0])
 | 
			
		||||
                                .split(" ")[-1]
 | 
			
		||||
                                .split("<")[0]
 | 
			
		||||
                            )
 | 
			
		||||
                            self.add_event_start_time(e_url, t)
 | 
			
		||||
                            title = e.select('a')[0].contents[0]
 | 
			
		||||
                            title = e.select("a")[0].contents[0]
 | 
			
		||||
                            self.add_event_title(e_url, title)
 | 
			
		||||
                            category = e.select("div#lieuevtcal span")
 | 
			
		||||
                            if len(category) > 0:
 | 
			
		||||
                                category = self.category_comedie2agenda(category[-1].contents[0])
 | 
			
		||||
                                category = self.category_comedie2agenda(
 | 
			
		||||
                                    category[-1].contents[0]
 | 
			
		||||
                                )
 | 
			
		||||
                                if category is not None:
 | 
			
		||||
                                    self.add_event_category(e_url, category)
 | 
			
		||||
                            location = e.select("div#lieuevtcal")[0].contents[-1].split("•")[-1]
 | 
			
		||||
                            location = (
 | 
			
		||||
                                e.select("div#lieuevtcal")[0]
 | 
			
		||||
                                .contents[-1]
 | 
			
		||||
                                .split("•")[-1]
 | 
			
		||||
                            )
 | 
			
		||||
                            self.add_event_location(e_url, location)
 | 
			
		||||
 | 
			
		||||
            
 | 
			
		||||
        
 | 
			
		||||
    
 | 
			
		||||
    def add_event_from_content(self, event_content, event_url, url_human = None, default_values = None, published = False):
 | 
			
		||||
    def add_event_from_content(
 | 
			
		||||
        self,
 | 
			
		||||
        event_content,
 | 
			
		||||
        event_url,
 | 
			
		||||
        url_human=None,
 | 
			
		||||
        default_values=None,
 | 
			
		||||
        published=False,
 | 
			
		||||
    ):
 | 
			
		||||
        soup = BeautifulSoup(event_content, "html.parser")
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        image = soup.select("#imgspec img")
 | 
			
		||||
        if image:
 | 
			
		||||
            image = image[0]["src"]
 | 
			
		||||
@@ -65,5 +86,17 @@ class CExtractor(TwoStepsExtractor):
 | 
			
		||||
 | 
			
		||||
        url_human = event_url
 | 
			
		||||
 | 
			
		||||
        self.add_event_with_props(event_url, None, None, None, None, description, [], recurrences=None, uuids=[event_url], url_human=url_human, published=published, image=image)
 | 
			
		||||
 | 
			
		||||
        self.add_event_with_props(
 | 
			
		||||
            event_url,
 | 
			
		||||
            None,
 | 
			
		||||
            None,
 | 
			
		||||
            None,
 | 
			
		||||
            None,
 | 
			
		||||
            description,
 | 
			
		||||
            [],
 | 
			
		||||
            recurrences=None,
 | 
			
		||||
            uuids=[event_url],
 | 
			
		||||
            url_human=url_human,
 | 
			
		||||
            published=published,
 | 
			
		||||
            image=image,
 | 
			
		||||
        )
 | 
			
		||||
 
 | 
			
		||||
@@ -3,31 +3,39 @@ import re
 | 
			
		||||
import json5
 | 
			
		||||
from datetime import timedelta
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# A class dedicated to get events from La Coopérative de Mai:
 | 
			
		||||
# URL: https://www.lacoope.org/concerts-calendrier/
 | 
			
		||||
class CExtractor(TwoStepsExtractor):
 | 
			
		||||
 | 
			
		||||
    nom_lieu = "La Coopérative de Mai"
 | 
			
		||||
 | 
			
		||||
    def build_event_url_list(self, content):
 | 
			
		||||
        soup = BeautifulSoup(content, "html.parser")
 | 
			
		||||
        script = soup.find('div', class_="js-filter__results").findChildren('script')
 | 
			
		||||
        script = soup.find("div", class_="js-filter__results").findChildren("script")
 | 
			
		||||
        if len(script) == 0:
 | 
			
		||||
            raise Exception("Cannot find events in the first page")
 | 
			
		||||
        script = script[0]
 | 
			
		||||
        search = re.search(r"window.fullCalendarContent = (.*)</script>", str(script), re.S)
 | 
			
		||||
        search = re.search(
 | 
			
		||||
            r"window.fullCalendarContent = (.*)</script>", str(script), re.S
 | 
			
		||||
        )
 | 
			
		||||
        if search:
 | 
			
		||||
            data = json5.loads(search.group(1))
 | 
			
		||||
            for e in data['events']:
 | 
			
		||||
                self.add_event_url(e['url'])
 | 
			
		||||
                if e['tag'] == "Gratuit":
 | 
			
		||||
                    self.add_event_tag(e['url'], 'gratuit')
 | 
			
		||||
            for e in data["events"]:
 | 
			
		||||
                self.add_event_url(e["url"])
 | 
			
		||||
                if e["tag"] == "Gratuit":
 | 
			
		||||
                    self.add_event_tag(e["url"], "gratuit")
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            raise Exception('Cannot extract events from javascript')
 | 
			
		||||
            raise Exception("Cannot extract events from javascript")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def add_event_from_content(self, event_content, event_url, url_human = None, default_values = None, published = False):
 | 
			
		||||
    def add_event_from_content(
 | 
			
		||||
        self,
 | 
			
		||||
        event_content,
 | 
			
		||||
        event_url,
 | 
			
		||||
        url_human=None,
 | 
			
		||||
        default_values=None,
 | 
			
		||||
        published=False,
 | 
			
		||||
    ):
 | 
			
		||||
        soup = BeautifulSoup(event_content, "html.parser")
 | 
			
		||||
 | 
			
		||||
        title = soup.find("h1").contents[0]
 | 
			
		||||
@@ -38,9 +46,9 @@ class CExtractor(TwoStepsExtractor):
 | 
			
		||||
 | 
			
		||||
        description = soup.find("div", class_="grid-concert-content")
 | 
			
		||||
        if description:
 | 
			
		||||
            description = description.find('div', class_="content-striped")
 | 
			
		||||
            description = description.find("div", class_="content-striped")
 | 
			
		||||
            if description:
 | 
			
		||||
                description = description.find('div', class_='wysiwyg')
 | 
			
		||||
                description = description.find("div", class_="wysiwyg")
 | 
			
		||||
                if description:
 | 
			
		||||
                    description = description.get_text()
 | 
			
		||||
        if description is None:
 | 
			
		||||
@@ -50,8 +58,8 @@ class CExtractor(TwoStepsExtractor):
 | 
			
		||||
 | 
			
		||||
        link_calendar = soup.select('a[href^="https://calendar.google.com/calendar/"]')
 | 
			
		||||
        if len(link_calendar) == 0:
 | 
			
		||||
            raise Exception('Cannot find the google calendar url')
 | 
			
		||||
            
 | 
			
		||||
            raise Exception("Cannot find the google calendar url")
 | 
			
		||||
 | 
			
		||||
        gg_cal = GGCalendar(link_calendar[0]["href"])
 | 
			
		||||
        start_day = gg_cal.start_day
 | 
			
		||||
        start_time = gg_cal.start_time
 | 
			
		||||
@@ -60,5 +68,20 @@ class CExtractor(TwoStepsExtractor):
 | 
			
		||||
        location = CExtractor.nom_lieu
 | 
			
		||||
        url_human = event_url
 | 
			
		||||
 | 
			
		||||
        self.add_event_with_props(event_url, title, category, start_day, location, description, tags, recurrences=None, uuids=[event_url], url_human=url_human, start_time=start_time, end_day=end_day, end_time=end_time, published=published, image=image)
 | 
			
		||||
 | 
			
		||||
        self.add_event_with_props(
 | 
			
		||||
            event_url,
 | 
			
		||||
            title,
 | 
			
		||||
            category,
 | 
			
		||||
            start_day,
 | 
			
		||||
            location,
 | 
			
		||||
            description,
 | 
			
		||||
            tags,
 | 
			
		||||
            recurrences=None,
 | 
			
		||||
            uuids=[event_url],
 | 
			
		||||
            url_human=url_human,
 | 
			
		||||
            start_time=start_time,
 | 
			
		||||
            end_day=end_day,
 | 
			
		||||
            end_time=end_time,
 | 
			
		||||
            published=published,
 | 
			
		||||
            image=image,
 | 
			
		||||
        )
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,6 @@ from datetime import timedelta
 | 
			
		||||
# A class dedicated to get events from La puce à l'oreille
 | 
			
		||||
# URL: https://www.lapucealoreille63.fr/
 | 
			
		||||
class CExtractor(TwoStepsExtractor):
 | 
			
		||||
    
 | 
			
		||||
    nom_lieu = "La Puce à l'Oreille"
 | 
			
		||||
 | 
			
		||||
    def build_event_url_list(self, content):
 | 
			
		||||
@@ -24,11 +23,19 @@ class CExtractor(TwoStepsExtractor):
 | 
			
		||||
                        title = re.sub(" +", " ", title)
 | 
			
		||||
                        self.add_event_title(e_url["href"], title)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def add_event_from_content(self, event_content, event_url, url_human = None, default_values = None, published = False):
 | 
			
		||||
    def add_event_from_content(
 | 
			
		||||
        self,
 | 
			
		||||
        event_content,
 | 
			
		||||
        event_url,
 | 
			
		||||
        url_human=None,
 | 
			
		||||
        default_values=None,
 | 
			
		||||
        published=False,
 | 
			
		||||
    ):
 | 
			
		||||
        soup = BeautifulSoup(event_content, "html.parser")
 | 
			
		||||
 | 
			
		||||
        start_day = self.parse_french_date(soup.find("h2").get_text()) # pas parfait, mais bordel que ce site est mal construit
 | 
			
		||||
        start_day = self.parse_french_date(
 | 
			
		||||
            soup.find("h2").get_text()
 | 
			
		||||
        )  # pas parfait, mais bordel que ce site est mal construit
 | 
			
		||||
 | 
			
		||||
        spans = soup.select("div[data-testid=richTextElement] span")
 | 
			
		||||
        start_time = None
 | 
			
		||||
@@ -62,12 +69,30 @@ class CExtractor(TwoStepsExtractor):
 | 
			
		||||
            image = image[0]["src"]
 | 
			
		||||
        else:
 | 
			
		||||
            image = None
 | 
			
		||||
        
 | 
			
		||||
        descriptions = soup.select("div[data-testid=mesh-container-content] div[data-testid=inline-content] div[data-testid=mesh-container-content] div[data-testid=richTextElement]")
 | 
			
		||||
 | 
			
		||||
        descriptions = soup.select(
 | 
			
		||||
            "div[data-testid=mesh-container-content] div[data-testid=inline-content] div[data-testid=mesh-container-content] div[data-testid=richTextElement]"
 | 
			
		||||
        )
 | 
			
		||||
        if descriptions:
 | 
			
		||||
            descriptions = [d.get_text() for d in descriptions]
 | 
			
		||||
            description = max(descriptions, key=len)
 | 
			
		||||
        else:
 | 
			
		||||
            description = None
 | 
			
		||||
 | 
			
		||||
        self.add_event_with_props(event_url, None, "Concert", start_day, location, description, tags, recurrences=None, uuids=[event_url], url_human=url_human, start_time=start_time, end_day=end_day, end_time=end_time, published=published, image=image)
 | 
			
		||||
        self.add_event_with_props(
 | 
			
		||||
            event_url,
 | 
			
		||||
            None,
 | 
			
		||||
            "Concert",
 | 
			
		||||
            start_day,
 | 
			
		||||
            location,
 | 
			
		||||
            description,
 | 
			
		||||
            tags,
 | 
			
		||||
            recurrences=None,
 | 
			
		||||
            uuids=[event_url],
 | 
			
		||||
            url_human=url_human,
 | 
			
		||||
            start_time=start_time,
 | 
			
		||||
            end_day=end_day,
 | 
			
		||||
            end_time=end_time,
 | 
			
		||||
            published=published,
 | 
			
		||||
            image=image,
 | 
			
		||||
        )
 | 
			
		||||
 
 | 
			
		||||
@@ -7,19 +7,17 @@ from datetime import timedelta
 | 
			
		||||
# A class dedicated to get events from Le Fotomat'
 | 
			
		||||
# URL: https://www.lefotomat.com/
 | 
			
		||||
class CExtractor(TwoStepsExtractor):
 | 
			
		||||
    
 | 
			
		||||
    nom_lieu = "Le Fotomat'"
 | 
			
		||||
 | 
			
		||||
    def category_fotomat2agenda(self, category):
 | 
			
		||||
        if not category:
 | 
			
		||||
            return None
 | 
			
		||||
        mapping = { "Concerts": "Concert"}
 | 
			
		||||
        mapping = {"Concerts": "Concert"}
 | 
			
		||||
        if category in mapping:
 | 
			
		||||
            return mapping[category]
 | 
			
		||||
        else:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def build_event_url_list(self, content):
 | 
			
		||||
        soup = BeautifulSoup(content, "xml")
 | 
			
		||||
 | 
			
		||||
@@ -34,10 +32,15 @@ class CExtractor(TwoStepsExtractor):
 | 
			
		||||
            category = self.category_fotomat2agenda(e.find("category").contents[0])
 | 
			
		||||
            if category:
 | 
			
		||||
                self.add_event_category(e_url, category)
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def add_event_from_content(self, event_content, event_url, url_human = None, default_values = None, published = False):
 | 
			
		||||
    def add_event_from_content(
 | 
			
		||||
        self,
 | 
			
		||||
        event_content,
 | 
			
		||||
        event_url,
 | 
			
		||||
        url_human=None,
 | 
			
		||||
        default_values=None,
 | 
			
		||||
        published=False,
 | 
			
		||||
    ):
 | 
			
		||||
        soup = BeautifulSoup(event_content, "html.parser")
 | 
			
		||||
        image = soup.select("div.post-content img.wp-post-image")
 | 
			
		||||
        if image:
 | 
			
		||||
@@ -62,11 +65,26 @@ class CExtractor(TwoStepsExtractor):
 | 
			
		||||
        tags = []
 | 
			
		||||
        for c in article[0]["class"]:
 | 
			
		||||
            if c.startswith("category-"):
 | 
			
		||||
                tag = '-'.join(c.split("-")[1:])
 | 
			
		||||
                tag = "-".join(c.split("-")[1:])
 | 
			
		||||
                if tag != "concerts":
 | 
			
		||||
                    tags.append(tag)
 | 
			
		||||
 | 
			
		||||
        url_human = event_url
 | 
			
		||||
 | 
			
		||||
        self.add_event_with_props(event_url, None, None, start_day, location, description, tags, recurrences=None, uuids=[event_url], url_human=url_human, start_time=start_time, end_day=end_day, end_time=end_time, published=published, image=image)
 | 
			
		||||
 | 
			
		||||
        self.add_event_with_props(
 | 
			
		||||
            event_url,
 | 
			
		||||
            None,
 | 
			
		||||
            None,
 | 
			
		||||
            start_day,
 | 
			
		||||
            location,
 | 
			
		||||
            description,
 | 
			
		||||
            tags,
 | 
			
		||||
            recurrences=None,
 | 
			
		||||
            uuids=[event_url],
 | 
			
		||||
            url_human=url_human,
 | 
			
		||||
            start_time=start_time,
 | 
			
		||||
            end_day=end_day,
 | 
			
		||||
            end_time=end_time,
 | 
			
		||||
            published=published,
 | 
			
		||||
            image=image,
 | 
			
		||||
        )
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@ from abc import ABC, abstractmethod
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Downloader(ABC):
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
@@ -16,7 +15,7 @@ class Downloader(ABC):
 | 
			
		||||
    def download(self, url, post=None):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def get_content(self, url, cache = None, post = None):
 | 
			
		||||
    def get_content(self, url, cache=None, post=None):
 | 
			
		||||
        if cache and os.path.exists(cache):
 | 
			
		||||
            print("Loading cache ({})".format(cache))
 | 
			
		||||
            with open(cache) as f:
 | 
			
		||||
@@ -35,11 +34,9 @@ class Downloader(ABC):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SimpleDownloader(Downloader):
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def download(self, url, post=None):
 | 
			
		||||
        print("Downloading {}".format(url))
 | 
			
		||||
 | 
			
		||||
@@ -56,9 +53,7 @@ class SimpleDownloader(Downloader):
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ChromiumHeadlessDownloader(Downloader):
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
        self.options = Options()
 | 
			
		||||
@@ -67,10 +62,9 @@ class ChromiumHeadlessDownloader(Downloader):
 | 
			
		||||
        self.options.add_argument("--no-sandbox")
 | 
			
		||||
        self.service = Service("/usr/bin/chromedriver")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def download(self, url, post=None):
 | 
			
		||||
        if post:
 | 
			
		||||
            raise Exception('POST method with Chromium headless not yet implemented')
 | 
			
		||||
            raise Exception("POST method with Chromium headless not yet implemented")
 | 
			
		||||
        print("Download {}".format(url))
 | 
			
		||||
        self.driver = webdriver.Chrome(service=self.service, options=self.options)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,11 +6,11 @@ import unicodedata
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def remove_accents(input_str):
 | 
			
		||||
    nfkd_form = unicodedata.normalize('NFKD', input_str)
 | 
			
		||||
    return u"".join([c for c in nfkd_form if not unicodedata.combining(c)])
 | 
			
		||||
    nfkd_form = unicodedata.normalize("NFKD", input_str)
 | 
			
		||||
    return "".join([c for c in nfkd_form if not unicodedata.combining(c)])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Extractor(ABC):
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.header = {}
 | 
			
		||||
        self.events = []
 | 
			
		||||
@@ -26,7 +26,20 @@ class Extractor(ABC):
 | 
			
		||||
            return start_day
 | 
			
		||||
 | 
			
		||||
    def guess_month(self, text):
 | 
			
		||||
        mths = ["jan", "fe", "mar", "av", "mai", "juin", "juill", "ao", "sep", "oct", "nov", "dec"]
 | 
			
		||||
        mths = [
 | 
			
		||||
            "jan",
 | 
			
		||||
            "fe",
 | 
			
		||||
            "mar",
 | 
			
		||||
            "av",
 | 
			
		||||
            "mai",
 | 
			
		||||
            "juin",
 | 
			
		||||
            "juill",
 | 
			
		||||
            "ao",
 | 
			
		||||
            "sep",
 | 
			
		||||
            "oct",
 | 
			
		||||
            "nov",
 | 
			
		||||
            "dec",
 | 
			
		||||
        ]
 | 
			
		||||
        t = remove_accents(text).lower()
 | 
			
		||||
        for i, m in enumerate(mths):
 | 
			
		||||
            if t.startswith(m):
 | 
			
		||||
@@ -35,14 +48,16 @@ class Extractor(ABC):
 | 
			
		||||
 | 
			
		||||
    def parse_french_date(self, text):
 | 
			
		||||
        # format NomJour Numero Mois Année
 | 
			
		||||
        m = re.search('[a-zA-ZéÉûÛ:.]+[ ]*([0-9]+)[er]*[ ]*([a-zA-ZéÉûÛ:.]+)[ ]*([0-9]+)', text)
 | 
			
		||||
        m = re.search(
 | 
			
		||||
            "[a-zA-ZéÉûÛ:.]+[ ]*([0-9]+)[er]*[ ]*([a-zA-ZéÉûÛ:.]+)[ ]*([0-9]+)", text
 | 
			
		||||
        )
 | 
			
		||||
        if m:
 | 
			
		||||
            day = m.group(1)
 | 
			
		||||
            month = self.guess_month(m.group(2))
 | 
			
		||||
            year = m.group(3)
 | 
			
		||||
        else:
 | 
			
		||||
            # format Numero Mois Annee
 | 
			
		||||
            m = re.search('([0-9]+)[er]*[ ]*([a-zA-ZéÉûÛ:.]+)[ ]*([0-9]+)', text)
 | 
			
		||||
            m = re.search("([0-9]+)[er]*[ ]*([a-zA-ZéÉûÛ:.]+)[ ]*([0-9]+)", text)
 | 
			
		||||
            if m:
 | 
			
		||||
                day = m.group(1)
 | 
			
		||||
                month = self.guess_month(m.group(2))
 | 
			
		||||
@@ -50,7 +65,7 @@ class Extractor(ABC):
 | 
			
		||||
            else:
 | 
			
		||||
                # TODO: consolider les cas non satisfaits
 | 
			
		||||
                return None
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if month is None:
 | 
			
		||||
            return None
 | 
			
		||||
        try:
 | 
			
		||||
@@ -66,28 +81,28 @@ class Extractor(ABC):
 | 
			
		||||
 | 
			
		||||
    def parse_french_time(self, text):
 | 
			
		||||
        # format heures minutes secondes
 | 
			
		||||
        m = re.search('([0-9]+)[ a-zA-Z:.]+([0-9]+)[ a-zA-Z:.]+([0-9]+)', text)
 | 
			
		||||
        m = re.search("([0-9]+)[ a-zA-Z:.]+([0-9]+)[ a-zA-Z:.]+([0-9]+)", text)
 | 
			
		||||
        if m:
 | 
			
		||||
            h = m.group(1)
 | 
			
		||||
            m = m.group(2)
 | 
			
		||||
            s = m.group(3)
 | 
			
		||||
        else:
 | 
			
		||||
            # format heures minutes
 | 
			
		||||
            m = re.search('([0-9]+)[ hH:.]+([0-9]+)', text)
 | 
			
		||||
            m = re.search("([0-9]+)[ hH:.]+([0-9]+)", text)
 | 
			
		||||
            if m:
 | 
			
		||||
                h = m.group(1)
 | 
			
		||||
                m = m.group(2)
 | 
			
		||||
                s = "0"
 | 
			
		||||
            else:
 | 
			
		||||
                # format heures
 | 
			
		||||
                m = re.search('([0-9]+)[ Hh:.]', text)
 | 
			
		||||
                m = re.search("([0-9]+)[ Hh:.]", text)
 | 
			
		||||
                if m:
 | 
			
		||||
                    h = m.group(1)
 | 
			
		||||
                    m = "0"
 | 
			
		||||
                    s = "0"
 | 
			
		||||
                else:
 | 
			
		||||
                    return None
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            h = int(h)
 | 
			
		||||
            m = int(m)
 | 
			
		||||
@@ -98,10 +113,10 @@ class Extractor(ABC):
 | 
			
		||||
            return None
 | 
			
		||||
        return time(h, m, s)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def extract(self, content, url, url_human = None, default_values = None, published = False):
 | 
			
		||||
    def extract(
 | 
			
		||||
        self, content, url, url_human=None, default_values=None, published=False
 | 
			
		||||
    ):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def set_downloader(self, downloader):
 | 
			
		||||
@@ -118,7 +133,25 @@ class Extractor(ABC):
 | 
			
		||||
    def clear_events(self):
 | 
			
		||||
        self.events = []
 | 
			
		||||
 | 
			
		||||
    def add_event(self, title, category, start_day, location, description, tags, uuids, recurrences=None, url_human=None, start_time=None, end_day=None, end_time=None, last_modified=None, published=False, image=None, image_alt=None):
 | 
			
		||||
    def add_event(
 | 
			
		||||
        self,
 | 
			
		||||
        title,
 | 
			
		||||
        category,
 | 
			
		||||
        start_day,
 | 
			
		||||
        location,
 | 
			
		||||
        description,
 | 
			
		||||
        tags,
 | 
			
		||||
        uuids,
 | 
			
		||||
        recurrences=None,
 | 
			
		||||
        url_human=None,
 | 
			
		||||
        start_time=None,
 | 
			
		||||
        end_day=None,
 | 
			
		||||
        end_time=None,
 | 
			
		||||
        last_modified=None,
 | 
			
		||||
        published=False,
 | 
			
		||||
        image=None,
 | 
			
		||||
        image_alt=None,
 | 
			
		||||
    ):
 | 
			
		||||
        if title is None:
 | 
			
		||||
            print("ERROR: cannot import an event without name")
 | 
			
		||||
            return
 | 
			
		||||
@@ -136,7 +169,7 @@ class Extractor(ABC):
 | 
			
		||||
            "tags": tags,
 | 
			
		||||
            "published": published,
 | 
			
		||||
            "image": image,
 | 
			
		||||
            "image_alt": image_alt
 | 
			
		||||
            "image_alt": image_alt,
 | 
			
		||||
        }
 | 
			
		||||
        # TODO: pourquoi url_human et non reference_url
 | 
			
		||||
        if url_human is not None:
 | 
			
		||||
@@ -157,10 +190,14 @@ class Extractor(ABC):
 | 
			
		||||
        self.events.append(event)
 | 
			
		||||
 | 
			
		||||
    def default_value_if_exists(self, default_values, key):
 | 
			
		||||
        return default_values[key] if default_values is not None and key in default_values else None
 | 
			
		||||
        return (
 | 
			
		||||
            default_values[key]
 | 
			
		||||
            if default_values is not None and key in default_values
 | 
			
		||||
            else None
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def get_structure(self):
 | 
			
		||||
        return { "header": self.header, "events": self.events}
 | 
			
		||||
        return {"header": self.header, "events": self.events}
 | 
			
		||||
 | 
			
		||||
    def clean_url(url):
 | 
			
		||||
        from .extractor_ical import ICALExtractor
 | 
			
		||||
@@ -178,4 +215,4 @@ class Extractor(ABC):
 | 
			
		||||
        if single_event:
 | 
			
		||||
            return [FacebookEventExtractor(single_event=True)]
 | 
			
		||||
        else:
 | 
			
		||||
            return [ICALExtractor(), FacebookEventExtractor(single_event=False)]
 | 
			
		||||
            return [ICALExtractor(), FacebookEventExtractor(single_event=False)]
 | 
			
		||||
 
 | 
			
		||||
@@ -9,13 +9,12 @@ from .extractor import *
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FacebookEventExtractor(Extractor):
 | 
			
		||||
 | 
			
		||||
    class SimpleFacebookEvent:
 | 
			
		||||
 | 
			
		||||
        def __init__(self, data):
 | 
			
		||||
            self.elements = {}
 | 
			
		||||
 | 
			
		||||
@@ -23,31 +22,40 @@ class FacebookEventExtractor(Extractor):
 | 
			
		||||
                self.elements[key] = data[key] if key in data else None
 | 
			
		||||
 | 
			
		||||
            if "parent_event" in data:
 | 
			
		||||
                self.parent = FacebookEventExtractor.SimpleFacebookEvent(data["parent_event"])
 | 
			
		||||
 | 
			
		||||
                self.parent = FacebookEventExtractor.SimpleFacebookEvent(
 | 
			
		||||
                    data["parent_event"]
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
    class FacebookEvent:
 | 
			
		||||
 | 
			
		||||
        name = "event"
 | 
			
		||||
        keys = [
 | 
			
		||||
                ["start_time_formatted", 'start_timestamp', 
 | 
			
		||||
                'is_past', 
 | 
			
		||||
                "name", 
 | 
			
		||||
                "price_info", 
 | 
			
		||||
                "cover_media_renderer", 
 | 
			
		||||
                "event_creator", 
 | 
			
		||||
                "id", 
 | 
			
		||||
                "day_time_sentence", 
 | 
			
		||||
                "event_place", 
 | 
			
		||||
                "comet_neighboring_siblings"],
 | 
			
		||||
                ["event_description"],
 | 
			
		||||
                ["start_timestamp", "end_timestamp"]
 | 
			
		||||
            [
 | 
			
		||||
                "start_time_formatted",
 | 
			
		||||
                "start_timestamp",
 | 
			
		||||
                "is_past",
 | 
			
		||||
                "name",
 | 
			
		||||
                "price_info",
 | 
			
		||||
                "cover_media_renderer",
 | 
			
		||||
                "event_creator",
 | 
			
		||||
                "id",
 | 
			
		||||
                "day_time_sentence",
 | 
			
		||||
                "event_place",
 | 
			
		||||
                "comet_neighboring_siblings",
 | 
			
		||||
            ],
 | 
			
		||||
            ["event_description"],
 | 
			
		||||
            ["start_timestamp", "end_timestamp"],
 | 
			
		||||
        ]
 | 
			
		||||
        rules = {
 | 
			
		||||
            "event_description": { "description": ["text"]},
 | 
			
		||||
            "cover_media_renderer": {"image_alt": ["cover_photo", "photo", "accessibility_caption"], "image": ["cover_photo", "photo", "full_image", "uri"]},
 | 
			
		||||
            "event_creator": { "event_creator_name": ["name"], "event_creator_url": ["url"] },
 | 
			
		||||
            "event_place": {"event_place_name": ["name"] }
 | 
			
		||||
            "event_description": {"description": ["text"]},
 | 
			
		||||
            "cover_media_renderer": {
 | 
			
		||||
                "image_alt": ["cover_photo", "photo", "accessibility_caption"],
 | 
			
		||||
                "image": ["cover_photo", "photo", "full_image", "uri"],
 | 
			
		||||
            },
 | 
			
		||||
            "event_creator": {
 | 
			
		||||
                "event_creator_name": ["name"],
 | 
			
		||||
                "event_creator_url": ["url"],
 | 
			
		||||
            },
 | 
			
		||||
            "event_place": {"event_place_name": ["name"]},
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        def __init__(self, i, event):
 | 
			
		||||
@@ -60,26 +68,36 @@ class FacebookEventExtractor(Extractor):
 | 
			
		||||
        def get_element(self, key):
 | 
			
		||||
            return self.elements[key] if key in self.elements else None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        def get_element_date(self, key):
 | 
			
		||||
            v = self.get_element(key)
 | 
			
		||||
            return datetime.fromtimestamp(v).date() if v is not None and v != 0 else None
 | 
			
		||||
        
 | 
			
		||||
            return (
 | 
			
		||||
                datetime.fromtimestamp(v).date() if v is not None and v != 0 else None
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        def get_element_time(self, key):
 | 
			
		||||
            v = self.get_element(key)
 | 
			
		||||
            return datetime.fromtimestamp(v).strftime('%H:%M') if v is not None and v != 0 else None
 | 
			
		||||
            return (
 | 
			
		||||
                datetime.fromtimestamp(v).strftime("%H:%M")
 | 
			
		||||
                if v is not None and v != 0
 | 
			
		||||
                else None
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        def add_fragment(self, i, event):
 | 
			
		||||
            self.fragments[i] = event
 | 
			
		||||
 | 
			
		||||
            if FacebookEventExtractor.FacebookEvent.keys[i] == ["start_timestamp", "end_timestamp"]:
 | 
			
		||||
            if FacebookEventExtractor.FacebookEvent.keys[i] == [
 | 
			
		||||
                "start_timestamp",
 | 
			
		||||
                "end_timestamp",
 | 
			
		||||
            ]:
 | 
			
		||||
                self.get_possible_end_timestamp(i, event)
 | 
			
		||||
            else:
 | 
			
		||||
                for k in FacebookEventExtractor.FacebookEvent.keys[i]:
 | 
			
		||||
                    if k == "comet_neighboring_siblings":
 | 
			
		||||
                        self.get_neighbor_events(event[k])
 | 
			
		||||
                    elif k in FacebookEventExtractor.FacebookEvent.rules:
 | 
			
		||||
                        for nk, rule in FacebookEventExtractor.FacebookEvent.rules[k].items():
 | 
			
		||||
                        for nk, rule in FacebookEventExtractor.FacebookEvent.rules[
 | 
			
		||||
                            k
 | 
			
		||||
                        ].items():
 | 
			
		||||
                            error = False
 | 
			
		||||
                            c = event[k]
 | 
			
		||||
                            for ki in rule:
 | 
			
		||||
@@ -92,83 +110,101 @@ class FacebookEventExtractor(Extractor):
 | 
			
		||||
                    else:
 | 
			
		||||
                        self.elements[k] = event[k]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        def get_possible_end_timestamp(self, i, data):
 | 
			
		||||
            self.possible_end_timestamp.append(dict((k, data[k]) for k in FacebookEventExtractor.FacebookEvent.keys[i]))
 | 
			
		||||
 | 
			
		||||
            self.possible_end_timestamp.append(
 | 
			
		||||
                dict((k, data[k]) for k in FacebookEventExtractor.FacebookEvent.keys[i])
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        def get_neighbor_events(self, data):
 | 
			
		||||
            self.neighbor_events = [FacebookEventExtractor.SimpleFacebookEvent(d) for d in data]
 | 
			
		||||
            self.neighbor_events = [
 | 
			
		||||
                FacebookEventExtractor.SimpleFacebookEvent(d) for d in data
 | 
			
		||||
            ]
 | 
			
		||||
 | 
			
		||||
        def __str__(self):
 | 
			
		||||
            return str(self.elements) + "\n Neighbors: " + ", ".join([ne.elements["id"] for ne in self.neighbor_events])
 | 
			
		||||
            return (
 | 
			
		||||
                str(self.elements)
 | 
			
		||||
                + "\n Neighbors: "
 | 
			
		||||
                + ", ".join([ne.elements["id"] for ne in self.neighbor_events])
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        def consolidate_current_event(self):
 | 
			
		||||
            if self.neighbor_events is not None and "id" in self.elements and "end_timestamp" not in self.elements:
 | 
			
		||||
            if (
 | 
			
		||||
                self.neighbor_events is not None
 | 
			
		||||
                and "id" in self.elements
 | 
			
		||||
                and "end_timestamp" not in self.elements
 | 
			
		||||
            ):
 | 
			
		||||
                if self.neighbor_events is not None and "id" in self.elements:
 | 
			
		||||
                    id = self.elements["id"]
 | 
			
		||||
                    for ne in self.neighbor_events:
 | 
			
		||||
                        if ne.elements["id"] == id:
 | 
			
		||||
                            self.elements["end_timestamp"] = ne.elements["end_timestamp"]
 | 
			
		||||
                            self.elements["end_timestamp"] = ne.elements[
 | 
			
		||||
                                "end_timestamp"
 | 
			
		||||
                            ]
 | 
			
		||||
 | 
			
		||||
            if "end_timestamp" not in self.elements and len(self.possible_end_timestamp) != 0:
 | 
			
		||||
            if (
 | 
			
		||||
                "end_timestamp" not in self.elements
 | 
			
		||||
                and len(self.possible_end_timestamp) != 0
 | 
			
		||||
            ):
 | 
			
		||||
                for s in self.possible_end_timestamp:
 | 
			
		||||
                    if "start_timestamp" in s and "start_timestamp" in self.elements and s["start_timestamp"] == self.elements["start_timestamp"]:
 | 
			
		||||
                    if (
 | 
			
		||||
                        "start_timestamp" in s
 | 
			
		||||
                        and "start_timestamp" in self.elements
 | 
			
		||||
                        and s["start_timestamp"] == self.elements["start_timestamp"]
 | 
			
		||||
                    ):
 | 
			
		||||
                        self.elements["end_timestamp"] = s["end_timestamp"]
 | 
			
		||||
                        break
 | 
			
		||||
 | 
			
		||||
        def find_event_fragment_in_array(array, event, first = True):
 | 
			
		||||
        def find_event_fragment_in_array(array, event, first=True):
 | 
			
		||||
            if isinstance(array, dict):
 | 
			
		||||
 | 
			
		||||
                seen = False
 | 
			
		||||
                for i, ks in enumerate(FacebookEventExtractor.FacebookEvent.keys):
 | 
			
		||||
                    if len(ks) == len([k for k in ks if k in array]):
 | 
			
		||||
                        seen = True
 | 
			
		||||
                        if event is None:
 | 
			
		||||
                                event = FacebookEventExtractor.FacebookEvent(i, array)
 | 
			
		||||
                            event = FacebookEventExtractor.FacebookEvent(i, array)
 | 
			
		||||
                        else:
 | 
			
		||||
                            event.add_fragment(i, array)
 | 
			
		||||
                        # only consider the first of FacebookEvent.keys
 | 
			
		||||
                        break
 | 
			
		||||
                if not seen:
 | 
			
		||||
                    for k in array:
 | 
			
		||||
                        event = FacebookEventExtractor.FacebookEvent.find_event_fragment_in_array(array[k], event, False)
 | 
			
		||||
                        event = FacebookEventExtractor.FacebookEvent.find_event_fragment_in_array(
 | 
			
		||||
                            array[k], event, False
 | 
			
		||||
                        )
 | 
			
		||||
            elif isinstance(array, list):
 | 
			
		||||
                for e in array:
 | 
			
		||||
                    event = FacebookEventExtractor.FacebookEvent.find_event_fragment_in_array(e, event, False)
 | 
			
		||||
                    event = FacebookEventExtractor.FacebookEvent.find_event_fragment_in_array(
 | 
			
		||||
                        e, event, False
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
            if event is not None and first:
 | 
			
		||||
                event.consolidate_current_event()
 | 
			
		||||
            return event
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        def build_event(self, url):
 | 
			
		||||
            image = self.get_element("image")
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                "title": self.get_element("name"), 
 | 
			
		||||
                "category": None, 
 | 
			
		||||
                "start_day": self.get_element_date("start_timestamp"), 
 | 
			
		||||
                "location": self.get_element("event_place_name"), 
 | 
			
		||||
                "description": self.get_element("description"), 
 | 
			
		||||
                "tags": [], 
 | 
			
		||||
                "title": self.get_element("name"),
 | 
			
		||||
                "category": None,
 | 
			
		||||
                "start_day": self.get_element_date("start_timestamp"),
 | 
			
		||||
                "location": self.get_element("event_place_name"),
 | 
			
		||||
                "description": self.get_element("description"),
 | 
			
		||||
                "tags": [],
 | 
			
		||||
                "uuids": [url],
 | 
			
		||||
                "url_human": url,
 | 
			
		||||
                "start_time": self.get_element_time("start_timestamp"), 
 | 
			
		||||
                "end_day": self.get_element_date("end_timestamp"), 
 | 
			
		||||
                "end_time": self.get_element_time("end_timestamp"), 
 | 
			
		||||
                "start_time": self.get_element_time("start_timestamp"),
 | 
			
		||||
                "end_day": self.get_element_date("end_timestamp"),
 | 
			
		||||
                "end_time": self.get_element_time("end_timestamp"),
 | 
			
		||||
                "image": self.get_element("image"),
 | 
			
		||||
                "image_alt": self.get_element("image"),
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def __init__(self, single_event=False):
 | 
			
		||||
        self.single_event = single_event
 | 
			
		||||
        super().__init__()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def clean_url(url):
 | 
			
		||||
 | 
			
		||||
        if FacebookEventExtractor.is_known_url(url):
 | 
			
		||||
            u = urlparse(url)
 | 
			
		||||
            return "https://www.facebook.com" + u.path
 | 
			
		||||
@@ -179,17 +215,20 @@ class FacebookEventExtractor(Extractor):
 | 
			
		||||
        u = urlparse(url)
 | 
			
		||||
        return u.netloc in ["facebook.com", "www.facebook.com", "m.facebook.com"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def extract(self, content, url, url_human = None, default_values = None, published = False):
 | 
			
		||||
    def extract(
 | 
			
		||||
        self, content, url, url_human=None, default_values=None, published=False
 | 
			
		||||
    ):
 | 
			
		||||
        # NOTE: this method does not use url_human = None and default_values = None
 | 
			
		||||
 | 
			
		||||
        # get step by step all information from the content
 | 
			
		||||
        fevent = None
 | 
			
		||||
        soup = BeautifulSoup(content, "html.parser")
 | 
			
		||||
        for json_script in soup.find_all('script', type="application/json"):
 | 
			
		||||
        for json_script in soup.find_all("script", type="application/json"):
 | 
			
		||||
            json_txt = json_script.get_text()
 | 
			
		||||
            json_struct = json.loads(json_txt)
 | 
			
		||||
            fevent = FacebookEventExtractor.FacebookEvent.find_event_fragment_in_array(json_struct, fevent)
 | 
			
		||||
            fevent = FacebookEventExtractor.FacebookEvent.find_event_fragment_in_array(
 | 
			
		||||
                json_struct, fevent
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        if fevent is not None:
 | 
			
		||||
            self.set_header(url)
 | 
			
		||||
@@ -198,5 +237,5 @@ class FacebookEventExtractor(Extractor):
 | 
			
		||||
            event["published"] = published
 | 
			
		||||
            self.add_event(**event)
 | 
			
		||||
            return self.get_structure()
 | 
			
		||||
            
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
        return None
 | 
			
		||||
 
 | 
			
		||||
@@ -14,13 +14,11 @@ from celery.utils.log import get_task_logger
 | 
			
		||||
logger = get_task_logger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ICALExtractor(Extractor):
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
 | 
			
		||||
    def get_item_from_vevent(self, event, name, raw = False):
 | 
			
		||||
    def get_item_from_vevent(self, event, name, raw=False):
 | 
			
		||||
        try:
 | 
			
		||||
            r = event.decoded(name)
 | 
			
		||||
            if raw:
 | 
			
		||||
@@ -31,7 +29,7 @@ class ICALExtractor(Extractor):
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
    def get_dt_item_from_vevent(self, event, name):
 | 
			
		||||
        item = self.get_item_from_vevent(event, name, raw = True)
 | 
			
		||||
        item = self.get_item_from_vevent(event, name, raw=True)
 | 
			
		||||
 | 
			
		||||
        day = None
 | 
			
		||||
        time = None
 | 
			
		||||
@@ -49,25 +47,26 @@ class ICALExtractor(Extractor):
 | 
			
		||||
    def clean_url(url):
 | 
			
		||||
        return url
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def extract(self, content, url, url_human = None, default_values = None, published = False):
 | 
			
		||||
    def extract(
 | 
			
		||||
        self, content, url, url_human=None, default_values=None, published=False
 | 
			
		||||
    ):
 | 
			
		||||
        warnings.filterwarnings("ignore", category=MarkupResemblesLocatorWarning)
 | 
			
		||||
 | 
			
		||||
        print("Extracting ical events from {}".format(url))
 | 
			
		||||
        self.set_header(url)
 | 
			
		||||
        self.clear_events()
 | 
			
		||||
        self.uuids = {}
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        calendar = icalendar.Calendar.from_ical(content)
 | 
			
		||||
 | 
			
		||||
        for event in calendar.walk('VEVENT'):
 | 
			
		||||
        for event in calendar.walk("VEVENT"):
 | 
			
		||||
            title = self.get_item_from_vevent(event, "SUMMARY")
 | 
			
		||||
            category = self.default_value_if_exists(default_values, "category")
 | 
			
		||||
 | 
			
		||||
            start_day, start_time = self.get_dt_item_from_vevent(event, "DTSTART")
 | 
			
		||||
 | 
			
		||||
            end_day, end_time = self.get_dt_item_from_vevent(event, "DTEND")
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            # if the start and end are only defined by dates (and not times),
 | 
			
		||||
            # then the event does not occurs on the last day (because it is the end
 | 
			
		||||
            # of the event...)
 | 
			
		||||
@@ -81,9 +80,9 @@ class ICALExtractor(Extractor):
 | 
			
		||||
            description = self.get_item_from_vevent(event, "DESCRIPTION")
 | 
			
		||||
            if description is not None:
 | 
			
		||||
                soup = BeautifulSoup(description, features="lxml")
 | 
			
		||||
                delimiter = '\n'
 | 
			
		||||
                for line_break in soup.findAll('br'):
 | 
			
		||||
                    line_break.replaceWith(delimiter)  
 | 
			
		||||
                delimiter = "\n"
 | 
			
		||||
                for line_break in soup.findAll("br"):
 | 
			
		||||
                    line_break.replaceWith(delimiter)
 | 
			
		||||
                description = soup.get_text()
 | 
			
		||||
 | 
			
		||||
            last_modified = self.get_item_from_vevent(event, "LAST_MODIFIED")
 | 
			
		||||
@@ -103,17 +102,21 @@ class ICALExtractor(Extractor):
 | 
			
		||||
            if related_to is not None:
 | 
			
		||||
                if related_to in self.uuids:
 | 
			
		||||
                    self.uuids[related_to] += 1
 | 
			
		||||
                    uuidrel = url + "#" + related_to + ":{:04}".format(self.uuids[related_to] - 1)
 | 
			
		||||
                    uuidrel = (
 | 
			
		||||
                        url
 | 
			
		||||
                        + "#"
 | 
			
		||||
                        + related_to
 | 
			
		||||
                        + ":{:04}".format(self.uuids[related_to] - 1)
 | 
			
		||||
                    )
 | 
			
		||||
                    # possible limitation: if the ordering is not original then related
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            tags = self.default_value_if_exists(default_values, "tags")
 | 
			
		||||
 | 
			
		||||
            last_modified = self.get_item_from_vevent(event, "LAST-MODIFIED", raw = True)
 | 
			
		||||
            last_modified = self.get_item_from_vevent(event, "LAST-MODIFIED", raw=True)
 | 
			
		||||
 | 
			
		||||
            recurrence_entries = {}
 | 
			
		||||
            for e in ["RRULE", "EXRULE", "EXDATE", "RDATE"]:
 | 
			
		||||
                i = self.get_item_from_vevent(event, e, raw = True)
 | 
			
		||||
                i = self.get_item_from_vevent(event, e, raw=True)
 | 
			
		||||
                if i is not None:
 | 
			
		||||
                    recurrence_entries[e] = i
 | 
			
		||||
 | 
			
		||||
@@ -122,7 +125,10 @@ class ICALExtractor(Extractor):
 | 
			
		||||
 | 
			
		||||
                for k, r in recurrence_entries.items():
 | 
			
		||||
                    if isinstance(r, list):
 | 
			
		||||
                        recurrences += "\n".join([k + ":" + e.to_ical().decode() for e in r]) + "\n"
 | 
			
		||||
                        recurrences += (
 | 
			
		||||
                            "\n".join([k + ":" + e.to_ical().decode() for e in r])
 | 
			
		||||
                            + "\n"
 | 
			
		||||
                        )
 | 
			
		||||
                    else:
 | 
			
		||||
                        recurrences += k + ":" + r.to_ical().decode() + "\n"
 | 
			
		||||
            else:
 | 
			
		||||
@@ -132,25 +138,74 @@ class ICALExtractor(Extractor):
 | 
			
		||||
                luuids = [event_url]
 | 
			
		||||
                if uuidrel is not None:
 | 
			
		||||
                    luuids += [uuidrel]
 | 
			
		||||
                self.add_event(title, category, start_day, location, description, tags, recurrences=recurrences, uuids=luuids, url_human=url_human, start_time=start_time, end_day=end_day, end_time=end_time, last_modified=last_modified, published=published)
 | 
			
		||||
 | 
			
		||||
                self.add_event(
 | 
			
		||||
                    title,
 | 
			
		||||
                    category,
 | 
			
		||||
                    start_day,
 | 
			
		||||
                    location,
 | 
			
		||||
                    description,
 | 
			
		||||
                    tags,
 | 
			
		||||
                    recurrences=recurrences,
 | 
			
		||||
                    uuids=luuids,
 | 
			
		||||
                    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()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# A variation on ICAL extractor that removes any even named "Busy"
 | 
			
		||||
class ICALNoBusyExtractor(ICALExtractor):
 | 
			
		||||
 | 
			
		||||
    def add_event(self, title, category, start_day, location, description, tags, uuids, recurrences=None, url_human=None, start_time=None, end_day=None, end_time=None, last_modified=None, published=False, image=None, image_alt=None):
 | 
			
		||||
        if title != 'Busy':
 | 
			
		||||
            super().add_event(title, category, start_day, location, description, tags, uuids, recurrences, url_human, start_time, end_day, end_time, last_modified, published, image, image_alt)
 | 
			
		||||
    def add_event(
 | 
			
		||||
        self,
 | 
			
		||||
        title,
 | 
			
		||||
        category,
 | 
			
		||||
        start_day,
 | 
			
		||||
        location,
 | 
			
		||||
        description,
 | 
			
		||||
        tags,
 | 
			
		||||
        uuids,
 | 
			
		||||
        recurrences=None,
 | 
			
		||||
        url_human=None,
 | 
			
		||||
        start_time=None,
 | 
			
		||||
        end_day=None,
 | 
			
		||||
        end_time=None,
 | 
			
		||||
        last_modified=None,
 | 
			
		||||
        published=False,
 | 
			
		||||
        image=None,
 | 
			
		||||
        image_alt=None,
 | 
			
		||||
    ):
 | 
			
		||||
        if title != "Busy":
 | 
			
		||||
            super().add_event(
 | 
			
		||||
                title,
 | 
			
		||||
                category,
 | 
			
		||||
                start_day,
 | 
			
		||||
                location,
 | 
			
		||||
                description,
 | 
			
		||||
                tags,
 | 
			
		||||
                uuids,
 | 
			
		||||
                recurrences,
 | 
			
		||||
                url_human,
 | 
			
		||||
                start_time,
 | 
			
		||||
                end_day,
 | 
			
		||||
                end_time,
 | 
			
		||||
                last_modified,
 | 
			
		||||
                published,
 | 
			
		||||
                image,
 | 
			
		||||
                image_alt,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# A variation on ICAL extractor that remove any visual composer anchors
 | 
			
		||||
class ICALNoVCExtractor(ICALExtractor):
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.parser = bbcode.Parser(newline="\n", drop_unrecognized=True, install_defaults=False)
 | 
			
		||||
        self.parser = bbcode.Parser(
 | 
			
		||||
            newline="\n", drop_unrecognized=True, install_defaults=False
 | 
			
		||||
        )
 | 
			
		||||
        self.parser.add_simple_formatter("vc_row", "%(value)s")
 | 
			
		||||
        self.parser.add_simple_formatter("vc_column", "%(value)s")
 | 
			
		||||
        self.parser.add_simple_formatter("vc_column_text", "%(value)s")
 | 
			
		||||
@@ -164,5 +219,40 @@ class ICALNoVCExtractor(ICALExtractor):
 | 
			
		||||
            result = self.parser.format(text)
 | 
			
		||||
            return result
 | 
			
		||||
 | 
			
		||||
    def add_event(self, title, category, start_day, location, description, tags, uuids, recurrences=None, url_human=None, start_time=None, end_day=None, end_time=None, last_modified=None, published=False, image=None, image_alt=None):
 | 
			
		||||
        super().add_event(title, category, start_day, location, self.clean_vc(description), tags, uuids, recurrences, url_human, start_time, end_day, end_time, last_modified, published, image, image_alt)
 | 
			
		||||
    def add_event(
 | 
			
		||||
        self,
 | 
			
		||||
        title,
 | 
			
		||||
        category,
 | 
			
		||||
        start_day,
 | 
			
		||||
        location,
 | 
			
		||||
        description,
 | 
			
		||||
        tags,
 | 
			
		||||
        uuids,
 | 
			
		||||
        recurrences=None,
 | 
			
		||||
        url_human=None,
 | 
			
		||||
        start_time=None,
 | 
			
		||||
        end_day=None,
 | 
			
		||||
        end_time=None,
 | 
			
		||||
        last_modified=None,
 | 
			
		||||
        published=False,
 | 
			
		||||
        image=None,
 | 
			
		||||
        image_alt=None,
 | 
			
		||||
    ):
 | 
			
		||||
        super().add_event(
 | 
			
		||||
            title,
 | 
			
		||||
            category,
 | 
			
		||||
            start_day,
 | 
			
		||||
            location,
 | 
			
		||||
            self.clean_vc(description),
 | 
			
		||||
            tags,
 | 
			
		||||
            uuids,
 | 
			
		||||
            recurrences,
 | 
			
		||||
            url_human,
 | 
			
		||||
            start_time,
 | 
			
		||||
            end_day,
 | 
			
		||||
            end_time,
 | 
			
		||||
            last_modified,
 | 
			
		||||
            published,
 | 
			
		||||
            image,
 | 
			
		||||
            image_alt,
 | 
			
		||||
        )
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,8 @@ from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from dateutil import parser
 | 
			
		||||
import datetime
 | 
			
		||||
 | 
			
		||||
class GGCalendar:
 | 
			
		||||
 | 
			
		||||
class GGCalendar:
 | 
			
		||||
    def __init__(self, url):
 | 
			
		||||
        self.url = url
 | 
			
		||||
        self.extract_info()
 | 
			
		||||
@@ -18,10 +18,10 @@ class GGCalendar:
 | 
			
		||||
        parsed_url = urlparse(self.url.replace("#", "%23"))
 | 
			
		||||
        params = parse_qs(parsed_url.query)
 | 
			
		||||
 | 
			
		||||
        self.location = params['location'][0] if 'location' in params else None
 | 
			
		||||
        self.title = params['text'][0] if 'text' in params else None
 | 
			
		||||
        if 'dates' in params:
 | 
			
		||||
            dates = [x.replace(" ", "+") for x in params['dates'][0].split("/")]
 | 
			
		||||
        self.location = params["location"][0] if "location" in params else None
 | 
			
		||||
        self.title = params["text"][0] if "text" in params else None
 | 
			
		||||
        if "dates" in params:
 | 
			
		||||
            dates = [x.replace(" ", "+") for x in params["dates"][0].split("/")]
 | 
			
		||||
            if len(dates) > 0:
 | 
			
		||||
                date = parser.parse(dates[0])
 | 
			
		||||
                self.start_day = date.date()
 | 
			
		||||
@@ -42,13 +42,11 @@ class GGCalendar:
 | 
			
		||||
            self.end_time = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# A class to extract events from URL with two steps:
 | 
			
		||||
# - first build a list of urls where the events will be found
 | 
			
		||||
# - then for each document downloaded from these urls, build the events
 | 
			
		||||
# This class is an abstract class
 | 
			
		||||
class TwoStepsExtractor(Extractor):
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
        self.event_urls = None
 | 
			
		||||
@@ -96,35 +94,83 @@ class TwoStepsExtractor(Extractor):
 | 
			
		||||
            self.event_properties[url] = {}
 | 
			
		||||
        self.event_properties[url]["location"] = loc
 | 
			
		||||
 | 
			
		||||
    def add_event_with_props(self, event_url, title, category, start_day, location, description, tags, uuids, recurrences=None, url_human=None, start_time=None, end_day=None, end_time=None, last_modified=None, published=False, image=None, image_alt=None):
 | 
			
		||||
 | 
			
		||||
    def add_event_with_props(
 | 
			
		||||
        self,
 | 
			
		||||
        event_url,
 | 
			
		||||
        title,
 | 
			
		||||
        category,
 | 
			
		||||
        start_day,
 | 
			
		||||
        location,
 | 
			
		||||
        description,
 | 
			
		||||
        tags,
 | 
			
		||||
        uuids,
 | 
			
		||||
        recurrences=None,
 | 
			
		||||
        url_human=None,
 | 
			
		||||
        start_time=None,
 | 
			
		||||
        end_day=None,
 | 
			
		||||
        end_time=None,
 | 
			
		||||
        last_modified=None,
 | 
			
		||||
        published=False,
 | 
			
		||||
        image=None,
 | 
			
		||||
        image_alt=None,
 | 
			
		||||
    ):
 | 
			
		||||
        if event_url in self.event_properties:
 | 
			
		||||
            if 'tags' in self.event_properties[event_url]:
 | 
			
		||||
                tags = tags + self.event_properties[event_url]['tags']
 | 
			
		||||
            if 'start_day' in self.event_properties[event_url]:
 | 
			
		||||
                start_day = self.event_properties[event_url]['start_day']
 | 
			
		||||
            if 'start_time' in self.event_properties[event_url]:
 | 
			
		||||
                start_time = self.event_properties[event_url]['start_time']
 | 
			
		||||
            if 'title' in self.event_properties[event_url]:
 | 
			
		||||
                title = self.event_properties[event_url]['title']
 | 
			
		||||
            if 'category' in self.event_properties[event_url]:
 | 
			
		||||
                category = self.event_properties[event_url]['category']
 | 
			
		||||
            if 'location' in self.event_properties[event_url]:
 | 
			
		||||
                location = self.event_properties[event_url]['location']
 | 
			
		||||
 | 
			
		||||
        self.add_event(title, category, start_day, location, description, tags, uuids, recurrences, url_human, start_time, end_day, end_time, last_modified, published, image, image_alt)
 | 
			
		||||
            if "tags" in self.event_properties[event_url]:
 | 
			
		||||
                tags = tags + self.event_properties[event_url]["tags"]
 | 
			
		||||
            if "start_day" in self.event_properties[event_url]:
 | 
			
		||||
                start_day = self.event_properties[event_url]["start_day"]
 | 
			
		||||
            if "start_time" in self.event_properties[event_url]:
 | 
			
		||||
                start_time = self.event_properties[event_url]["start_time"]
 | 
			
		||||
            if "title" in self.event_properties[event_url]:
 | 
			
		||||
                title = self.event_properties[event_url]["title"]
 | 
			
		||||
            if "category" in self.event_properties[event_url]:
 | 
			
		||||
                category = self.event_properties[event_url]["category"]
 | 
			
		||||
            if "location" in self.event_properties[event_url]:
 | 
			
		||||
                location = self.event_properties[event_url]["location"]
 | 
			
		||||
 | 
			
		||||
        self.add_event(
 | 
			
		||||
            title,
 | 
			
		||||
            category,
 | 
			
		||||
            start_day,
 | 
			
		||||
            location,
 | 
			
		||||
            description,
 | 
			
		||||
            tags,
 | 
			
		||||
            uuids,
 | 
			
		||||
            recurrences,
 | 
			
		||||
            url_human,
 | 
			
		||||
            start_time,
 | 
			
		||||
            end_day,
 | 
			
		||||
            end_time,
 | 
			
		||||
            last_modified,
 | 
			
		||||
            published,
 | 
			
		||||
            image,
 | 
			
		||||
            image_alt,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def build_event_url_list(self, content):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    @abstractmethod
 | 
			
		||||
    def add_event_from_content(self, event_content, event_url, url_human = None, default_values = None, published = False):
 | 
			
		||||
    def add_event_from_content(
 | 
			
		||||
        self,
 | 
			
		||||
        event_content,
 | 
			
		||||
        event_url,
 | 
			
		||||
        url_human=None,
 | 
			
		||||
        default_values=None,
 | 
			
		||||
        published=False,
 | 
			
		||||
    ):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def extract(self, content, url, url_human = None, default_values = None, published = False, only_future=True):
 | 
			
		||||
    def extract(
 | 
			
		||||
        self,
 | 
			
		||||
        content,
 | 
			
		||||
        url,
 | 
			
		||||
        url_human=None,
 | 
			
		||||
        default_values=None,
 | 
			
		||||
        published=False,
 | 
			
		||||
        only_future=True,
 | 
			
		||||
    ):
 | 
			
		||||
        self.only_future = only_future
 | 
			
		||||
        self.now = datetime.datetime.now().date()
 | 
			
		||||
        self.set_header(url)
 | 
			
		||||
@@ -133,24 +179,25 @@ class TwoStepsExtractor(Extractor):
 | 
			
		||||
        self.url = url
 | 
			
		||||
        self.event_urls = []
 | 
			
		||||
        self.event_properties.clear()
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
        # first build the event list
 | 
			
		||||
        self.build_event_url_list(content)
 | 
			
		||||
 | 
			
		||||
        if self.event_urls is None:
 | 
			
		||||
            raise Exception('Unable to find the event list from the main document')
 | 
			
		||||
            raise Exception("Unable to find the event list from the main document")
 | 
			
		||||
 | 
			
		||||
        if self.downloader is None:
 | 
			
		||||
            raise Exception('The downloader is not defined')
 | 
			
		||||
            raise Exception("The downloader is not defined")
 | 
			
		||||
 | 
			
		||||
        # then process each element of the list
 | 
			
		||||
        for i, event_url in enumerate(self.event_urls):
 | 
			
		||||
            # first download the content associated with this link
 | 
			
		||||
            content_event = self.downloader.get_content(event_url)
 | 
			
		||||
            if content_event is None:
 | 
			
		||||
                raise Exception(_('Cannot extract event from url {}').format(event_url))
 | 
			
		||||
                raise Exception(_("Cannot extract event from url {}").format(event_url))
 | 
			
		||||
            # then extract event information from this html document
 | 
			
		||||
            self.add_event_from_content(content_event, event_url, url_human, default_values, published)
 | 
			
		||||
            
 | 
			
		||||
        return self.get_structure()
 | 
			
		||||
            self.add_event_from_content(
 | 
			
		||||
                content_event, event_url, url_human, default_values, published
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        return self.get_structure()
 | 
			
		||||
 
 | 
			
		||||
@@ -5,15 +5,16 @@ from .extractor import *
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class URL2Events:
 | 
			
		||||
 | 
			
		||||
    def __init__(self, downloader = SimpleDownloader(), extractor = None, single_event=False):
 | 
			
		||||
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self, downloader=SimpleDownloader(), extractor=None, single_event=False
 | 
			
		||||
    ):
 | 
			
		||||
        self.downloader = downloader
 | 
			
		||||
        self.extractor = extractor
 | 
			
		||||
        self.single_event = single_event
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def process(self, url, url_human = None, cache = None, default_values = None, published = False):
 | 
			
		||||
    def process(
 | 
			
		||||
        self, url, url_human=None, cache=None, default_values=None, published=False
 | 
			
		||||
    ):
 | 
			
		||||
        content = self.downloader.get_content(url, cache)
 | 
			
		||||
 | 
			
		||||
        if content is None:
 | 
			
		||||
@@ -21,13 +22,14 @@ class URL2Events:
 | 
			
		||||
 | 
			
		||||
        if self.extractor is not None:
 | 
			
		||||
            self.extractor.set_downloader(self.downloader)
 | 
			
		||||
            return self.extractor.extract(content, url, url_human, default_values, published)
 | 
			
		||||
            return self.extractor.extract(
 | 
			
		||||
                content, url, url_human, default_values, published
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            # if the extractor is not defined, use a list of default extractors
 | 
			
		||||
            for e in Extractor.get_default_extractors(self.single_event):
 | 
			
		||||
                    e.set_downloader(self.downloader)
 | 
			
		||||
                    events = e.extract(content, url, url_human, default_values, published)
 | 
			
		||||
                    if events is not None:
 | 
			
		||||
                        return events
 | 
			
		||||
                e.set_downloader(self.downloader)
 | 
			
		||||
                events = e.extract(content, url, url_human, default_values, published)
 | 
			
		||||
                if events is not None:
 | 
			
		||||
                    return events
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,4 +1,4 @@
 | 
			
		||||
from os import getenv as os_getenv, path as os_path # noqa
 | 
			
		||||
from os import getenv as os_getenv, path as os_path  # noqa
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
@@ -18,7 +18,7 @@ ALLOWED_HOSTS = os_getenv("ALLOWED_HOSTS", "localhost").split(",")
 | 
			
		||||
 | 
			
		||||
if DEBUG:
 | 
			
		||||
    CSRF_TRUSTED_ORIGINS = os_getenv("CSRF_TRUSTED_ORIGINS", "http://localhost").split(
 | 
			
		||||
            ","
 | 
			
		||||
        ","
 | 
			
		||||
    )
 | 
			
		||||
    CORS_ORIGIN_ALLOW_ALL = True
 | 
			
		||||
else:
 | 
			
		||||
@@ -41,21 +41,21 @@ INSTALLED_APPS = [
 | 
			
		||||
    "corsheaders",
 | 
			
		||||
    "agenda_culturel",
 | 
			
		||||
    "colorfield",
 | 
			
		||||
    'django_extensions',
 | 
			
		||||
    'django_better_admin_arrayfield',
 | 
			
		||||
    'django_filters',
 | 
			
		||||
    'compressor',
 | 
			
		||||
    'ckeditor',
 | 
			
		||||
    'recurrence',
 | 
			
		||||
    'location_field.apps.DefaultConfig',
 | 
			
		||||
    'django.contrib.postgres',
 | 
			
		||||
    "django_extensions",
 | 
			
		||||
    "django_better_admin_arrayfield",
 | 
			
		||||
    "django_filters",
 | 
			
		||||
    "compressor",
 | 
			
		||||
    "ckeditor",
 | 
			
		||||
    "recurrence",
 | 
			
		||||
    "location_field.apps.DefaultConfig",
 | 
			
		||||
    "django.contrib.postgres",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
MIDDLEWARE = [
 | 
			
		||||
    "django.middleware.security.SecurityMiddleware",
 | 
			
		||||
    "whitenoise.middleware.WhiteNoiseMiddleware",
 | 
			
		||||
    "django.contrib.sessions.middleware.SessionMiddleware",
 | 
			
		||||
    'django.middleware.locale.LocaleMiddleware',
 | 
			
		||||
    "django.middleware.locale.LocaleMiddleware",
 | 
			
		||||
    "corsheaders.middleware.CorsMiddleware",  # CorsMiddleware should be placed as high as possible,
 | 
			
		||||
    "django.middleware.common.CommonMiddleware",
 | 
			
		||||
    "django.middleware.csrf.CsrfViewMiddleware",
 | 
			
		||||
@@ -65,10 +65,10 @@ MIDDLEWARE = [
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
CKEDITOR_CONFIGS = {
 | 
			
		||||
    'default': {
 | 
			
		||||
        'toolbar': 'full',
 | 
			
		||||
        'removePlugins': 'stylesheetparser',
 | 
			
		||||
        'allowedContent': True,
 | 
			
		||||
    "default": {
 | 
			
		||||
        "toolbar": "full",
 | 
			
		||||
        "removePlugins": "stylesheetparser",
 | 
			
		||||
        "allowedContent": True,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -136,8 +136,8 @@ USE_I18N = True
 | 
			
		||||
USE_TZ = True
 | 
			
		||||
 | 
			
		||||
LANGUAGES = (
 | 
			
		||||
    ('en-us', _('English')),
 | 
			
		||||
    ('fr', _('French')),
 | 
			
		||||
    ("en-us", _("English")),
 | 
			
		||||
    ("fr", _("French")),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -154,9 +154,9 @@ MEDIA_URL = "media/"
 | 
			
		||||
MEDIA_ROOT = os_path.join(BASE_DIR, "media")
 | 
			
		||||
 | 
			
		||||
STATICFILES_FINDERS = [
 | 
			
		||||
    'django.contrib.staticfiles.finders.FileSystemFinder',
 | 
			
		||||
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
 | 
			
		||||
    'compressor.finders.CompressorFinder'
 | 
			
		||||
    "django.contrib.staticfiles.finders.FileSystemFinder",
 | 
			
		||||
    "django.contrib.staticfiles.finders.AppDirectoriesFinder",
 | 
			
		||||
    "compressor.finders.CompressorFinder",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
# Default primary key field type
 | 
			
		||||
@@ -185,9 +185,7 @@ CELERY_BROKER_URL = REDIS_URL
 | 
			
		||||
CELERY_RESULT_BACKEND = REDIS_URL
 | 
			
		||||
 | 
			
		||||
# SCSS
 | 
			
		||||
COMPRESS_PRECOMPILERS = (
 | 
			
		||||
    ('text/x-scss', 'django_libsass.SassCompiler'),
 | 
			
		||||
)
 | 
			
		||||
COMPRESS_PRECOMPILERS = (("text/x-scss", "django_libsass.SassCompiler"),)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# EMAIL settings
 | 
			
		||||
@@ -213,7 +211,7 @@ RECURRENCE_I18N_URL = "javascript-catalog"
 | 
			
		||||
# location field
 | 
			
		||||
 | 
			
		||||
LOCATION_FIELD = {
 | 
			
		||||
    'map.provider': 'openstreetmap',
 | 
			
		||||
    'provider.openstreetmap.max_zoom': 18,
 | 
			
		||||
    'search.provider': 'addok',
 | 
			
		||||
    "map.provider": "openstreetmap",
 | 
			
		||||
    "provider.openstreetmap.max_zoom": 18,
 | 
			
		||||
    "search.provider": "addok",
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
COMPRESS_OFFLINE = False
 | 
			
		||||
LIBSASS_OUTPUT_STYLE = 'compressed'
 | 
			
		||||
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
 | 
			
		||||
LIBSASS_OUTPUT_STYLE = "compressed"
 | 
			
		||||
STATICFILES_STORAGE = "django.contrib.staticfiles.storage.ManifestStaticFilesStorage"
 | 
			
		||||
 
 | 
			
		||||
@@ -10,24 +10,28 @@ register = template.Library()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def html_to_rgb(hex_color):
 | 
			
		||||
    """ takes a color like #87c95f and produces a desaturate color """
 | 
			
		||||
    """takes a color like #87c95f and produces a desaturate color"""
 | 
			
		||||
    if len(hex_color) != 7:
 | 
			
		||||
        raise Exception("Passed %s into color_variant(), needs to be in #87c95f format." % hex_color)
 | 
			
		||||
        raise Exception(
 | 
			
		||||
            "Passed %s into color_variant(), needs to be in #87c95f format." % hex_color
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    rgb_hex = [hex_color[x:x + 2] for x in [1, 3, 5]]
 | 
			
		||||
    rgb_hex = [hex_color[x : x + 2] for x in [1, 3, 5]]
 | 
			
		||||
    rgb_in = [int(hex_value, 16) for hex_value in rgb_hex]
 | 
			
		||||
 | 
			
		||||
    return [x / 255 for x in rgb_in]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def rgb_to_html(rgb):
 | 
			
		||||
    new_rgb_int = [min([255, max([0, int(i * 255)])]) for i in rgb] # make sure new values are between 0 and 255
 | 
			
		||||
    
 | 
			
		||||
    new_rgb_int = [
 | 
			
		||||
        min([255, max([0, int(i * 255)])]) for i in rgb
 | 
			
		||||
    ]  # make sure new values are between 0 and 255
 | 
			
		||||
 | 
			
		||||
    # hex() produces "0x88", we want just "88"
 | 
			
		||||
    return "#" + "".join([("0" + hex(i)[2:])[-2:] for i in new_rgb_int])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_relative_luminance(hex_color):
 | 
			
		||||
 | 
			
		||||
    rgb = html_to_rgb(hex_color)
 | 
			
		||||
    R = rgb[0] / 12.92 if rgb[0] <= 0.04045 else ((rgb[0] + 0.055) / 1.055) ** 2.4
 | 
			
		||||
    G = rgb[1] / 12.92 if rgb[1] <= 0.04045 else ((rgb[1] + 0.055) / 1.055) ** 2.4
 | 
			
		||||
@@ -35,7 +39,8 @@ def get_relative_luminance(hex_color):
 | 
			
		||||
 | 
			
		||||
    return 0.2126 * R + 0.7152 * G + 0.0722 * B
 | 
			
		||||
 | 
			
		||||
def adjust_lightness_saturation(hex_color, shift_lightness = 0.0, scale_saturation=1):
 | 
			
		||||
 | 
			
		||||
def adjust_lightness_saturation(hex_color, shift_lightness=0.0, scale_saturation=1):
 | 
			
		||||
    rgb = html_to_rgb(hex_color)
 | 
			
		||||
 | 
			
		||||
    h, l, s = colorsys.rgb_to_hls(*rgb)
 | 
			
		||||
@@ -43,16 +48,18 @@ def adjust_lightness_saturation(hex_color, shift_lightness = 0.0, scale_saturati
 | 
			
		||||
    l += shift_lightness
 | 
			
		||||
    s *= scale_saturation
 | 
			
		||||
 | 
			
		||||
    r, g, b =  colorsys.hls_to_rgb(h, l, s)
 | 
			
		||||
    r, g, b = colorsys.hls_to_rgb(h, l, s)
 | 
			
		||||
 | 
			
		||||
    return rgb_to_html([r, g, b])
 | 
			
		||||
 | 
			
		||||
def adjust_color(color, alpha = 1):
 | 
			
		||||
 | 
			
		||||
def adjust_color(color, alpha=1):
 | 
			
		||||
    return color + ("0" + hex(int(alpha * 255))[2:])[-2:]
 | 
			
		||||
 | 
			
		||||
def background_color_adjust_color(color, alpha = 1):
 | 
			
		||||
 | 
			
		||||
def background_color_adjust_color(color, alpha=1):
 | 
			
		||||
    result = " background-color: " + adjust_color(color, alpha) + ";"
 | 
			
		||||
    if get_relative_luminance(color) < .5:
 | 
			
		||||
    if get_relative_luminance(color) < 0.5:
 | 
			
		||||
        result += " color: #fff;"
 | 
			
		||||
    else:
 | 
			
		||||
        result += " color: #000;"
 | 
			
		||||
@@ -62,20 +69,27 @@ def background_color_adjust_color(color, alpha = 1):
 | 
			
		||||
@register.simple_tag
 | 
			
		||||
def css_categories():
 | 
			
		||||
    result = '<style type="text/css">'
 | 
			
		||||
    
 | 
			
		||||
    cats = [{"color": c.color, "css_class": c.css_class()} for c in Category.objects.all()]
 | 
			
		||||
    cats.append({"color": Category.default_color, "css_class": Category.default_css_class})
 | 
			
		||||
 | 
			
		||||
    cats = [
 | 
			
		||||
        {"color": c.color, "css_class": c.css_class()} for c in Category.objects.all()
 | 
			
		||||
    ]
 | 
			
		||||
    cats.append(
 | 
			
		||||
        {"color": Category.default_color, "css_class": Category.default_css_class}
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    for c in cats:
 | 
			
		||||
 | 
			
		||||
        result += "." + c["css_class"] + " {"
 | 
			
		||||
        result += background_color_adjust_color(adjust_lightness_saturation(c["color"], .2, 0.8), 0.8)
 | 
			
		||||
        result += background_color_adjust_color(
 | 
			
		||||
            adjust_lightness_saturation(c["color"], 0.2, 0.8), 0.8
 | 
			
		||||
        )
 | 
			
		||||
        result += "}"
 | 
			
		||||
 | 
			
		||||
        result += "*:hover>." + c["css_class"] + " {"
 | 
			
		||||
        result += background_color_adjust_color(adjust_lightness_saturation(c["color"], 0.02, 1.0))
 | 
			
		||||
        result += background_color_adjust_color(
 | 
			
		||||
            adjust_lightness_saturation(c["color"], 0.02, 1.0)
 | 
			
		||||
        )
 | 
			
		||||
        result += "}"
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        result += "." + c["css_class"] + ".circ-cat, "
 | 
			
		||||
        result += "form ." + c["css_class"] + ", "
 | 
			
		||||
        result += ".selected ." + c["css_class"] + " {"
 | 
			
		||||
@@ -91,22 +105,27 @@ def css_categories():
 | 
			
		||||
        result += "." + c["css_class"] + ".circ-cat:hover, "
 | 
			
		||||
        result += "form ." + c["css_class"] + ":hover, "
 | 
			
		||||
        result += "a.selected:hover ." + c["css_class"] + " {"
 | 
			
		||||
        result += background_color_adjust_color(adjust_lightness_saturation(c["color"], 0.2, 1.2))
 | 
			
		||||
        result += background_color_adjust_color(
 | 
			
		||||
            adjust_lightness_saturation(c["color"], 0.2, 1.2)
 | 
			
		||||
        )
 | 
			
		||||
        result += "}"
 | 
			
		||||
 | 
			
		||||
        result += "." + c["css_class"] + ".circ-cat.recurrent:hover, "
 | 
			
		||||
        result += ".selected.recurrent:hover ." + c["css_class"] + " {"
 | 
			
		||||
        result += "background: none;"
 | 
			
		||||
        result += "color: " + adjust_color(adjust_lightness_saturation(c["color"], 0.2, 1.2)) + ";"
 | 
			
		||||
        result += (
 | 
			
		||||
            "color: "
 | 
			
		||||
            + adjust_color(adjust_lightness_saturation(c["color"], 0.2, 1.2))
 | 
			
		||||
            + ";"
 | 
			
		||||
        )
 | 
			
		||||
        result += "}"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    result += '</style>'
 | 
			
		||||
    result += "</style>"
 | 
			
		||||
    return mark_safe(result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.filter
 | 
			
		||||
def small_cat(category, url=None, contrast=True, selected=True, recurrence=False):
 | 
			
		||||
 | 
			
		||||
    name = Category.default_name if category is None else category.name
 | 
			
		||||
    css_class = Category.default_css_class if category is None else category.css_class()
 | 
			
		||||
 | 
			
		||||
@@ -114,16 +133,50 @@ def small_cat(category, url=None, contrast=True, selected=True, recurrence=False
 | 
			
		||||
    class_selected = " selected" if selected else ""
 | 
			
		||||
    class_recurrence = " recurrent" if recurrence else ""
 | 
			
		||||
 | 
			
		||||
    content = ('<span class="' + css_class + '">' + picto_from_name("repeat", name + ' [récurrent]') + '</span>') if recurrence else ('<span class="cat ' + css_class + '"></span>')
 | 
			
		||||
    content = (
 | 
			
		||||
        (
 | 
			
		||||
            '<span class="'
 | 
			
		||||
            + css_class
 | 
			
		||||
            + '">'
 | 
			
		||||
            + picto_from_name("repeat", name + " [récurrent]")
 | 
			
		||||
            + "</span>"
 | 
			
		||||
        )
 | 
			
		||||
        if recurrence
 | 
			
		||||
        else ('<span class="cat ' + css_class + '"></span>')
 | 
			
		||||
    )
 | 
			
		||||
    if url is None:
 | 
			
		||||
        return mark_safe('<span class="small-cat' + class_recurrence + class_contrast + class_selected + '" role="button">' + content + " " + name + "</span>")
 | 
			
		||||
        return mark_safe(
 | 
			
		||||
            '<span class="small-cat'
 | 
			
		||||
            + class_recurrence
 | 
			
		||||
            + class_contrast
 | 
			
		||||
            + class_selected
 | 
			
		||||
            + '" role="button">'
 | 
			
		||||
            + content
 | 
			
		||||
            + " "
 | 
			
		||||
            + name
 | 
			
		||||
            + "</span>"
 | 
			
		||||
        )
 | 
			
		||||
    else:
 | 
			
		||||
        return mark_safe('<a class="small-cat' + class_recurrence + class_contrast + class_selected + '" role="button" href="' + url + '">' + content + " " + name + "</a>")
 | 
			
		||||
        return mark_safe(
 | 
			
		||||
            '<a class="small-cat'
 | 
			
		||||
            + class_recurrence
 | 
			
		||||
            + class_contrast
 | 
			
		||||
            + class_selected
 | 
			
		||||
            + '" role="button" href="'
 | 
			
		||||
            + url
 | 
			
		||||
            + '">'
 | 
			
		||||
            + content
 | 
			
		||||
            + " "
 | 
			
		||||
            + name
 | 
			
		||||
            + "</a>"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.filter
 | 
			
		||||
def small_cat_no_selected(category, url=None):
 | 
			
		||||
    return small_cat(category, url=url, selected=False)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.filter
 | 
			
		||||
def small_cat_recurrent(category, recurrence=False):
 | 
			
		||||
    return small_cat(category, url=None, selected=True, recurrence=recurrence)
 | 
			
		||||
@@ -139,9 +192,17 @@ def circle_cat(category, recurrence=False):
 | 
			
		||||
        n = category.name
 | 
			
		||||
 | 
			
		||||
    if recurrence:
 | 
			
		||||
        return mark_safe('<span class="cat recurrent ' + c  + ' circ-cat">' + picto_from_name("repeat", n + ' [récurrent]') + "</span>")
 | 
			
		||||
        return mark_safe(
 | 
			
		||||
            '<span class="cat recurrent '
 | 
			
		||||
            + c
 | 
			
		||||
            + ' circ-cat">'
 | 
			
		||||
            + picto_from_name("repeat", n + " [récurrent]")
 | 
			
		||||
            + "</span>"
 | 
			
		||||
        )
 | 
			
		||||
    else:
 | 
			
		||||
        return mark_safe('<span class="cat ' + c  + ' circ-cat" data-tooltip="' + n + '"></span>')
 | 
			
		||||
        return mark_safe(
 | 
			
		||||
            '<span class="cat ' + c + ' circ-cat" data-tooltip="' + n + '"></span>'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.simple_tag
 | 
			
		||||
@@ -149,6 +210,23 @@ def show_legend(filter):
 | 
			
		||||
    current_url = filter.get_url_without_filters()
 | 
			
		||||
    cats = Category.objects.all()
 | 
			
		||||
    if filter.is_active(only_categories=True):
 | 
			
		||||
        return mark_safe(" ".join([small_cat(c, current_url + "?category=" + str(c.pk) if not filter.is_selected(c) else None, contrast=filter.is_selected(c)) for c in cats]))
 | 
			
		||||
        return mark_safe(
 | 
			
		||||
            " ".join(
 | 
			
		||||
                [
 | 
			
		||||
                    small_cat(
 | 
			
		||||
                        c,
 | 
			
		||||
                        current_url + "?category=" + str(c.pk)
 | 
			
		||||
                        if not filter.is_selected(c)
 | 
			
		||||
                        else None,
 | 
			
		||||
                        contrast=filter.is_selected(c),
 | 
			
		||||
                    )
 | 
			
		||||
                    for c in cats
 | 
			
		||||
                ]
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
    else:
 | 
			
		||||
        return mark_safe(" ".join([small_cat(c, current_url + "?category=" + str(c.pk)) for c in cats]))
 | 
			
		||||
        return mark_safe(
 | 
			
		||||
            " ".join(
 | 
			
		||||
                [small_cat(c, current_url + "?category=" + str(c.pk)) for c in cats]
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,20 @@ register = template.Library()
 | 
			
		||||
def show_badge_contactmessages(placement="top"):
 | 
			
		||||
    nb_open = ContactMessage.nb_open_contactmessages()
 | 
			
		||||
    if nb_open != 0:
 | 
			
		||||
        return mark_safe('<a href="' + reverse_lazy("contactmessages") + '?closed=false" class="badge" data-placement="' + placement + '"data-tooltip="' + str(nb_open) + ' message' + pluralize(nb_open) + ' à traiter">' + picto_from_name("mail") + " " + str(nb_open) + '</a>')
 | 
			
		||||
        return mark_safe(
 | 
			
		||||
            '<a href="'
 | 
			
		||||
            + reverse_lazy("contactmessages")
 | 
			
		||||
            + '?closed=false" class="badge" data-placement="'
 | 
			
		||||
            + placement
 | 
			
		||||
            + '"data-tooltip="'
 | 
			
		||||
            + str(nb_open)
 | 
			
		||||
            + " message"
 | 
			
		||||
            + pluralize(nb_open)
 | 
			
		||||
            + ' à traiter">'
 | 
			
		||||
            + picto_from_name("mail")
 | 
			
		||||
            + " "
 | 
			
		||||
            + str(nb_open)
 | 
			
		||||
            + "</a>"
 | 
			
		||||
        )
 | 
			
		||||
    else:
 | 
			
		||||
        return ""
 | 
			
		||||
        return ""
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ from .utils_extra import picto_from_name
 | 
			
		||||
 | 
			
		||||
register = template.Library()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.simple_tag
 | 
			
		||||
def show_badge_duplicated(placement="top"):
 | 
			
		||||
    duplicated = DuplicatedEvents.objects.all()
 | 
			
		||||
@@ -22,6 +23,20 @@ def show_badge_duplicated(placement="top"):
 | 
			
		||||
            d.delete()
 | 
			
		||||
 | 
			
		||||
    if nb_duplicated != 0:
 | 
			
		||||
        return mark_safe('<a href="' + reverse_lazy("duplicates") + '" class="badge" data-placement="' + placement + '" data-tooltip="' + str(nb_duplicated) + ' dupliqué' + pluralize(nb_duplicated) + ' à valider">' + picto_from_name("copy") + " " + str(nb_duplicated) + '</a>')
 | 
			
		||||
        return mark_safe(
 | 
			
		||||
            '<a href="'
 | 
			
		||||
            + reverse_lazy("duplicates")
 | 
			
		||||
            + '" class="badge" data-placement="'
 | 
			
		||||
            + placement
 | 
			
		||||
            + '" data-tooltip="'
 | 
			
		||||
            + str(nb_duplicated)
 | 
			
		||||
            + " dupliqué"
 | 
			
		||||
            + pluralize(nb_duplicated)
 | 
			
		||||
            + ' à valider">'
 | 
			
		||||
            + picto_from_name("copy")
 | 
			
		||||
            + " "
 | 
			
		||||
            + str(nb_duplicated)
 | 
			
		||||
            + "</a>"
 | 
			
		||||
        )
 | 
			
		||||
    else:
 | 
			
		||||
        return ""
 | 
			
		||||
        return ""
 | 
			
		||||
 
 | 
			
		||||
@@ -11,26 +11,36 @@ from .utils_extra import picto_from_name
 | 
			
		||||
 | 
			
		||||
register = template.Library()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.filter
 | 
			
		||||
def in_date(event, date):
 | 
			
		||||
    return event.filter((Q(start_day__lte=date) & Q(end_day__gte=date)) | (Q(end_day=None) & Q(start_day=date)))
 | 
			
		||||
    return event.filter(
 | 
			
		||||
        (Q(start_day__lte=date) & Q(end_day__gte=date))
 | 
			
		||||
        | (Q(end_day=None) & Q(start_day=date))
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.filter
 | 
			
		||||
def can_show_start_time(event, day=None):
 | 
			
		||||
    if not day is None and day == event.start_day:
 | 
			
		||||
            return True
 | 
			
		||||
        return True
 | 
			
		||||
    return event.start_time and (not event.end_day or event.end_day == event.start_day)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.filter
 | 
			
		||||
def can_show_end_time(event, day=None):
 | 
			
		||||
    if not day is None and day == event.end_day and event.start_day != event.end_day:
 | 
			
		||||
            return True
 | 
			
		||||
        return True
 | 
			
		||||
    return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.filter
 | 
			
		||||
def need_complete_display(event, display_full=True):
 | 
			
		||||
    return event.end_day and event.end_day != event.start_day and (event.start_time or event.end_time or display_full)
 | 
			
		||||
    return (
 | 
			
		||||
        event.end_day
 | 
			
		||||
        and event.end_day != event.start_day
 | 
			
		||||
        and (event.start_time or event.end_time or display_full)
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.filter
 | 
			
		||||
@@ -47,15 +57,44 @@ def picto_status(event):
 | 
			
		||||
def show_badges_events(placement="top"):
 | 
			
		||||
    nb_drafts = Event.nb_draft_events()
 | 
			
		||||
    if nb_drafts != 0:
 | 
			
		||||
        return mark_safe('<a href="' + reverse_lazy("moderation") + '?status=draft" class="badge" data-placement="' + placement + '" data-tooltip="' + str(nb_drafts) + ' brouillon' + pluralize(nb_drafts) + ' à valider">' + picto_from_name("calendar") + " " + str(nb_drafts) + '</a>')
 | 
			
		||||
        return mark_safe(
 | 
			
		||||
            '<a href="'
 | 
			
		||||
            + reverse_lazy("moderation")
 | 
			
		||||
            + '?status=draft" class="badge" data-placement="'
 | 
			
		||||
            + placement
 | 
			
		||||
            + '" data-tooltip="'
 | 
			
		||||
            + str(nb_drafts)
 | 
			
		||||
            + " brouillon"
 | 
			
		||||
            + pluralize(nb_drafts)
 | 
			
		||||
            + ' à valider">'
 | 
			
		||||
            + picto_from_name("calendar")
 | 
			
		||||
            + " "
 | 
			
		||||
            + str(nb_drafts)
 | 
			
		||||
            + "</a>"
 | 
			
		||||
        )
 | 
			
		||||
    else:
 | 
			
		||||
        return ""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.simple_tag
 | 
			
		||||
def show_badge_unknown_places(placement="top"):
 | 
			
		||||
    nb_unknown = Event.objects.filter(exact_location__isnull=True).count()
 | 
			
		||||
    if nb_unknown != 0:
 | 
			
		||||
        return mark_safe('<a href="' + reverse_lazy("view_unknown_places") + '" class="badge" data-placement="' + placement + '" data-tooltip="' + str(nb_unknown) + ' événement' + pluralize(nb_unknown) + ' sans lieu défini">' + picto_from_name("map-pin") + " " + str(nb_unknown) + '</a>')
 | 
			
		||||
        return mark_safe(
 | 
			
		||||
            '<a href="'
 | 
			
		||||
            + reverse_lazy("view_unknown_places")
 | 
			
		||||
            + '" class="badge" data-placement="'
 | 
			
		||||
            + placement
 | 
			
		||||
            + '" data-tooltip="'
 | 
			
		||||
            + str(nb_unknown)
 | 
			
		||||
            + " événement"
 | 
			
		||||
            + pluralize(nb_unknown)
 | 
			
		||||
            + ' sans lieu défini">'
 | 
			
		||||
            + picto_from_name("map-pin")
 | 
			
		||||
            + " "
 | 
			
		||||
            + str(nb_unknown)
 | 
			
		||||
            + "</a>"
 | 
			
		||||
        )
 | 
			
		||||
    else:
 | 
			
		||||
        return ""
 | 
			
		||||
 | 
			
		||||
@@ -64,6 +103,7 @@ def show_badge_unknown_places(placement="top"):
 | 
			
		||||
def event_field_verbose_name(the_field):
 | 
			
		||||
    return Event._meta.get_field(the_field).verbose_name
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.simple_tag
 | 
			
		||||
def field_to_html(field, key):
 | 
			
		||||
    if field is None:
 | 
			
		||||
@@ -71,13 +111,17 @@ def field_to_html(field, key):
 | 
			
		||||
    elif key == "description":
 | 
			
		||||
        return urlize(mark_safe(linebreaks(field)))
 | 
			
		||||
    elif key == "reference_urls":
 | 
			
		||||
        return mark_safe("<ul>" + "".join(['<li><a href="' + u + '">' + u + '</a></li>' for u in field]) + "</ul>")
 | 
			
		||||
        return mark_safe(
 | 
			
		||||
            "<ul>"
 | 
			
		||||
            + "".join(['<li><a href="' + u + '">' + u + "</a></li>" for u in field])
 | 
			
		||||
            + "</ul>"
 | 
			
		||||
        )
 | 
			
		||||
    elif key == "image":
 | 
			
		||||
        return mark_safe('<a href="' + field + '">' + field + '</a>')
 | 
			
		||||
        return mark_safe('<a href="' + field + '">' + field + "</a>")
 | 
			
		||||
    elif key == "local_image":
 | 
			
		||||
        if field:
 | 
			
		||||
            return mark_safe('<img src="' + field.url + '" />')
 | 
			
		||||
        else:
 | 
			
		||||
            return "-"
 | 
			
		||||
    else:
 | 
			
		||||
        return field
 | 
			
		||||
        return field
 | 
			
		||||
 
 | 
			
		||||
@@ -11,13 +11,37 @@ from .utils_extra import picto_from_name
 | 
			
		||||
 | 
			
		||||
register = template.Library()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.simple_tag
 | 
			
		||||
def show_badge_failed_rimports(placement="top"):
 | 
			
		||||
    newest = BatchImportation.objects.filter(recurrentImport=OuterRef("pk")).order_by("-created_date")
 | 
			
		||||
    nb_failed = RecurrentImport.objects.annotate(last_run_status=Subquery(newest.values("status")[:1])).filter(last_run_status=BatchImportation.STATUS.FAILED).count()
 | 
			
		||||
 | 
			
		||||
    newest = BatchImportation.objects.filter(recurrentImport=OuterRef("pk")).order_by(
 | 
			
		||||
        "-created_date"
 | 
			
		||||
    )
 | 
			
		||||
    nb_failed = (
 | 
			
		||||
        RecurrentImport.objects.annotate(
 | 
			
		||||
            last_run_status=Subquery(newest.values("status")[:1])
 | 
			
		||||
        )
 | 
			
		||||
        .filter(last_run_status=BatchImportation.STATUS.FAILED)
 | 
			
		||||
        .count()
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    if nb_failed != 0:
 | 
			
		||||
        return mark_safe('<a href="' + reverse_lazy("recurrent_imports") + '" class="badge error" data-placement="' + placement + '" data-tooltip="' + str(nb_failed) + ' importation' + pluralize(nb_failed) + ' récurrente' + pluralize(nb_failed) + ' en erreur">' + picto_from_name("alert-triangle") + " " + str(nb_failed) + '</a>')
 | 
			
		||||
        return mark_safe(
 | 
			
		||||
            '<a href="'
 | 
			
		||||
            + reverse_lazy("recurrent_imports")
 | 
			
		||||
            + '" class="badge error" data-placement="'
 | 
			
		||||
            + placement
 | 
			
		||||
            + '" data-tooltip="'
 | 
			
		||||
            + str(nb_failed)
 | 
			
		||||
            + " importation"
 | 
			
		||||
            + pluralize(nb_failed)
 | 
			
		||||
            + " récurrente"
 | 
			
		||||
            + pluralize(nb_failed)
 | 
			
		||||
            + ' en erreur">'
 | 
			
		||||
            + picto_from_name("alert-triangle")
 | 
			
		||||
            + " "
 | 
			
		||||
            + str(nb_failed)
 | 
			
		||||
            + "</a>"
 | 
			
		||||
        )
 | 
			
		||||
    else:
 | 
			
		||||
        return ""
 | 
			
		||||
        return ""
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ from django.db.models import Q
 | 
			
		||||
 | 
			
		||||
register = template.Library()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.simple_tag
 | 
			
		||||
def get_static_content_by_name(name):
 | 
			
		||||
    result = StaticContent.objects.filter(name=name)
 | 
			
		||||
@@ -14,7 +15,8 @@ def get_static_content_by_name(name):
 | 
			
		||||
    else:
 | 
			
		||||
        return result[0]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.simple_tag
 | 
			
		||||
def concat_all(*args):
 | 
			
		||||
    """concatenate all args"""
 | 
			
		||||
    return ''.join(map(str, args))
 | 
			
		||||
    return "".join(map(str, args))
 | 
			
		||||
 
 | 
			
		||||
@@ -4,15 +4,30 @@ from django.urls import reverse_lazy
 | 
			
		||||
 | 
			
		||||
register = template.Library()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.filter
 | 
			
		||||
def tag_button(tag, link=False, strike=False):
 | 
			
		||||
    strike_class = " strike" if strike else ""
 | 
			
		||||
    if link:
 | 
			
		||||
        return mark_safe('<a href="' + reverse_lazy('view_tag', {"tag": tag}) +'" role="button" class="small-cat' + strike_class + '">' +  tag + '</a>')
 | 
			
		||||
        return mark_safe(
 | 
			
		||||
            '<a href="'
 | 
			
		||||
            + reverse_lazy("view_tag", {"tag": tag})
 | 
			
		||||
            + '" role="button" class="small-cat'
 | 
			
		||||
            + strike_class
 | 
			
		||||
            + '">'
 | 
			
		||||
            + tag
 | 
			
		||||
            + "</a>"
 | 
			
		||||
        )
 | 
			
		||||
    else:
 | 
			
		||||
        return mark_safe('<span role="button" class="small-cat' + strike_class + '">' +  tag + '</span>')
 | 
			
		||||
        return mark_safe(
 | 
			
		||||
            '<span role="button" class="small-cat'
 | 
			
		||||
            + strike_class
 | 
			
		||||
            + '">'
 | 
			
		||||
            + tag
 | 
			
		||||
            + "</span>"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.filter
 | 
			
		||||
def tag_button_strike(tag, link=False):
 | 
			
		||||
    return tag_button(tag, link, strike=True)
 | 
			
		||||
    return tag_button(tag, link, strike=True)
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ def hostname(url):
 | 
			
		||||
 | 
			
		||||
@register.filter
 | 
			
		||||
def add_de(txt):
 | 
			
		||||
    return ("d'" if txt[0].lower() in ['a', 'e', 'i', 'o', 'u', 'y'] else "de ") + txt
 | 
			
		||||
    return ("d'" if txt[0].lower() in ["a", "e", "i", "o", "u", "y"] else "de ") + txt
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.filter
 | 
			
		||||
@@ -39,7 +39,7 @@ def first_day_of_this_week(d):
 | 
			
		||||
 | 
			
		||||
@register.filter
 | 
			
		||||
def last_day_of_this_week(d):
 | 
			
		||||
    return date.fromisocalendar(d.year, week(d), 7)    
 | 
			
		||||
    return date.fromisocalendar(d.year, week(d), 7)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.filter
 | 
			
		||||
@@ -57,22 +57,33 @@ def calendar_classes(d, fixed_style):
 | 
			
		||||
 | 
			
		||||
@register.filter
 | 
			
		||||
def url_day(d):
 | 
			
		||||
    return reverse_lazy("day_view", kwargs={"year": d.year, "month": d.month, "day": d.day})
 | 
			
		||||
    return reverse_lazy(
 | 
			
		||||
        "day_view", kwargs={"year": d.year, "month": d.month, "day": d.day}
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.simple_tag
 | 
			
		||||
def picto_from_name(name, datatooltip=""):
 | 
			
		||||
    result = '<svg width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' + \
 | 
			
		||||
        '<use href="' + static("images/feather-sprite.svg") + '#' + name + '" />' + \
 | 
			
		||||
        '</svg>'
 | 
			
		||||
    result = (
 | 
			
		||||
        '<svg width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">'
 | 
			
		||||
        + '<use href="'
 | 
			
		||||
        + static("images/feather-sprite.svg")
 | 
			
		||||
        + "#"
 | 
			
		||||
        + name
 | 
			
		||||
        + '" />'
 | 
			
		||||
        + "</svg>"
 | 
			
		||||
    )
 | 
			
		||||
    if datatooltip != "":
 | 
			
		||||
        result = '<span data-tooltip="' + datatooltip + '">' + result + '</span>'
 | 
			
		||||
        result = '<span data-tooltip="' + datatooltip + '">' + result + "</span>"
 | 
			
		||||
 | 
			
		||||
    return mark_safe(result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.filter
 | 
			
		||||
def int_to_abc(d):
 | 
			
		||||
    return auc[int(d)]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.filter(is_safe=True)
 | 
			
		||||
@stringfilter
 | 
			
		||||
def truncatechars_middle(value, arg):
 | 
			
		||||
@@ -83,17 +94,19 @@ def truncatechars_middle(value, arg):
 | 
			
		||||
    if len(value) <= ln:
 | 
			
		||||
        return value
 | 
			
		||||
    else:
 | 
			
		||||
        return '{}...{}'.format(value[:ln//2], value[-((ln+1)//2):])
 | 
			
		||||
        return "{}...{}".format(value[: ln // 2], value[-((ln + 1) // 2) :])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.filter
 | 
			
		||||
def frdate(d):
 | 
			
		||||
    return ('!' + d).replace(" 1 ", " 1er ").replace("!1 ", "!1er ")[1:]
 | 
			
		||||
    return ("!" + d).replace(" 1 ", " 1er ").replace("!1 ", "!1er ")[1:]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.filter
 | 
			
		||||
def get_item(dictionary, key):
 | 
			
		||||
    return dictionary.get(key)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.filter
 | 
			
		||||
def remove_id_prefix(value):
 | 
			
		||||
    return int(value.replace("id_", ""))
 | 
			
		||||
    
 | 
			
		||||
@@ -11,34 +11,58 @@ from .views import *
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    path("", home, name="home"),
 | 
			
		||||
    path("semaine/<int:year>/<int:week>/", week_view, name='week_view'),
 | 
			
		||||
    path("mois/<int:year>/<int:month>/", month_view, name='month_view'),
 | 
			
		||||
    path("jour/<int:year>/<int:month>/<int:day>/", day_view, name='day_view'),
 | 
			
		||||
    path("semaine/<int:year>/<int:week>/", week_view, name="week_view"),
 | 
			
		||||
    path("mois/<int:year>/<int:month>/", month_view, name="month_view"),
 | 
			
		||||
    path("jour/<int:year>/<int:month>/<int:day>/", day_view, name="day_view"),
 | 
			
		||||
    path("aujourdhui/", day_view, name="aujourdhui"),
 | 
			
		||||
    path("cette-semaine/", week_view, name="cette_semaine"),
 | 
			
		||||
    path("ce-mois-ci", month_view, name="ce_mois_ci"),
 | 
			
		||||
    path("tag/<t>/", view_tag, name='view_tag'),
 | 
			
		||||
    path("tags/", tag_list, name='view_all_tags'),
 | 
			
		||||
    path("moderation/", moderation, name='moderation'),
 | 
			
		||||
    path("event/<int:year>/<int:month>/<int:day>/<int:pk>-<extra>", EventDetailView.as_view(), name="view_event"),
 | 
			
		||||
    path("tag/<t>/", view_tag, name="view_tag"),
 | 
			
		||||
    path("tags/", tag_list, name="view_all_tags"),
 | 
			
		||||
    path("moderation/", moderation, name="moderation"),
 | 
			
		||||
    path(
 | 
			
		||||
        "event/<int:year>/<int:month>/<int:day>/<int:pk>-<extra>",
 | 
			
		||||
        EventDetailView.as_view(),
 | 
			
		||||
        name="view_event",
 | 
			
		||||
    ),
 | 
			
		||||
    path("event/<int:pk>/edit", EventUpdateView.as_view(), name="edit_event"),
 | 
			
		||||
    path("event/<int:pk>/change-status/<status>", change_status_event, name="change_status_event"),
 | 
			
		||||
    path(
 | 
			
		||||
        "event/<int:pk>/change-status/<status>",
 | 
			
		||||
        change_status_event,
 | 
			
		||||
        name="change_status_event",
 | 
			
		||||
    ),
 | 
			
		||||
    path("event/<int:pk>/delete", EventDeleteView.as_view(), name="delete_event"),
 | 
			
		||||
    path("event/<int:year>/<int:month>/<int:day>/<int:pk>/set_duplicate", set_duplicate, name="set_duplicate"),
 | 
			
		||||
    path(
 | 
			
		||||
        "event/<int:year>/<int:month>/<int:day>/<int:pk>/set_duplicate",
 | 
			
		||||
        set_duplicate,
 | 
			
		||||
        name="set_duplicate",
 | 
			
		||||
    ),
 | 
			
		||||
    path("event/<int:pk>/moderate", EventModerateView.as_view(), name="moderate_event"),
 | 
			
		||||
    path("ajouter", import_from_url, name="add_event"),
 | 
			
		||||
    path("admin/", admin.site.urls),
 | 
			
		||||
    path('accounts/', include('django.contrib.auth.urls')),
 | 
			
		||||
    path("accounts/", include("django.contrib.auth.urls")),
 | 
			
		||||
    path("test_app/", include("test_app.urls")),
 | 
			
		||||
    path("static-content/create", StaticContentCreateView.as_view(), name="create_static_content"),
 | 
			
		||||
    path("static-content/<int:pk>/edit", StaticContentUpdateView.as_view(), name="edit_static_content"),
 | 
			
		||||
    path('rechercher', event_search, name='event_search'),
 | 
			
		||||
    path('rechercher/complet/', event_search_full, name='event_search_full'),
 | 
			
		||||
    path('mentions-legales', mentions_legales, name='mentions_legales'),
 | 
			
		||||
    path('a-propos', about, name='about'),
 | 
			
		||||
    path('contact', ContactMessageCreateView.as_view(), name='contact'),
 | 
			
		||||
    path('contactmessages', contactmessages, name='contactmessages'),
 | 
			
		||||
    path('contactmessage/<int:pk>', ContactMessageUpdateView.as_view(), name='contactmessage'),
 | 
			
		||||
    path(
 | 
			
		||||
        "static-content/create",
 | 
			
		||||
        StaticContentCreateView.as_view(),
 | 
			
		||||
        name="create_static_content",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "static-content/<int:pk>/edit",
 | 
			
		||||
        StaticContentUpdateView.as_view(),
 | 
			
		||||
        name="edit_static_content",
 | 
			
		||||
    ),
 | 
			
		||||
    path("rechercher", event_search, name="event_search"),
 | 
			
		||||
    path("rechercher/complet/", event_search_full, name="event_search_full"),
 | 
			
		||||
    path("mentions-legales", mentions_legales, name="mentions_legales"),
 | 
			
		||||
    path("a-propos", about, name="about"),
 | 
			
		||||
    path("contact", ContactMessageCreateView.as_view(), name="contact"),
 | 
			
		||||
    path("contactmessages", contactmessages, name="contactmessages"),
 | 
			
		||||
    path(
 | 
			
		||||
        "contactmessage/<int:pk>",
 | 
			
		||||
        ContactMessageUpdateView.as_view(),
 | 
			
		||||
        name="contactmessage",
 | 
			
		||||
    ),
 | 
			
		||||
    path("imports/", imports, name="imports"),
 | 
			
		||||
    path("imports/add", add_import, name="add_import"),
 | 
			
		||||
    path("imports/<int:pk>/cancel", cancel_import, name="cancel_import"),
 | 
			
		||||
@@ -46,26 +70,72 @@ urlpatterns = [
 | 
			
		||||
    path("rimports/run", run_all_rimports, name="run_all_rimports"),
 | 
			
		||||
    path("rimports/add", RecurrentImportCreateView.as_view(), name="add_rimport"),
 | 
			
		||||
    path("rimports/<int:pk>/view", view_rimport, name="view_rimport"),
 | 
			
		||||
    path("rimports/<int:pk>/edit", RecurrentImportUpdateView.as_view(), name="edit_rimport"),
 | 
			
		||||
    path("rimports/<int:pk>/delete", RecurrentImportDeleteView.as_view(), name="delete_rimport"),
 | 
			
		||||
    path(
 | 
			
		||||
        "rimports/<int:pk>/edit",
 | 
			
		||||
        RecurrentImportUpdateView.as_view(),
 | 
			
		||||
        name="edit_rimport",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "rimports/<int:pk>/delete",
 | 
			
		||||
        RecurrentImportDeleteView.as_view(),
 | 
			
		||||
        name="delete_rimport",
 | 
			
		||||
    ),
 | 
			
		||||
    path("rimports/<int:pk>/run", run_rimport, name="run_rimport"),
 | 
			
		||||
    path("catrules/", categorisation_rules, name="categorisation_rules"),
 | 
			
		||||
    path("catrules/add", CategorisationRuleCreateView.as_view(), name="add_catrule"),
 | 
			
		||||
    path("catrules/<int:pk>/edit", CategorisationRuleUpdateView.as_view(), name="edit_catrule"),
 | 
			
		||||
    path("catrules/<int:pk>/delete", CategorisationRuleDeleteView.as_view(), name="delete_catrule"),
 | 
			
		||||
    path(
 | 
			
		||||
        "catrules/<int:pk>/edit",
 | 
			
		||||
        CategorisationRuleUpdateView.as_view(),
 | 
			
		||||
        name="edit_catrule",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "catrules/<int:pk>/delete",
 | 
			
		||||
        CategorisationRuleDeleteView.as_view(),
 | 
			
		||||
        name="delete_catrule",
 | 
			
		||||
    ),
 | 
			
		||||
    path("catrules/apply", apply_categorisation_rules, name="apply_catrules"),
 | 
			
		||||
    path("duplicates/", duplicates, name="duplicates"),
 | 
			
		||||
    path("duplicates/<int:pk>", DuplicatedEventsDetailView.as_view(), name="view_duplicate"),
 | 
			
		||||
    path(
 | 
			
		||||
        "duplicates/<int:pk>",
 | 
			
		||||
        DuplicatedEventsDetailView.as_view(),
 | 
			
		||||
        name="view_duplicate",
 | 
			
		||||
    ),
 | 
			
		||||
    path("duplicates/<int:pk>/fix", fix_duplicate, name="fix_duplicate"),
 | 
			
		||||
    path("duplicates/<int:pk>/merge", merge_duplicate, name="merge_duplicate"),
 | 
			
		||||
    path("mquestions/", ModerationQuestionListView.as_view(), name="view_mquestions"),
 | 
			
		||||
    path("mquestions/add", ModerationQuestionCreateView.as_view(), name="add_mquestion"),
 | 
			
		||||
    path("mquestions/<int:pk>/", ModerationQuestionDetailView.as_view(), name="view_mquestion"),
 | 
			
		||||
    path("mquestions/<int:pk>/edit", ModerationQuestionUpdateView.as_view(), name="edit_mquestion"),
 | 
			
		||||
    path("mquestions/<int:pk>/delete", ModerationQuestionDeleteView.as_view(), name="delete_mquestion"),
 | 
			
		||||
    path("mquestions/<int:qpk>/answers/add", ModerationAnswerCreateView.as_view(), name="add_manswer"),
 | 
			
		||||
    path("mquestions/<int:qpk>/answers/<int:pk>/edit", ModerationAnswerUpdateView.as_view(), name="edit_manswer"),
 | 
			
		||||
    path("mquestions/<int:qpk>/answers/<int:pk>/delete", ModerationAnswerDeleteView.as_view(), name="delete_manswer"),
 | 
			
		||||
    path(
 | 
			
		||||
        "mquestions/add", ModerationQuestionCreateView.as_view(), name="add_mquestion"
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "mquestions/<int:pk>/",
 | 
			
		||||
        ModerationQuestionDetailView.as_view(),
 | 
			
		||||
        name="view_mquestion",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "mquestions/<int:pk>/edit",
 | 
			
		||||
        ModerationQuestionUpdateView.as_view(),
 | 
			
		||||
        name="edit_mquestion",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "mquestions/<int:pk>/delete",
 | 
			
		||||
        ModerationQuestionDeleteView.as_view(),
 | 
			
		||||
        name="delete_mquestion",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "mquestions/<int:qpk>/answers/add",
 | 
			
		||||
        ModerationAnswerCreateView.as_view(),
 | 
			
		||||
        name="add_manswer",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "mquestions/<int:qpk>/answers/<int:pk>/edit",
 | 
			
		||||
        ModerationAnswerUpdateView.as_view(),
 | 
			
		||||
        name="edit_manswer",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "mquestions/<int:qpk>/answers/<int:pk>/delete",
 | 
			
		||||
        ModerationAnswerDeleteView.as_view(),
 | 
			
		||||
        name="delete_manswer",
 | 
			
		||||
    ),
 | 
			
		||||
    path("404/", page_not_found, name="page_not_found"),
 | 
			
		||||
    path("500/", internal_server_error, name="internal_server_error"),
 | 
			
		||||
    path("place/<int:pk>", PlaceDetailView.as_view(), name="view_place"),
 | 
			
		||||
@@ -73,11 +143,22 @@ urlpatterns = [
 | 
			
		||||
    path("place/<int:pk>/delete", PlaceDeleteView.as_view(), name="delete_place"),
 | 
			
		||||
    path("places/", PlaceListView.as_view(), name="view_places"),
 | 
			
		||||
    path("places/add", PlaceCreateView.as_view(), name="add_place"),
 | 
			
		||||
    path("places/add/<int:pk>", PlaceFromEventCreateView.as_view(), name="add_place_from_event"),
 | 
			
		||||
    path("events/unknown-places", UnknownPlacesListView.as_view(), name="view_unknown_places"),
 | 
			
		||||
    path(
 | 
			
		||||
        "places/add/<int:pk>",
 | 
			
		||||
        PlaceFromEventCreateView.as_view(),
 | 
			
		||||
        name="add_place_from_event",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "events/unknown-places",
 | 
			
		||||
        UnknownPlacesListView.as_view(),
 | 
			
		||||
        name="view_unknown_places",
 | 
			
		||||
    ),
 | 
			
		||||
    path("events/unknown-places/fix", fix_unknown_places, name="fix_unknown_places"),
 | 
			
		||||
    path("event/<int:pk>/addplace", UnknownPlaceAddView.as_view(), name="add_place_to_event"),
 | 
			
		||||
 | 
			
		||||
    path(
 | 
			
		||||
        "event/<int:pk>/addplace",
 | 
			
		||||
        UnknownPlaceAddView.as_view(),
 | 
			
		||||
        name="add_place_to_event",
 | 
			
		||||
    ),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
if settings.DEBUG:
 | 
			
		||||
@@ -87,11 +168,15 @@ if settings.DEBUG:
 | 
			
		||||
# If you already have a js_info_dict dictionary, just add
 | 
			
		||||
# 'recurrence' to the existing 'packages' tuple.
 | 
			
		||||
js_info_dict = {
 | 
			
		||||
    'packages': ('recurrence', ),
 | 
			
		||||
    "packages": ("recurrence",),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# jsi18n can be anything you like here
 | 
			
		||||
urlpatterns += [ path('jsi18n.js', JavaScriptCatalog.as_view(packages=['recurrence']), name='jsi18n'), ]
 | 
			
		||||
urlpatterns += [
 | 
			
		||||
    path(
 | 
			
		||||
        "jsi18n.js", JavaScriptCatalog.as_view(packages=["recurrence"]), name="jsi18n"
 | 
			
		||||
    ),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
handler404 = 'agenda_culturel.views.page_not_found'
 | 
			
		||||
handler500 = 'agenda_culturel.views.internal_server_error'
 | 
			
		||||
handler404 = "agenda_culturel.views.page_not_found"
 | 
			
		||||
handler500 = "agenda_culturel.views.internal_server_error"
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user