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