Compare commits

..

1 Commits

Author SHA1 Message Date
Jean-Marie Favreau
f4c9514048 On renomme "moderation" en "recent" (plus logique) 2024-11-13 11:01:27 +01:00
134 changed files with 2264 additions and 6858 deletions

View File

@ -36,7 +36,7 @@ Pour ajouter une nouvelle source custom:
### Récupérer un dump du prod sur un serveur dev
* sur le serveur de dev:
* ```docker exec -i agenda_culturel-backend python3 manage.py dumpdata --natural-foreign --natural-primary --format=json --exclude=admin.logentry --indent=2 > fixtures/postgres-backup-20241101.json``` (à noter qu'ici on oublie les comptes, qu'il faudra recréer)
* ```docker exec -i agenda_culturel-backend python3 manage.py dumpdata --format=json --exclude=admin.logentry --exclude=auth.group --exclude=auth.permission --exclude=auth.user --exclude=contenttypes --indent=2 > fixtures/postgres-backup-20241101.json``` (à noter qu'ici on oublie les comptes, qu'il faudra recréer)
* sur le serveur de prod:
* On récupère le dump json ```scp $SERVEUR:$PATH/fixtures/postgres-backup-20241101.json src/fixtures/```
* ```scripts/reset-database.sh FIXTURE COMMIT``` où ```FIXTURE``` est le timestamp dans le nom de la fixture, et ```COMMIT``` est l'ID du commit git correspondant à celle en prod sur le serveur au moment de la création de la fixture

View File

@ -5,11 +5,10 @@ WORKDIR /usr/src/app
RUN --mount=type=cache,target=/var/cache/apt \
apt-get update && \
apt-get install --no-install-recommends -y build-essential libpq-dev gettext chromium-driver gdal-bin fonts-symbola \
apt-get install --no-install-recommends -y build-essential libpq-dev gettext chromium-driver gdal-bin \
&& rm -rf /var/lib/apt/lists/*
COPY src/requirements.txt ./requirements.txt
RUN --mount=type=cache,target=/root/.cache/pip \

View File

@ -32,7 +32,7 @@ http {
error_page 502 /static/html/500.html;
error_page 503 /static/html/500.html;
if ($http_user_agent ~* (Amazonbot|meta-externalagent|ClaudeBot)) {
if ($http_user_agent ~* "(?:Amazonbot)") {
return 444;
}

View File

@ -1,43 +0,0 @@
#!/usr/bin/python3
# coding: utf-8
import os
import json
import sys
# getting the name of the directory
# where the this file is present.
current = os.path.dirname(os.path.realpath(__file__))
# Getting the parent directory name
# where the current directory is present.
parent = os.path.dirname(current)
# adding the parent directory to
# the sys.path.
sys.path.append(parent)
from src.agenda_culturel.import_tasks.downloader import *
from src.agenda_culturel.import_tasks.extractor import *
from src.agenda_culturel.import_tasks.importer import *
from src.agenda_culturel.import_tasks.custom_extractors import *
if __name__ == "__main__":
u2e = URL2Events(SimpleDownloader(), lerio.CExtractor())
url = "https://www.cinemalerio.com/evenements/"
url_human = "https://www.cinemalerio.com/evenements/"
try:
events = u2e.process(url, url_human, cache = "cache-le-rio.html", default_values = {"location": "Cinéma le Rio", "category": "Cinéma"}, published = True)
exportfile = "events-le-roi.json"
print("Saving events to file {}".format(exportfile))
with open(exportfile, "w") as f:
json.dump(events, f, indent=4, default=str)
except Exception as e:
print("Exception: " + str(e))

View File

@ -73,10 +73,6 @@ git checkout $COMMIT
echobold "Setup database stucture according to the selected commit"
docker exec -i agenda_culturel-backend python3 manage.py migrate agenda_culturel
# remove all elements in database
echobold "Flush database"
docker exec -i agenda_culturel-backend python3 manage.py flush --no-input
# import data
echobold "Import data"
docker exec -i agenda_culturel-backend python3 manage.py loaddata --format=json $FFILE
@ -89,4 +85,7 @@ git checkout main
echobold "Update database"
docker exec -i agenda_culturel-backend python3 manage.py migrate agenda_culturel
# create superuser
echobold "Create superuser"
docker exec -ti agenda_culturel-backend python3 manage.py createsuperuser

View File

@ -9,9 +9,8 @@ from .models import (
BatchImportation,
RecurrentImport,
Place,
Message,
ReferenceLocation,
Organisation
ContactMessage,
ReferenceLocation
)
from django_better_admin_arrayfield.admin.mixins import DynamicArrayMixin
from django_better_admin_arrayfield.forms.widgets import DynamicArrayWidget
@ -25,9 +24,8 @@ admin.site.register(DuplicatedEvents)
admin.site.register(BatchImportation)
admin.site.register(RecurrentImport)
admin.site.register(Place)
admin.site.register(Message)
admin.site.register(ContactMessage)
admin.site.register(ReferenceLocation)
admin.site.register(Organisation)
class URLWidget(DynamicArrayWidget):

View File

@ -117,23 +117,6 @@ class DayInCalendar:
if e.start_time is None
else e.start_time
)
self.today_night = False
if self.is_today():
self.today_night = True
now = timezone.now()
nday = now.date()
ntime = now.time()
found = False
for idx,e in enumerate(self.events):
if (nday < e.start_day) or (nday == e.start_day and e.start_time and ntime <= e.start_time):
self.events[idx].is_first_after_now = True
found = True
break
if found:
self.today_night = False
def is_today_after_events(self):
return self.is_today() and self.today_night
def events_by_category_ordered(self):
from .models import Category
@ -192,13 +175,12 @@ class IntervalInDay(DayInCalendar):
self.id = self.id + '-' + str(id)
class CalendarList:
def __init__(self, firstdate, lastdate, filter=None, exact=False, ignore_dup=None, qs=None):
def __init__(self, firstdate, lastdate, filter=None, exact=False, ignore_dup=None):
self.firstdate = firstdate
self.lastdate = lastdate
self.now = date.today()
self.filter = filter
self.ignore_dup = ignore_dup
self.qs = qs
if exact:
self.c_firstdate = self.firstdate
@ -237,12 +219,9 @@ class CalendarList:
def fill_calendar_days(self):
if self.filter is None:
if self.qs is None:
from .models import Event
from .models import Event
qs = Event.objects.all()
else:
qs = self.qs
qs = Event.objects.all()
else:
qs = self.filter.qs
@ -250,7 +229,7 @@ class CalendarList:
qs = qs.exclude(other_versions=self.ignore_dup)
startdatetime = timezone.make_aware(datetime.combine(self.c_firstdate, time.min), timezone.get_default_timezone())
lastdatetime = timezone.make_aware(datetime.combine(self.c_lastdate, time.max), timezone.get_default_timezone())
qs = qs.filter(
self.events = qs.filter(
(Q(recurrence_dtend__isnull=True) & Q(recurrence_dtstart__lte=lastdatetime))
| (
Q(recurrence_dtend__isnull=False)
@ -259,15 +238,11 @@ class CalendarList:
| Q(recurrence_dtend__lt=startdatetime)
)
)
| (Q(start_day__lte=self.c_firstdate) & (Q(end_day__isnull=True) | Q(end_day__gte=self.c_firstdate)))
).filter(
Q(other_versions__isnull=True) |
Q(other_versions__representative=F('pk')) |
Q(other_versions__representative__isnull=True)
).order_by("start_time", "title__unaccent__lower")
qs = qs.select_related("exact_location").select_related("category").select_related("other_versions").select_related("other_versions__representative")
self.events = qs
).order_by("start_time", "title__unaccent__lower").prefetch_related("exact_location").prefetch_related("category").prefetch_related("other_versions")
firstdate = datetime.fromordinal(self.c_firstdate.toordinal())
if firstdate.tzinfo is None or firstdate.tzinfo.utcoffset(firstdate) is None:
@ -315,14 +290,14 @@ class CalendarList:
def time_intervals_list_first(self):
return self.time_intervals_list(True)
def export_to_ics(self, request):
def export_to_ics(self):
from .models import Event
events = [event for day in self.get_calendar_days().values() for event in day.events]
return Event.export_to_ics(events, request)
return Event.export_to_ics(events)
class CalendarMonth(CalendarList):
def __init__(self, year, month, filter, qs=None):
def __init__(self, year, month, filter):
self.year = year
self.month = month
r = calendar.monthrange(year, month)
@ -330,7 +305,7 @@ class CalendarMonth(CalendarList):
first = date(year, month, 1)
last = date(year, month, r[1])
super().__init__(first, last, filter, qs)
super().__init__(first, last, filter)
def get_month_name(self):
return self.firstdate.strftime("%B")
@ -343,14 +318,14 @@ class CalendarMonth(CalendarList):
class CalendarWeek(CalendarList):
def __init__(self, year, week, filter, qs=None):
def __init__(self, year, week, filter):
self.year = year
self.week = week
first = date.fromisocalendar(self.year, self.week, 1)
last = date.fromisocalendar(self.year, self.week, 7)
super().__init__(first, last, filter, qs)
super().__init__(first, last, filter)
def next_week(self):
return self.firstdate + timedelta(days=7)
@ -360,8 +335,8 @@ class CalendarWeek(CalendarList):
class CalendarDay(CalendarList):
def __init__(self, date, filter=None, qs=None):
super().__init__(date, date, filter=filter, qs=qs, exact=True)
def __init__(self, date, filter=None):
super().__init__(date, date, filter, exact=True)
def get_events(self):
return self.calendar_days_list()[0].events

View File

@ -6,8 +6,7 @@ from celery.schedules import crontab
from celery.utils.log import get_task_logger
from celery.exceptions import MaxRetriesExceededError
import time as time_
from django.conf import settings
from celery.signals import worker_ready
from contextlib import contextmanager
@ -148,8 +147,6 @@ def run_recurrent_import_internal(rimport, downloader, req_id):
extractor = c3c.CExtractor()
elif rimport.processor == RecurrentImport.PROCESSOR.ARACHNEE:
extractor = arachnee.CExtractor()
elif rimport.processor == RecurrentImport.PROCESSOR.LERIO:
extractor = lerio.CExtractor()
else:
extractor = None
@ -165,14 +162,13 @@ def run_recurrent_import_internal(rimport, downloader, req_id):
location = rimport.defaultLocation
tags = rimport.defaultTags
published = rimport.defaultPublished
organisers = [] if rimport.defaultOrganiser is None else [rimport.defaultOrganiser.pk]
try:
# get events from website
events = u2e.process(
url,
browsable_url,
default_values={"category": category, "location": location, "tags": tags, "organisers": organisers},
default_values={"category": category, "location": location, "tags": tags},
published=published,
)
@ -251,23 +247,6 @@ def daily_imports(self):
run_recurrent_imports_from_list([imp.pk for imp in imports])
SCREENSHOT_FILE = settings.MEDIA_ROOT + '/screenshot.png'
@app.task(bind=True)
def screenshot(self):
downloader = ChromiumHeadlessDownloader(noimage=False)
downloader.screenshot("https://pommesdelune.fr", SCREENSHOT_FILE)
@worker_ready.connect
def at_start(sender, **k):
if not os.path.isfile(SCREENSHOT_FILE):
logger.info("Init screenshot file")
with sender.app.connection() as conn:
sender.app.send_task('agenda_culturel.celery.screenshot', None, connection=conn)
else:
logger.info("Screenshot file already exists")
@app.task(bind=True)
def run_all_recurrent_imports(self):
from agenda_culturel.models import RecurrentImport
@ -309,7 +288,7 @@ def weekly_imports(self):
run_recurrent_imports_from_list([imp.pk for imp in imports])
@app.task(base=ChromiumTask, bind=True)
def import_events_from_url(self, url, cat, tags, force=False, user_id=None):
def import_events_from_url(self, url, cat, force=False):
from .db_importer import DBImporterEvents
from agenda_culturel.models import RecurrentImport, BatchImportation
from agenda_culturel.models import Event, Category
@ -343,7 +322,7 @@ def import_events_from_url(self, url, cat, tags, force=False, user_id=None):
# set default values
values = {}
if cat is not None:
values = {"category": cat, "tags": tags}
values = {"category": cat}
# get event
events = u2e.process(
@ -355,7 +334,7 @@ def import_events_from_url(self, url, cat, tags, force=False, user_id=None):
json_events = json.dumps(events, default=str)
# import events (from json)
success, error_message = importer.import_events(json_events, user_id)
success, error_message = importer.import_events(json_events)
# finally, close task
close_import_task(self.request.id, success, error_message, importer)
@ -372,14 +351,13 @@ def import_events_from_url(self, url, cat, tags, force=False, user_id=None):
@app.task(base=ChromiumTask, bind=True)
def import_events_from_urls(self, urls_cat_tags, user_id=None):
for ucat in urls_cat_tags:
def import_events_from_urls(self, urls_and_cats):
for ucat in urls_and_cats:
if ucat is not None:
url = ucat[0]
cat = ucat[1]
tags = ucat[2]
import_events_from_url.delay(url, cat, tags, user_id=user_id)
import_events_from_url.delay(url, cat)
app.conf.beat_schedule = {
@ -388,10 +366,6 @@ app.conf.beat_schedule = {
# Daily imports at 3:14 a.m.
"schedule": crontab(hour=3, minute=14),
},
"daily_screenshot": {
"task": "agenda_culturel.celery.screenshot",
"schedule": crontab(hour=3, minute=3),
},
"weekly_imports": {
"task": "agenda_culturel.celery.weekly_imports",
# Daily imports on Mondays at 2:22 a.m.

View File

@ -11,7 +11,6 @@ class DBImporterEvents:
def __init__(self, celery_id):
self.celery_id = celery_id
self.error_message = ""
self.user_id = None
self.init_result_properties()
self.today = timezone.now().date().isoformat()
@ -35,10 +34,9 @@ class DBImporterEvents:
def get_nb_removed_events(self):
return self.nb_removed
def import_events(self, json_structure, user_id=None):
def import_events(self, json_structure):
print(json_structure)
self.init_result_properties()
self.user_id = user_id
try:
structure = json.loads(json_structure)
@ -97,7 +95,7 @@ class DBImporterEvents:
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, user_id=self.user_id
self.event_objects, remove_missing_from_source=self.url
)
def is_valid_event_structure(self, event):

View File

@ -1,505 +0,0 @@
import django_filters
from django.utils.translation import gettext_lazy as _
from django import forms
from django.contrib.postgres.search import SearchQuery, SearchHeadline
from django.db.models import Count, Q
from django.http import QueryDict
from django.contrib.gis.measure import D
from django.forms import (
ModelForm,
ValidationError,
TextInput,
Form,
URLField,
MultipleHiddenInput,
Textarea,
CharField,
ChoiceField,
RadioSelect,
MultipleChoiceField,
BooleanField,
HiddenInput,
ModelChoiceField,
)
from .forms import (
URLSubmissionForm,
EventForm,
BatchImportationForm,
FixDuplicates,
SelectEventInList,
MergeDuplicates,
RecurrentImportForm,
CategorisationRuleImportForm,
CategorisationForm,
EventAddPlaceForm,
PlaceForm,
)
from .models import (
ReferenceLocation,
RecurrentImport,
Tag,
Event,
Category,
Message,
DuplicatedEvents
)
class EventFilter(django_filters.FilterSet):
RECURRENT_CHOICES = [
("remove_recurrent", "Masquer les événements récurrents"),
("only_recurrent", "Montrer uniquement les événements récurrents"),
]
DISTANCE_CHOICES = [5, 10, 15, 30]
position = django_filters.ModelChoiceFilter(
label="À proximité de",
method="no_filter",
empty_label=_("Select a location"),
queryset=ReferenceLocation.objects.all().order_by("-main", "name__unaccent")
)
radius = django_filters.ChoiceFilter(
label="Dans un rayon de",
method="no_filter",
choices=[(x, str(x) + " km") for x in DISTANCE_CHOICES],
null_label=None,
empty_label=None
)
exclude_tags = django_filters.MultipleChoiceFilter(
label="Exclure les étiquettes",
choices=[],
lookup_expr="icontains",
field_name="tags",
exclude=True,
widget=forms.SelectMultiple,
)
tags = django_filters.MultipleChoiceFilter(
label="Inclure les étiquettes",
choices=[],
lookup_expr="icontains",
conjoined=True,
field_name="tags",
widget=forms.SelectMultiple,
)
recurrences = django_filters.ChoiceFilter(
label="Inclure la récurrence",
choices=RECURRENT_CHOICES,
method="filter_recurrences",
)
category = django_filters.ModelMultipleChoiceFilter(
label="Filtrer par catégories",
field_name="category__id",
to_field_name="id",
queryset=Category.objects.all(),
widget=MultipleHiddenInput,
)
status = django_filters.MultipleChoiceFilter(
label="Filtrer par status",
choices=Event.STATUS.choices,
field_name="status",
widget=forms.CheckboxSelectMultiple,
)
class Meta:
model = Event
fields = ["category", "tags", "exclude_tags", "status", "recurrences"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not kwargs["request"].user.is_authenticated:
self.form.fields.pop("status")
self.form.fields["exclude_tags"].choices = Tag.get_tag_groups(exclude=True, nb_suggestions=0)
self.form.fields["tags"].choices = Tag.get_tag_groups(include=True)
def filter_recurrences(self, queryset, name, value):
# construct the full lookup expression
lookup = "__".join([name, "isnull"])
return queryset.filter(**{lookup: value == "remove_recurrent"})
def no_filter(self, queryset, name, value):
return queryset
@property
def qs(self):
parent = super().qs
if self.get_cleaned_data("position") is None or self.get_cleaned_data("radius") is None:
return parent
d = self.get_cleaned_data("radius")
p = self.get_cleaned_data("position")
if not isinstance(d, str) or not isinstance(p, ReferenceLocation):
return parent
p = p.location
return parent.exclude(exact_location=False).filter(exact_location__location__distance_lt=(p, D(km=d)))
def get_url(self):
if isinstance(self.form.data, QueryDict):
return self.form.data.urlencode()
else:
return ""
def get_full_url(self):
return self.request.get_full_path()
def get_url_remove_categories(self, catpks, full_path = None):
if full_path is None:
full_path = self.request.get_full_path()
result = full_path
for catpk in catpks:
result = result.replace('category=' + str(catpk), '')
result = result.replace('?&', '?')
result = result.replace('&&', '&')
return result
def get_url_add_categories(self, catpks, full_path = None):
if full_path is None:
full_path = self.request.get_full_path()
result = full_path
for catpk in catpks:
result = result + ('&' if '?' in full_path else '?') + 'category=' + str(catpk)
return result
def get_url_without_filters_only_cats(self):
return self.get_url_without_filters(True)
def get_url_without_filters(self, only_categories=False):
if only_categories:
# on repart d'une url sans option
result = self.request.get_full_path().split("?")[0]
# on ajoute toutes les catégories
result = self.get_url_add_categories([c.pk for c in self.get_categories()], result)
else:
# on supprime toutes les catégories
result = self.get_url_remove_categories([c.pk for c in self.get_categories()])
return result
def get_cleaned_data(self, name):
try:
return self.form.cleaned_data[name]
except AttributeError:
return {}
except KeyError:
return {}
def get_categories(self):
return self.get_cleaned_data("category")
def has_category(self):
return "category" in self.form.cleaned_data and len(self.get_cleaned_data("category")) > 0
def get_tags(self):
return self.get_cleaned_data("tags")
def get_exclude_tags(self):
return self.get_cleaned_data("exclude_tags")
def get_status(self):
return self.get_cleaned_data("status")
def get_position(self):
return self.get_cleaned_data("position")
def get_radius(self):
return self.get_cleaned_data("radius")
def to_str(self, prefix=''):
self.form.full_clean()
result = ' '.join([c.name for c in self.get_categories()] + [t for t in self.get_tags()] + ["~" + t for t in self.get_exclude_tags()] + [str(self.get_position()), str(self.get_radius())])
if len(result) > 0:
result = prefix + result
return result
def get_status_names(self):
if "status" in self.form.cleaned_data:
return [
dict(Event.STATUS.choices)[s] for s in self.get_cleaned_data("status")
]
else:
return []
def get_recurrence_filtering(self):
if "recurrences" in self.form.cleaned_data:
d = dict(self.RECURRENT_CHOICES)
v = self.form.cleaned_data["recurrences"]
if v in d:
return d[v]
else:
return ""
else:
return ""
def is_resetable(self, only_categories=False):
if only_categories:
return len(self.get_cleaned_data("category")) != 0
else:
if self.request.user.is_authenticated:
if (
len(self.get_cleaned_data("status")) != 1
or
self.get_cleaned_data("status")[0] != Event.STATUS.PUBLISHED
):
return True
else:
if (
len(self.get_cleaned_data("status")) != 0
):
return True
return (
len(self.get_cleaned_data("tags")) != 0
or len(self.get_cleaned_data("exclude_tags")) != 0
or len(self.get_cleaned_data("recurrences")) != 0
or ((not self.get_cleaned_data("position") is None) and (not self.get_cleaned_data("radius") is None))
)
def is_active(self, only_categories=False):
if only_categories:
return len(self.get_cleaned_data("category")) != 0
else:
return (
len(self.get_cleaned_data("status")) != 0
or len(self.get_cleaned_data("tags")) != 0
or len(self.get_cleaned_data("exclude_tags")) != 0
or len(self.get_cleaned_data("recurrences")) != 0
or ((not self.get_cleaned_data("position") is None) and (not self.get_cleaned_data("radius") is None))
)
def is_selected(self, cat):
return "category" in self.form.cleaned_data and cat in self.form.cleaned_data["category"]
def is_selected_tag(self, tag):
return "tags" in self.form.cleaned_data and tag in self.form.cleaned_data["tags"]
def get_url_add_tag(self, tag):
full_path = self.request.get_full_path()
result = full_path + ('&' if '?' in full_path else '?') + 'tags=' + str(tag)
return result
def tag_exists(self, tag):
return tag in [t[0] for g in self.form.fields["tags"].choices for t in g[1]]
def set_default_values(request):
if request.user.is_authenticated:
if request.GET.get('status', None) == None:
tempdict = request.GET.copy()
tempdict['status'] = 'published'
request.GET = tempdict
return request
return request
def get_position_radius(self):
if self.get_cleaned_data("position") is None or self.get_cleaned_data("radius") is None:
return ""
else:
return str(self.get_cleaned_data("position")) + ' (' + str(self.get_cleaned_data("radius")) + ' km)'
def is_filtered_by_position_radius(self):
return not self.get_cleaned_data("position") is None and not self.get_cleaned_data("radius") is None
def get_url_add_suggested_position(self, location):
result = self.request.get_full_path()
return result + ('&' if '?' in result else '?') + 'position=' + str(location.pk) + "&radius=" + str(location.suggested_distance)
class EventFilterAdmin(django_filters.FilterSet):
status = django_filters.MultipleChoiceFilter(
choices=Event.STATUS.choices, widget=forms.CheckboxSelectMultiple
)
representative = django_filters.MultipleChoiceFilter(
label=_("Representative version"),
choices=[(True, _("Yes")), (False, _("Non"))],
method="filter_by_representative",
widget=forms.CheckboxSelectMultiple)
import_sources = django_filters.ModelChoiceFilter(
label=_("Imported from"),
method="filter_by_source",
queryset=RecurrentImport.objects.all().order_by("name__unaccent")
)
def filter_by_source(self, queryset, name, value):
src = RecurrentImport.objects.get(pk=value.pk).source
return queryset.filter(import_sources__contains=[src])
def filter_by_representative(self, queryset, name, value):
if value is None or len(value) != 1:
return queryset
else:
q = (Q(other_versions__isnull=True) |
Q(other_versions__representative=F('pk')) |
Q(other_versions__representative__isnull=True))
if value[0] == True:
return queryset.filter(q)
else:
return queryset.exclude(q)
class Meta:
model = Event
fields = ["status"]
class MessagesFilterAdmin(django_filters.FilterSet):
closed = django_filters.MultipleChoiceFilter(
label="Status",
choices=((True, _("Closed")), (False, _("Open"))),
widget=forms.CheckboxSelectMultiple,
)
spam = django_filters.MultipleChoiceFilter(
label="Spam",
choices=((True, _("Spam")), (False, _("Non spam"))),
widget=forms.CheckboxSelectMultiple,
)
class Meta:
model = Message
fields = ["closed", "spam"]
class SimpleSearchEventFilter(django_filters.FilterSet):
q = django_filters.CharFilter(method="custom_filter",
label=_("Search"),
widget=forms.TextInput(attrs={"type": "search"})
)
status = django_filters.MultipleChoiceFilter(
label="Filtrer par status",
choices=Event.STATUS.choices,
field_name="status",
widget=forms.CheckboxSelectMultiple,
)
def custom_filter(self, queryset, name, value):
search_query = SearchQuery(value, config="french")
qs = queryset.filter(
Q(title__icontains=value)
| Q(category__name__icontains=value)
| Q(tags__icontains=[value])
| Q(exact_location__name__icontains=value)
| Q(description__icontains=value)
)
for f in ["title", "category__name", "exact_location__name", "description"]:
params = {
f
+ "_hl": SearchHeadline(
f,
search_query,
start_sel='<span class="highlight">',
stop_sel="</span>",
config="french",
)
}
qs = qs.annotate(**params)
return qs
class Meta:
model = Event
fields = ["q"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not kwargs["request"].user.is_authenticated:
self.form.fields.pop("status")
class SearchEventFilter(django_filters.FilterSet):
tags = django_filters.CharFilter(lookup_expr="icontains")
title = django_filters.CharFilter(method="hl_filter_contains")
location = django_filters.CharFilter(method="hl_filter_contains")
description = django_filters.CharFilter(method="hl_filter_contains")
start_day = django_filters.DateFromToRangeFilter(
widget=django_filters.widgets.RangeWidget(attrs={"type": "date"})
)
status = django_filters.MultipleChoiceFilter(
label="Filtrer par status",
choices=Event.STATUS.choices,
field_name="status",
widget=forms.CheckboxSelectMultiple,
)
o = django_filters.OrderingFilter(
# tuple-mapping retains order
fields=(
("title", "title"),
("description", "description"),
("start_day", "start_day"),
),
)
def hl_filter_contains(self, queryset, name, value):
# first check if it contains
filter_contains = {name + "__contains": value}
queryset = queryset.filter(**filter_contains)
# then hightlight the result
search_query = SearchQuery(value, config="french")
params = {
name
+ "_hl": SearchHeadline(
name,
search_query,
start_sel='<span class="highlight">',
stop_sel="</span>",
config="french",
)
}
return queryset.annotate(**params)
class Meta:
model = Event
fields = ["title", "location", "description", "category", "tags", "start_day"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not kwargs["request"].user.is_authenticated:
self.form.fields.pop("status")
class DuplicatedEventsFilter(django_filters.FilterSet):
fixed = django_filters.BooleanFilter(
label="Résolu",
field_name='representative', method="fixed_qs")
class Meta:
model = DuplicatedEvents
fields = []
def fixed_qs(self, queryset, name, value):
return DuplicatedEvents.not_fixed_qs(queryset, value)
class RecurrentImportFilter(django_filters.FilterSet):
name = django_filters.ModelMultipleChoiceFilter(
label="Filtrer par nom",
field_name="name",
queryset=RecurrentImport.objects.all().order_by("name__unaccent")
)
class Meta:
model = RecurrentImport
fields = ["name"]

View File

@ -16,15 +16,14 @@ from django.forms import (
)
from django_better_admin_arrayfield.forms.widgets import DynamicArrayWidget
from .utils import PlaceGuesser
from .models import (
Event,
RecurrentImport,
CategorisationRule,
ModerationAnswer,
ModerationQuestion,
Place,
Category,
Tag,
Message
)
from django.conf import settings
from django.core.files import File
@ -33,119 +32,24 @@ from django.utils.translation import gettext_lazy as _
from string import ascii_uppercase as auc
from .templatetags.utils_extra import int_to_abc
from django.utils.safestring import mark_safe
from django.utils.timezone import localtime
from django.utils.formats import localize
from .templatetags.event_extra import event_field_verbose_name, field_to_html
import os
import logging
logger = logging.getLogger(__name__)
class GroupFormMixin:
template_name = 'agenda_culturel/forms/div_group.html'
class FieldGroup:
def __init__(self, id, label, display_label=False, maskable=False, default_masked=True):
self.id = id
self.label = label
self.display_label = display_label
self.maskable = maskable
self.default_masked = default_masked
def toggle_field_name(self):
return 'group_' + self.id
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.groups = []
def add_group(self, *args, **kwargs):
self.groups.append(GroupFormMixin.FieldGroup(*args, **kwargs))
if self.groups[-1].maskable:
self.fields[self.groups[-1].toggle_field_name()] = BooleanField(required=False)
self.fields[self.groups[-1].toggle_field_name()].toggle_group = True
def get_fields_in_group(self, g):
return [f for f in self.visible_fields() if not hasattr(f.field, "toggle_group") and hasattr(f.field, "group_id") and f.field.group_id == g.id]
def get_no_group_fields(self):
return [f for f in self.visible_fields() if not hasattr(f.field, "toggle_group") and (not hasattr(f.field, "group_id") or f.field.group_id == None)]
def fields_by_group(self):
return [(g, self.get_fields_in_group(g)) for g in self.groups] + [(GroupFormMixin.FieldGroup("other", _("Other")), self.get_no_group_fields())]
def clean(self):
result = super().clean()
if result:
data = dict(self.data)
# for each masked group, we remove data
for g in self.groups:
if g.maskable and not g.toggle_field_name() in data:
fields = self.get_fields_in_group(g)
for f in fields:
self.cleaned_data[f.name] = None
return result
class TagForm(ModelForm):
required_css_class = 'required'
class Meta:
model = Tag
fields = ["name", "description", "in_included_suggestions", "in_excluded_suggestions", "principal"]
widgets = {
"name": HiddenInput()
}
class TagRenameForm(Form):
required_css_class = 'required'
name = CharField(
label=_('Name of new tag'),
required=True
)
force = BooleanField(
label=_('Force renaming despite the existence of events already using the chosen tag.'),
)
def __init__(self, *args, **kwargs):
force = kwargs.pop("force", False)
name = kwargs.pop("name", None)
super().__init__(*args, **kwargs)
if not (force or (not len(args) == 0 and 'force' in args[0])):
del self.fields["force"]
if not name is None and self.fields["name"].initial is None:
self.fields["name"].initial = name
def is_force(self):
return "force" in self.fields and self.cleaned_data["force"] == True
class URLSubmissionForm(Form):
required_css_class = 'required'
url = URLField(max_length=512)
category = ModelChoiceField(
label=_("Category"),
queryset=Category.objects.all().order_by("name"),
initial=None,
help_text=_('Optional. If you don''t specify a category, we''ll find it for you.'),
required=False,
)
tags = MultipleChoiceField(
label=_("Tags"),
initial=None,
choices=[],
required=False
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["tags"].choices = Tag.get_tag_groups(all=True)
@ -158,45 +62,22 @@ class DynamicArrayWidgetTags(DynamicArrayWidget):
class RecurrentImportForm(ModelForm):
required_css_class = 'required'
defaultTags = MultipleChoiceField(
label=_("Tags"),
initial=None,
choices=[],
required=False
)
class Meta:
model = RecurrentImport
fields = "__all__"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["defaultTags"].choices = Tag.get_tag_groups(all=True)
widgets = {
"defaultTags": DynamicArrayWidgetTags(),
}
class CategorisationRuleImportForm(ModelForm):
required_css_class = 'required'
class Meta:
model = CategorisationRule
fields = "__all__"
class EventForm(GroupFormMixin, ModelForm):
required_css_class = 'required'
class EventForm(ModelForm):
old_local_image = CharField(widget=HiddenInput(), required=False)
simple_cloning = CharField(widget=HiddenInput(), required=False)
tags = MultipleChoiceField(
label=_("Tags"),
initial=None,
choices=[],
required=False
)
class Meta:
model = Event
@ -205,11 +86,7 @@ class EventForm(GroupFormMixin, ModelForm):
"modified_date",
"moderated_date",
"import_sources",
"image",
"moderated_by_user",
"modified_by_user",
"created_by_user",
"imported_by_user"
"image"
]
widgets = {
"start_day": TextInput(
@ -231,72 +108,21 @@ class EventForm(GroupFormMixin, ModelForm):
"other_versions": HiddenInput(),
"uuids": MultipleHiddenInput(),
"reference_urls": DynamicArrayWidgetURLs(),
"tags": DynamicArrayWidgetTags(),
}
def __init__(self, *args, **kwargs):
is_authenticated = kwargs.pop("is_authenticated", False)
self.cloning = kwargs.pop("is_cloning", False)
self.simple_cloning = kwargs.pop("is_simple_cloning", False)
super().__init__(*args, **kwargs)
if not is_authenticated:
del self.fields["status"]
del self.fields["organisers"]
self.fields['category'].queryset = self.fields['category'].queryset.order_by('name')
self.fields['category'].empty_label = None
self.fields['category'].initial = Category.get_default_category()
self.fields['tags'].choices = Tag.get_tag_groups(all=True)
# set groups
self.add_group('main', _('Main fields'))
self.fields['title'].group_id = 'main'
self.add_group('start', _('Start of event'))
self.fields['start_day'].group_id = 'start'
self.fields['start_time'].group_id = 'start'
self.add_group('end', _('End of event'))
self.fields['end_day'].group_id = 'end'
self.fields['end_time'].group_id = 'end'
self.add_group('recurrences',
_('This is a recurring event'),
maskable=True,
default_masked=not (self.instance and
self.instance.recurrences and
self.instance.recurrences.rrules and
len(self.instance.recurrences.rrules) > 0))
self.fields['recurrences'].group_id = 'recurrences'
self.add_group('details', _('Details'))
self.fields['description'].group_id = 'details'
if is_authenticated:
self.fields['organisers'].group_id = 'details'
self.add_group('location', _('Location'))
self.fields['location'].group_id = 'location'
self.fields['exact_location'].group_id = 'location'
self.add_group('illustration', _('Illustration'))
self.fields['local_image'].group_id = 'illustration'
self.fields['image_alt'].group_id = 'illustration'
if is_authenticated:
self.add_group('meta-admin', _('Meta information'))
self.fields['category'].group_id = 'meta-admin'
self.fields['tags'].group_id = 'meta-admin'
self.fields['status'].group_id = 'meta-admin'
else:
self.add_group('meta', _('Meta information'))
self.fields['category'].group_id = 'meta'
self.fields['tags'].group_id = 'meta'
def is_clone_from_url(self):
return self.cloning
def is_simple_clone_from_url(self):
return self.simple_cloning
def clean_end_day(self):
start_day = self.cleaned_data.get("start_day")
@ -333,69 +159,11 @@ class EventForm(GroupFormMixin, ModelForm):
self.cleaned_data['old_local_image'] != "":
basename = self.cleaned_data['old_local_image']
old = settings.MEDIA_ROOT + "/" + basename
if os.path.isfile(old):
self.cleaned_data['local_image'] = File(name=basename, file=open(old, "rb"))
self.cleaned_data['local_image'] = File(name=basename, file=open(old, "rb"))
class MultipleChoiceFieldAcceptAll(MultipleChoiceField):
def validate(self, value):
pass
class EventModerateForm(ModelForm):
required_css_class = 'required'
tags = MultipleChoiceField(
label=_("Tags"),
help_text=_('Select tags from existing ones.'),
required=False
)
new_tags = MultipleChoiceFieldAcceptAll(
label=_("New tags"),
help_text=_('Create new labels (sparingly). Note: by starting your tag with the characters “TW:”, you''ll create a “trigger warning” tag, and the associated events will be announced as such.'),
widget=DynamicArrayWidget(),
required=False
)
class Meta:
model = Event
fields = [
"status",
"category",
"organisers",
"exact_location",
"tags"
]
widgets = {
"status": RadioSelect
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['category'].queryset = self.fields['category'].queryset.order_by('name')
self.fields['category'].empty_label = None
self.fields['category'].initial = Category.get_default_category()
self.fields['tags'].choices = Tag.get_tag_groups(all=True)
def clean_new_tags(self):
return list(set(self.cleaned_data.get("new_tags")))
def clean(self):
super().clean()
if self.cleaned_data['tags'] is None:
self.cleaned_data['tags'] = []
if not self.cleaned_data.get('new_tags') is None:
self.cleaned_data['tags'] += self.cleaned_data.get('new_tags')
self.cleaned_data['tags'] = list(set(self.cleaned_data['tags']))
class BatchImportationForm(Form):
required_css_class = 'required'
json = CharField(
label="JSON",
widget=Textarea(attrs={"rows": "10"}),
@ -405,8 +173,6 @@ class BatchImportationForm(Form):
class FixDuplicates(Form):
required_css_class = 'required'
action = ChoiceField()
def __init__(self, *args, **kwargs):
@ -484,6 +250,7 @@ class FixDuplicates(Form):
def get_selected_event(self, edup):
selected = self.get_selected_event_code()
logger.warning("selected " + str(selected))
for e in edup.get_duplicated():
if e.pk == selected:
return e
@ -491,9 +258,7 @@ class FixDuplicates(Form):
class SelectEventInList(Form):
required_css_class = 'required'
event = ChoiceField(label=_('Event'))
event = ChoiceField()
def __init__(self, *args, **kwargs):
events = kwargs.pop("events", None)
@ -505,9 +270,7 @@ class SelectEventInList(Form):
class MergeDuplicates(Form):
required_css_class = 'required'
checkboxes_fields = ["reference_urls", "description", "tags"]
checkboxes_fields = ["reference_urls", "description"]
def __init__(self, *args, **kwargs):
self.duplicates = kwargs.pop("duplicates", None)
@ -548,17 +311,17 @@ class MergeDuplicates(Form):
'<li><a href="' + e.get_absolute_url() + '">' + e.title + "</a></li>"
)
result += (
"<li>Création&nbsp;: " + localize(e.created_date) + "</li>"
"<li>Création&nbsp;: " + localize(localtime(e.created_date)) + "</li>"
)
result += (
"<li>Dernière modification&nbsp;: "
+ localize(e.modified_date)
+ localize(localtime(e.modified_date))
+ "</li>"
)
if e.imported_date:
result += (
"<li>Dernière importation&nbsp;: "
+ localize(e.imported_date)
+ localize(localtime(e.imported_date))
+ "</li>"
)
result += "</ul>"
@ -609,7 +372,7 @@ class MergeDuplicates(Form):
result += '<input id="' + id + '" name="' + key + '"'
if key in MergeDuplicates.checkboxes_fields:
result += ' type="checkbox"'
if checked and value in checked:
if value in checked:
result += " checked"
else:
result += ' type="radio"'
@ -641,7 +404,7 @@ class MergeDuplicates(Form):
result = []
for s in selected:
for e in self.duplicates.get_duplicated():
if e.pk == s:
if e.pk == selected:
result.append(e)
break
return result
@ -654,9 +417,47 @@ class MergeDuplicates(Form):
return None
class CategorisationForm(Form):
required_css_class = 'required'
class ModerationQuestionForm(ModelForm):
class Meta:
model = ModerationQuestion
fields = "__all__"
class ModerationAnswerForm(ModelForm):
class Meta:
model = ModerationAnswer
exclude = ["question"]
widgets = {
"adds_tags": DynamicArrayWidgetTags(),
"removes_tags": DynamicArrayWidgetTags(),
}
class ModerateForm(ModelForm):
class Meta:
model = Event
fields = []
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
mqs = ModerationQuestion.objects.all()
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,
)
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):
def __init__(self, *args, **kwargs):
if "events" in kwargs:
events = kwargs.pop("events", None)
@ -687,8 +488,6 @@ class CategorisationForm(Form):
class EventAddPlaceForm(Form):
required_css_class = 'required'
place = ModelChoiceField(
label=_("Place"),
queryset=Place.objects.all().order_by("name"),
@ -725,9 +524,7 @@ class EventAddPlaceForm(Form):
return self.instance
class PlaceForm(GroupFormMixin, ModelForm):
required_css_class = 'required'
class PlaceForm(ModelForm):
apply_to_all = BooleanField(
initial=True,
label=_(
@ -741,70 +538,13 @@ class PlaceForm(GroupFormMixin, ModelForm):
fields = "__all__"
widgets = {"location": TextInput()}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.add_group('header', _('Header'))
self.fields['name'].group_id = 'header'
self.add_group('address', _('Address'))
self.fields['address'].group_id = 'address'
self.fields['postcode'].group_id = 'address'
self.fields['city'].group_id = 'address'
self.fields['location'].group_id = 'address'
self.add_group('meta', _('Meta'))
self.fields['aliases'].group_id = 'meta'
self.add_group('information', _('Information'))
self.fields['description'].group_id = 'information'
def as_grid(self):
result = ('<div class="grid"><div>'
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>
<input type="checkbox" role="switch" id="lock_position">Verrouiller la position</lock>
<script>
document.getElementById("lock_position").onclick = function() {
const field = document.getElementById("id_location");
if (this.checked)
field.setAttribute("readonly", true);
else
field.removeAttribute("readonly");
}
</script>
</div></div>''')
return mark_safe(result)
+ '</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")
class MessageForm(ModelForm):
class Meta:
model = Message
fields = ["subject", "name", "email", "message", "related_event"]
widgets = {"related_event": HiddenInput(), "user": HiddenInput() }
def __init__(self, *args, **kwargs):
self.event = kwargs.pop("event", False)
self.internal = kwargs.pop("internal", False)
super().__init__(*args, **kwargs)
self.fields['related_event'].required = False
if self.internal:
self.fields.pop("name")
self.fields.pop("email")
class MessageEventForm(ModelForm):
class Meta:
model = Message
fields = ["message"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["message"].label = _("Add a comment")

View File

@ -70,17 +70,17 @@ class CExtractor(TwoStepsExtractorNoPause):
tags = []
if first_cat in ["grand spectacle"]:
category = "Spectacles"
tags.append("💃 danse")
tags.append("danse")
elif first_cat in ["theatre", "humour / one man show"]:
category = "Spectacles"
tags.append("🎭 théâtre")
tags.append("théâtre")
elif first_cat in ["chanson francaise", "musique du monde", "pop / rock", "rap", "rnb", "raggae", "variete"]:
category = "Fêtes & Concerts"
tags.append("🎵 concert")
tags.append("concert")
elif first_cat in ["comedie musicale", "humour / one man show", "spectacle equestre"]:
category = "Spectacles"
elif first_cat in ["spectacle pour enfant"]:
tags = ["🎈 jeune public"]
tags = ["jeune public"]
category = None
else:
category = None

View File

@ -11,7 +11,7 @@ class CExtractor(TwoStepsExtractor):
if not category:
return None
mapping = {"Théâtre": "Spectacles", "Concert": "Fêtes & Concerts", "Projection": "Cinéma"}
mapping_tag = {"Théâtre": "🎭 théâtre", "Concert": "🎵 concert", "Projection": None}
mapping_tag = {"Théâtre": "théâtre", "Concert": "concert", "Projection": None}
if category in mapping:
return mapping[category], mapping_tag[category]
else:

View File

@ -3,12 +3,6 @@ from ..extractor_facebook import FacebookEvent
import json5
from bs4 import BeautifulSoup
import json
import os
from datetime import datetime
import logging
logger = logging.getLogger(__name__)
# A class dedicated to get events from a facebook events page
@ -19,27 +13,10 @@ class CExtractor(TwoStepsExtractor):
def build_event_url_list(self, content):
soup = BeautifulSoup(content, "html.parser")
debug = False
found = False
links = soup.find_all("a")
for link in links:
if link.get("href").startswith('https://www.facebook.com/events/'):
self.add_event_url(link.get('href').split('?')[0])
found = True
if not found and debug:
directory = "errors/"
if not os.path.exists(directory):
os.makedirs(directory)
now = datetime.now()
filename = directory + now.strftime("%Y%m%d_%H%M%S") + ".html"
logger.warning("cannot find any event link in events page. Save content page in " + filename)
with open(filename, "w") as text_file:
text_file.write("<!-- " + self.url + " -->\n\n")
text_file.write(content)
def add_event_from_content(
@ -65,7 +42,4 @@ class CExtractor(TwoStepsExtractor):
event["published"] = published
self.add_event(default_values, **event)
else:
logger.warning("cannot find any event in page")

View File

@ -9,12 +9,6 @@ class CExtractor(TwoStepsExtractor):
nom_lieu = "La Comédie de Clermont"
url_referer = "https://lacomediedeclermont.com/saison24-25/"
def is_to_import_from_url(self, url):
if any(keyword in url for keyword in ["podcast", "on-debriefe", "popcorn", "rencontreautour","rencontre-autour"]):
return False
else:
return True
def category_comedie2agenda(self, category):
mapping = {
"Théâtre": "Spectacles",
@ -24,8 +18,8 @@ class CExtractor(TwoStepsExtractor):
"PopCorn Live": "Sans catégorie",
}
mapping_tag = {
"Théâtre": "🎭 théâtre",
"Danse": "💃 danse",
"Théâtre": "théâtre",
"Danse": "danse",
"Rencontre": None,
"Sortie de résidence": "sortie de résidence",
"PopCorn Live": None,
@ -56,35 +50,33 @@ class CExtractor(TwoStepsExtractor):
e_url = (
e.select("a")[0]["href"] + "#" + d
) # a "fake" url specific for each day of this show
if self.is_to_import_from_url(e_url):
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]
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]
)
self.add_event_start_time(e_url, t)
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, tag = self.category_comedie2agenda(
category[-1].contents[0]
)
self.add_event_start_time(e_url, t)
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, tag = self.category_comedie2agenda(
category[-1].contents[0]
)
if category:
self.add_event_category(e_url, category)
if tag:
self.add_event_tag(e_url, tag)
location = (
e.select("div#lieuevtcal")[0]
.contents[-1]
.split("")[-1]
)
if location.replace(" ", "") == "":
location = self.nom_lieu
self.add_event_location(e_url, location)
if category:
self.add_event_category(e_url, category)
if tag:
self.add_event_tag(e_url, tag)
location = (
e.select("div#lieuevtcal")[0]
.contents[-1]
.split("")[-1]
)
if location.replace(" ", "") == "":
location = self.nom_lieu
self.add_event_location(e_url, location)
def add_event_from_content(
self,
@ -106,15 +98,6 @@ class CExtractor(TwoStepsExtractor):
description = soup.select("#descspec")
if description and len(description) > 0:
description = description[0].get_text().replace("Lire plus...", "")
# on ajoute éventuellement les informations complémentaires
d_suite = ""
for d in ["typedesc", "dureedesc", "lieuspec"]:
comp_desc = soup.select("#" + d)
if comp_desc and len(comp_desc) > 0:
d_suite += "\n\n" + comp_desc[0].get_text()
if d_suite != "":
description += "\n\n> Informations complémentaires:" + d_suite
else:
description = None

View File

@ -22,7 +22,7 @@ class CExtractor(TwoStepsExtractor):
for e in data["events"]:
self.add_event_url(e["url"])
if e["tag"] == "Gratuit":
self.add_event_tag(e["url"], "💶 gratuit")
self.add_event_tag(e["url"], "gratuit")
else:
raise Exception("Cannot extract events from javascript")
@ -53,7 +53,7 @@ class CExtractor(TwoStepsExtractor):
if description is None:
description = ""
tags = ["🎵 concert"]
tags = ["concert"]
link_calendar = soup.select('a[href^="https://calendar.google.com/calendar/"]')
if len(link_calendar) == 0:

View File

@ -58,7 +58,7 @@ class CExtractor(TwoStepsExtractor):
end_day = Extractor.guess_end_day(start_day, start_time, end_time)
url_human = event_url
tags = ["🎵 concert"]
tags = ["concert"]
image = soup.select("wow-image img[fetchpriority=high]")
if image:

View File

@ -10,7 +10,7 @@ class CExtractor(TwoStepsExtractor):
if not category:
return None
mapping = {"Concerts": "Fêtes & Concerts"}
mapping_tag = {"Concerts": "🎵 concert"}
mapping_tag = {"Concerts": "concert"}
if category in mapping:
return mapping[category], mapping_tag[category]
else:

View File

@ -1,91 +0,0 @@
from ..generic_extractors import *
from bs4 import BeautifulSoup
from datetime import datetime
# A class dedicated to get events from Cinéma Le Rio (Clermont-Ferrand)
# URL: https://www.cinemalerio.com/evenements/
class CExtractor(TwoStepsExtractorNoPause):
def __init__(self):
super().__init__()
self.possible_dates = {}
self.theater = None
def build_event_url_list(self, content, infuture_days=180):
soup = BeautifulSoup(content, "html.parser")
links = soup.select("td.seance_link a")
if links:
for l in links:
print(l["href"])
self.add_event_url(l["href"])
def to_text_select_one(soup, filter):
e = soup.select_one(filter)
if e is None:
return None
else:
return e.text
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.select_one("h1").text
alerte_date = CExtractor.to_text_select_one(soup, ".alerte_date")
if alerte_date is None:
return
dh = alerte_date.split("à")
# if date is not found, we skip
if len(dh) != 2:
return
date = Extractor.parse_french_date(dh[0], default_year=datetime.now().year)
time = Extractor.parse_french_time(dh[1])
synopsis = CExtractor.to_text_select_one(soup, ".synopsis_bloc")
special_titre = CExtractor.to_text_select_one(soup, ".alerte_titre")
special = CExtractor.to_text_select_one(soup, ".alerte_text")
# it's not a specific event: we skip it
special_lines = None if special is None else special.split('\n')
if special is None or len(special_lines) == 0 or \
(len(special_lines) == 1 and special_lines[0].strip().startswith('En partenariat')):
return
description = "\n\n".join([x for x in [synopsis, special_titre, special] if not x is None])
image = soup.select_one(".col1 img")
image_alt = None
if not image is None:
image_alt = image["alt"]
image = image["src"]
self.add_event_with_props(
default_values,
event_url,
title,
None,
date,
None,
description,
[],
recurrences=None,
uuids=[event_url],
url_human=event_url,
start_time=time,
end_day=None,
end_time=None,
published=published,
image=image,
image_alt=image_alt
)

View File

@ -66,7 +66,7 @@ class SimpleDownloader(Downloader):
class ChromiumHeadlessDownloader(Downloader):
def __init__(self, pause=True, noimage=True):
def __init__(self, pause=True):
super().__init__()
self.pause = pause
self.options = Options()
@ -78,31 +78,17 @@ class ChromiumHeadlessDownloader(Downloader):
self.options.add_argument("--disable-dev-shm-usage")
self.options.add_argument("--disable-browser-side-navigation")
self.options.add_argument("--disable-gpu")
if noimage:
self.options.add_experimental_option(
"prefs", {
# block image loading
"profile.managed_default_content_settings.images": 2,
}
)
self.options.add_experimental_option(
"prefs", {
# block image loading
"profile.managed_default_content_settings.images": 2,
}
)
self.service = Service("/usr/bin/chromedriver")
self.driver = webdriver.Chrome(service=self.service, options=self.options)
def screenshot(self, url, path_image):
print("Screenshot {}".format(url))
try:
self.driver.get(url)
if self.pause:
time.sleep(2)
self.driver.save_screenshot(path_image)
except:
print(f">> Exception: {URL}")
return False
return True
def download(self, url, referer=None, post=None):
if post:
raise Exception("POST method with Chromium headless not yet implemented")

View File

@ -2,7 +2,7 @@ from abc import ABC, abstractmethod
from datetime import datetime, time, date, timedelta
import re
import unicodedata
from django.utils import timezone
@ -49,7 +49,7 @@ class Extractor(ABC):
return i + 1
return None
def parse_french_date(text, default_year=None):
def parse_french_date(text):
# format NomJour Numero Mois Année
m = re.search(
"[a-zA-ZéÉûÛ:.]+[ ]*([0-9]+)[er]*[ ]*([a-zA-ZéÉûÛ:.]+)[ ]*([0-9]+)", text
@ -73,15 +73,8 @@ class Extractor(ABC):
month = int(m.group(2))
year = m.group(3)
else:
# format Numero Mois Annee
m = re.search("([0-9]+)[er]*[ ]*([a-zA-ZéÉûÛ:.]+)", text)
if m:
day = m.group(1)
month = Extractor.guess_month(m.group(2))
year = default_year
else:
# TODO: consolider les cas non satisfaits
return None
# TODO: consolider les cas non satisfaits
return None
if month is None:
return None
@ -194,7 +187,6 @@ class Extractor(ABC):
"start_day": start_day,
"uuids": uuids,
"location": location if location else self.default_value_if_exists(default_values, "location"),
"organisers": self.default_value_if_exists(default_values, "organisers"),
"description": description,
"tags": tags + tags_default,
"published": published,
@ -247,28 +239,6 @@ class Extractor(ABC):
from .extractor_ggcal_link import GoogleCalendarLinkEventExtractor
if single_event:
return [FacebookEventExtractor(), GoogleCalendarLinkEventExtractor(), EventNotFoundExtractor()]
return [FacebookEventExtractor(), GoogleCalendarLinkEventExtractor()]
else:
return [ICALExtractor(), FacebookEventExtractor(), GoogleCalendarLinkEventExtractor(), EventNotFoundExtractor()]
# A class that only produce a not found event
class EventNotFoundExtractor(Extractor):
def extract(
self, content, url, url_human=None, default_values=None, published=False
):
self.set_header(url)
self.clear_events()
self.add_event(default_values, "événement sans titre depuis " + url,
None, timezone.now().date(), None,
"l'import a échoué, la saisie doit se faire manuellement à partir de l'url source " + url,
[], [url], published=False, url_human=url)
return self.get_structure()
def clean_url(url):
return url
return [ICALExtractor(), FacebookEventExtractor(), GoogleCalendarLinkEventExtractor()]

View File

@ -239,7 +239,7 @@ class FacebookEventExtractor(Extractor):
result = "https://www.facebook.com" + u.path
# remove name in the url
match = re.match(r"(.*/events)/s/([a-zA-Z-][a-zA-Z-0-9-]+)/([0-9/]*)", result)
match = re.match(r"(.*/events)/s/([a-zA-Z-][a-zA-Z-0-9]+)/([0-9/]*)", result)
if match:
result = match[1] + "/" + match[3]

View File

@ -264,13 +264,9 @@ class TwoStepsExtractorNoPause(TwoStepsExtractor):
only_future=True,
ignore_404=True
):
if hasattr(self.downloader, "pause"):
pause = self.downloader.pause
else:
pause = False
pause = self.downloader.pause
self.downloader.pause = False
result = super().extract(content, url, url_human, default_values, published, only_future, ignore_404)
self.downloader.pause = pause
return result
return result

View File

@ -1,11 +1,6 @@
from .downloader import *
from .extractor import *
import logging
logger = logging.getLogger(__name__)
class URL2Events:
def __init__(
@ -34,9 +29,8 @@ class URL2Events:
else:
# if the extractor is not defined, use a list of default extractors
for e in Extractor.get_default_extractors(self.single_event):
logger.warning('Extractor::' + type(e).__name__)
e.set_downloader(self.downloader)
events = e.extract(content, url, url_human, default_values, published)
if events is not None and len(events) > 0:
if events is not None:
return events
return None

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,11 @@
# Generated by Django 4.2.9 on 2024-10-10 20:35
from django.db import migrations
from agenda_culturel.models import Place
from django.contrib.gis.geos import Point
def change_coord_format(apps, schema_editor):
Place = apps.get_model("agenda_culturel", "Place")
places = Place.objects.values("location", "location_pt").all()
places = Place.objects.all()
for p in places:
l = p.location.split(',')
@ -13,15 +13,14 @@ def change_coord_format(apps, schema_editor):
p.location_pt = Point(float(l[1]), float(l[0]))
else:
p.location_pt = Point(3.08333, 45.783329)
p.save(update_fields=["location_pt"])
p.save()
def reverse_coord_format(apps, schema_editor):
Place = apps.get_model("agenda_culturel", "Place")
places = Place.objects.values("location", "location_pt").all()
places = Place.objects.all()
for p in places:
p.location = ','.join([p.location_pt[1], p.location_pt[0]])
p.save(update_fields=["location"])
p.save()

View File

@ -184,6 +184,15 @@ def update_database_new(apps, schema_editor):
def update_database_old(apps, schema_editor):
update_database(apps, new_cats)
def add_tags(apps, schema_editor):
Tag = apps.get_model("agenda_culturel", "Tag")
new_tags = ["cinéma", "théâtre", "concert", "conférence", "exposition"]
new_tags = [Tag(name=t, description="", principal=True) for t in new_tags if Tag.objects.filter(name=t).count() == 0]
Tag.objects.bulk_create(new_tags)
def do_nothing(apps, schema_editor):
pass
@ -197,6 +206,7 @@ class Migration(migrations.Migration):
migrations.RunPython(create_new_categories, reverse_code=delete_new_categories),
migrations.RunPython(update_preserved_categories_new, reverse_code=update_preserved_categories_old),
migrations.RunPython(update_database_new, reverse_code=update_database_old),
migrations.RunPython(delete_old_categories, reverse_code=create_old_categories)
migrations.RunPython(delete_old_categories, reverse_code=create_old_categories),
migrations.RunPython(add_tags, reverse_code=do_nothing)
]

View File

@ -1,19 +0,0 @@
# Generated by Django 4.2.9 on 2024-11-13 09:56
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0108_remove_duplicated_categories'),
]
operations = [
migrations.DeleteModel(
name='ModerationAnswer',
),
migrations.DeleteModel(
name='ModerationQuestion',
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 4.2.9 on 2024-11-13 17:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0109_delete_moderationanswer_delete_moderationquestion'),
]
operations = [
migrations.AddField(
model_name='tag',
name='in_excluded_suggestions',
field=models.BooleanField(default=False, help_text='This tag will be part of the excluded suggestions.', verbose_name='In excluded suggestions'),
),
migrations.AddField(
model_name='tag',
name='in_included_suggestions',
field=models.BooleanField(default=False, help_text='This tag will be part of the included suggestions.', verbose_name='In included suggestions'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 4.2.9 on 2024-11-17 12:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0110_tag_in_excluded_suggestions_and_more'),
]
operations = [
migrations.AlterField(
model_name='referencelocation',
name='main',
field=models.IntegerField(default=0, help_text='This location is one of the main locations (shown first higher values).', verbose_name='Main'),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 4.2.9 on 2024-11-20 15:42
from django.db import migrations
import django_ckeditor_5.fields
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0111_alter_referencelocation_main'),
]
operations = [
migrations.AddField(
model_name='place',
name='description',
field=django_ckeditor_5.fields.CKEditor5Field(blank=True, help_text='Description of the place, including accessibility.', null=True, verbose_name='Description'),
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 4.2.9 on 2024-11-20 21:40
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0112_place_description'),
]
operations = [
migrations.RemoveField(
model_name='tag',
name='category',
),
]

View File

@ -1,35 +0,0 @@
# Generated by Django 4.2.9 on 2024-11-22 10:12
from django.db import migrations, models
import django.db.models.deletion
import django_ckeditor_5.fields
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0113_remove_tag_category'),
]
operations = [
migrations.CreateModel(
name='Organisation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='Organisation name', max_length=512, unique=True, verbose_name='Name')),
('website', models.URLField(blank=True, help_text='Website of the organisation', max_length=1024, null=True, verbose_name='Website')),
('description', django_ckeditor_5.fields.CKEditor5Field(blank=True, help_text='Description of the organisation.', null=True, verbose_name='Description')),
('principal_place', models.ForeignKey(blank=True, help_text='Place mainly associated with this organizer. Mainly used if there is a similarity in the name, to avoid redundant displays.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='agenda_culturel.place', verbose_name='Principal place')),
],
),
migrations.AddField(
model_name='event',
name='organisers',
field=models.ManyToManyField(blank=True, help_text='list of event organisers. Organizers will only be displayed if one of them does not normally use the venue.', related_name='organised_events', to='agenda_culturel.organisation', verbose_name='Location (free form)'),
),
migrations.AddField(
model_name='recurrentimport',
name='defaultOrganiser',
field=models.ForeignKey(blank=True, default=None, help_text='Organiser of each imported event', null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='agenda_culturel.organisation', verbose_name='Organiser'),
),
]

View File

@ -1,22 +0,0 @@
# Generated by Django 4.2.9 on 2024-11-22 10:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0114_organisation_event_organisers_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='organisation',
options={'verbose_name': 'Organisation', 'verbose_name_plural': 'Organisations'},
),
migrations.AlterField(
model_name='event',
name='organisers',
field=models.ManyToManyField(blank=True, help_text='list of event organisers. Organizers will only be displayed if one of them does not normally use the venue.', related_name='organised_events', to='agenda_culturel.organisation', verbose_name='Organisers'),
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 4.2.9 on 2024-11-23 09:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0115_alter_organisation_options_alter_event_organisers'),
]
operations = [
migrations.AddIndex(
model_name='event',
index=models.Index(fields=['start_day', 'start_time'], name='agenda_cult_start_d_68ab5f_idx'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 4.2.9 on 2024-11-23 10:11
from django.db import migrations, models
import django.db.models.functions.text
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0116_event_agenda_cult_start_d_68ab5f_idx'),
]
operations = [
migrations.AddIndex(
model_name='event',
index=models.Index(models.F('start_time'), django.db.models.functions.text.Lower('title'), name='start_time title'),
),
]

View File

@ -1,33 +0,0 @@
# Generated by Django 4.2.9 on 2024-11-23 10:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0117_event_start_time_title'),
]
operations = [
migrations.AlterModelOptions(
name='tag',
options={'verbose_name': 'Étiquette', 'verbose_name_plural': 'Étiquettes'},
),
migrations.AddIndex(
model_name='category',
index=models.Index(fields=['name'], name='agenda_cult_name_28aa03_idx'),
),
migrations.AddIndex(
model_name='referencelocation',
index=models.Index(fields=['name'], name='agenda_cult_name_76f079_idx'),
),
migrations.AddIndex(
model_name='staticcontent',
index=models.Index(fields=['name'], name='agenda_cult_name_fe4995_idx'),
),
migrations.AddIndex(
model_name='tag',
index=models.Index(fields=['name'], name='agenda_cult_name_9c9c74_idx'),
),
]

View File

@ -1,74 +0,0 @@
# Generated by Django 4.2.9 on 2024-11-27 09:00
from django.db import migrations, models
import django.db.models.deletion
import django_better_admin_arrayfield.models.fields
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0118_alter_tag_options_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='tag',
options={'verbose_name': 'Tag', 'verbose_name_plural': 'Tags'},
),
migrations.AlterField(
model_name='event',
name='category',
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='agenda_culturel.category', verbose_name='Category'),
),
migrations.AlterField(
model_name='event',
name='description',
field=models.TextField(blank=True, null=True, verbose_name='Description'),
),
migrations.AlterField(
model_name='event',
name='end_day',
field=models.DateField(blank=True, null=True, verbose_name='End day'),
),
migrations.AlterField(
model_name='event',
name='end_time',
field=models.TimeField(blank=True, null=True, verbose_name='End time'),
),
migrations.AlterField(
model_name='event',
name='exact_location',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='agenda_culturel.place', verbose_name='Location'),
),
migrations.AlterField(
model_name='event',
name='image',
field=models.URLField(blank=True, help_text='External URL of the illustration image', max_length=1024, null=True, verbose_name='Illustration (URL)'),
),
migrations.AlterField(
model_name='event',
name='local_image',
field=models.ImageField(blank=True, max_length=1024, null=True, upload_to='', verbose_name='Illustration'),
),
migrations.AlterField(
model_name='event',
name='start_day',
field=models.DateField(verbose_name='Start day'),
),
migrations.AlterField(
model_name='event',
name='start_time',
field=models.TimeField(blank=True, null=True, verbose_name='Start time'),
),
migrations.AlterField(
model_name='event',
name='tags',
field=django_better_admin_arrayfield.models.fields.ArrayField(base_field=models.CharField(max_length=64), blank=True, null=True, size=None, verbose_name='Tags'),
),
migrations.AlterField(
model_name='event',
name='title',
field=models.CharField(max_length=512, verbose_name='Title'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 4.2.9 on 2024-11-27 18:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0119_alter_tag_options_alter_event_category_and_more'),
]
operations = [
migrations.AddField(
model_name='referencelocation',
name='suggested_distance',
field=models.IntegerField(default=None, help_text='If this distance is given, this location is part of the suggested filters.', null=True, verbose_name='Suggested distance (km)'),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 4.2.9 on 2024-11-27 22:13
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0120_referencelocation_suggested_distance'),
]
operations = [
migrations.AddField(
model_name='contactmessage',
name='related_event',
field=models.ForeignKey(default=None, help_text='The message is associated with this event.', null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='agenda_culturel.event', verbose_name='Related event'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 4.2.9 on 2024-11-29 13:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0121_contactmessage_related_event'),
]
operations = [
migrations.AlterField(
model_name='recurrentimport',
name='processor',
field=models.CharField(choices=[('ical', 'ical'), ('icalnobusy', 'ical no busy'), ('icalnovc', 'ical no VC'), ('lacoope', 'lacoope.org'), ('lacomedie', 'la comédie'), ('lefotomat', 'le fotomat'), ('lapucealoreille', "la puce à l'oreille"), ('Plugin wordpress MEC', 'Plugin wordpress MEC'), ('Facebook events', "Événements d'une page FB"), ('cour3coquins', 'la cour des 3 coquins'), ('arachnee', 'Arachnée concert'), ('rio', 'Le Rio')], default='ical', max_length=20, verbose_name='Processor'),
),
]

View File

@ -1,36 +0,0 @@
# Generated by Django 4.2.9 on 2024-11-29 18:18
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('agenda_culturel', '0122_alter_recurrentimport_processor'),
]
operations = [
migrations.AddField(
model_name='event',
name='created_by_user',
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='created_events', to=settings.AUTH_USER_MODEL, verbose_name='Author of the event creation'),
),
migrations.AddField(
model_name='event',
name='imported_by_user',
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='imported_events', to=settings.AUTH_USER_MODEL, verbose_name='Author of the last importation'),
),
migrations.AddField(
model_name='event',
name='moderated_by_user',
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='moderated_events', to=settings.AUTH_USER_MODEL, verbose_name='Author of the last moderation'),
),
migrations.AddField(
model_name='event',
name='modified_by_user',
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='modified_events', to=settings.AUTH_USER_MODEL, verbose_name='Author of the last modification'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 4.2.9 on 2024-12-06 21:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0123_event_created_by_user_event_imported_by_user_and_more'),
]
operations = [
migrations.AddField(
model_name='place',
name='postcode',
field=models.CharField(blank=True, help_text='The post code is not displayed, but makes it easier to find an address when you enter it.', null=True, verbose_name='Postcode'),
),
]

View File

@ -1,21 +0,0 @@
# Generated by Django 4.2.9 on 2024-12-11 11:33
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0124_place_postcode'),
]
operations = [
migrations.RenameModel(
old_name='ContactMessage',
new_name='Message',
),
migrations.AlterModelOptions(
name='message',
options={'verbose_name': 'Message', 'verbose_name_plural': 'Messages'},
),
]

View File

@ -1,21 +0,0 @@
# Generated by Django 4.2.9 on 2024-12-11 11:56
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('agenda_culturel', '0125_rename_contactmessage_message_alter_message_options'),
]
operations = [
migrations.AddField(
model_name='message',
name='user',
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to=settings.AUTH_USER_MODEL, verbose_name='Author of the message'),
),
]

View File

@ -1,25 +0,0 @@
# Generated by Django 4.2.9 on 2024-12-11 19:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0126_message_user'),
]
operations = [
migrations.AddIndex(
model_name='event',
index=models.Index(fields=['end_day', 'end_time'], name='agenda_cult_end_day_4660a5_idx'),
),
migrations.AddIndex(
model_name='event',
index=models.Index(fields=['status'], name='agenda_cult_status_893243_idx'),
),
migrations.AddIndex(
model_name='event',
index=models.Index(fields=['recurrence_dtstart', 'recurrence_dtend'], name='agenda_cult_recurre_a8911c_idx'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 4.2.9 on 2024-12-11 19:12
from django.db import migrations, models
import django.db.models.functions.text
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0127_event_agenda_cult_end_day_4660a5_idx_and_more'),
]
operations = [
migrations.AddIndex(
model_name='event',
index=models.Index(models.F('start_time'), models.F('start_day'), models.F('end_day'), models.F('end_time'), django.db.models.functions.text.Lower('title'), name='datetimes title'),
),
]

View File

@ -1,57 +0,0 @@
# Generated by Django 4.2.9 on 2024-12-11 19:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0128_event_datetimes_title'),
]
operations = [
migrations.AddIndex(
model_name='batchimportation',
index=models.Index(fields=['created_date'], name='agenda_cult_created_a23990_idx'),
),
migrations.AddIndex(
model_name='batchimportation',
index=models.Index(fields=['status'], name='agenda_cult_status_54b205_idx'),
),
migrations.AddIndex(
model_name='batchimportation',
index=models.Index(fields=['created_date', 'recurrentImport'], name='agenda_cult_created_0296e4_idx'),
),
migrations.AddIndex(
model_name='duplicatedevents',
index=models.Index(fields=['representative'], name='agenda_cult_represe_9a4fa2_idx'),
),
migrations.AddIndex(
model_name='message',
index=models.Index(fields=['related_event'], name='agenda_cult_related_79de3c_idx'),
),
migrations.AddIndex(
model_name='message',
index=models.Index(fields=['user'], name='agenda_cult_user_id_42dc88_idx'),
),
migrations.AddIndex(
model_name='message',
index=models.Index(fields=['date'], name='agenda_cult_date_049c71_idx'),
),
migrations.AddIndex(
model_name='message',
index=models.Index(fields=['spam', 'closed'], name='agenda_cult_spam_22f9b3_idx'),
),
migrations.AddIndex(
model_name='place',
index=models.Index(fields=['name'], name='agenda_cult_name_222846_idx'),
),
migrations.AddIndex(
model_name='place',
index=models.Index(fields=['city'], name='agenda_cult_city_156dc7_idx'),
),
migrations.AddIndex(
model_name='place',
index=models.Index(fields=['location'], name='agenda_cult_locatio_6f3c05_idx'),
),
]

File diff suppressed because it is too large Load Diff

View File

@ -14,8 +14,7 @@ APP_ENV = os_getenv("APP_ENV", "dev")
DEBUG = os_getenv("DEBUG", "true").lower() in ["True", "true", "1", "yes", "y"]
ALLOWED_HOSTS = os_getenv("ALLOWED_HOSTS", "localhost").split(",")
if DEBUG:
ALLOWED_HOSTS = ALLOWED_HOSTS + ['testserver']
if DEBUG:
CSRF_TRUSTED_ORIGINS = os_getenv("CSRF_TRUSTED_ORIGINS", "http://localhost").split(
@ -56,10 +55,9 @@ INSTALLED_APPS = [
"robots",
"debug_toolbar",
"cache_cleaner",
"honeypot",
]
HONEYPOT_FIELD_NAME = "alias_name"
SITE_ID = 1
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
@ -73,7 +71,6 @@ MIDDLEWARE = [
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"debug_toolbar.middleware.DebugToolbarMiddleware",
'django.contrib.sites.middleware.CurrentSiteMiddleware',
# "django.middleware.cache.UpdateCacheMiddleware",
# "django.middleware.common.CommonMiddleware",
# "django.middleware.cache.FetchFromCacheMiddleware",
@ -147,9 +144,10 @@ TIME_ZONE = "Europe/Paris"
USE_I18N = True
USE_TZ = False
USE_TZ = True
LANGUAGES = (
("en-us", _("English")),
("fr", _("French")),
)

View File

@ -1,13 +0,0 @@
from django.contrib import sitemaps
from django.urls import reverse
class StaticViewSitemap(sitemaps.Sitemap):
priority = 0.5
changefreq = "daily"
def items(self):
return ["home", "cette_semaine", "ce_mois_ci", "aujourdhui", "a_venir", "about", "contact"]
def location(self, item):
return reverse(item)

File diff suppressed because one or more lines are too long

View File

@ -1,589 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="196.25674mm"
height="195.90508mm"
viewBox="0 0 196.25674 195.90508"
version="1.1"
id="svg5"
xml:space="preserve"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="alunissage.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="0.67644895"
inkscape:cx="338.53257"
inkscape:cy="476.75438"
inkscape:window-width="1920"
inkscape:window-height="1020"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showguides="true"><sodipodi:guide
position="87.392073,277.9802"
orientation="0,-1"
id="guide14594"
inkscape:locked="false" /></sodipodi:namedview><defs
id="defs2"><inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 95.972889 : 1"
inkscape:vp_y="0 : 999.99997 : 0"
inkscape:vp_z="210.00001 : 47.405071 : 1"
inkscape:persp3d-origin="105 : -2.0949277 : 1"
id="perspective240" /><mask
maskUnits="userSpaceOnUse"
id="mask15432"><rect
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect15434"
width="178.99583"
height="178.99583"
x="-2.2718451"
y="12.442876"
ry="12.835461"
transform="rotate(-6.4517442)" /></mask><mask
maskUnits="userSpaceOnUse"
id="mask15436"><rect
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect15438"
width="178.99583"
height="178.99583"
x="-2.2718451"
y="12.442876"
ry="12.835461"
transform="rotate(-6.4517442)" /></mask></defs><g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-6.8716276,-21.840211)"><rect
style="fill:#003737;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect14856"
width="253.23183"
height="167.6301"
x="-24.006699"
y="-10.841731"
ry="12.835461"
mask="url(#mask15436)"
transform="rotate(11.86311,-35.469528,138.62496)" /><path
sodipodi:type="star"
style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path15414-7"
inkscape:flatsided="false"
sodipodi:sides="5"
sodipodi:cx="117.51744"
sodipodi:cy="29.413408"
sodipodi:r1="11.625795"
sodipodi:r2="5.8128977"
sodipodi:arg1="-0.43776009"
sodipodi:arg2="0.19055844"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 128.04696,24.485096 -4.82184,6.029317 2.23323,7.390235 -7.22426,-2.722688 -6.33842,4.407635 0.35701,-7.712031 -6.15059,-4.666166 7.4449,-2.043609 2.53715,-7.291484 4.24419,6.44901 z"
inkscape:transform-center-x="-0.079786515"
inkscape:transform-center-y="-0.21888701"
transform="matrix(0.26075369,0.0547741,-0.0547741,0.26075369,114.71036,76.196413)" /><path
id="path14500"
mask="url(#mask15432)"
style="fill:#c5beb2;fill-opacity:1;stroke:none;stroke-linecap:round;stroke-linejoin:round"
d="m 52.195264,77.951831 c -0.0995,-0.0019 -0.200891,0.01327 -0.300757,0.04754 -0.456533,0.156667 -0.698235,0.650377 -0.541569,1.10691 l 2.971911,8.660453 c -3.387288,2.212886 -7.162412,5.308918 -10.404532,3.340364 -1.529299,-0.928561 -15.94685,17.177302 -17.573605,17.823722 -32.2185841,12.80254 -53.107353,33.35874 -53.107352,56.53505 2e-6,38.81626 58.59402,70.28305 130.87366,70.28305 72.27965,0 130.87419,-31.46679 130.87419,-70.28305 0,-38.81626 -59.1882,-79.527878 -130.87419,-70.283051 -23.606895,3.044413 -33.441303,-5.838959 -44.427257,-9.480042 -0.903167,-0.299337 -2.018561,2.77e-4 -3.188953,0.712101 l -0.656291,0.399459 -2.839103,-8.273397 c -0.122395,-0.356666 -0.450783,-0.582285 -0.806152,-0.589111 z"
transform="rotate(11.86311,-33.77711,138.8008)" /><g
id="g1576"
style="stroke-width:0.6;stroke-dasharray:none"
transform="rotate(11.86311,105,119.79275)"><path
style="fill:#fff7e7;fill-opacity:0.55895;stroke:none;stroke-width:0.6;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 102.8368,132.68366 c 1.03176,3.55726 17.34099,3.88136 17.73538,2.3582 0.39438,-1.52316 -1.52847,-2.36494 -1.52847,-2.36494 -1.08228,3.21929 -7.28172,2.38299 -13.21987,2.03963 z"
id="path1162-6"
sodipodi:nodetypes="csccc" /><path
style="fill:#ffffff;fill-opacity:0.294988;stroke:none;stroke-width:0.6;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 102.6432,132.68366 c -0.93632,0.70147 0.77648,-0.0567 -1.60327,0.4643 1.39003,2.05537 5.21589,1.69949 5.99161,2.3598 l -3.68801,-1.88107 z"
id="path1533"
sodipodi:nodetypes="ccccc" /><path
style="fill:#000000;fill-opacity:0.294988;stroke:none;stroke-width:0.6;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 120.6255,133.36309 c 1.54184,3.26173 -10.17172,2.70066 -13.59396,2.14467 8.66787,2.8241 15.90667,3.7079 17.91461,-0.53782 -2.54611,-0.0305 -3.04763,-0.99487 -4.32065,-1.60685 z"
id="path1533-7"
sodipodi:nodetypes="cccc" /><path
style="fill:none;stroke:#000000;stroke-width:0.6;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 102.6432,132.68366 c 1.03176,3.55726 17.34099,3.88136 17.73538,2.3582 0.39438,-1.52316 -1.52847,-2.36494 -1.52847,-2.36494"
id="path1162" /></g><g
id="g1576-5"
transform="matrix(0.51059462,0.20026137,-0.21398251,0.68212303,154.90211,50.373061)"
style="stroke-width:0.679551;stroke-dasharray:none"><path
style="fill:#fff7e7;fill-opacity:0.55895;stroke:none;stroke-width:0.679551;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 103.34353,133.62669 c 1.03176,3.55726 16.83426,2.93833 17.22865,1.41517 0.39438,-1.52316 -1.52847,-2.36494 -1.52847,-2.36494 -0.19099,0.56812 -1.44864,0.34803 -1.9288,0.68854 -2.24071,1.58902 -6.40085,1.63386 -11.29107,1.35109 z"
id="path1162-6-3"
sodipodi:nodetypes="cscscc" /><path
style="fill:#ffffff;fill-opacity:0.294988;stroke:none;stroke-width:0.679551;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 103.34353,133.62669 c -0.93632,0.70147 0.77648,-0.0567 -1.60327,0.4643 1.39003,2.05537 4.51556,0.75646 5.29128,1.41677 z"
id="path1533-5"
sodipodi:nodetypes="ccccc" /><path
style="fill:#000000;fill-opacity:0.294988;stroke:none;stroke-width:0.679551;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 119.62517,132.53874 c 4.06792,4.14821 -10.28515,2.76677 -13.80133,2.17781 8.66787,2.8241 18.39394,5.38385 20.40188,1.13813 -3.61173,-2.57319 -5.4227,-2.93647 -6.60055,-3.31594 z"
id="path1533-7-6"
sodipodi:nodetypes="cccc" /><path
style="fill:none;stroke:#000000;stroke-width:0.679551;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 106.64826,132.51145 c -11.319438,2.14423 12.45226,3.77801 13.73032,2.53041 3.06792,-2.99478 -5.8967,-3.56684 -6.68296,-3.13405"
id="path1162-2"
sodipodi:nodetypes="csc" /></g><g
id="g1576-9"
transform="matrix(1.9425519,0.40805381,-0.39033153,1.8581845,-7.6916871,-95.795008)"
style="stroke-width:0.390962;stroke-dasharray:none"><path
style="fill:#fff7e7;fill-opacity:0.55895;stroke:none;stroke-width:0.390962;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 102.8368,132.68366 c 3.10079,2.69207 17.34099,3.88136 17.73538,2.3582 0.39438,-1.52316 -0.91154,-1.8437 -0.91154,-1.8437 -1.81421,2.61261 -7.77833,1.56442 -13.71648,1.22106 z"
id="path1162-6-1"
sodipodi:nodetypes="csccc" /><path
style="fill:#ffffff;fill-opacity:0.294988;stroke:none;stroke-width:0.390962;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 102.6432,132.68366 c -0.93632,0.70147 0.77648,-0.0567 -1.60327,0.4643 1.39003,2.05537 5.38317,0.9933 6.15889,1.65361 l -3.85529,-1.17488 z"
id="path1533-2"
sodipodi:nodetypes="ccccc" /><path
style="fill:#000000;fill-opacity:0.294988;stroke:none;stroke-width:0.390962;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 119.79645,132.67692 c 3.69728,4.17523 -9.13801,2.69648 -12.59763,2.12465 0.36908,0.83812 13.1969,4.98485 16.72321,1.10249 -0.33987,-0.41869 -2.85256,-2.61516 -4.12558,-3.22714 z"
id="path1533-7-7"
sodipodi:nodetypes="cccc" /><path
style="fill:none;stroke:#000000;stroke-width:0.390962;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 106.05918,131.55532 c -1.77289,-0.20918 -3.71225,0.78168 -3.22238,1.1286 3.42679,2.42499 17.19672,3.69085 17.54178,2.3582 0.21258,-0.82098 -0.24806,-1.44401 -0.71794,-1.84396"
id="path1162-0"
sodipodi:nodetypes="cssc" /></g><g
id="g1576-9-9"
transform="matrix(0.84888437,-0.29780159,0.16736444,0.96227663,-93.058232,39.699665)"
style="stroke-width:0.462959;stroke-dasharray:none"><path
style="fill:#fff7e7;fill-opacity:0.55895;stroke:none;stroke-width:0.462959;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 102.8368,132.68366 c 1.03176,3.55726 17.34099,3.88136 17.73538,2.3582 0.39438,-1.52316 -0.95533,-1.64385 -0.95533,-1.64385 -1.08228,3.21929 -7.85486,1.6619 -13.79301,1.31854 z"
id="path1162-6-1-3"
sodipodi:nodetypes="csccc" /><path
style="fill:#000000;fill-opacity:0.294988;stroke:none;stroke-width:0.462959;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 120.6255,133.36309 c 1.54184,3.26173 -10.20493,2.23076 -13.62717,1.67477 8.66787,2.8241 15.08072,5.33692 17.08866,1.0912 -1.40447,-0.83726 -2.18847,-2.15399 -3.46149,-2.76597 z"
id="path1533-7-7-0"
sodipodi:nodetypes="cccc" /><path
style="fill:none;stroke:#000000;stroke-width:0.462959;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 104.98821,132.30093 c -2.97484,-0.16805 -2.07389,0.81205 -1.79877,1.01131 3.05188,2.21032 16.82736,3.12682 17.18914,1.72962 0.14178,-0.54755 -0.0159,-1.00705 -0.27606,-1.37105"
id="path1162-0-6"
sodipodi:nodetypes="cssc" /></g><g
id="g13683"
transform="matrix(1.1100761,0.33327025,-0.33327025,1.1100761,40.007817,-13.329961)"
style="stroke-width:0.999979;stroke-dasharray:none"><path
style="fill:#010000;fill-opacity:0.254893;stroke:none;stroke-width:0.707892;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 154.14006,144.23649 c 0.96552,4.45831 37.8521,2.93154 43.31493,0.19171 13.87936,-2.9076 -21.07506,-6.50284 -27.9251,-6.90834 -4.61395,0.0322 -15.93897,3.74541 -15.38983,6.71663 z"
id="path38464-7-3"
sodipodi:nodetypes="cccc"
transform="matrix(1.4126153,0,0,1.4126153,-123.1007,-68.10763)" /><path
style="fill:#010000;fill-opacity:0.254893;stroke:none;stroke-width:0.999979;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 101.41768,142.86865 c 0.56665,2.63741 20.69879,2.10306 23.65815,0.54228 7.55455,-1.57625 -11.56752,-4.04179 -15.31224,-4.34869 -2.51963,-0.0268 -8.67203,2.04966 -8.34591,3.80641 z"
id="path38464-7-3-1"
sodipodi:nodetypes="cccc" /><path
style="fill:#fffaed;fill-opacity:1;stroke:#2d002d;stroke-width:0.999979;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 120.30447,118.03891 -2.84964,9.16842 11.39857,0.61949 3.09743,-0.61949 -1.85846,-10.53129 z"
id="path4251" /><rect
style="fill:#ded6c3;fill-opacity:1;stroke:#2d002d;stroke-width:0.999979;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect611"
width="2.1245613"
height="30.266191"
x="138.8942"
y="56.15984"
ry="0.68848425"
transform="rotate(23.593176)" /><path
id="rect611-5"
style="fill:#e3c56d;fill-opacity:1;stroke:#2d002d;stroke-width:0.999979;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none"
d="m 96.397714,128.03026 0.685099,0.29921 c 0.349537,0.15266 0.508031,0.55695 0.355371,0.90649 l -2.798277,6.40709 c -0.15266,0.34953 -4.707521,1.9948 -5.057058,1.84214 l -0.6851,-0.29921 c -0.349537,-0.15266 -0.67203,-0.69387 -0.355371,-0.90649 l 4.953966,-3.32627 1.994881,-4.56759 c 0.152659,-0.34954 0.556951,-0.50803 0.906489,-0.35537 z"
sodipodi:nodetypes="ssssssscss" /><rect
style="fill:#969184;fill-opacity:1;stroke:#2d002d;stroke-width:0.999979;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect611-3-6"
width="1.5656743"
height="26.733509"
x="-82.484116"
y="149.62541"
ry="0.68848425"
transform="matrix(-0.91641041,0.40023989,0.40023989,0.91641041,0,0)" /><rect
style="fill:#c2bbaa;fill-opacity:1;stroke:#2d002d;stroke-width:0.999979;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect611-3-6-7"
width="1.9859811"
height="27.541084"
x="147.46117"
y="46.883339"
ry="0.68848425"
transform="rotate(23.593176)" /><rect
style="fill:#ded6c3;fill-opacity:1;stroke:#2d002d;stroke-width:0.999979;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect611-3"
width="2.1245613"
height="30.266191"
x="-90.528412"
y="155.31551"
ry="0.68848425"
transform="matrix(-0.91641041,0.40023989,0.40023989,0.91641041,0,0)" /><path
style="fill:#fffaed;fill-opacity:1;stroke:#2d002d;stroke-width:0.999979;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 125.74289,77.772227 -13.47406,5.823183 1.85846,8.672825 11.79229,0.697766 z"
id="path4138"
sodipodi:nodetypes="ccccc" /><path
style="fill:#99978f;fill-opacity:1;stroke:#2d002d;stroke-width:0.999979;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 135.31376,80.374076 -9.57087,-2.601849 -2.51485,14.293188 11.83793,1.441795 z"
id="path4138-5"
sodipodi:nodetypes="ccccc" /><path
style="fill:#fffaed;fill-opacity:1;stroke:#2d002d;stroke-width:0.999979;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 105.09002,106.45299 7.6744,-3.15788 6.19488,-0.37169 2.08204,-10.246073 9.75647,0.577307 1.97857,10.807906 8.74996,-0.78515 4.21251,2.40094 -1.68214,0.43101 -9.50403,14.6199 -23.12458,0.14575 z"
id="path4148"
sodipodi:nodetypes="cccccccccccc" /><path
style="fill:#43423e;fill-opacity:1;stroke:none;stroke-width:0.999979;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 115.05652,103.47421 -2.61954,1.27227 6.03364,1.0729 10.80028,0.28998 8.31727,-0.5 3.1935,-1.8063 -8.29002,0.63247 -0.60277,-4.08554 -10.78233,-1.921271 z"
id="path5886"
sodipodi:nodetypes="cccccccccc" /><path
style="fill:#c9c5ba;fill-opacity:1;stroke:#2d002d;stroke-width:0.999979;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 129.2709,97.103982 -5.22158,4.319628 -2.94277,-2.994889 1.62836,-8.44273 2.95585,-3.079736"
id="path4146"
sodipodi:nodetypes="ccccc" /><path
style="fill:#a29f96;fill-opacity:1;stroke:#2d002d;stroke-width:0.999979;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 141.17794,101.27836 -9.8941,1.84077 -7.23452,-1.69552 4.64514,-3.842756"
id="path4146-6"
sodipodi:nodetypes="cccc" /><path
style="fill:#fffaed;fill-opacity:1;stroke:#2d002d;stroke-width:0.999979;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 127.67311,98.800416 13.50483,2.477954 3.46913,-4.832008 -6.81436,-9.787903 c 0,0 -5.94708,-2.354053 -6.31877,-1.982359 -0.37169,0.371692 -5.82318,2.230155 -5.82318,2.230155 l -0.1239,8.672824 z"
id="path751" /><path
style="fill:#d6d2c7;fill-opacity:1;stroke:#2d002d;stroke-width:0.999979;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 106.50828,94.250596 4.27378,-3.978166 h 2.23016 l 8.02912,2.404919 1.75878,3.060015 c 0,0 0.24779,4.336416 -0.1239,4.212516 -0.37169,-0.123897 -3.71692,2.97354 -3.71692,2.97354 l -6.19488,0.37169 c 0,0 -2.84964,0.74339 -3.22134,0.61949 -0.37169,-0.1239 -3.0348,-3.96472 -3.0348,-3.96472 z"
id="path4140" /><path
style="fill:#96938b;fill-opacity:1;stroke:#2d002d;stroke-width:0.999979;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 111.76884,94.583154 11.03128,1.57438 -0.1239,4.212506 -3.71692,2.97354 -7.80555,0.26127 z"
id="path4140-7"
sodipodi:nodetypes="cccccc" /><path
style="fill:#fffaed;fill-opacity:1;stroke:#2d002d;stroke-width:0.999979;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 106.50828,94.250596 4.27378,-3.978166 h 2.23016 l 3.44263,4.764064 -0.68593,4.733009 -3.0045,3.525607 -3.22134,0.61949 -3.0348,-3.96472 z"
id="path4140-2"
sodipodi:nodetypes="ccccccccc" /><path
style="fill:#d4aa00;fill-opacity:1;stroke:#2d002d;stroke-width:0.999979;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 135.44739,107.04067 8.60932,-0.93131 -0.64536,13.81457 -8.85867,0.80533 z"
id="path4142" /><path
style="fill:#d4aa00;fill-opacity:1;stroke:#2d002d;stroke-width:0.999979;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 105.09002,106.45299 9.37357,-0.6336 0.80533,15.05562 h -7.68164 z"
id="path4144" /><rect
style="fill:#84827b;fill-opacity:1;stroke:#2d002d;stroke-width:0.999979;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect4379"
width="15.495975"
height="7.5343547"
x="116.95485"
y="111.96205"
ry="0.68848425" /><path
id="rect611-5-3-2"
style="fill:#e3c56d;fill-opacity:1;stroke:#2d002d;stroke-width:0.999979;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none"
d="m 143.65023,123.32624 -0.6851,0.29921 c -0.34954,0.15266 -0.50965,0.55766 -0.35537,0.90649 l 1.94251,4.39209 c -0.1135,0.3747 3.00822,0.0602 1.14357,-2.68983 l -1.13913,-2.55259 c -0.15544,-0.34831 -0.55695,-0.50802 -0.90648,-0.35537 z"
sodipodi:nodetypes="sssccss" /><path
id="rect611-5-3-2-1"
style="fill:#e3c56d;fill-opacity:1;stroke:#2d002d;stroke-width:0.999979;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none"
d="m 108.24636,122.39419 0.6851,0.29921 c 0.34954,0.15266 0.50965,0.55766 0.35537,0.90649 l -1.94251,4.39209 c 0.1135,0.3747 -3.00822,0.0602 -1.14357,-2.68983 l 1.13913,-2.55259 c 0.15544,-0.34831 0.55695,-0.50802 0.90648,-0.35537 z"
sodipodi:nodetypes="sssccss" /><g
id="g11682"
transform="translate(68.350363,-11.452931)"
style="stroke-width:0.999979;stroke-dasharray:none"><g
id="g11781"
transform="matrix(-0.01139951,0.54218684,-0.54218684,-0.01139951,138.28173,106.48398)"
style="stroke-width:1.84394;stroke-dasharray:none"><path
style="fill:#eb4402;fill-opacity:1;stroke:#000000;stroke-width:1.84394;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 71.402761,191.27378 c -2.113043,0.27054 -16.51583,0.34774 -21.68703,-7.93835 -5.171192,-8.2861 -3.76397,-13.58169 -2.23407,-17.59746 1.52989,-4.01577 2.90744,-5.92547 5.80687,-8.7927 2.89944,-2.86723 5.99845,-8.59829 11.9153,-8.91316 5.91685,-0.31487 13.24668,1.03438 18.14476,6.51896 15.030422,13.3405 3.49522,36.12115 -11.94583,36.72271 z"
id="path38521"
sodipodi:nodetypes="czzzzcc" /><path
style="fill:none;stroke:#000000;stroke-width:1.84394;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 52.045331,168.07345 c 0,0 4.20442,-1.16049 5.95753,-1.68797 0.53786,-0.16183 0.79434,-3.57452 0.69505,-3.97169 -0.0993,-0.39717 -1.09221,-2.4823 -1.09221,-2.4823"
id="path39627"
sodipodi:nodetypes="cszc" /><path
style="fill:#510701;fill-opacity:1;stroke:#000000;stroke-width:1.84394;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 57.857211,165.65634 c -0.10531,-0.14042 -1.84121,-1.73181 -2.63288,-2.7733 -0.79167,-1.04149 -1.69399,-2.52693 -2.0712,-3.45155 -0.37721,-0.92462 -0.73899,-1.48616 -0.45636,-1.91264 0.28262,-0.42648 0.73423,-0.30422 1.08825,-0.18241 0.35403,0.12181 0.68488,0.55237 0.77231,0.84252 0.0874,0.29015 -0.11967,0.3973 -0.10531,0.667 0.0144,0.2697 -0.0456,0.27495 0.24573,0.91273 0.29133,0.63778 1.16007,1.94648 1.82547,2.70309 0.6654,0.75661 1.8591,1.04674 2.08899,1.83989 0.22989,0.79315 -0.0927,2.0968 -0.60935,2.08381 -0.51669,-0.013 -0.21586,-0.90466 -0.21586,-0.90466"
id="path39629"
sodipodi:nodetypes="czzzzzzzzzzc" /><path
style="fill:#006060;fill-opacity:1;stroke:#000000;stroke-width:1.84394;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 54.017211,161.49841 c -8.81401,3.40711 -10.967102,13.7965 -9.431327,18.16737 1.535777,4.37087 2.112697,8.01053 6.643607,9.05239 -1.87367,-6.18344 -0.6994,-9.06026 -0.24737,-13.55754 0.46657,-4.6417 3.29718,-7.78272 3.03509,-13.66222"
id="path1596-0"
sodipodi:nodetypes="czcac" /></g></g><path
id="rect611-5-3"
style="fill:#e3c56d;fill-opacity:1;stroke:#2d002d;stroke-width:0.999979;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none"
d="m 154.06944,128.30107 -0.6851,0.29921 c -0.34954,0.15266 -0.50803,0.55695 -0.35537,0.90649 l 2.79827,6.40709 c 0.15266,0.34953 4.70752,1.9948 5.05706,1.84214 l 0.6851,-0.29921 c 0.34954,-0.15266 0.67203,-0.69387 0.35537,-0.90649 l -4.95396,-3.32627 -1.99489,-4.56759 c -0.15265,-0.34954 -0.55695,-0.50803 -0.90648,-0.35537 z"
sodipodi:nodetypes="ssssssscss" /><path
style="fill:#010000;fill-opacity:0.0677805;stroke:none;stroke-width:0.999979;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:0.448687"
d="m 55.100582,93.762192 c 2.207116,5.124587 9.321543,8.525138 5.634754,17.629658 9.491228,-1.65632 17.957048,-1.89985 23.547213,-12.53699 -7.75868,1.85165 -15.669366,-1.119461 -22.122416,-2.661586 -3.017395,-0.721085 -6.271335,-2.616591 -7.059551,-2.431082 z"
id="path1056"
sodipodi:nodetypes="cccsc" /></g><g
id="g13651"
transform="matrix(1.1630717,-0.1292531,0.1292531,1.1630717,-24.892917,-1.9386015)"><path
style="fill:#008989;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 42.665935,137.70912 c -1.194356,-3.34818 -2.12459,-5.15105 -4.19392,-6.18037 -3.842759,-1.91146 -7.019594,-1.96348 -11.288621,-1.52093 -2.496518,0.2588 -5.02284,1.69478 -6.752563,3.33155 4.475795,1.38898 6.045382,3.25862 9.364751,3.91095 4.212272,0.82782 8.580238,0.30587 12.870353,0.4588"
id="path1596"
sodipodi:nodetypes="cccccc" /><path
style="fill:#010000;fill-opacity:0.268258;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 49.779293,186.08404 c 0.96552,4.45831 25.01987,3.96877 30.4827,1.22894 13.87936,-2.9076 -16.08645,-5.72863 -22.93649,-6.13413 -4.61395,0.0322 -8.09535,1.93397 -7.54621,4.90519 z"
id="path38464-7"
sodipodi:nodetypes="cccc"
transform="matrix(1.036102,0,0,1.036102,-21.713571,-27.542843)" /><path
style="fill:#a30b0e;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 33.983044,165.18272 c 0,0 1.529564,0.72114 2.455335,0.77196 0.925764,0.0508 1.834838,-0.45446 2.932539,-0.57897 1.097708,-0.1245 2.418383,-0.24077 3.637575,-0.11265 1.219185,0.12811 1.952311,1.0547 3.609267,0.88462 1.656956,-0.17008 3.491919,-0.39037 5.666685,-2.36448 2.174767,-1.9741 4.865746,-7.29359 5.848185,-10.05882 0.982432,-2.76524 0.703154,-5.07155 0.643539,-6.39325 -0.05959,-1.3217 -0.208211,-1.49572 -0.208211,-1.49572"
id="path6267"
sodipodi:nodetypes="czzzzzzzc" /><path
style="fill:#c07f16;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 38.461151,136.9473 c 1.35573,0.37324 2.309562,0.8613 4.096886,1.11122 0.994124,-0.15196 1.841666,-0.0987 3.149379,-0.68969 l -0.995306,6.88319 c 0,0 -4.907344,0.0713 -4.771272,-0.3995 0.136064,-0.47077 -1.479687,-6.90522 -1.479687,-6.90522 z"
id="path24930"
sodipodi:nodetypes="ccccsc" /><path
style="fill:#f1e890;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 43.034803,142.60286 c -0.935713,-2.59107 -3.812328,-6.78313 -7.729574,-6.24009 -3.917239,0.54304 -7.35144,5.76415 -7.971266,11.25782 -0.619818,5.49367 5.398984,17.75754 7.434345,17.89209 2.355997,0.40404 4.34644,-1.17116 4.886578,-1.87052 0.540145,-0.69936 0.768947,1.03528 1.371544,1.15912 0.602598,0.12384 1.077606,-0.73571 1.812803,-0.55339 0.735197,0.18233 2.932036,1.63625 4.414655,0.73425 6.133481,-3.8826 11.61906,-11.23177 11.344545,-18.37487 -0.167597,-4.36102 -3.17722,-9.92296 -7.46061,-10.7591 -5.082506,-0.99215 -6.805686,2.91188 -8.10302,6.75469 z"
id="path6257"
sodipodi:nodetypes="czzczzzcssc" /><path
style="fill:#744e0b;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 42.473921,135.50687 -0.310153,-1.00963 0.689003,-0.35083 0.613684,0.15782 0.04829,3.81859 v 2.48554 c -0.267731,0.17111 -0.618885,0.0114 -0.964969,-0.1281 -2.2e-5,-0.0968 -0.07585,-4.97339 -0.07585,-4.97339 z"
id="path6259"
sodipodi:nodetypes="cccccccc" /><path
style="fill:#60140f;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 46.582777,147.89717 c -0.07872,0.32449 -0.06696,0.58859 -0.03684,0.95049 0.03013,0.36191 -0.201055,1.05388 0.249846,1.20205 0.450901,0.14817 1.057495,-0.43819 1.206215,-1.08137 0.14872,-0.64317 -0.926857,-2.09678 -0.926857,-2.09678 0,0 -0.413643,0.70112 -0.492369,1.02561 z"
id="path6261"
sodipodi:nodetypes="zzzzcz" /><path
style="fill:#60140f;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 38.823632,147.137 c -0.387631,0.0217 -0.158494,0.22509 -0.289482,0.55826 -0.130995,0.33317 -0.62437,1.08311 -0.406436,1.46373 0.217941,0.38061 0.737903,0.41781 1.075111,0.2859 0.337208,-0.13192 0.573508,-0.4352 0.567666,-0.89527 -0.0058,-0.46008 -0.559227,-1.43438 -0.946859,-1.41262 z"
id="path6263"
sodipodi:nodetypes="zzzzzz" /><path
style="fill:#f5dc0c;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
d="m 42.742754,144.31587 -0.592736,3.33348 -0.231085,5.08667 0.729398,2.86364 0.550576,-3.43708 -0.02801,-2.59306 z"
id="path6265" /><path
style="fill:#ff6600;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 63.571721,126.76017 c 1.094536,11.13944 0.818311,21.64168 -1.928813,33.00992 0,0 3.528354,-4.80953 3.519073,-2.8943 -0.02658,5.48412 2.630888,-29.5498 1.591832,-29.40137 z"
id="path3853"
sodipodi:nodetypes="ccscc" /><path
d="m 57.958721,157.97494 c 0,0 -0.355132,-4.22053 2.994472,-5.6209 0,0 3.297109,-0.26031 3.603353,0.72059 0,0 3.320156,-0.85575 3.303628,1.60641 0,0 0.195132,0.99026 -0.655974,1.50929 0,0 2.073953,0.72031 0.890684,2.60951 l -1.146963,0.99133 c 0,0 1.14913,1.69296 -0.90507,2.53692 0,0 -0.619668,0.61141 -1.665989,0.14018 0,0 -2.66452,1.56645 -4.971036,-0.58839 0,0 -1.499942,-0.54491 -1.447105,-3.90494"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0.729413;stroke-dasharray:none;stroke-opacity:1"
id="path2902" /><path
d="m 57.958721,157.97494 c 0,0 -0.355132,-4.22053 2.994472,-5.6209 0,0 3.231666,-0.34283 3.53791,0.63807 0,0 3.385599,-0.77323 3.369071,1.68893 0,0 0.195132,0.99026 -0.655974,1.50929 0,0 2.073953,0.72031 0.890684,2.60951 l -1.146963,0.99133 c 0,0 1.14913,1.69296 -0.90507,2.53692 0,0 -0.619668,0.61141 -1.665989,0.14018 0,0 -2.66452,1.56645 -4.971036,-0.58839 0,0 -1.499942,-0.54491 -1.447105,-3.90494 z"
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path2904"
sodipodi:nodetypes="ccccccccccc" /><path
d="m 64.491103,152.99211 c 0.356304,1.02136 -0.206169,1.90598 -0.452664,2.81293 -0.285407,1.0506 0.531528,2.66525 -0.872464,3.18617 -3.056254,1.13409 -2.820371,-4.2538 -1.556164,-5.13196"
style="fill:#ffffff;stroke:#000000;stroke-width:0.729413;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path2906"
sodipodi:nodetypes="cccc" /><path
d="m 60.574986,156.18338 c -0.429251,-0.21891 -0.583819,-0.69727 -0.547068,-1.18435"
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path2908" /><path
d="m 67.2042,156.19033 c -0.717377,0.62372 -2.974059,1.02627 -3.230626,0.32462"
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path2910"
sodipodi:nodetypes="cc" /><path
d="m 67.436314,159.29719 c -1.317174,0.93586 -2.860074,0.81234 -3.832335,-0.34865"
style="fill:none;stroke:#000000;stroke-width:0.729413;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path2912" /><path
style="fill:#ff6600;stroke:none;stroke-width:0.729413;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 61.642908,159.77009 v 0 l -0.196868,6.76242 c 0.138065,0.90621 3.047532,1.30724 2.913473,0.10362 -0.0029,-0.0261 0.07136,-3.35353 0.144362,-4.1272 -2.987756,-1.7577 -1.763448,-2.73153 -2.860967,-2.73884 z"
id="path3853-6-4"
sodipodi:nodetypes="cccscc" /><path
d="m 62.40537,159.2052 c -1.058657,1.47852 2.221477,3.35076 2.098505,3.30373"
style="fill:none;stroke:#000000;stroke-width:0.729413;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path2914"
sodipodi:nodetypes="cc" /><path
d="m 61.475024,158.90397 c -0.304724,0.26545 -0.690797,0.6674 -1.175003,0.65813"
style="fill:none;stroke:#000000;stroke-width:0.729413;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
id="path2916" /><path
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 61.678487,158.97468 v 0 l -0.232447,7.55783 c 0.06153,1.02855 2.935348,1.19145 2.91348,0.10362 -6.49e-4,-0.0263 0.07135,-3.35353 0.144355,-4.1272"
id="path3853-6"
sodipodi:nodetypes="cccsc" /><path
style="fill:#ff6600;stroke:none;stroke-width:0.729413;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 63.641927,124.75769 v 0 l -0.196869,-6.76242 c 0.138065,-0.90621 3.047532,-1.30724 2.913473,-0.10362 -0.0029,0.0261 0.07136,3.35353 0.144362,4.1272 -2.987755,1.7577 -1.763448,2.73153 -2.860966,2.73884 z"
id="path3853-6-4-7"
sodipodi:nodetypes="cccscc" /><path
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 63.677506,125.5531 v 0 l -0.232448,-7.55783 c 0.06153,-1.02855 2.935348,-1.19145 2.913481,-0.10362 -6.49e-4,0.0263 0.07135,3.35353 0.144355,4.1272"
id="path3853-6-9"
sodipodi:nodetypes="cccsc" /><path
style="fill:#00b7b7;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 63.295152,131.49341 c 0.393707,0.51918 1.612281,0.99644 3.217121,0.7211 0,0 3.906619,1.4742 5.936024,1.38123 0.747349,-0.0342 1.382417,-0.72421 2.130338,-0.70639 2.811582,0.067 7.872764,3.03397 7.872764,3.03397 L 82.999,117.56681 c 0,0 -5.28177,-0.18252 -7.822554,0.42013 -0.958982,0.22745 -1.734177,1.04049 -2.709213,1.18432 -1.988881,0.29339 -5.988418,-0.7172 -5.988418,-0.7172 -0.919318,0.55366 -2.006773,0.51884 -3.183663,0.17099 z"
id="path12766"
sodipodi:nodetypes="ccssccssccc" /></g><path
sodipodi:type="star"
style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path15414"
inkscape:flatsided="false"
sodipodi:sides="5"
sodipodi:cx="117.51744"
sodipodi:cy="29.413408"
sodipodi:r1="11.625795"
sodipodi:r2="5.8128977"
sodipodi:arg1="-0.43776009"
sodipodi:arg2="0.19055844"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 128.04696,24.485096 -4.82184,6.029317 2.23323,7.390235 -7.22426,-2.722688 -6.33842,4.407635 0.35701,-7.712031 -6.15059,-4.666166 7.4449,-2.043609 2.53715,-7.291484 4.24419,6.44901 z"
inkscape:transform-center-x="-0.15550371"
inkscape:transform-center-y="-0.42661129"
transform="matrix(0.5082118,0.10675533,-0.10675533,0.5082118,75.701411,28.86227)" /><path
sodipodi:type="star"
style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path15414-2"
inkscape:flatsided="false"
sodipodi:sides="5"
sodipodi:cx="117.51744"
sodipodi:cy="29.413408"
sodipodi:r1="11.625795"
sodipodi:r2="5.8128977"
sodipodi:arg1="-0.43776009"
sodipodi:arg2="0.19055844"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 128.04696,24.485096 -4.82184,6.029317 2.23323,7.390235 -7.22426,-2.722688 -6.33842,4.407635 0.35701,-7.712031 -6.15059,-4.666166 7.4449,-2.043609 2.53715,-7.291484 4.24419,6.44901 z"
inkscape:transform-center-x="-0.11728461"
inkscape:transform-center-y="-0.32177051"
transform="matrix(0.38331637,0.0805197,-0.0805197,0.38331637,133.48215,38.188561)" /><path
sodipodi:type="star"
style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path15414-2-2"
inkscape:flatsided="false"
sodipodi:sides="5"
sodipodi:cx="117.51744"
sodipodi:cy="29.413408"
sodipodi:r1="11.625795"
sodipodi:r2="5.8128977"
sodipodi:arg1="-0.43776009"
sodipodi:arg2="0.19055844"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 128.04696,24.485096 -4.82184,6.029317 2.23323,7.390235 -7.22426,-2.722688 -6.33842,4.407635 0.35701,-7.712031 -6.15059,-4.666166 7.4449,-2.043609 2.53715,-7.291484 4.24419,6.44901 z"
inkscape:transform-center-x="-0.054016501"
inkscape:transform-center-y="-0.14818899"
transform="matrix(0.17653378,0.03708281,-0.03708281,0.17653378,74.231021,67.773286)" /><path
sodipodi:type="star"
style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path15414-2-6"
inkscape:flatsided="false"
sodipodi:sides="5"
sodipodi:cx="117.51744"
sodipodi:cy="29.413408"
sodipodi:r1="11.625795"
sodipodi:r2="5.8128977"
sodipodi:arg1="-0.43776009"
sodipodi:arg2="0.19055844"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 128.04696,24.485096 -4.82184,6.029317 2.23323,7.390235 -7.22426,-2.722688 -6.33842,4.407635 0.35701,-7.712031 -6.15059,-4.666166 7.4449,-2.043609 2.53715,-7.291484 4.24419,6.44901 z"
inkscape:transform-center-x="-0.10331594"
inkscape:transform-center-y="-0.28344015"
transform="matrix(0.33765476,0.070928,-0.070928,0.33765476,18.883161,63.313526)" /><path
sodipodi:type="star"
style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path15414-0"
inkscape:flatsided="false"
sodipodi:sides="5"
sodipodi:cx="117.51744"
sodipodi:cy="29.413408"
sodipodi:r1="11.625795"
sodipodi:r2="5.8128977"
sodipodi:arg1="-0.43776009"
sodipodi:arg2="0.19055844"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 128.04696,24.485096 -4.82184,6.029317 2.23323,7.390235 -7.22426,-2.722688 -6.33842,4.407635 0.35701,-7.712031 -6.15059,-4.666166 7.4449,-2.043609 2.53715,-7.291484 4.24419,6.44901 z"
inkscape:transform-center-x="-0.15550652"
inkscape:transform-center-y="-0.42661155"
transform="matrix(0.5082118,0.10675533,-0.10675533,0.5082118,126.66807,99.717697)" /><path
sodipodi:type="star"
style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path15416"
inkscape:flatsided="false"
sodipodi:sides="5"
sodipodi:cx="156.13559"
sodipodi:cy="46.936283"
sodipodi:r1="5.2078781"
sodipodi:r2="2.6039393"
sodipodi:arg1="-0.43409146"
sodipodi:arg2="0.19422707"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 160.86045,44.745921 -2.16988,2.692944 0.98824,3.314168 -3.23167,-1.231515 -2.84658,1.964009 0.1726,-3.454063 -2.74753,-2.100344 3.33835,-0.903212 1.14851,-3.262094 1.89061,2.895847 z"
inkscape:transform-center-x="-0.14002212"
inkscape:transform-center-y="-0.36225272"
transform="rotate(11.86311,-94.558638,67.518227)" /><path
sodipodi:type="star"
style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path15418"
inkscape:flatsided="false"
sodipodi:sides="5"
sodipodi:cx="65.107475"
sodipodi:cy="28.732956"
sodipodi:r1="11.61906"
sodipodi:r2="5.8095307"
sodipodi:arg1="2.7426294"
sodipodi:arg2="3.3709479"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 54.400928,33.246533 5.04915,-5.834372 -1.943775,-7.466965 7.109091,2.999107 6.500846,-4.156059 -0.65549,7.687923 5.961519,4.898379 -7.514207,1.75229 -2.816425,7.183424 -3.988544,-6.604947 z"
inkscape:transform-center-x="0.43783231"
inkscape:transform-center-y="0.68483807"
transform="rotate(11.86311,-35.469528,138.62496)" /><path
sodipodi:type="star"
style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path15420"
inkscape:flatsided="false"
sodipodi:sides="5"
sodipodi:cx="29.340046"
sodipodi:cy="46.625675"
sodipodi:r1="4.2978187"
sodipodi:r2="2.1489098"
sodipodi:arg1="0.47181668"
sodipodi:arg2="1.1001352"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 33.168302,48.579056 -2.853778,-0.03813 -1.649258,2.32926 -0.845606,-2.725885 -2.724907,-0.848757 2.331164,-1.646565 -0.03483,-2.85382 2.286346,1.708253 2.703382,-0.915002 -0.918125,2.702323 z"
inkscape:transform-center-x="0.34606094"
inkscape:transform-center-y="-0.06710565"
transform="rotate(11.86311,-35.469528,138.62496)" /><path
sodipodi:type="star"
style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path15422"
inkscape:flatsided="false"
sodipodi:sides="5"
sodipodi:cx="105.60214"
sodipodi:cy="62.81638"
sodipodi:r1="2.3678966"
sodipodi:r2="1.1839486"
sodipodi:arg1="0.48083515"
sodipodi:arg2="1.1091537"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 107.70154,63.911579 -1.57204,-0.03518 -0.9202,1.275066 -0.45233,-1.505976 -1.49702,-0.481145 1.29249,-0.89556 -0.005,-1.57243 1.25113,0.952488 1.49392,-0.49067 -0.51925,1.484231 z"
inkscape:transform-center-x="0.18428571"
inkscape:transform-center-y="-0.043561917"
transform="rotate(11.86311,-35.469528,138.62496)" /><path
sodipodi:type="star"
style="fill:#ffcc00;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path15414-5"
inkscape:flatsided="false"
sodipodi:sides="5"
sodipodi:cx="117.51744"
sodipodi:cy="29.413408"
sodipodi:r1="11.625795"
sodipodi:r2="5.8128977"
sodipodi:arg1="-0.43776009"
sodipodi:arg2="0.19055844"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 128.04696,24.485096 -4.82184,6.029317 2.23323,7.390235 -7.22426,-2.722688 -6.33842,4.407635 0.35701,-7.712031 -6.15059,-4.666166 7.4449,-2.043609 2.53715,-7.291484 4.24419,6.44901 z"
inkscape:transform-center-x="-0.094976792"
inkscape:transform-center-y="-0.26055941"
transform="matrix(0.31039809,0.06520244,-0.06520244,0.31039809,0.09441247,63.170233)" /><path
style="fill:#ffffff;fill-opacity:0.147971;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 49.931372,110.52643 c -4.164658,0.57437 -11.397846,8.11038 -17.096209,12.16736 2.641961,4.65789 5.268944,5.61059 7.896367,6.67201 0.698573,-4.82863 3.070763,-10.68288 9.199842,-18.83937 z"
id="path1154"
sodipodi:nodetypes="cccc" /><path
style="fill:#000000;fill-opacity:0.124582;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 74.850969,128.37443 1.67781,14.87299 c 0.555976,-0.0887 0.798905,-0.36021 1.00794,-0.65158 -0.555596,-5.28217 -1.141753,-10.59146 -0.941037,-15.20195 -0.386159,0.44667 -0.949792,0.78452 -1.744713,0.98054 z"
id="path1156"
sodipodi:nodetypes="ccccc" /><path
style="fill:#ffffff;fill-opacity:0.355131;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 67.779789,128.47502 1.511355,13.66693 c -0.147003,0.28974 -0.72278,0.31653 -1.265416,0.36367 l -1.305133,-13.59823 c 0.606151,-0.0574 0.913932,-0.20785 1.061531,-0.40815"
id="path1158"
sodipodi:nodetypes="ccccc" /><path
style="fill:#000000;fill-opacity:0.391885;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 114.37266,178.32121 c 0,0 3.30143,3.56816 9.722,3.14179 6.42058,-0.42637 9.65822,-5.25905 11.32829,-9.07146 -7.10641,6.30325 -14.07144,7.76835 -21.05029,5.92967 z"
id="path1160"
sodipodi:nodetypes="czcc" /></g></svg>

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

View File

@ -43,10 +43,6 @@ const update_datetimes = (event) => {
start_day.oldvalue = start_day.value;
}
else {
new_date = new Date(start_day.value);
end_day.value = formatDate(new_date);
}
}
else {
if (end_day.value && end_time.value && start_day.value) {

View File

@ -1,673 +0,0 @@
var SequentialLoader = function() {
var SL = {
loadJS: function(src, onload) {
//console.log(src);
// add to pending list
this._load_pending.push({'src': src, 'onload': onload});
// check if not already loading
if ( ! this._loading) {
this._loading = true;
// load first
this.loadNextJS();
}
},
loadNextJS: function() {
// get next
var next = this._load_pending.shift();
if (next == undefined) {
// nothing to load
this._loading = false;
return;
}
// check not loaded
if (this._load_cache[next.src] != undefined) {
next.onload();
this.loadNextJS();
return; // already loaded
}
else {
this._load_cache[next.src] = 1;
}
// load
var el = document.createElement('script');
el.type = 'application/javascript';
el.src = next.src;
// onload callback
var self = this;
el.onload = function(){
//console.log('Loaded: ' + next.src);
// trigger onload
next.onload();
// try to load next
self.loadNextJS();
};
document.body.appendChild(el);
},
_loading: false,
_load_pending: [],
_load_cache: {}
};
return {
loadJS: SL.loadJS.bind(SL)
}
};
!function($){
var LocationFieldCache = {
load: [],
onload: {},
isLoading: false
};
var LocationFieldResourceLoader;
$.locationField = function(options) {
var LocationField = {
options: $.extend({
provider: 'google',
providerOptions: {
google: {
api: '//maps.google.com/maps/api/js',
mapType: 'ROADMAP'
}
},
searchProvider: 'google',
id: 'map',
latLng: '0,0',
mapOptions: {
zoom: 9
},
basedFields: $(),
inputField: $(),
suffix: '',
path: '',
fixMarker: true
}, options),
providers: /google|openstreetmap|mapbox/,
searchProviders: /google|yandex|nominatim|addok/,
render: function() {
this.$id = $('#' + this.options.id);
if ( ! this.providers.test(this.options.provider)) {
this.error('render failed, invalid map provider: ' + this.options.provider);
return;
}
if ( ! this.searchProviders.test(this.options.searchProvider)) {
this.error('render failed, invalid search provider: ' + this.options.searchProvider);
return;
}
var self = this;
this.loadAll(function(){
var mapOptions = self._getMapOptions(),
map = self._getMap(mapOptions);
var marker = self._getMarker(map, mapOptions.center);
// fix issue w/ marker not appearing
if (self.options.provider == 'google' && self.options.fixMarker)
self.__fixMarker();
// watch based fields
self._watchBasedFields(map, marker);
});
},
fill: function(latLng) {
this.options.inputField.val(latLng.lat + ',' + latLng.lng);
},
search: function(map, marker, address) {
if (this.options.searchProvider === 'google') {
var provider = new GeoSearch.GoogleProvider({ apiKey: this.options.providerOptions.google.apiKey });
provider.search({query: address}).then(data => {
if (data.length > 0) {
var result = data[0],
latLng = new L.LatLng(result.y, result.x);
marker.setLatLng(latLng);
map.panTo(latLng);
}
});
}
else if (this.options.searchProvider === 'yandex') {
// https://yandex.com/dev/maps/geocoder/doc/desc/concepts/input_params.html
var url = 'https://geocode-maps.yandex.ru/1.x/?format=json&geocode=' + address;
if (typeof this.options.providerOptions.yandex.apiKey !== 'undefined') {
url += '&apikey=' + this.options.providerOptions.yandex.apiKey;
}
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.onload = function () {
if (request.status >= 200 && request.status < 400) {
var data = JSON.parse(request.responseText);
var pos = data.response.GeoObjectCollection.featureMember[0].GeoObject.Point.pos.split(' ');
var latLng = new L.LatLng(pos[1], pos[0]);
marker.setLatLng(latLng);
map.panTo(latLng);
} else {
console.error('Yandex geocoder error response');
}
};
request.onerror = function () {
console.error('Check connection to Yandex geocoder');
};
request.send();
}
else if (this.options.searchProvider === 'addok') {
var url = 'https://api-adresse.data.gouv.fr/search/?limit=1&q=' + address;
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.onload = function () {
if (request.status >= 200 && request.status < 400) {
var data = JSON.parse(request.responseText);
var pos = data.features[0].geometry.coordinates;
var latLng = new L.LatLng(pos[1], pos[0]);
marker.setLatLng(latLng);
map.panTo(latLng);
} else {
console.error('Addok geocoder error response');
}
};
request.onerror = function () {
console.error('Check connection to Addok geocoder');
};
request.send();
}
else if (this.options.searchProvider === 'nominatim') {
var url = '//nominatim.openstreetmap.org/search?format=json&q=' + address;
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.onload = function () {
if (request.status >= 200 && request.status < 400) {
var data = JSON.parse(request.responseText);
if (data.length > 0) {
var pos = data[0];
var latLng = new L.LatLng(pos.lat, pos.lon);
marker.setLatLng(latLng);
map.panTo(latLng);
} else {
console.error(address + ': not found via Nominatim');
}
} else {
console.error('Nominatim geocoder error response');
}
};
request.onerror = function () {
console.error('Check connection to Nominatim geocoder');
};
request.send();
}
},
loadAll: function(onload) {
this.$id.html('Loading...');
// resource loader
if (LocationFieldResourceLoader == undefined)
LocationFieldResourceLoader = SequentialLoader();
this.load.loader = LocationFieldResourceLoader;
this.load.path = this.options.path;
var self = this;
this.load.common(function(){
var mapProvider = self.options.provider,
onLoadMapProvider = function() {
var searchProvider = self.options.searchProvider + 'SearchProvider',
onLoadSearchProvider = function() {
self.$id.html('');
onload();
};
if (self.load[searchProvider] != undefined) {
self.load[searchProvider](self.options.providerOptions[self.options.searchProvider] || {}, onLoadSearchProvider);
}
else {
onLoadSearchProvider();
}
};
if (self.load[mapProvider] != undefined) {
self.load[mapProvider](self.options.providerOptions[mapProvider] || {}, onLoadMapProvider);
}
else {
onLoadMapProvider();
}
});
},
load: {
google: function(options, onload) {
var js = [
this.path + '/@googlemaps/js-api-loader/index.min.js',
this.path + '/Leaflet.GoogleMutant.js',
];
this._loadJSList(js, function(){
const loader = new google.maps.plugins.loader.Loader({
apiKey: options.apiKey,
version: "weekly",
});
loader.load().then(() => onload());
});
},
googleSearchProvider: function(options, onload) {
onload();
//var url = options.api;
//if (typeof options.apiKey !== 'undefined') {
// url += url.indexOf('?') === -1 ? '?' : '&';
// url += 'key=' + options.apiKey;
//}
//var js = [
// url,
// this.path + '/l.geosearch.provider.google.js'
// ];
//this._loadJSList(js, function(){
// // https://github.com/smeijer/L.GeoSearch/issues/57#issuecomment-148393974
// L.GeoSearch.Provider.Google.Geocoder = new google.maps.Geocoder();
// onload();
//});
},
yandexSearchProvider: function (options, onload) {
onload();
},
mapbox: function(options, onload) {
onload();
},
openstreetmap: function(options, onload) {
onload();
},
common: function(onload) {
var self = this,
js = [
// map providers
this.path + '/leaflet/leaflet.js',
// search providers
this.path + '/leaflet-geosearch/geosearch.umd.js',
],
css = [
// map providers
this.path + '/leaflet/leaflet.css'
];
// Leaflet docs note:
// Include Leaflet JavaScript file *after* Leaflets CSS
// https://leafletjs.com/examples/quick-start/
this._loadCSSList(css, function(){
self._loadJSList(js, onload);
});
},
_loadJS: function(src, onload) {
this.loader.loadJS(src, onload);
},
_loadJSList: function(srclist, onload) {
this.__loadList(this._loadJS, srclist, onload);
},
_loadCSS: function(src, onload) {
if (LocationFieldCache.onload[src] != undefined) {
onload();
}
else {
LocationFieldCache.onload[src] = 1;
onloadCSS(loadCSS(src), onload);
}
},
_loadCSSList: function(srclist, onload) {
this.__loadList(this._loadCSS, srclist, onload);
},
__loadList: function(fn, srclist, onload) {
if (srclist.length > 1) {
for (var i = 0; i < srclist.length-1; ++i) {
fn.call(this, srclist[i], function(){});
}
}
fn.call(this, srclist[srclist.length-1], onload);
}
},
error: function(message) {
console.log(message);
this.$id.html(message);
},
_getMap: function(mapOptions) {
var map = new L.Map(this.options.id, mapOptions), layer;
if (this.options.provider == 'google') {
layer = new L.gridLayer.googleMutant({
type: this.options.providerOptions.google.mapType.toLowerCase(),
});
}
else if (this.options.provider == 'openstreetmap') {
layer = new L.tileLayer(
'//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18
});
}
else if (this.options.provider == 'mapbox') {
layer = new L.tileLayer(
'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
maxZoom: 18,
accessToken: this.options.providerOptions.mapbox.access_token,
id: 'mapbox/streets-v11'
});
}
map.addLayer(layer);
return map;
},
_getMapOptions: function() {
return $.extend(this.options.mapOptions, {
center: this._getLatLng()
});
},
_getLatLng: function() {
var l = this.options.latLng.split(',').map(parseFloat);
return new L.LatLng(l[0], l[1]);
},
_getMarker: function(map, center) {
var self = this,
markerOptions = {
draggable: true
};
var marker = L.marker(center, markerOptions).addTo(map);
marker.on('dragstart', function(){
if (self.options.inputField.is('[readonly]'))
marker.dragging.disable();
else
marker.dragging.enable();
});
// fill input on dragend
marker.on('dragend move', function(){
if (!self.options.inputField.is('[readonly]'))
self.fill(this.getLatLng());
});
// place marker on map click
map.on('click', function(e){
if (!self.options.inputField.is('[readonly]')) {
marker.setLatLng(e.latlng);
marker.dragging.enable();
}
});
return marker;
},
_watchBasedFields: function(map, marker) {
var self = this,
basedFields = this.options.basedFields,
onchangeTimer,
onchange = function() {
if (!self.options.inputField.is('[readonly]')) {
var values = basedFields.map(function() {
var value = $(this).val();
return value === '' ? null : value;
});
var address = values.toArray().join(', ');
clearTimeout(onchangeTimer);
onchangeTimer = setTimeout(function(){
self.search(map, marker, address);
}, 300);
}
};
basedFields.each(function(){
var el = $(this);
if (el.is('select'))
el.change(onchange);
else
el.keyup(onchange);
});
if (this.options.inputField.val() === '') {
var values = basedFields.map(function() {
var value = $(this).val();
return value === '' ? null : value;
});
var address = values.toArray().join(', ');
if (address !== '')
onchange();
}
},
__fixMarker: function() {
$('.leaflet-map-pane').css('z-index', '2 !important');
$('.leaflet-google-layer').css('z-index', '1 !important');
}
}
return {
render: LocationField.render.bind(LocationField)
}
}
function dataLocationFieldObserver(callback) {
function _findAndEnableDataLocationFields() {
var dataLocationFields = $('input[data-location-field-options]');
dataLocationFields
.filter(':not([data-location-field-observed])')
.attr('data-location-field-observed', true)
.each(callback);
}
var observer = new MutationObserver(function(mutations){
_findAndEnableDataLocationFields();
});
var container = document.documentElement || document.body;
$(container).ready(function(){
_findAndEnableDataLocationFields();
});
observer.observe(container, {attributes: true});
}
dataLocationFieldObserver(function(){
var el = $(this);
var name = el.attr('name'),
options = el.data('location-field-options'),
basedFields = options.field_options.based_fields,
pluginOptions = {
id: 'map_' + name,
inputField: el,
latLng: el.val() || '0,0',
suffix: options['search.suffix'],
path: options['resources.root_path'],
provider: options['map.provider'],
searchProvider: options['search.provider'],
providerOptions: {
google: {
api: options['provider.google.api'],
apiKey: options['provider.google.api_key'],
mapType: options['provider.google.map_type']
},
mapbox: {
access_token: options['provider.mapbox.access_token']
},
yandex: {
apiKey: options['provider.yandex.api_key']
},
},
mapOptions: {
zoom: options['map.zoom']
}
};
// prefix
var prefixNumber;
try {
prefixNumber = name.match(/-(\d+)-/)[1];
} catch (e) {}
if (options.field_options.prefix) {
var prefix = options.field_options.prefix;
if (prefixNumber != null) {
prefix = prefix.replace(/__prefix__/, prefixNumber);
}
basedFields = basedFields.map(function(n){
return prefix + n
});
}
// based fields
pluginOptions.basedFields = $(basedFields.map(function(n){
return '#id_' + n
}).join(','));
// render
$.locationField(pluginOptions).render();
});
}(jQuery || django.jQuery);
/*!
loadCSS: load a CSS file asynchronously.
[c]2015 @scottjehl, Filament Group, Inc.
Licensed MIT
*/
(function(w){
"use strict";
/* exported loadCSS */
var loadCSS = function( href, before, media ){
// Arguments explained:
// `href` [REQUIRED] is the URL for your CSS file.
// `before` [OPTIONAL] is the element the script should use as a reference for injecting our stylesheet <link> before
// By default, loadCSS attempts to inject the link after the last stylesheet or script in the DOM. However, you might desire a more specific location in your document.
// `media` [OPTIONAL] is the media type or query of the stylesheet. By default it will be 'all'
var doc = w.document;
var ss = doc.createElement( "link" );
var ref;
if( before ){
ref = before;
}
else {
var refs = ( doc.body || doc.getElementsByTagName( "head" )[ 0 ] ).childNodes;
ref = refs[ refs.length - 1];
}
var sheets = doc.styleSheets;
ss.rel = "stylesheet";
ss.href = href;
// temporarily set media to something inapplicable to ensure it'll fetch without blocking render
ss.media = "only x";
// Inject link
// Note: the ternary preserves the existing behavior of "before" argument, but we could choose to change the argument to "after" in a later release and standardize on ref.nextSibling for all refs
// Note: `insertBefore` is used instead of `appendChild`, for safety re: http://www.paulirish.com/2011/surefire-dom-element-insertion/
ref.parentNode.insertBefore( ss, ( before ? ref : ref.nextSibling ) );
// A method (exposed on return object for external use) that mimics onload by polling until document.styleSheets until it includes the new sheet.
var onloadcssdefined = function( cb ){
var resolvedHref = ss.href;
var i = sheets.length;
while( i-- ){
if( sheets[ i ].href === resolvedHref ){
return cb();
}
}
setTimeout(function() {
onloadcssdefined( cb );
});
};
// once loaded, set link's media back to `all` so that the stylesheet applies once it loads
ss.onloadcssdefined = onloadcssdefined;
onloadcssdefined(function() {
ss.media = media || "all";
});
return ss;
};
// commonjs
if( typeof module !== "undefined" ){
module.exports = loadCSS;
}
else {
w.loadCSS = loadCSS;
}
}( typeof global !== "undefined" ? global : this ));
/*!
onloadCSS: adds onload support for asynchronous stylesheets loaded with loadCSS.
[c]2014 @zachleat, Filament Group, Inc.
Licensed MIT
*/
/* global navigator */
/* exported onloadCSS */
function onloadCSS( ss, callback ) {
ss.onload = function() {
ss.onload = null;
if( callback ) {
callback.call( ss );
}
};
// This code is for browsers that dont support onload, any browser that
// supports onload should use that instead.
// No support for onload:
// * Android 4.3 (Samsung Galaxy S4, Browserstack)
// * Android 4.2 Browser (Samsung Galaxy SIII Mini GT-I8200L)
// * Android 2.3 (Pantech Burst P9070)
// Weak inference targets Android < 4.4
if( "isApplicationInstalled" in navigator && "onloadcssdefined" in ss ) {
ss.onloadcssdefined( callback );
}
}

View File

@ -147,7 +147,7 @@ details[role="list"] summary + ul li.selected>a:hover {
}
}
.suggestions {
.suggested-tags {
font-size: 80%;
}
}
@ -198,18 +198,8 @@ details[role="list"] summary + ul li.selected>a:hover {
@extend .outline;
font-size: 90%;
padding: 0.15em 0.4em 0.3em 0.4em;
text-align: left;
}
.suggestions .small-cat {
overflow: visible;
}
.small-location {
@extend .small-cat;
border-color: var(--contrast);
color: var(--contrast);
}
.circ-cat.circ-large {
height: 2.6em;
@ -277,12 +267,6 @@ svg {
}
}
@media only screen and (min-width: 600px) {
.details-entete {
padding-left: 28%;
}
}
.ephemeris-hour {
@extend .ephemeris;
padding: 1.5em 0.1em;
@ -329,7 +313,6 @@ footer [data-tooltip] {
scroll-behavior: smooth;
transition-duration: 200ms;
.cat {
margin-right: 0;
}
@ -566,12 +549,6 @@ article#filters {
font-size: 100%;
}
}
.helptext {
display: block;
margin-top: 0;
}
header .remarque {
font-style: italic;
}
@ -590,8 +567,6 @@ header .remarque {
.slide-buttons {
float: right;
max-width: 30em;
text-align: right;
}
.left-buttons {
display: inline-block;
@ -888,7 +863,6 @@ nav>div {
}
.badge.error {
background: #b71c1c;
border-color: #cb1b1b;
@ -900,25 +874,6 @@ nav>div {
color: var(--secondary-inverse);
}
.tw-badge {
background: black;
border-color: black;
color: #f2f207;
font-weight: bold;
padding: 0.2em 0.4em;
font-size: 60%;
opacity: 80%;
}
.tw-prefix {
background: black;
color: #f2f207;
height: 2.1em;
margin: -0.5em 0 -0.5em -0.5em;
padding: 0.3em;
display: inline-block;
}
form [role="button"], form button {
margin: var(--spacing) 0 var(--spacing) 0;
}
@ -931,10 +886,6 @@ form .buttons [role="button"] {
margin: 2em auto;
}
.min-y-grid {
min-height: 4em;
}
@media (min-width: 992px) {
.grid.two-columns {
grid-column-gap: var(--nav-element-spacing-vertical);
@ -979,7 +930,7 @@ aside nav.paragraph li a, aside .no-breakline li a {
}
/* mise en forme pour les récurrences */
article form div .recurrence-widget {
article form p .recurrence-widget {
width: 100%;
border: 0;
@ -1159,17 +1110,6 @@ table .buttons {
}
}
.image-flottante {
width: 30%;
float: right;
margin: 0 1em;
img {
width: 100%;
margin: -4em 0 0 0;
}
}
img.preview {
height: 100px;
}
@ -1312,31 +1252,15 @@ img.preview {
}
}
.choices__list--dropdown {
font-size: 80%;
display: block;
display: none;
will-change: display;
background: var(--background-color);
padding: 0.4em;
border-radius: 0.2em;
box-shadow: 0 -1px 0 rgba(115, 130, 140, 0.2);
position: relative;
max-height: 300px;
overflow: auto;
-webkit-overflow-scrolling: touch;
will-change: scroll-position;
}
.choices__list--dropdown .choices__group,
.choices__list--dropdown .choices__item {
display: none;
}
.choices__list--dropdown .choices__item.visible,
.choices__list--dropdown.is-active .choices__item {
display: inline-block;
}
.choices__list--dropdown .choices__group.visible,
.choices__list--dropdown.is-active .choices__group {
.choices__list--dropdown.is-active {
display: block;
}
}
@ -1344,15 +1268,17 @@ img.preview {
.stick-bottom {
background: var(--card-sectionning-background-color);
margin-right: calc(var(--block-spacing-horizontal) * -1);
margin-left: calc(var(--block-spacing-horizontal) * -1);
padding: calc(var(--block-spacing-vertical) * 0.66) var(--block-spacing-horizontal);
margin-bottom: calc(var(--block-spacing-vertical) * -1);
position: sticky;
bottom: 0;
box-shadow: 0 -1px 0 rgba(115, 130, 140, 0.2);
article {
.stick-bottom {
background: var(--card-sectionning-background-color);
margin-right: calc(var(--block-spacing-horizontal) * -1);
margin-left: calc(var(--block-spacing-horizontal) * -1);
padding: calc(var(--block-spacing-vertical) * 0.66) var(--block-spacing-horizontal);
margin-bottom: calc(var(--block-spacing-vertical) * -1);
position: sticky;
bottom: 0;
box-shadow: 0 -1px 0 rgba(115, 130, 140, 0.2);
}
}
/*#menu-rechercher, #menu-ajouter, #menu-configurer {
@ -1394,11 +1320,7 @@ img.preview {
}
.a-venir article.single-event {
scroll-margin-top: 7em;
}
.a-venir, .place, .tag, .tag-descriptions, .organisation {
.a-venir, .place, .tag, .tag-descriptions {
article#filters {
margin: 2em 0;
}
@ -1435,17 +1357,6 @@ img.preview {
}
}
.header-complement {
float: none;
}
@media only screen and (min-width: 992px) {
.header-complement {
float: left;
clear: both;
}
}
form.messages div, form.moderation-events {
@media only screen and (min-width: 992px) {
display: grid;
@ -1459,249 +1370,4 @@ form.messages div, form.moderation-events {
clear: both;
float: left;
}
}
.moderate-preview .event-body {
max-height: 400px;
overflow-y: auto;
}
#moderate-form #id_status {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
label.required::after {
content: ' *';
color: red;
}
.contenu #event_form {
div#group_start, div#group_end {
grid-column: auto;
.body_group {
display: grid;
grid-gap: 10px;
grid-template-columns: repeat(2, 1fr);
}
}
}
/* group form */
@media only screen and (min-width: 992px) {
.contenu #event_form form {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 10px;
>div {
grid-column: 1 / 3;
}
div#group_meta, div#group_meta-admin {
.body_group {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 10px;
div {
grid-column: auto;
}
}
}
div#group_meta {
.body_group {
display: grid;
grid-template-columns: repeat(2, 1fr);
}
}
}
}
.maskable_group .body_group.closed {
display: none;
}
.form-place {
display: grid;
grid-template-columns: repeat(1, 1fr);
row-gap: .5em;
margin-bottom: 0.5em;
.map-widget {
grid-row: 3;
}
#group_address .body_group {
display: grid;
grid-template-columns: repear(2, 1fr);
column-gap: .5em;
#div_id_address, #div_id_location {
grid-column: 1 / 3;
}
}
}
@media only screen and (min-width: 992px) {
.form-place {
grid-template-columns: repeat(2, 1fr);
.map-widget {
grid-column: 2 / 3;
grid-row: 1 / 3;
}
#group_other {
grid-column: 1 / 3;
}
}
}
.line-now {
font-size: 60%;
div {
display: grid;
grid-template-columns: fit-content(2em) auto;
column-gap: .2em;
color: red;
.line {
margin-top: .7em;
border-top: 1px solid red;
}
}
margin-bottom: 0;
list-style: none;
}
.a-venir .line-now {
margin-left: -2em;
}
#chronology {
.entree {
display: grid;
grid-template-columns: fit-content(2em) auto;
column-gap: .7em;
.texte {
background: var(--background-color);
padding: 0.1em 0.8em;
border-radius: var(--border-radius);
p {
font-size: 100%;
}
p:last-child {
margin-bottom: 0.1em;
}
}
}
font-size: 85%;
footer {
margin-top: 1.8em;
padding: 0.2em .8em;
}
.ts {
@extend .badge-small;
border-radius: var(--border-radius);
display: inline-block;
width: 14em;
margin-right: 1.2em;
}
}
.moderation_heatmap {
overflow-x: auto;
table {
max-width: 600px;
margin: auto;
.total, .month {
display: none;
}
.label {
display: none;
}
th {
font-size: 90%;
text-align: center;
}
td {
font-size: 80%;
text-align: center;
}
tbody th {
text-align: right;
}
.ratio {
padding: 0.1em;
a, .a {
margin: auto;
border-radius: var(--border-radius);
color: black;
padding: 0;
display: block;
max-width: 6em;
width: 3.2em;
height: 2em;
line-height: 2em;
text-decoration: none;
}
}
.score_0 {
a, .a {
background: rgb(0, 128, 0);
}
a:hover {
background: rgb(0, 176, 0);
}
}
.score_1 {
a, .a {
background: rgb(255, 255, 0);
}
a:hover {
background: rgb(248, 248, 121);
}
}
.score_2 {
a, .a {
background: rgb(255, 166, 0);
}
a:hover {
background: rgb(255, 182, 47);
}
}
.score_3 {
a, .a {
background: rgb(255, 0, 0);
}
a:hover {
background: rgb(255, 91, 91);
}
}
.score_4 {
a, .a {
background: rgb(128, 0, 128);
color: white;
}
a:hover {
background: rgb(178, 0, 178);
}
}
@media only screen and (min-width: 1800px) {
.total, .month {
display: inline;
opacity: .35;
}
.label {
display: table-cell;
}
}
@media only screen and (min-width: 1600px) {
.ratio {
a, .a {
width: 5em;
height: 3.2em;
line-height: 3.2em;
}
}
font-size: 100%;
}
}
}
}

View File

@ -17,52 +17,8 @@
{% block content %}
<div class="grid two-columns">
<div id="contenu-principal">
<div>
<article>
<header>
<div class="slide-buttons">
<a href="{% url 'moderate' %}" role="button">Modérer {% picto_from_name "check-square" %}</a>
</div>
<h2>Modération à venir</h2>
</header>
<div class="grid">
<div>
{% url 'administration' as local_url %}
{% include "agenda_culturel/static_content.html" with name="administration" url_path=local_url %}
</div>
<div class="moderation_heatmap">
{% for w in nb_not_moderated %}
<table>
<thead>
<th class="label"></th>
{% for m in w %}
<th><a href="{% url 'day_view' m.start_day.year m.start_day.month m.start_day.day %}">{{ m.start_day|date:"d" }}<span class="month"> {{ m.start_day|date:"M"|lower }}</span></a></th>
{% endfor %}
</thead>
<tbody>
<tr>
<th class="label">reste à modérer</h>
{% for m in w %}
<td class="ratio score_{{ m.note }}">
<{% if m.not_moderated > 0 %}a href="{% if m.is_today %}
{% url 'moderate' %}
{% else %}
{% url 'moderate_from_date' m.start_day.year m.start_day.month m.start_day.day %}
{% endif %}"{% else %}span class="a"{% endif %}>
{{ m.not_moderated }}<span class="total"> / {{ m.nb_events }}</span></{% if m.not_moderated > 0 %}a{% else %}span{% endif %}>
</td>
{% endfor %}
</tr>
</tbody>
</table>
{% endfor %}
</div>
</div>
</article>
<article>
<article>
<header>
<h2>Activité des derniers jours</h2>
</header>
@ -76,8 +32,7 @@
{% include "agenda_culturel/rimports-info-inc.html" with all=1 %}</p>
</article>
</div>
<article>
<header>
<div class="slide-buttons">

View File

@ -47,21 +47,15 @@
<p>
{% picto_from_name "calendar" %}
{% if event.end_day and event.end_day != event.start_day %}du{% else %}le{% endif %}
{{ event.start_day|date|frdate }}
{% if event.start_time %} {% if not event.end_day or event.end_day == event.start_day %}{% if event.end_time %}de{% else %}à{% endif %}{% endif %}
{{ event.start_time }}
{% endif %}
{% if event.end_day and event.end_day != event.start_day %}
au {% if event.end_day and event.end_day != event.start_day %}{{ event.end_day|date|frdate }}{% endif %}
{% endif %}
{% if event.end_time %} {% if not event.end_day|date|frdate or event.end_day == event.start_day %}jusqu'à{% endif %} {{ event.end_time }}{% endif %} </p>
{% include "agenda_culturel/date-times-inc.html" with event=event %}
</p>
</header>
<div class="body-fixed">{{ event.description |linebreaks }}</div>
<p>
{% for tag in event.sorted_tags %}
{% for tag in event.tags %}
{{ tag | tag_button }}
{% endfor %}
</p>

View File

@ -1,27 +0,0 @@
{% extends "agenda_culturel/page-admin.html" %}
{% block fluid %}{% endblock %}
{% block content %}
<article>
<header>
<h1>{% block title %}{% block og_title %}Vider le cache{% endblock %}{% endblock %}</h1>
</header>
<form method="post">{% csrf_token %}
<p>Êtes-vous sûr·e de vouloir vider le cache&nbsp;? Toutes les pages seront
générées lors de leur consultation, mais cela peut ralentir temporairemenet l'expérience de navigation.
</p>
{{ form }}
<footer>
<div class="grid buttons">
<a href="{{ cancel_url }}" role="button" class="secondary">Annuler</a>
<input type="submit" value="Confirmer">
</div>
</footer>
</form>
</article>
{% endblock %}

View File

@ -0,0 +1,37 @@
{% extends "agenda_culturel/page-admin.html" %}
{% load static %}
{% block title %}{% block og_title %}Contact{% endblock %}{% endblock %}
{% block entete_header %}
<script>window.CKEDITOR_BASEPATH = '/static/ckeditor/ckeditor/';</script>
<script src="/static/admin/js/vendor/jquery/jquery.js"></script>
<script src="/static/admin/js/jquery.init.js"></script>
<link href="{% static 'css/django_better_admin_arrayfield.min.css' %}" type="text/css" media="all" rel="stylesheet">
<script src="{% static 'js/django_better_admin_arrayfield.min.js' %}"></script>
<script src="{% static 'js/adjust_datetimes.js' %}"></script>
{% endblock %}
{% block fluid %}{% endblock %}
{% block content %}
<h1>Contact</h1>
<article>
{% url 'contact' as local_url %}
{% include "agenda_culturel/static_content.html" with name="contact" url_path=local_url %}
</article>
<article>
<header>
<p class="message warning"><strong>Attention&nbsp:</strong> n'utilisez pas le formulaire ci-dessous pour proposer un événement, il sera ignoré. Utilisez plutôt la page <a href="{% url 'add_event' %}">ajouter un événement</a>.</p>
</header>
<form method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Envoyer">
</form>
</article>
{% endblock %}

View File

@ -24,18 +24,17 @@
<article>
<header>
<div class="slide-buttons">
<a href="{% url 'delete_message' object.id %}" role="button" data-tooltip="Supprimer le message">Supprimer {% picto_from_name "trash-2" %}</a>
<a href="{% url 'delete_contactmessage' object.id %}" role="button" data-tooltip="Supprimer le message">Supprimer {% picto_from_name "trash-2" %}</a>
</div>
<h1>Modération du message «&nbsp;{{ object.subject }}&nbsp;»</h1>
<ul>
<li>Date&nbsp;: {{ object.date.date }} à {{ object.date.time }}</li>
<li>Auteur&nbsp;: {% if object.user %}<em>{{ object.user }}</em>{% else %}{{ object.name }}{% endif %} {% if object.email %}<a href="mailto:{{ object.email }}">{{ object.email }}</a>{% endif %}</li>
{% if object.related_event %}<li>Événement associé&nbsp;: <a href="{{ object.related_event.get_absolute_url }}">{{ object.related_event.title }}</a> du {{ object.related_event.start_day }}</li>{% endif %}
<li>Date&nbsp;: {{ object.date }}</li>
<li>Auteur&nbsp;: {{ object.name }} <a href="mailto:{{ object.email }}">{{ object.email }}</a></li>
</ul>
</header>
<div>
{{ object.message | safe }}
{{ object.message }}
</div>
</article>
@ -47,7 +46,7 @@
</article>
</div>
{% include "agenda_culturel/side-nav.html" with current="messages" %}
{% include "agenda_culturel/side-nav.html" with current="contactmessages" %}
</div>
{% endblock %}

View File

@ -26,8 +26,7 @@
<h1>Derniers messages de contact reçus</h1>
<form method="get" class="form django-form recent messages">
{{ filter.form.as_div }}<br />
<button type="submit">Filtrer</button>
</form>
<button type="submit">Filtrer</button><br />
</header>
<table role="grid">
@ -36,7 +35,6 @@
<th>Date</th>
<th>Sujet</th>
<th>Auteur</th>
<th>Événement</th>
<th>Fermé</th>
<th>Spam</th>
</tr>
@ -45,9 +43,8 @@
{% for obj in paginator_filter %}
<tr>
<td>{{ obj.date }}</td>
<td><a href="{% url 'message' obj.pk %}">{{ obj.subject }}</a></td>
<td>{% if obj.user %}<em>{{ obj.user }}</em>{% else %}{{ obj.name }}{% endif %}</td>
<td>{% if obj.related_event %}<a href="{{ obj.related_event.get_absolute_url }}">{{ obj.related_event.pk }}</a>{% else %}/{% endif %}</td>
<td><a href="{% url 'contactmessage' obj.pk %}">{{ obj.subject }}</a></td>
<td>{{ obj.name }}</td>
<td>{% if obj.closed %}{% picto_from_name "check-square" "fermé" %}{% else %}{% picto_from_name "square" "ouvert" %}{% endif %}</td>
<td>{% if obj.spam %}{% picto_from_name "check-square" "spam" %}{% else %}{% picto_from_name "square" "non spam" %}{% endif %}</td>
</tr>
@ -59,7 +56,7 @@
</footer>
</article>
{% include "agenda_culturel/side-nav.html" with current="messages" %}
{% include "agenda_culturel/side-nav.html" with current="contactmessages" %}
</div>
{% endblock %}

View File

@ -0,0 +1,10 @@
{% load utils_extra %}
{{ event.start_day|date|frdate }}
{% if event.start_time %} {% if not event.end_day or event.end_day == event.start_day %}{% if event.end_time %}de{% else %}à{% endif %}{% endif %}
{{ event.start_time }}
{% endif %}
{% if event.end_day and event.end_day != event.start_day %}
au {% if event.end_day and event.end_day != event.start_day %}{{ event.end_day|date|frdate }}{% endif %}
{% endif %}
{% if event.end_time %} {% if not event.end_day|date|frdate or event.end_day == event.start_day %}jusqu'à{% endif %} {{ event.end_time }}{% endif %}

View File

@ -0,0 +1,82 @@
{% load cat_extra %}
{% load event_extra %}
{% load utils_extra %}
{% load static %}
{% with day.date|date:"Y-m-d" as daytag %}
{% with "date-"|add:daytag as daytag %}
<article class="day{{ day|calendar_classes:fixed_style }}" id="{{ daytag }}">
{% if day.is_today %}
<script>
document.addEventListener("DOMContentLoaded", function(e) {
agenda = document.getElementById("calendar");
todayArticle = document.getElementById("today").parentElement;
left = todayArticle.offsetLeft;
top = todayArticle.offsetTop;
agenda.scrollTo({top: top, left: left});
});
</script>
{% endif %}
<header{% if day.is_today %} id="today"{% endif %}>
<{% if headers != "" %}{{ headers }}{% else %}h2{% endif %}><a href="{{ day.date | url_day }}?{{ filter.get_url }}">{{ day.date | date:"l j" }}</a></{% if headers != "" %}{{ headers }}{% else %}h2{% endif %}>
</header>
{% if day.events %}
{% if resume == 1 %}
<ul>
{% for category, events in day.events_by_category_ordered %}
<li>{{ events.0.category | circle_cat }}
<a href="{{ day.date | url_day }}?{{ filter.get_url|add_url_category:events.0.category }}" data-target="{{ daytag }}-category-{{ events.0.category.pk }}" onClick="toggleModal(event)">{{ events | length }} {{ category }}</a></li>
<dialog id="{{ daytag }}-category-{{ events.0.category.pk }}">
<article>
<header>
<a href="#{{ daytag }}-category-{{ events.0.category.pk }}"
aria-label="Fermer"
class="close"
data-target="{{ daytag }}-category-{{ events.0.category.pk }}"
onClick="toggleModal(event)"></a>
<h3>{{ events.0.category | small_cat }} du {{ day.date | date:"l j F" }}</h3>
</header>
<ul>
{% for event in events %}
<li>
{% if event.start_day == day.date and event.start_time %}
{{ event.start_time }}
{% endif %}
<a href="{{ event.get_absolute_url }}">{{ event|picto_status }} {{ event.title }}</a>
</li>
{% endfor %}
</ul>
<footer>
<div class="buttons">
<a href="{{ day.date | url_day }}?{{ filter.get_url }}" role="button">Voir la journée <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' %}#chevron-right" />
</svg></a>
</div>
</footer>
</article>
</dialog>
{% endfor %}
<ul>
{% else %}
<ul>
{% for event in day.events %}
<li>{{ event.category | circle_cat:event.has_recurrences }}
{% if event.start_day == day.date and event.start_time %}
{{ event.start_time }}
{% endif %}
{{ event|picto_status }} <a href="{{ event.get_absolute_url }}" data-target="event-{{ event.id }}" onClick="toggleModal(event)">{{ event.title }}</a>
<dialog id="event-{{ event.id }}">
{% include "agenda_culturel/single-event/event-modal-inc.html" with event=event filter=filter %}
</dialog>
</li>
{% endfor %}
</ul>
{% endif %}
{% endif %}
</ul>
</article>
{% endwith %}
{% endwith %}

View File

@ -1,18 +1,13 @@
{% load utils_extra %}
{% if event.moderated_date %}
<a href="{% url 'moderate_event' event.id %}" role="button">Modérer de nouveau {% picto_from_name "check-square" %}</a>
{% else %}
<a href="{% url 'moderate_event' event.id %}" role="button">Modérer {% picto_from_name "check-square" %}</a>
{% endif %}
<!-- a href="{% url 'moderate_event' event.id %}" role="button">modérer {% picto_from_name "edit" %}</a-->
{% if event.pure_import %}
{% with event.get_local_version as local %}
{% if local %}
<a href="{{ local.get_absolute_url }}" role="button">voir la version locale {% picto_from_name "eye" %}</a>
{% else %}
<a href="{% url 'clone_edit' event.id %}" role="button">modifier en copie locale {% picto_from_name "plus-circle" %}</a>
<a href="{% url 'clone_edit' event.id %}" role="button">créer une copie locale {% picto_from_name "plus-circle" %}</a>
{% endif %}
{% endwith %}
{% else %}
@ -41,6 +36,3 @@
<a href="{% url 'delete_event' event.id %}" role="button">supprimer définitivement {% picto_from_name "x-circle" %}</a>
{% endif %}
{% if with_clone %}
<a href="{% url 'simple_clone_edit' event.id %}" role="button">Dupliquer {% picto_from_name "copy" %}</a>
{% endif %}

View File

@ -1,13 +1,13 @@
{% if user.is_authenticated %}
<p class="footer">Création&nbsp;: {{ event.created_date }}{% if event.created_by_user %} par <em>{{ event.created_by_user.username }}</em>{% endif %}
<p class="footer">Création&nbsp;: {{ event.created_date }}
{% if event.modified %}
— dernière modification&nbsp;: {{ event.modified_date }}{% if event.modified_by_user %} par <em>{{ event.modified_by_user.username }}</em>{% endif %}
— dernière modification&nbsp;: {{ event.modified_date }}
{% endif %}
{% if event.imported_date %}
— dernière importation&nbsp;: {{ event.imported_date }}{% if event.imported_by_user %} par <em>{{ event.imported_by_user.username }}</em>{% endif %}
— dernière importation&nbsp;: {{ event.imported_date }}
{% endif %}
{% if event.moderated_date %}
— dernière modération&nbsp;: {{ event.moderated_date }}{% if event.moderated_by_user %} par <em>{{ event.moderated_by_user.username }}</em>{% endif %}
— dernière modération&nbsp;: {{ event.moderated_date }}
{% endif %}
{% if event.pure_import %}
<strong>version importée</strong>

View File

@ -1,12 +1,10 @@
<footer class="remarque">
<strong>Informations complémentaires non éditables</strong>
Informations complémentaires non éditables&nbsp;:
<ul>
{% if not allbutdates %}
{% if object.created_date %}<li>Création&nbsp;: {{ object.created_date }}{% if object.created_by_user %} par <em>{{ object.created_by_user.username }}</em>{% endif %}</li>{% endif %}
{% if object.modified_date %}<li>Dernière modification&nbsp;: {{ object.modified_date }}{% if object.modified_by_user %} par <em>{{ object.modified_by_user.username }}</em>{% endif %}</li>{% endif %}
{% if object.moderated_date %}<li>Dernière modération&nbsp;: {{ object.moderated_date }}{% if object.moderated_by_user %} par <em>{{ object.moderated_by_user.username }}</em>{% endif %}</li>{% endif %}
{% if object.imported_date %}<li>Dernière importation&nbsp;: {{ object.imported_date }}{% if object.imported_by_user %} par <em>{{ object.imported_by_user.username }}</em>{% endif %}</li>{% endif %}
{% endif %}
{% if object.created_date %}<li>Création&nbsp;: {{ object.created_date }}</li>{% endif %}
{% if object.modified_date %}<li>Dernière modification&nbsp;: {{ object.modified_date }}</li>{% endif %}
{% if object.moderated_date %}<li>Dernière modération&nbsp;: {{ object.moderated_date }}</li>{% endif %}
{% if object.imported_date %}<li>Dernière importation&nbsp;: {{ object.imported_date }}</li>{% endif %}
{% if object.uuids %}
{% if object.uuids|length > 0 %}
<li>UUIDs (identifiants uniques d'événements dans les sources)&nbsp;:

View File

@ -0,0 +1,14 @@
{% if event.exact_location %}
{% if nolink %}
{{ event.exact_location.name }}, {{ event.exact_location.city }}
{% else %}
<a href="{{ event.exact_location.get_absolute_url }}">{{ event.exact_location.name }}, {{ event.exact_location.city }}</a>
{% endif %}
{% else %}
{% if perms.agenda_culturel.change_event and perms.agenda_culturel.change_place %}
<a href="{% url 'add_place_to_event' event.pk %}" class="missing-data">{{ event.location }}</a>
{% else %}
{{ event.location }}
{% endif %}
{% endif %}

View File

@ -14,18 +14,16 @@ Mettre l'événement {{ event.title }} à la corbeille
{% block content %}
<h1>
{% if status == "published" %}
Publier l'événement {{ event.title }}
{% elif status == "draft" %}
Déplacer l'événement {{ event.title }} dans les brouillons
{% elif status == "trash" %}
Mettre l'événement {{ event.title }} à la corbeille
{% endif %}
</h1>
<article>
<header>
<h1>
{% if status == "published" %}
Publier l'événement {{ event.title }}
{% elif status == "draft" %}
Déplacer l'événement {{ event.title }} dans les brouillons
{% elif status == "trash" %}
Mettre l'événement {{ event.title }} à la corbeille
{% endif %}
</h1>
</header>
<form method="post">{% csrf_token %}
<p class="large">Êtes-vous sûr·e de vouloir
{% if status == "published" %}

View File

@ -4,11 +4,7 @@
{% block title %}{% block og_title %}
{% if object %}{% if form.is_clone_from_url %}
Création d'une copie locale de {% else %}
Édition de l'événement {{ object.title }} ({{ object.start_day }})
{% endif %}
{% if form.is_simple_clone_from_url %}
Duplication de {% else %}
Création d'une copie de {% else %}
Édition de l'événement {{ object.title }} ({{ object.start_day }})
{% endif %}
{% else %}
@ -48,21 +44,13 @@ Duplication de {% else %}
{% block content %}
{% load static_content_extra %}
<article id="event_form">
<article>
<header>
{% if object %}
<h1>{% if form.is_clone_from_url %}
Création d'une copie locale de {% else %}
{% if form.is_simple_clone_from_url %}
Duplication de {% else %}
Édition de l'événement{% endif %}{% endif %} {{ object.title }} ({{ object.start_day }})</h1>
{% if form.is_simple_clone_from_url %}
<p>Vous allez créer une copie de l'événement original qui n'aura plus aucun lien avec ce dernier. Cette possibilité
est généralement utilisée pour créer un événement semblable à une date différente, par exemple.
</p>
{% endif %}
Création d'une copie de {% else %}
Édition de l'événement{% endif %} {{ object.title }} ({{ object.start_day }})</h1>
{% else %}
{% if from_import %}
<h1>Ajuster l'événement importé</h1>
{% else %}
@ -72,14 +60,14 @@ Duplication de {% else %}
{% endif %}
</header>
{% if form.is_clone_from_url or form.is_simple_clone_from_url %}
<div id="container"></div>
{% if form.is_clone_from_url %}
{% url "add_event_details" as urlparam %}
{% endif %}
<form method="post" action="{{ urlparam }}" enctype="multipart/form-data">{% csrf_token %}
{{ form.media }}
{{ form }}
<div class="grid buttons stick-bottom">
{{ form.as_p }}
<div class="grid buttons">
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{{ object.get_absolute_url }}{% endif %}" role="button" class="secondary">Annuler</a>
<input type="submit" value="Enregistrer">
</div>
@ -94,44 +82,5 @@ Duplication de {% else %}
const element = document.querySelector('#id_exact_location');
const choices = new Choices(element);
show_firstgroup = {
choice(classes, choice) {
const i = Choices.defaults.templates.choice.call(this, classes, choice);
if (this.first_group !== null && choice.groupId == this.first_group)
i.classList.add("visible");
return i;
},
choiceGroup(classes, group) {
const g = Choices.defaults.templates.choiceGroup.call(this, classes, group);
if (this.first_group === undefined && group.value == "Suggestions")
this.first_group = group.id;
if (this.first_group !== null && group.id == this.first_group)
g.classList.add("visible");
return g;
}
};
const tags = document.querySelector('#id_tags');
const choices_tags = new Choices(tags,
{
placeholderValue: 'Sélectionner les étiquettes à ajouter',
allowHTML: true,
delimiter: ',',
removeItemButton: true,
shouldSort: false,
callbackOnCreateTemplates: () => (show_firstgroup)
}
);
const organisers = document.querySelector('#id_organisers');
const choices_organisers = new Choices(organisers,
{
placeholderValue: 'Sélectionner les organisateurs ',
allowHTML: true,
delimiter: ',',
removeItemButton: true,
shouldSort: true,
});
</script>
{% endblock %}

View File

@ -1,131 +0,0 @@
{% extends "agenda_culturel/page.html" %}
{% load static %}
{% load utils_extra %}
{% load cat_extra %}
{% block title %}{% block og_title %}Modération de l'événement {{ object.title }}{% endblock %}{% endblock %}
{% block ajouter-bouton %}{% block ajouter-menu %}{% endblock %}{% endblock %}
{% block entete_header %}
<script src="{% static 'choicejs/choices.min.js' %}"></script>
{% css_categories %}
<script src="/static/admin/js/vendor/jquery/jquery.js"></script>
<script src="/static/admin/js/jquery.init.js"></script>
<link href="{% static 'css/django_better_admin_arrayfield.min.css' %}" type="text/css" media="all" rel="stylesheet">
<script src="{% static 'js/django_better_admin_arrayfield.min.js' %}"></script>
{% endblock %}
{% block content %}
{% load static_content_extra %}
<article>
<header>
<h1>Modération de l'événement {{ object.title }}</h1>
<p class="remarque">En utilisant cet outil de modération, l'événement ne sera pas modifié{% if event.pure_import %}
et restera considéré comme une version importée, peut importe les modifications d'étiquette,
de catégorie ou de lieu que vous lui appliquerez{% endif %}.
</p>
</header>
<form method="post" enctype="multipart/form-data" id="moderate-form">{% csrf_token %}
<div class="grid moderate-preview">
<div>
{% include "agenda_culturel/single-event/event-single-inc.html" with event=event onlyedit=1 %}
{% with event.get_concurrent_events as concurrent_events %}
{% if concurrent_events %}
<article>
<header>
<h2>En même temps</h2>
<p class="remarque">{% if concurrent_events|length > 1 %}Plusieurs événements se déroulent en même temps.{% else %}Un autre événement se déroule en même temps.{% endif %}</p>
</header>
<ul>
{% for e in concurrent_events %}
<li>
{{ e.category|circle_cat }} {% if e.start_time %}{{ e.start_time }}{% else %}<em>toute la journée</em>{% endif %} <a href="{{ e.get_absolute_url }}">{{ e.title }}</a>
</li>
{% endfor %}
</ul>
</article>
{% endif %}
{% endwith %}
</div>
<article>
<header>
<h2>Modification des méta-informations</h2>
{% if event.moderated_date %}
<p class="message info">Cet événement a déjà été modéré {% if event.moderation_by_user %}par {<em>{ event.moderation_by_user.username }}</em> {% endif %}le {{ event.moderated_date }}.
Vous pouvez bien sûr modifier de nouveau ces méta-informations en utilisant
le formulaire ci-après.
</p>
{% endif %}
</header>
{{ form.media }}
{{ form.as_p }}
</article>
</div>
<div class="grid buttons">
{% if pred %}
<a href="{% url 'moderate_event' pred %}" class="secondary" role="button">&lt; Revenir au précédent</a>
{% else %}
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{{ object.get_absolute_url }}{% endif %}" role="button" class="secondary">Annuler</a>
{% endif %}
<input type="submit" value="Enregistrer" name="save">
<input type="submit" value="Enregistrer et passer au suivant &gt;" name="save_and_next">
<a href="{% url 'moderate_event_next' event.pk %}" class="secondary" role="button">Passer au suivant sans enregistrer &gt;</a>
</div>
</form>
</article>
<script>
const element = document.querySelector('#id_exact_location');
const choices = new Choices(element);
show_firstgroup = {
choice(classes, choice) {
const i = Choices.defaults.templates.choice.call(this, classes, choice);
i.classList.add("visible");
return i;
},
choiceGroup(classes, group) {
const g = Choices.defaults.templates.choiceGroup.call(this, classes, group);
g.classList.add("visible");
return g;
}
};
const tags = document.querySelector('#id_tags');
const choices_tags = new Choices(tags,
{
placeholderValue: 'Sélectionner les étiquettes à ajouter',
allowHTML: true,
delimiter: ',',
removeItemButton: true,
shouldSort: false,
callbackOnCreateTemplates: () => (show_firstgroup)
}
);
const organisers = document.querySelector('#id_organisers');
const choices_organisers = new Choices(organisers,
{
placeholderValue: 'Sélectionner les organisateurs ',
allowHTML: true,
delimiter: ',',
removeItemButton: true,
shouldSort: true,
});
</script>
{% endblock %}

View File

@ -0,0 +1,39 @@
{% extends "agenda_culturel/event_form.html" %}
{% load static %}
{% load cat_extra %}
{% block entete_header %}
{% css_categories %}
{% endblock %}
{% block title %}{% block og_title %}Modérer {{ object.title }}{% endblock %}{% endblock %}
{% block content %}
{% load static_content_extra %}
<article>
<header>
<h1>Modération de l'événement {{ object.title }} ({{ object.start_day }})</h1>
</header>
{% include "agenda_culturel/single-event/event-single-inc.html" with event=object noedit=1 %}
<div id="container"></div>
<form method="post">{% csrf_token %}
{{ form.media }}
{{ form.as_p }}
<div class="grid buttons">
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{{ object.get_absolute_url }}{% endif %}" role="button" class="secondary">Annuler</a>
<input type="submit" value="Enregistrer">
</div>
</form>
{% include "agenda_culturel/event-info-inc.html" %}
</article>
{% endblock %}

View File

@ -1,40 +0,0 @@
{% extends "agenda_culturel/page.html" %}
{% load utils_extra %}
{% load static %}
{% block title %}{% block og_title %}Erreur pendant la recherche d'événement suivant{% endblock %}{% endblock %}
{% block entete_header %}
<script src="{% static 'js/modal.js' %}"></script>
{% endblock %}
{% block fluid %}{% endblock %}
{% block content %}
<article>
<header>
<h1>Erreur pendant la recherche d'événement suivant</h1>
</header>
{% if object %}
<p>Nous n'avons pas été capable de trouver un événement à modérer après
l'événement <a href="{{ object.get_absolute_url }}">{{ object.title }}</a>. Peut-être est-ce le dernier événement
avant la fin du monde&nbsp;?
</p>
{% else %}
<p>Nous n'avons pas été capable de trouver l'événement #{{ pk }} sur l'agenda.
Il a certainement été supprimé depuis que vous l'avez consulté.
</p>
{% endif %}
<footer>
<div class="grid buttons">
<a href="{% url 'recent' %}" role="button">Retour aux événements récents</a>
</div>
</footer>
</article>
{% endblock %}

View File

@ -2,7 +2,6 @@
{% load tag_extra %}
{% load utils_extra %}
{% load static %}
{% load locations_extra %}
{% if noarticle == 0 %}
<article id="filters">
@ -14,7 +13,6 @@
<div class="filtres">
<details {% if filter.is_active %}class="active"{% endif %} {% if filter.form and filter.form.errors %}open=""{% endif %}>
{{ filter.form.non_field_errors }}
<summary role="button" class="outline secondary">
{% if filter.is_active %}
<strong>Filtres&nbsp;: </strong>
@ -22,7 +20,7 @@
{{ t | tag_button:"true" }}
{% endfor %}
{% for t in filter.get_exclude_tags %}
{{ t | tag_button_strike:"true" }}
{{ t | tag_button_strike }}
{% endfor %}
{% for s in filter.get_status_names %}
{{ s }}
@ -41,6 +39,7 @@
<form method="get" class="form django-form main-filter">
<div class="grid two-columns-equal">
<article>
<header>Localisation</header>
{% for f in filter.form.visible_fields %}
{% if f.id_for_label and f.id_for_label in "id_position,id_radius" %}
<div>
@ -52,6 +51,7 @@
{% endfor %}
</article>
<article>
<header>Autres filtres</header>
{% for f in filter.form.visible_fields %}
{% if not f.id_for_label or not f.id_for_label in "id_position,id_radius" %}
<p>
@ -66,50 +66,25 @@
<button type="submit">Appliquer le filtre</button>
</form>
</details>
<div class="suggestions">
Suggestion&nbsp;:
{% show_suggested_positions filter=filter %}
{% show_suggested_tags filter=filter %}
<div class="suggested-tags">
{% show_suggested_tags filter=filter %}
</div>
</div>
<div class="clear"></div>
</div>
<script src="{% static 'choicejs/choices.min.js' %}"></script>
<script>
show_firstgroup = {
choice(classes, choice) {
const i = Choices.defaults.templates.choice.call(this, classes, choice);
if (this.first_group !== null && choice.groupId == this.first_group)
i.classList.add("visible");
return i;
},
choiceGroup(classes, group) {
const g = Choices.defaults.templates.choiceGroup.call(this, classes, group);
if (this.first_group === undefined && group.value == "Suggestions")
this.first_group = group.id;
if (this.first_group !== null && group.id == this.first_group)
g.classList.add("visible");
return g;
}
};
const position = document.querySelector('#id_position');
const choices_position = new Choices(position,
{
shouldSort: false,
}
);
const choices_position = new Choices(position);
const tags = document.querySelector('#id_tags');
const choices_tags = new Choices(tags,
{
placeholderValue: 'Chercher les étiquettes à inclure',
allowHTML: true,
delimiter: ',',
editItems: true,
removeItemButton: true,
shouldSort: false,
callbackOnCreateTemplates: () => (show_firstgroup)
}
}
);
const exclude_tags = document.querySelector('#id_exclude_tags');
const choices_exclude_tags = new Choices(exclude_tags,
@ -117,12 +92,15 @@
placeholderValue: 'Chercher les étiquettes à exclure',
allowHTML: true,
delimiter: ',',
editItems: true,
removeItemButton: true,
shouldSort: false,
callbackOnCreateTemplates: () => (show_firstgroup)
}
);
document.querySelector("#id_position .choices__inner").classList.add("contrast");
document.querySelector("#id_position .choices__inner").classList.add("outline");
document.querySelector("#id_position .choices__inner").setAttribute("role", "button");
</script>
{% if noarticle == 0 %}

View File

@ -1,67 +0,0 @@
{{ errors }}
{% if errors and not fields %}
<p>{% for field in hidden_fields %}{{ field }}{% endfor %}</p>
{% endif %}
{% for group, fields in form.fields_by_group %}
<div {% if group.maskable %}class="maskable_group"{% endif %} id="group_{{ group.id }}">
{% if group.maskable %}
<input
class="toggle_body"
type="checkbox"
id="maskable_group_{{ group.id }}"
name="group_{{ group.id }}"
{% if not group.default_masked %}checked{% endif %}
><label for="maskable_group_{{ group.id }}">{{ group.label }}</label>
{% endif %}
<div class="error_group">
{% for field in fields %}
<div id="error_{{ field.auto_id }}">
{{ field.errors }}
</div>
{% endfor %}
</div>
<div class="body_group">
{% for field in fields %}
<div id="div_{{ field.auto_id }}">
<div{% with classes=field.css_classes %}{% if classes %} class="{{ classes }}"{% endif %}{% endwith %}>
{% if field.label %}{{ field.label_tag }}{% endif %}
{{ field }}
{% if field.help_text %}
<span class="helptext"{% if field.auto_id %} id="{{ field.auto_id }}_helptext"{% endif %}>{{ field.help_text|safe }}</span>
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
{% if not errors %}
{% for field in hidden_fields %}{{ field }}{% endfor %}
{% endif %}
<script>
const maskables = document.querySelectorAll('.maskable_group');
maskables.forEach(function (item) {
if (!item.checked) {
item.querySelector('.body_group').classList.add('closed');
}
console.log('item ' + item);
item.querySelector('.toggle_body').addEventListener('change', (event) => {
if (event.currentTarget.checked) {
item.querySelector('.body_group').classList.remove('closed');
} else {
item.querySelector('.body_group').classList.add('closed');
}
})
window.addEventListener('load', function(event) {
if (item.querySelector('.toggle_body').checked) {
item.querySelector('.body_group').classList.remove('closed');
}
else {
item.querySelector('.body_group').classList.add('closed');
}
});
})
</script>

View File

@ -31,35 +31,4 @@
tout de même ajouter l'événement en le décrivant sur la page <a href="{% url 'add_event_details' %}" >d'ajout d'événement</a>.</p>
</article>
<script src="{% static 'choicejs/choices.min.js' %}"></script>
<script>
show_firstgroup = {
choice(classes, choice) {
const i = Choices.defaults.templates.choice.call(this, classes, choice);
if (this.first_group !== null && choice.groupId == this.first_group)
i.classList.add("visible");
return i;
},
choiceGroup(classes, group) {
const g = Choices.defaults.templates.choiceGroup.call(this, classes, group);
if (this.first_group === undefined && group.value == "Suggestions")
this.first_group = group.id;
if (this.first_group !== null && group.id == this.first_group)
g.classList.add("visible");
return g;
}
};
const tags = document.querySelector('#id_tags');
const choices_tags = new Choices(tags,
{
placeholderValue: 'Sélectionner les étiquettes à ajouter',
allowHTML: true,
delimiter: ',',
removeItemButton: true,
shouldSort: false,
callbackOnCreateTemplates: () => (show_firstgroup)
}
);
</script>
{% endblock %}

View File

@ -18,62 +18,27 @@
{% url 'event_import_urls' as local_url %}
{% include "agenda_culturel/static_content.html" with name="import_set" url_path=local_url %}
</header>
<p>Si l'import automatique ne marche pas, ou si l'événement n'est pas en ligne, vous pouvez
tout de même ajouter l'événement en le décrivant sur la page <a href="{% url 'add_event_details' %}" >d'ajout d'événement</a>.</p>
</article>
<form method="post">
{{ formset.management_form }}
{% csrf_token %}
{% for form in formset %}
<article>
<header>
<h2>Nouvel événement #{{ forloop.counter }}</h2>
</header>
<h2>Nouvel événement #{{ forloop.counter }}</h2>
{{ form.as_p }}
</article>
{% endfor %}
<div class="stick-bottom">
<input type="submit"value="Lancer l'import" id="import-button">
</div>
</form>
<script src="{% static 'choicejs/choices.min.js' %}"></script>
<script>
show_firstgroup = {
choice(classes, choice) {
const i = Choices.defaults.templates.choice.call(this, classes, choice);
if (this.first_group !== null && choice.groupId == this.first_group)
i.classList.add("visible");
return i;
},
choiceGroup(classes, group) {
const g = Choices.defaults.templates.choiceGroup.call(this, classes, group);
if (this.first_group === undefined && group.value == "Suggestions")
this.first_group = group.id;
if (this.first_group !== null && group.id == this.first_group)
g.classList.add("visible");
return g;
}
};
const tags = document.querySelectorAll("select[name$='-tags']");
Array.from(tags).forEach((element,index) => {
console.log(element);
const choices_tags = new Choices(element,
{
placeholderValue: 'Sélectionner les étiquettes à ajouter',
allowHTML: true,
delimiter: ',',
removeItemButton: true,
shouldSort: false,
callbackOnCreateTemplates: () => (show_firstgroup)
}
);
});
</script>
<footer>
<p>Si l'import automatique ne marche pas, ou si l'événement n'est pas en ligne, vous pouvez
tout de même ajouter l'événement en le décrivant sur la page <a href="{% url 'add_event_details' %}" >d'ajout d'événement</a>.</p>
</footer>
</article>
<dialog id="modal-import">
<article aria-busy="true">
Veuillez patienter, lien en cours d'importation...
</article>
</dialog>
{% endblock %}

View File

@ -1,51 +0,0 @@
{% extends "agenda_culturel/page-admin.html" %}
{% load static %}
{% load honeypot %}
{% block title %}{% block og_title %}{% if form.event %}Contact au sujet de l'événement {{ form.event.title }}{% else %}
Contact{% endif %}{% endblock %}{% endblock %}
{% block entete_header %}
<script>window.CKEDITOR_BASEPATH = '/static/ckeditor/ckeditor/';</script>
<script src="/static/admin/js/vendor/jquery/jquery.js"></script>
<script src="/static/admin/js/jquery.init.js"></script>
<link href="{% static 'css/django_better_admin_arrayfield.min.css' %}" type="text/css" media="all" rel="stylesheet">
<script src="{% static 'js/django_better_admin_arrayfield.min.js' %}"></script>
<script src="{% static 'js/adjust_datetimes.js' %}"></script>
{% endblock %}
{% block fluid %}{% endblock %}
{% block content %}
<article>
<header>
<h1>{% if form.event %}Contact au sujet de l'événement «&nbsp;{{ form.event.title }}&nbsp;»{% else %}
Contact{% endif %}</h1>
{% if not form.event %}
<article>
{% url 'contact' as local_url %}
{% include "agenda_culturel/static_content.html" with name="contact" url_path=local_url %}
</article>
{% endif %}
<p class="message warning"><strong>Attention&nbsp;:</strong> n'utilisez pas le formulaire ci-dessous pour proposer un événement, il sera ignoré. Utilisez plutôt la page <a href="{% url 'add_event' %}">ajouter un événement</a>.</p>
{% if form.event %}
<p>Tu nous contactes au sujet de l'événement «&nbsp;{{ form.event.title }}&nbsp;» du {{ form.event.start_day }}.
N'hésites pas à nous indiquer le maximum de contexte et à nous laisser ton adresse
afin que l'on puisse répondre à tes demandes ou remarques.
</p>
{% endif %}
</header>
<form method="post">{% csrf_token %}
{% render_honeypot_field "alias_name" %}
{{ form.media }}
{{ form.as_p }}
<input type="submit" value="Envoyer">
</form>
</article>
{% endblock %}

View File

@ -0,0 +1,20 @@
{% extends "agenda_culturel/page-admin.html" %}
{% block title %}{% block og_title %}Supprimer la réponse #{{ object.pk }}{% endblock %}{% endblock %}
{% block fluid %}{% endblock %}
{% block content %}
<h1>Suppression de la réponse de modération {{ object.pk }}</h1>
<form method="post">{% csrf_token %}
<p>Êtes-vous sûr·e de vouloir supprimer la réponse de modération #{{object.pk}} «&nbsp;{{ object.answer }}&nbsp;» associée à la question «&nbsp;{{ object.question.question }}&nbsp;»&nbsp;?
</p>
{{ form }}
<div class="grid buttons">
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{{ object.get_absolute_url }}{% endif %}" role="button" class="secondary">Annuler</a>
<input type="submit" value="Confirmer">
</div>
</form>
{% endblock %}

View File

@ -0,0 +1,31 @@
{% extends "agenda_culturel/page-admin.html" %}
{% load static %}
{% block title %}{% block og_title %}{% if form.instance.pk %}Modification{% else %}Création{% endif %} d'une réponse de modération{% endblock %}{% endblock %}
{% block entete_header %}
<script>window.CKEDITOR_BASEPATH = '/static/ckeditor/ckeditor/';</script>
<script src="/static/admin/js/vendor/jquery/jquery.js"></script>
<script src="/static/admin/js/jquery.init.js"></script>
<link href="{% static 'css/django_better_admin_arrayfield.min.css' %}" type="text/css" media="all" rel="stylesheet">
<script src="{% static 'js/django_better_admin_arrayfield.min.js' %}"></script>
<script src="{% static 'js/adjust_datetimes.js' %}"></script>
{% endblock %}
{% block content %}
<h1>{% if form.instance.pk %}Modification{% else %}Création{% endif %} d'une réponse de modération</h1>
<p>{% if form.instance.pk %}Modifier{% else %}Ajouter{% endif %} une réponse à la question « {{ question }} »</p>
<article>
<form method="post">{% csrf_token %}
{{ form.as_p }}
<div class="grid buttons">
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{% url 'view_mquestion' question.pk %}{% endif %}" role="button" class="secondary">Annuler</a>
<input type="submit" value="Envoyer">
</div>
</form>
</article>
{% endblock %}

View File

@ -0,0 +1,20 @@
{% extends "agenda_culturel/page-admin.html" %}
{% block title %}{% block og_title %}Supprimer la question #{{ object.pk }}{% endblock %}{% endblock %}
{% block fluid %}{% endblock %}
{% block content %}
<h1>Suppression de la question de modération {{ object.pk }}</h1>
<form method="post">{% csrf_token %}
<p>Êtes-vous sûr·e de vouloir supprimer la question de modération #{{object.pk}} «&nbsp;{{ object.question }}&nbsp;» ainsi que les réponses associées&nbsp;?
</p>
{{ form }}
<div class="grid buttons">
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{{ object.get_absolute_url }}{% endif %}" role="button" class="secondary">Annuler</a>
<input type="submit" value="Confirmer">
</div>
</form>
{% endblock %}

View File

@ -0,0 +1,71 @@
{% extends "agenda_culturel/page-admin.html" %}
{% block title %}{% block og_title %}Question de modération #{{ object.pk }}{% endblock %}{% endblock %}
{% load tag_extra %}
{% load utils_extra %}
{% load cat_extra %}
{% block entete_header %}
{% css_categories %}
{% endblock %}
{% block sidemenu-bouton %}
<li><a href="#contenu-principal" aria-label="Aller au contenu">{% picto_from_name "chevron-up" %}</a></li>
<li><a href="#sidebar" aria-label="Aller au menu latéral">{% picto_from_name "chevron-down" %}</a></li>
{% endblock %}
{% block content %}
<div class="grid two-columns">
<article>
<header>
<a href="{% url 'view_mquestions' %}" role="button">&lt; Retour</a>
<div class="slide-buttons">
<a href="{% url 'delete_mquestion' object.pk %}" role="button">Supprimer</a>
<a href="{% url 'edit_mquestion' object.pk %}" role="button">Modifier</a>
<a href="{% url 'add_manswer' object.pk %}" role="button">Ajouter une réponse {% picto_from_name "plus-circle" %}</a>
</div>
<h1>Question de modération #{{ object.pk }}</h1>
<p>{{ object.question }}</p>
</header>
{% if object.answers %}
{% for answer in object.answers.all %}
<article>
<div class="slide-buttons">
<a href="{% url 'edit_manswer' object.pk answer.pk %}" role="button">Modifier</a>
<a href="{% url 'delete_manswer' object.pk answer.pk %}" role="button">Supprimer</a>
</div>
<header>
<h4><strong>Réponse #{{ answer.pk }}&nbsp;:</strong> «&nbsp;{{ answer.answer }}&nbsp;»</h4>
{% if answer.adds_tags %}
<p>Cette réponse ajoute les étiquettes suivantes à l'événement&nbsp;:
{% for tag in answer.adds_tags %}
{{ tag | tag_button }}
{% endfor %}
</p>
{% else %}
<p><em>Cette réponse n'ajoute pas d'étiquette à l'événement.</em></p>
{% endif %}
{% if answer.removes_tags %}
<p>Cette réponse supprimer les étiquettes suivantes à l'événement&nbsp;:
{% for tag in answer.removes_tags %}
{{ tag | tag_button }}
{% endfor %}
</p>
{% else %}
<p><em>Cette réponse ne supprime pas d'étiquette à l'événement.</em></p>
{% endif %}
</header>
</article>
{% endfor %}
{% else %}
Il n'y a pas encore de réponse associée à cette question.
{% endif %}
</article>
{% include "agenda_culturel/side-nav.html" with current="moderationquestions" %}
</div>
{% endblock %}

View File

@ -0,0 +1,23 @@
{% extends "agenda_culturel/page-admin.html" %}
{% load static %}
{% block title %}{% block og_title %}
{% if form.instance.pk %}Modification{% else %}Création{% endif %} d'une question de modération
{% endblock %}{% endblock %}
{% block content %}
<h1>{% if form.instance.pk %}Modification{% else %}Création{% endif %} d'une question de modération</h1>
<article>
<form method="post">{% csrf_token %}
{{ form.as_p }}
<div class="grid buttons">
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{{ object.get_absolute_url }}{% endif %}" role="button" class="secondary">Annuler</a>
<input type="submit" value="Envoyer">
</div>
</form>
</article>
{% endblock %}

View File

@ -0,0 +1,71 @@
{% extends "agenda_culturel/page-admin.html" %}
{% block title %}{% block og_title %}Questions de modération{% endblock %}{% endblock %}
{% load utils_extra %}
{% load cat_extra %}
{% block entete_header %}
{% css_categories %}
{% endblock %}
{% block sidemenu-bouton %}
<li><a href="#contenu-principal" aria-label="Aller au contenu">{% picto_from_name "chevron-up" %}</a></li>
<li><a href="#sidebar" aria-label="Aller au menu latéral">{% picto_from_name "chevron-down" %}</a></li>
{% endblock %}
{% block content %}
<div class="grid two-columns">
<article>
<header>
<a class="slide-buttons" href="{% url 'add_mquestion'%}" role="button">Ajouter {% picto_from_name "plus-circle" %}</a>
<h1>Questions de modération</h1>
</header>
{% if object_list %}
{% for question in object_list %}
<article>
<header>
<a class="slide-buttons" href="{{ question.get_absolute_url }}" role="button">Détails...</a>
<h2>Question #{{ question.pk }}&nbsp;: {{ question.question }}</h2>
<p>{% if question.answers %}
<p>réponses possibles&nbsp;:</p>
<ul>
{% for answer in question.answers.all %}
<li>{{ answer.answer }}</li>
{% endfor %}
</ul>
{% else %}
<p><em>aucune réponse définie</em></p>
{% endif %}
</p>
</header>
</article>
{% endfor %}
{% else %}
<p>Il n'y a aucune question définie.</p>
{% endif %}
<footer>
<span>
{% if page_obj.has_previous %}
<a href="?page=1" role="button">&laquo; premier</a>
<a href="?page={{ page_obj.previous_page_number }}" role="button">précédent</a>
{% endif %}
<span>
Page {{ page_obj.number }} sur {{ page_obj.paginator.num_pages }}
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}" role="button">suivant</a>
<a href="?page={{ page_obj.paginator.num_pages }}" role="button">dernier &raquo;</a>
{% endif %}
</span>
</footer>
</article>
{% include "agenda_culturel/side-nav.html" with current="moderationquestions" %}
</div>
{% endblock %}

View File

@ -4,11 +4,9 @@
<a href="?page={{ page_obj.previous_page_number }}" role="button">précédent</a>
{% endif %}
{% if page_obj.paginator.num_pages != 1 %}
<span>
Page {{ page_obj.number }} sur {{ page_obj.paginator.num_pages }}
</span>
{% endif %}
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}" role="button">suivant</a>

View File

@ -1,25 +0,0 @@
{% extends "agenda_culturel/page.html" %}
{% block title %}{% block og_title %}Supprimer l'organisateur {{ object.name }}{% endblock %}{% endblock %}
{% block fluid %}{% endblock %}
{% block configurer-bouton %}{% endblock %}
{% block content %}
<article>
<header>
<h1>Supprimer l'organisateur {{ object.name }}</h1>
</header>
<form method="post">{% csrf_token %}
<p>Êtes-vous sûr·e de vouloir supprimer l'organisateur «&nbsp;{{ object.name }} ({{ object.pk }})&nbsp;»&nbsp;?</p>
{{ form }}
<div class="grid buttons">
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{{ object.get_absolute_url }}{% endif %}" role="button" class="secondary">Annuler</a>
<input type="submit" value="Confirmer">
</div>
</form>
</article>
{% endblock %}

View File

@ -1,95 +0,0 @@
{% extends "agenda_culturel/page.html" %}
{% block title %}{% block og_title %}{{ object.name }}{% endblock %}{% endblock %}
{% load tag_extra %}
{% load utils_extra %}
{% load cat_extra %}
{% load static %}
{% load cache %}
{% load l10n %}
{% block entete_header %}
{% css_categories %}
<script src="/static/admin/js/vendor/jquery/jquery.js"></script>
<script src="/static/admin/js/jquery.init.js"></script>
<script src="{% static 'location_field/leaflet/leaflet.js' %}"></script>
<link href="{% static 'location_field/leaflet/leaflet.css' %}" type="text/css" media="all" rel="stylesheet">
{% endblock %}
{% block fluid %}{% endblock %}
{% block body-class %}organisation{% endblock %}
{% block content %}
<article>
<header>
<a href="{% url 'view_organisations' %}" role="button">{% picto_from_name "chevron-left" %} Toutes les organisations</a>
{% if perms.agenda_culturel.change_organisation %}
<div class="slide-buttons">
<a href="{% url 'edit_organisation' object.pk %}" role="button">Modifier {% picto_from_name "edit-3" %}</a>
<a href="{% url 'delete_organisation' object.pk %}" role="button">Supprimer {% picto_from_name "trash-2" %}</a>
</div>
{% endif %}
<h1>{{ object.name }}</h1>
{% if object.website or object.principal_place %}
<ul>
{% if object.website %}
<li><strong>Site internet&nbsp;:</strong> <a href="{{ object.website }}">{{ object.website }}</a></li>
{% endif %}
{% if object.principal_place %}
<li><strong>Lieu principal&nbsp;:</strong> <a href="{{ object.principal_place.get_absolute_url }}">{{ object.principal_place }}</a></li>
{% endif %}
</ul>
{% endif %}
{% if user.is_authenticated %}
{% if object.recurrentimport_set.all|length == 1 %}
<p>Cet organisateur est associé à l'import récurrent <a href="{{ object.recurrentimport_set.all.0.get_absolute_url }}">{{ object.recurrentimport_set.all.0 }}</a>.</p>
{% endif %}
{% if object.recurrentimport_set.count > 1 %}
<p>Cet organisateur est associé aux imports récurrents&nbsp;:</p>
<ul>
{% for ir in object.recurrentimport_set.all %}
<li><a href="{{ ir.get_absolute_url }}">{{ ir }}</a></li>
{% endfor %}
</ul>
{% endif %}
{% endif %}
{{ object.description|safe }}
</header>
{% with cache_timeout=user.is_authenticated|yesno:"30,600" %}
{% cache cache_timeout organisation_list user.is_authenticated object page_obj.number past %}
<div class="slide-buttons">
{% if past %}
<a href="{{ object.get_absolute_url }}" role="button">Voir les événements à venir</a>
{% else %}
<a href="{% url 'view_organisation_past' object.pk %}" role="button">Voir les événements passés</a>
{% endif %}
</div>
{% if past %}
<h2>Événements passés</h2>
{% else %}
<h2>Événements à venir</h2>
{% endif %}
{% if object_list %}
{% include "agenda_culturel/navigation.html" with page_obj=page_obj %}
{% for event in object_list %}
{% include "agenda_culturel/single-event/event-elegant-inc.html" with event=event day=0 no_location=1 %}
{% endfor %}
{% include "agenda_culturel/navigation.html" with page_obj=page_obj %}
{% else %}
<p><em>Aucun événement</em></p>
{% endif %}
{% endcache %}
{% endwith %}
<footer>
</footer>
</article>
{% endblock %}

View File

@ -1,43 +0,0 @@
{% extends "agenda_culturel/page-admin.html" %}
{% load static %}
{% block entete_header %}
<script src="{% url 'jsi18n' %}"></script>
<script src="/static/admin/js/vendor/jquery/jquery.js"></script>
<script src="/static/admin/js/jquery.init.js"></script>
<script src="{% static 'choicejs/choices.min.js' %}"></script>
<script>window.CKEDITOR_BASEPATH = '/static/ckeditor/ckeditor/';</script>
{% endblock %}
{% block title %}{% block og_title %}{% if object %}Description de {{ object.name }}{% else %}Description d'un organisme{% endif %}{% endblock %}{% endblock %}
{% block fluid %}{% endblock %}
{% block content %}
<article>
<header>
<h1>{% if object %}Description de {{ object.name }}{% else %}Description d'un organisme{% endif %}</h1>
</header>
<form method="post">{% csrf_token %}
{{ form.media }}
{{ form.as_p }}
<div class="grid buttons">
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{% if object %}{{ object.get_absolute_url }}{% else %}{% url 'view_organisations' %}{% endif %}{% endif %}" role="button" class="secondary">Annuler</a>
<input type="submit" value="Envoyer">
</div>
</form>
</article>
<script>
const places = document.querySelector('#id_principal_place');
const choices_places = new Choices(places,
{
shouldSort: false,
}
);
</script>
{% endblock %}

View File

@ -1,76 +0,0 @@
{% extends "agenda_culturel/page.html" %}
{% block title %}{% block og_title %}Liste des organismes{% endblock %}{% endblock %}
{% block fluid %}{% endblock %}
{% load utils_extra %}
{% load cat_extra %}
{% block entete_header %}
{% css_categories %}
{% endblock %}
{% block sidemenu-bouton %}
<li><a href="#contenu-principal" aria-label="Aller au contenu">{% picto_from_name "chevron-up" %}</a></li>
<li><a href="#sidebar" aria-label="Aller au menu latéral">{% picto_from_name "chevron-down" %}</a></li>
{% endblock %}
{% block content %}
<article>
<header>
{% if user.is_authenticated %}
<div class="slide-buttons">
<a href="{% url 'add_organisation' %}" role="button">Ajouter {% picto_from_name "plus-circle" %}</a>
</div>
{% endif %}
<h1>Liste des organismes</h1>
</header>
{% include "agenda_culturel/navigation.html" with page_obj=page_obj %}
</article>
{% if object_list %}
{% for organisation in object_list %}
<article>
<header>
{% if user.is_authenticated %}
<div class="slide-buttons">
<a href="{% url 'edit_organisation' organisation.pk %}" role="button">Modifier {% picto_from_name "edit-3" %}</a>
<a href="{% url 'delete_organisation' organisation.pk %}" role="button">Supprimer {% picto_from_name "trash-2" %}</a>
</div>
{% endif %}
<h2>{{ organisation.name }}</h2>
</header>
{% if organisation.website or organisation.principal_place %}
<ul>
{% if organisation.website %}
<li><strong>Site internet&nbsp;:</strong> <a href="{{ organisation.website }}">{{ organisation.website }}</a></li>
{% endif %}
{% if organisation.principal_place %}
<li><strong>Lieu principal&nbsp;:</strong> <a href="{{ organisation.principal_place.get_absolute_url }}">{{ organisation.principal_place }}</a></li>
{% endif %}
</ul>
{% endif %}
<footer>
{% if organisation.organised_events.all|length > 1 %}
<p class="slide-buttons"><a role="button" href="{{ organisation.get_absolute_url }}">voir les {{ organisation.organised_events.all|length }} événements {% picto_from_name "chevron-right" %}</a></p>
{% else %}
<p class="slide-buttons"><a role="button" href="{{ organisation.get_absolute_url }}">voir les détails {% picto_from_name "chevron-right" %}</a></p>
{% endif %}
<div style="clear: both"></div>
</footer>
</article>
{% endfor %}
{% else %}
<p>Il n'y a aucun organisme défini.</p>
{% endif %}
<article>
<footer>
{% include "agenda_culturel/navigation.html" with page_obj=page_obj %}
</footer>
</article>
{% endblock %}

View File

@ -3,11 +3,10 @@
{% load cat_extra %}
{% load utils_extra %}
{% load event_extra %}
{% load cache %}
{% block title %}{% block og_title %}{{ event.title }}{% endblock %}{% endblock %}
{% block og_image %}{% if event.has_image_url %}{{ event|get_image_uri:request }}{% else %}{{ block.super }}{% endif %}{% endblock %}
{% block og_image %}{% if event.has_image_url %}{{ event.get_image_url }}{% else %}{{ block.super }}{% endif %}{% endblock %}
{% block og_description %}{% if event.description %}{{ event.description |truncatewords:20|linebreaks }}{% else %}{{ block.super }}{% endif %}{% endblock %}
{% block entete_header %}
@ -17,59 +16,21 @@
{% block content %}
<div class="grid two-columns">
<div>
{% with cache_timeout=user.is_authenticated|yesno:"30,600" %}
{% cache cache_timeout event_body user.is_authenticated event %}
{% include "agenda_culturel/single-event/event-single-inc.html" with event=event filter=filter %}
{% endcache %}
{% endwith %}
{% if user.is_authenticated %}
<article id="chronology">
<article>
<header>
<h2>Chronologie</h2>
<h2>Informations internes</h2>
</header>
{% for step in event.chronology %}
{% if step.is_date %}
<div class="entree dateline">
<div><span class="ts">{{ step.timestamp }}</span></div>
<div>
{% if step.data == "created_date" %}<em>création</em>{% if event.created_by_user %} par {{ event.created_by_user.username }}{% endif %}{% endif %}
{% if step.data == "modified_date" %}<em>dernière modification</em>{% if event.modified_by_user %} par {{ event.modified_by_user.username }}{% endif %}{% endif %}
{% if step.data == "moderated_date" %}<em>dernière modération</em>{% if event.moderated_by_user %} par {{ event.moderated_by_user.username }}{% endif %}{% endif %}
{% if step.data == "imported_date" %}<em>dernière importation</em>{% if event.imported_by_user %} par {{ event.imported_by_user.username }}{% endif %}{% endif %}
</div>
</div>
{% else %}
<div class="entree">
<div><span class="ts">{{ step.timestamp }}</span></div>
<div>
<header><strong>Message</strong>{% if step.data.related_event and event != step.data.related_event %} sur <a href="{{ step.data.related_event.get_absolute_url }}">une autre</a> version{% endif %}&nbsp;: <a href="{{ step.data.get_absolute_url }}">{{ step.data.subject|truncatechars:20 }}</a> {% if step.data.user %} par <em>{{ step.data.user }}</em>{% else %} par {{ step.data.name }} (<a href="mailto: {{ step.data.email }}">{{ step.data.email }}</a>){% endif %}</header>
<div class="texte">{{ step.data.message|safe }}</div>
{% if step.data.comments %}
<div><strong>Commentaire&nbsp;:</strong> {{ step.data.comments }}</div>
{% endif %}
</div>
</div>
{% endif %}
{% endfor %}
<form method="post">{% csrf_token %}
{{ form.media }}
{{ form.as_p }}
<input type="submit" value="Commenter">
</form>
{% include "agenda_culturel/event-info-inc.html" with allbutdates=1 %}
{% include "agenda_culturel/event-info-inc.html" with object=event %}
</article>
{% endif %}
</div>
{% with cache_timeout=user.is_authenticated|yesno:"30,600" %}
{% cache cache_timeout event_aside user.is_authenticated event %}
<aside>
{% with event.get_concurrent_events as concurrent_events %}
{% if concurrent_events %}
@ -90,7 +51,39 @@
</article>
{% endif %}
{% endwith %}
<article>
{% with event.get_nb_events_same_dates as nb_events_same_dates %}
{% with nb_events_same_dates|length as c_dates %}
<header>
<h2>Voir aussi</h2>
{% if c_dates != 1 %}
<p class="remarque">
Retrouvez ci-dessous tous les événements
{% if event.is_single_day %}
à la même date
{% else %}
aux mêmes dates
{% endif %}
que l'événement affiché.
</p>
{% endif %}
</header>
<nav>
{% if c_dates == 1 %}
<a role="button" href="{% url 'day_view' nb_events_same_dates.0.1.year nb_events_same_dates.0.1.month nb_events_same_dates.0.1.day %}">Toute la journée</a>
{% else %}
<ul>
{% for nbevents_date in nb_events_same_dates %}
<li>
<a href="{% url 'day_view' nbevents_date.1.year nbevents_date.1.month nbevents_date.1.day %}">{{ nbevents_date.0 }} événement{{ nbevents_date.0 | pluralize }} le {{ nbevents_date.1 }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
</nav>
{% endwith %}
{% endwith %}
</article>
{% if event.other_versions and not event.other_versions.fixed %}
{% with poss_dup=event.get_other_versions|only_allowed:user.is_authenticated %}
{% if poss_dup|length > 0 %}
@ -129,15 +122,12 @@
{% else %}
Signaler comme doublon
{% endif %}</a>
<a role="button" href="{% url 'message_for_event' event.pk %}">Signaler cet événement</a>
</article>
</article>
</aside>
{% endcache %}
{% endwith %}
</div>
{% endblock %}

View File

@ -1,6 +1,9 @@
{% extends "agenda_culturel/page.html" %}
{% load i18n %}
{% load cat_extra %}
{% load event_extra %}
{% load utils_extra %}
@ -22,11 +25,12 @@
{% block content %}
{% get_current_language as LANGUAGE_CODE %}
{% with cache_timeout=user.is_authenticated|yesno:"30,600" %}
{% cache cache_timeout month user.is_authenticated calendar.firstdate filter.to_str LANGUAGE_CODE %}
{% include "agenda_culturel/filter-inc.html" with filter=filter noarticle=0 %}
{% with cache_timeout=user.is_authenticated|yesno:"30,600" %}
{% cache cache_timeout month user.is_authenticated calendar.firstdate filter.get_url %}
<article>
<header>
@ -60,75 +64,12 @@
{% endif %}
<div class="grid week-in-month">
{% for day in calendar.calendar_days_list %}
{% for d in calendar.calendar_days_list %}
{% if forloop.counter0|divisibleby:7 %}
{% if not forloop.first %}</div><div class="grid week-in-month">{% endif %}
<div id="week-{{ day.date.isocalendar.1 }}" class="entete-semaine"><h2><a href="{% url 'week_view' calendar.year day.week %}?{{ filter.get_url }}">Semaine {{ d.week }}</a></h2></div>
<div id="week-{{ d.date.isocalendar.1 }}" class="entete-semaine"><h2><a href="{% url 'week_view' calendar.year d.week %}?{{ filter.get_url }}">Semaine {{ d.week }}</a></h2></div>
{% endif %}
{% with day.date|date:"Y-m-d" as daytag %}
{% with "date-"|add:daytag as daytag %}
<article class="day{{ day|calendar_classes:calendar.all_in_past }}" id="{{ daytag }}">
{% if day.is_today %}
<script>
document.addEventListener("DOMContentLoaded", function(e) {
agenda = document.getElementById("calendar");
todayArticle = document.getElementById("today").parentElement;
left = todayArticle.offsetLeft;
top = todayArticle.offsetTop;
agenda.scrollTo({top: top, left: left});
});
</script>
{% endif %}
<header{% if day.is_today %} id="today"{% endif %}>
<h3><a href="{{ day.date | url_day }}?{{ filter.get_url }}">{{ day.date | date:"l j" }}</a></h3}>
</header>
{% if day.events %}
<ul>
{% for category, events in day.events_by_category_ordered %}
<li>{{ events.0.category | circle_cat }}
<a href="{{ day.date | url_day }}?{{ filter.get_url|add_url_category:events.0.category }}" data-target="{{ daytag }}-category-{{ events.0.category.pk }}" onClick="toggleModal(event)">{{ events | length }} {{ category }}</a></li>
<dialog id="{{ daytag }}-category-{{ events.0.category.pk }}">
<article>
<header>
<a href="#{{ daytag }}-category-{{ events.0.category.pk }}"
aria-label="Fermer"
class="close"
data-target="{{ daytag }}-category-{{ events.0.category.pk }}"
onClick="toggleModal(event)"></a>
<h3>{{ events.0.category | small_cat }} du {{ day.date | date:"l j F" }}</h3>
</header>
<ul>
{% for event in events %}
<li>
{% if event.start_day == day.date and event.start_time %}
{{ event.start_time }}
{% endif %}
<a href="{{ event.get_absolute_url }}">{{ event|picto_status }} {{ event.title|no_emoji }} {{ event|tw_badge }}</a>
</li>
{% endfor %}
</ul>
<footer>
<div class="buttons">
<a href="{{ day.date | url_day }}?{{ filter.get_url }}" role="button">Voir la journée <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' %}#chevron-right" />
</svg></a>
</div>
</footer>
</article>
</dialog>
{% endfor %}
<ul>
{% endif %}
</ul>
</article>
{% endwith %}
{% endwith %}
{% include "agenda_culturel/day-inc.html" with day=d resume=1 fixed_style=calendar.all_in_past filter=filter headers="h3" %}
{% endfor %}
</div>
{% if calendar.lastdate|shift_day:+1|not_after_last %}

View File

@ -4,7 +4,6 @@
{% load cat_extra %}
{% load utils_extra %}
{% load tag_extra %}
{% block entete_header %}
{% css_categories %}
@ -39,13 +38,12 @@
<li><strong>Adresse naviguable&nbsp;:</strong> <a href="{{ object.browsable_url }}">{{ object.browsable_url }}</a></li>
<li><strong>Valeurs par défaut&nbsp;:</strong>
<ul>
<li><strong>Publié&nbsp;:</strong> {{ object.defaultPublished|yesno:"Oui,Non" }}</li>
{% if object.defaultLocation %}<li><strong>Localisation&nbsp;:</strong> {{ object.defaultLocation }}</li>{% endif %}
<li><strong>Publié&nbsp;:</strong> {{ object.defaultPublished }}</li>
<li><strong>Localisation&nbsp;:</strong> {{ object.defaultLocation }}</li>
<li><strong>Catégorie&nbsp;:</strong> {{ object.defaultCategory }}</li>
{% if object.defaultOrganiser %}<li><strong>Organisateur&nbsp;:</strong> <a href="{{ object.defaultOrganiser.get_absolute_url }}">{{ object.defaultOrganiser }}</a></li>{% endif %}
<li><strong>Étiquettes&nbsp;:</strong>
{% for tag in object.defaultTags %}
<a href="{% url 'view_tag' tag %}">{{ tag|tw_highlight }}</a>{% if not forloop.last %}, {% endif %}
{{ tag }}{% if not forloop.last %}, {% endif %}
{% endfor %}
</li>
</ul>

View File

@ -1,12 +1,10 @@
{% extends "agenda_culturel/page-single.html" %}
{% load static %}
{% block content %}
<article>
<header>
<h1>{{ title }}</h1>
</header>
<div class="image-flottante"><img src="{% static 'images/alunissage.svg' %}" alt="deux pommes qui alunissent" /></div>
{% include "agenda_culturel/static_content.html" with name=static_content url_path=url_path %}
<ul>
{% for rimport in rimports %}

View File

@ -1,6 +1,7 @@
{% extends "agenda_culturel/page.html" %}
{% load i18n %}
{% load cache %}
{% load cat_extra %}
{% load event_extra %}
@ -20,6 +21,9 @@
{% block content %}
{% get_current_language as LANGUAGE_CODE %}
{% with cache_timeout=user.is_authenticated|yesno:"30,600" %}
{% cache cache_timeout upcoming user.is_authenticated calendar.firstdate filter.to_str calendar.calendar_days_list|length LANGUAGE_CODE %}
<article id="filters">
<header><h1 id="index-avenir">{% block title %}{% block og_title %}
@ -36,9 +40,6 @@
{% include "agenda_culturel/filter-inc.html" with filter=filter noarticle=1 %}
{% with cache_timeout=user.is_authenticated|yesno:"30,600" %}
{% cache cache_timeout upcoming user.is_authenticated calendar.firstdate filter.get_url calendar.calendar_days_list|length %}
<footer>
<a href="{% if calendar.calendar_days_list|length == 1 %}{% url 'day_view' date_pred.year date_pred.month date_pred.day %}{% else %}{% url 'a_venir_jour' date_pred.year date_pred.month date_pred.day %}{% endif %}" aria-label="dates précédentes" role="button">
{% picto_from_name "chevrons-left" %}
@ -95,19 +96,13 @@
<h3>{{ ti.short_name }} <a class="badge simple" href="#{{ ti.id }}" data-tooltip="Aller à {{ ti.name }}">{{ ti.events|length }} {% picto_from_name "chevrons-down" %}</a></h3>
<ul>
{% for event in ti.events %}
{% if event.is_first_after_now %}
<li class="line-now"><div><div>{% now "H:i" %}</div><div class="line"></div></div></li>
{% endif %}
<li>{{ event.category | circle_cat:event.has_recurrences }}
{% if event.start_time %}
{{ event.start_time }}
{% endif %}
{{ event|picto_status }} <a href="#event-{{ event.id }}">{{ event.title|no_emoji }}</a> {{ event|tw_badge }}
{{ event|picto_status }} <a href="#event-{{ event.id }}">{{ event.title }}</a>
</li>
{% endfor %}
{% if forloop.last and cd.is_today_after_events %}
<li class="line-now"><div><div>{% now "H:i" %}</div><div class="line"></div></div></li>
{% endif %}
</ul>
{% endif %}
{% endfor %}

View File

@ -1,12 +1,13 @@
{% extends "agenda_culturel/page.html" %}
{% load i18n %}
{% load cache %}
{% load cat_extra %}
{% load event_extra %}
{% load utils_extra %}
{% load static %}
{% load tag_extra %}
{% load i18n %}
{% block entete_header %}
{% css_categories %}
@ -20,10 +21,11 @@
{% block content %}
{% include "agenda_culturel/filter-inc.html" with filter=filter noarticle=0 %}
{% get_current_language as LANGUAGE_CODE %}
{% with cache_timeout=user.is_authenticated|yesno:"30,600" %}
{% cache cache_timeout week user.is_authenticated calendar.firstdate filter.get_url %}
{% cache cache_timeout week user.is_authenticated calendar.firstdate filter.to_str LANGUAGE_CODE %}
{% include "agenda_culturel/filter-inc.html" with filter=filter noarticle=0 %}
<article>
<header>
@ -37,7 +39,7 @@
<div>
{% if calendar.firstdate|shift_day:-1|not_before_first %}
{% if calendar.lastdate|not_after_last %}
<a role="button" href="{% url 'week_view' calendar.previous_week|weekyear calendar.previous_week|week %}?{{ filter.get_url }}">
<a role="button" href="{% url 'week_view' calendar.previous_week.year calendar.previous_week|week %}?{{ filter.get_url }}">
{% picto_from_name "chevron-left" %} précédente</a>
{% endif %}
{% endif %}
@ -45,7 +47,7 @@
{% if calendar.lastdate|shift_day:+1|not_after_last %}
{% if calendar.lastdate|not_before_first %}
<div class="right">
<a role="button" href="{% url 'week_view' calendar.next_week|weekyear calendar.next_week|week %}?{{ filter.get_url }}">suivante
<a role="button" href="{% url 'week_view' calendar.next_week.year calendar.next_week|week %}?{{ filter.get_url }}">suivante
{% picto_from_name "chevron-right" %}
</a>
</div>
@ -57,116 +59,19 @@
<div class="slider-button slider-button-inside button-left hidden">{% picto_from_name "arrow-left" %}</div>
{% if calendar.firstdate|shift_day:-1|not_before_first %}
{% if calendar.lastdate|not_after_last %}
<div class="slider-button slider-button-page button-left hidden"><a href="{% url 'week_view' calendar.previous_week|weekyear calendar.previous_week|week %}?{{ filter.get_url }}">{% picto_from_name "chevrons-left" %}</a></div>
<div class="slider-button slider-button-page button-left hidden"><a href="{% url 'week_view' calendar.previous_week.year calendar.previous_week|week %}?{{ filter.get_url }}">{% picto_from_name "chevrons-left" %}</a></div>
{% endif %}
{% endif %}
<div class="grid">
{% for day in calendar.calendar_days_list %}
{% with day.date|date:"Y-m-d" as daytag %}
{% with "date-"|add:daytag as daytag %}
<article class="day{{ day|calendar_classes:calendar.all_in_past }}" id="{{ daytag }}">
{% if day.is_today %}
<script>
document.addEventListener("DOMContentLoaded", function(e) {
agenda = document.getElementById("calendar");
todayArticle = document.getElementById("today").parentElement;
left = todayArticle.offsetLeft;
top = todayArticle.offsetTop;
agenda.scrollTo({top: top, left: left});
});
</script>
{% endif %}
<header{% if day.is_today %} id="today"{% endif %}>
<h2><a href="{{ day.date | url_day }}?{{ filter.get_url }}">{{ day.date | date:"l j" }}</a></h2>
</header>
{% if day.events %}
<ul>
{% for event in day.events %}
{% if event.is_first_after_now %}
<li class="line-now"><div><div>{% now "H:i" %}</div><div class="line"></div></div></li>
{% endif %}
<li>{{ event.category | circle_cat:event.has_recurrences }}
{% if event.start_day == day.date and event.start_time %}
{{ event.start_time }}
{% endif %}
{{ event|picto_status }} <a href="{{ event.get_absolute_url }}" data-target="event-{{ event.id }}" onClick="toggleModal(event)">{{ event.title|no_emoji }}</a>
{{ event|tw_badge }}
<dialog id="event-{{ event.id }}">
<article>
<header>
<a href="#event-{{ event.id }}"
aria-label="Fermer"
class="close"
data-target="event-{{ event.id }}"
onClick="toggleModal(event)"></a>
<h3>{{ event.category|small_cat_recurrent:event.has_recurrences }} {{ event|picto_status }} {{ event.title }} {{ event|picto_visibility:user.is_authenticated }}</h3>
<p>
{% picto_from_name "map-pin" %}
{% if event.exact_location %}
{{ event.exact_location.name }}, {{ event.exact_location.city }}
{% else %}
{% if perms.agenda_culturel.change_event and perms.agenda_culturel.change_place %}
<a href="{% url 'add_place_to_event' event.pk %}" class="missing-data">{{ event.location }}</a>
{% else %}
{{ event.location }}
{% endif %}
{% endif %} </p>
<p>
{% picto_from_name "calendar" %}
{% if event.end_day and event.end_day != event.start_day %}du{% else %}le{% endif %}
{{ event.start_day|date|frdate }}
{% if event.start_time %} {% if not event.end_day or event.end_day == event.start_day %}{% if event.end_time %}de{% else %}à{% endif %}{% endif %}
{{ event.start_time }}
{% endif %}
{% if event.end_day and event.end_day != event.start_day %}
au {% if event.end_day and event.end_day != event.start_day %}{{ event.end_day|date|frdate }}{% endif %}
{% endif %}
{% if event.end_time %} {% if not event.end_day|date|frdate or event.end_day == event.start_day %}jusqu'à{% endif %} {{ event.end_time }}{% endif %}
</p>
</header>
<div class="body-fixed">{{ event.description |linebreaks }}</div>
<p>
{% for tag in event.sorted_tags %}
{{ tag | tag_button }}
{% endfor %}
</p>
<footer class="infos-and-buttons">
<div class="buttons">
<a href="{{ event.get_absolute_url }}" role="button">Voir l'événement <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' %}#chevron-right" />
</svg></a>
</div>
</footer>
</article>
</dialog>
</li>
{% endfor %}
{% if day.is_today_after_events %}
<li class="line-now"><div><div>{% now "H:i" %}</div><div class="line"></div></div></li>
{% endif %}
</ul>
{% endif %}
</ul>
</article>
{% endwith %}
{% endwith %}
{% for d in calendar.calendar_days_list %}
{% include "agenda_culturel/day-inc.html" with day=d resume=0 fixed_style=calendar.all_in_past filter=filter headers="" %}
{% endfor %}
</div>
<div class="slider-button slider-button-inside button-right hidden">{% picto_from_name "arrow-right" %}</div>
{% if calendar.lastdate|shift_day:+1|not_after_last %}
{% if calendar.lastdate|not_before_first %}
<div class="slider-button slider-button-page button-right hidden"><a href="{% url 'week_view' calendar.next_week|weekyear calendar.next_week|week %}?{{ filter.get_url }}">{% picto_from_name "chevrons-right" %}</a></div>
<div class="slider-button slider-button-page button-right hidden"><a href="{% url 'week_view' calendar.next_week.year calendar.next_week|week %}?{{ filter.get_url }}">{% picto_from_name "chevrons-right" %}</a></div>
{% endif %}
{% endif %}

View File

@ -1,11 +1,5 @@
<!DOCTYPE html>
<html lang="fr">
{% load event_extra %}
{% load cache %}
{% load messages_extra %}
{% load utils_extra %}
{% load duplicated_extra %}
{% load rimports_extra %}
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@ -15,7 +9,7 @@
{% load static %}
<meta property="og:title" content="Pommes de lune — {% block og_title %}{% endblock %}" />
<meta property="og:description" content="{% block og_description %}Événements culturels à Clermont-Ferrand et aux environs{% endblock %}" />
<meta property="og:image" content="{% block og_image %}https://{{ request.get_host }}{% get_media_prefix %}screenshot.png{% endblock %}" />
<meta property="og:image" content="{% block og_image %}{% static 'images/capture.png' %}{% endblock %}" />
<meta property="og:url" content="{{ request.build_absolute_uri }}" />
{% if debug %}
@ -33,7 +27,12 @@
{% block entete_header %}
{% endblock %}
</head>
{% load event_extra %}
{% load cache %}
{% load contactmessages_extra %}
{% load utils_extra %}
{% load duplicated_extra %}
{% load rimports_extra %}
<body class="{% block body-class %}contenu{% endblock %}">
<div id="boutons-fixes">
<ul>
@ -51,7 +50,7 @@
{% if user.is_authenticated %}{% block configurer-menu %}<li id="menu-configurer" class="configurer-bouton"><a href="{% url 'administration' %}">Administrer {% picto_from_name "settings" %}</a></li>{% endblock %}{% endif %}
{% block ajouter-menu %}<li id="menu-ajouter" class="ajouter-bouton"><a href="{% url 'add_event' %}">Ajouter un événement {% picto_from_name "plus-circle" %}</a></li>{% endblock %}
{% block rechercher-menu %}<li id="menu-rechercher" class="rechercher-bouton"><a href="{% url 'event_search' %}">Rechercher {% picto_from_name "search" %}</a></li>{% endblock %}
<li><a href="{% url 'a_venir' %}{% block a_venir_parameters %}{% endblock %}">Maintenant</a></li>
<li><a href="{% url 'a_venir' %}{% block a_venir_parameters %}{% endblock %}">À venir</a></li>
<li><a href="{% url 'cette_semaine' %}{% block cette_semaine_parameters %}{% endblock %}">Cette semaine</a></li>
<li><a href="{% url 'ce_mois_ci' %}{% block ce_mois_ci_parameters %}{% endblock %}">Ce mois-ci</a></li>
</ul>
@ -66,7 +65,8 @@
<li>
<div>
{% if perms.agenda_culturel.view_recurrentimport %}
{% show_badges_rimports "bottom" %}
{% show_badge_rimports "bottom" "failed" %}
{% show_badge_rimports "bottom" "running" %}
{% endif %}
{% if perms.agenda_culturel.change_event %}
{% show_badges_events "bottom" %}
@ -77,8 +77,8 @@
{% if perms.agenda_culturel.change_place and perms.agenda_culturel.change_event %}
{% show_badge_unknown_places "bottom" %}
{% endif %}
{% if perms.agenda_culturel.view_message %}
{% show_badge_messages "bottom" %}
{% if perms.agenda_culturel.view_contactmessage %}
{% show_badge_contactmessages "bottom" %}
{% endif %}
{% if user.is_authenticated %}
{{ user.username }} @
@ -108,9 +108,6 @@
<div>
<a href="{% url 'view_places' %}">{% picto_from_name "map-pin" %} lieux</a>
</div>
<div>
<a href="{% url 'view_organisations' %}">{% picto_from_name "users" %} organisateurs</a>
</div>
<div>
<a href="{% url 'view_all_tags' %}">{% picto_from_name "tag" %} étiquettes</a>
</div>

View File

@ -7,6 +7,7 @@
{% load cat_extra %}
{% load static %}
{% load cache %}
{% load i18n %}
{% load l10n %}
{% block entete_header %}
@ -48,28 +49,6 @@
{% endif %}
{% endwith %}
</ul>
{% if object.description and not object.description|html_vide %}
<h2>Description du lieu</h2>
{{ object.description|safe }}
{% endif %}
{% with object.organisation_set.all as organisations %}
{% if organisations|length == 1 %}
<p>L'organisme <a href="{{ organisations.0.get_absolute_url }}">{{ organisations.0 }}</a> organise régulièrement des événements dans ce lieu.</p>
{% endif %}
{% if organisations|length > 1 %}
<p>Les organismes suivants utilisent régulièrement ce lieu&nbsp;:</p>
<ul>
{% for o in organisations %}
<li><a href="{{ o.get_absolute_url }}">{{ o }}</a></li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
</div>
<div>
<div id="map_location" style="width: 100%; aspect-ratio: 16/16"></div>
@ -85,6 +64,7 @@
<p>Voir aussi <a href="{% url 'view_places' %}">les autres lieux</a></p>
</div>
</div>
{% get_current_language as LANGUAGE_CODE %}
{% with cache_timeout=user.is_authenticated|yesno:"30,600" %}
{% cache cache_timeout place_list user.is_authenticated object page_obj.number past %}
<div class="slide-buttons">
@ -94,12 +74,12 @@
<a href="{% url 'view_place_past' object.pk %}" role="button">Voir les événements passés</a>
{% endif %}
</div>
{% if past %}
<h2>Événements passés</h2>
{% else %}
<h2>Événements à venir</h2>
{% endif %}
{% if object_list %}
{% if past %}
<h2>Événements passés</h2>
{% else %}
<h2>Événements à venir</h2>
{% endif %}
{% include "agenda_culturel/navigation.html" with page_obj=page_obj %}

View File

@ -22,33 +22,16 @@
<article>
{% if event %}
<p>Création d'un lieu depuis l'événement « {{ event }} » (voir en bas de page le détail de l'événement).</p>
<p><strong>Remarque&nbsp;:</strong> les champs ont été pré-remplis à partir de la description sous forme libre et n'est probablement pas parfaite.</p>
{% endif %}
<form method="post">{% csrf_token %}
<div class="grid form-place">
{{ form }}
<div class="map-widget">
<div id="map_location" style="width: 100%; aspect-ratio: 16/9"></div>
<p>Cliquez pour ajuster la position GPS</p>
<input type="checkbox" role="switch" id="lock_position">Verrouiller la position</input>
<script>
document.getElementById("lock_position").onclick = function() {
const field = document.getElementById("id_location");
if (this.checked)
field.setAttribute("readonly", true);
else
field.removeAttribute("readonly");
}
</script>
</div>
</div>
{{ form.as_grid }}
<div class="grid buttons">
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{{ object.get_absolute_url }}{% endif %}" role="button" class="secondary">Annuler</a>
<input type="submit" value="Envoyer">
</div>
</form>
{% if event %}
<h2>Détails de l'événement&nbsp;:</h2>
<h2>Description du lieu&nbsp;:</h2>
{% include "agenda_culturel/single-event/event-single-inc.html" with event=event noedit=1 %}
{% endif %}
</article>

Some files were not shown because too many files have changed in this diff Show More