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