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
136 changed files with 2271 additions and 6986 deletions

View File

@ -36,7 +36,7 @@ Pour ajouter une nouvelle source custom:
### Récupérer un dump du prod sur un serveur dev ### Récupérer un dump du prod sur un serveur dev
* sur le serveur de 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: * sur le serveur de prod:
* On récupère le dump json ```scp $SERVEUR:$PATH/fixtures/postgres-backup-20241101.json src/fixtures/``` * 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 * ```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 \ RUN --mount=type=cache,target=/var/cache/apt \
apt-get update && \ 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/* && rm -rf /var/lib/apt/lists/*
COPY src/requirements.txt ./requirements.txt COPY src/requirements.txt ./requirements.txt
RUN --mount=type=cache,target=/root/.cache/pip \ RUN --mount=type=cache,target=/root/.cache/pip \

View File

@ -32,7 +32,7 @@ http {
error_page 502 /static/html/500.html; error_page 502 /static/html/500.html;
error_page 503 /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; 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" echobold "Setup database stucture according to the selected commit"
docker exec -i agenda_culturel-backend python3 manage.py migrate agenda_culturel 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 # import data
echobold "Import data" echobold "Import data"
docker exec -i agenda_culturel-backend python3 manage.py loaddata --format=json $FFILE docker exec -i agenda_culturel-backend python3 manage.py loaddata --format=json $FFILE
@ -89,4 +85,7 @@ git checkout main
echobold "Update database" echobold "Update database"
docker exec -i agenda_culturel-backend python3 manage.py migrate agenda_culturel 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, BatchImportation,
RecurrentImport, RecurrentImport,
Place, Place,
Message, ContactMessage,
ReferenceLocation, ReferenceLocation
Organisation
) )
from django_better_admin_arrayfield.admin.mixins import DynamicArrayMixin from django_better_admin_arrayfield.admin.mixins import DynamicArrayMixin
from django_better_admin_arrayfield.forms.widgets import DynamicArrayWidget from django_better_admin_arrayfield.forms.widgets import DynamicArrayWidget
@ -25,9 +24,8 @@ admin.site.register(DuplicatedEvents)
admin.site.register(BatchImportation) admin.site.register(BatchImportation)
admin.site.register(RecurrentImport) admin.site.register(RecurrentImport)
admin.site.register(Place) admin.site.register(Place)
admin.site.register(Message) admin.site.register(ContactMessage)
admin.site.register(ReferenceLocation) admin.site.register(ReferenceLocation)
admin.site.register(Organisation)
class URLWidget(DynamicArrayWidget): class URLWidget(DynamicArrayWidget):

View File

@ -117,23 +117,6 @@ class DayInCalendar:
if e.start_time is None if e.start_time is None
else e.start_time 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): def events_by_category_ordered(self):
from .models import Category from .models import Category
@ -192,13 +175,12 @@ class IntervalInDay(DayInCalendar):
self.id = self.id + '-' + str(id) self.id = self.id + '-' + str(id)
class CalendarList: 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.firstdate = firstdate
self.lastdate = lastdate self.lastdate = lastdate
self.now = date.today() self.now = date.today()
self.filter = filter self.filter = filter
self.ignore_dup = ignore_dup self.ignore_dup = ignore_dup
self.qs = qs
if exact: if exact:
self.c_firstdate = self.firstdate self.c_firstdate = self.firstdate
@ -237,12 +219,9 @@ class CalendarList:
def fill_calendar_days(self): def fill_calendar_days(self):
if self.filter is None: if self.filter is None:
if self.qs is None: from .models import Event
from .models import Event
qs = Event.objects.all() qs = Event.objects.all()
else:
qs = self.qs
else: else:
qs = self.filter.qs qs = self.filter.qs
@ -250,7 +229,7 @@ class CalendarList:
qs = qs.exclude(other_versions=self.ignore_dup) qs = qs.exclude(other_versions=self.ignore_dup)
startdatetime = timezone.make_aware(datetime.combine(self.c_firstdate, time.min), timezone.get_default_timezone()) 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()) 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=True) & Q(recurrence_dtstart__lte=lastdatetime))
| ( | (
Q(recurrence_dtend__isnull=False) Q(recurrence_dtend__isnull=False)
@ -259,15 +238,11 @@ class CalendarList:
| Q(recurrence_dtend__lt=startdatetime) | 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( ).filter(
Q(other_versions__isnull=True) | Q(other_versions__isnull=True) |
Q(other_versions__representative=F('pk')) | Q(other_versions__representative=F('pk')) |
Q(other_versions__representative__isnull=True) Q(other_versions__representative__isnull=True)
).order_by("start_time", "title__unaccent__lower") ).order_by("start_time", "title__unaccent__lower").prefetch_related("exact_location").prefetch_related("category").prefetch_related("other_versions")
qs = qs.select_related("exact_location").select_related("category").select_related("other_versions").select_related("other_versions__representative")
self.events = qs
firstdate = datetime.fromordinal(self.c_firstdate.toordinal()) firstdate = datetime.fromordinal(self.c_firstdate.toordinal())
if firstdate.tzinfo is None or firstdate.tzinfo.utcoffset(firstdate) is None: if firstdate.tzinfo is None or firstdate.tzinfo.utcoffset(firstdate) is None:
@ -315,14 +290,14 @@ class CalendarList:
def time_intervals_list_first(self): def time_intervals_list_first(self):
return self.time_intervals_list(True) return self.time_intervals_list(True)
def export_to_ics(self, request): def export_to_ics(self):
from .models import Event from .models import Event
events = [event for day in self.get_calendar_days().values() for event in day.events] 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): class CalendarMonth(CalendarList):
def __init__(self, year, month, filter, qs=None): def __init__(self, year, month, filter):
self.year = year self.year = year
self.month = month self.month = month
r = calendar.monthrange(year, month) r = calendar.monthrange(year, month)
@ -330,7 +305,7 @@ class CalendarMonth(CalendarList):
first = date(year, month, 1) first = date(year, month, 1)
last = date(year, month, r[1]) last = date(year, month, r[1])
super().__init__(first, last, filter, qs) super().__init__(first, last, filter)
def get_month_name(self): def get_month_name(self):
return self.firstdate.strftime("%B") return self.firstdate.strftime("%B")
@ -343,14 +318,14 @@ class CalendarMonth(CalendarList):
class CalendarWeek(CalendarList): class CalendarWeek(CalendarList):
def __init__(self, year, week, filter, qs=None): def __init__(self, year, week, filter):
self.year = year self.year = year
self.week = week self.week = week
first = date.fromisocalendar(self.year, self.week, 1) first = date.fromisocalendar(self.year, self.week, 1)
last = date.fromisocalendar(self.year, self.week, 7) last = date.fromisocalendar(self.year, self.week, 7)
super().__init__(first, last, filter, qs) super().__init__(first, last, filter)
def next_week(self): def next_week(self):
return self.firstdate + timedelta(days=7) return self.firstdate + timedelta(days=7)
@ -360,8 +335,8 @@ class CalendarWeek(CalendarList):
class CalendarDay(CalendarList): class CalendarDay(CalendarList):
def __init__(self, date, filter=None, qs=None): def __init__(self, date, filter=None):
super().__init__(date, date, filter=filter, qs=qs, exact=True) super().__init__(date, date, filter, exact=True)
def get_events(self): def get_events(self):
return self.calendar_days_list()[0].events 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.utils.log import get_task_logger
from celery.exceptions import MaxRetriesExceededError from celery.exceptions import MaxRetriesExceededError
import time as time_ import time as time_
from django.conf import settings
from celery.signals import worker_ready
from contextlib import contextmanager from contextlib import contextmanager
@ -148,8 +147,6 @@ def run_recurrent_import_internal(rimport, downloader, req_id):
extractor = c3c.CExtractor() extractor = c3c.CExtractor()
elif rimport.processor == RecurrentImport.PROCESSOR.ARACHNEE: elif rimport.processor == RecurrentImport.PROCESSOR.ARACHNEE:
extractor = arachnee.CExtractor() extractor = arachnee.CExtractor()
elif rimport.processor == RecurrentImport.PROCESSOR.LERIO:
extractor = lerio.CExtractor()
else: else:
extractor = None extractor = None
@ -165,22 +162,16 @@ def run_recurrent_import_internal(rimport, downloader, req_id):
location = rimport.defaultLocation location = rimport.defaultLocation
tags = rimport.defaultTags tags = rimport.defaultTags
published = rimport.defaultPublished published = rimport.defaultPublished
organisers = [] if rimport.defaultOrganiser is None else [rimport.defaultOrganiser.pk]
try: try:
# get events from website # get events from website
events = u2e.process( events = u2e.process(
url, url,
browsable_url, browsable_url,
default_values={"category": category, "location": location, "tags": tags, "organisers": organisers}, default_values={"category": category, "location": location, "tags": tags},
published=published, published=published,
) )
# force location if required
if rimport.forceLocation and location:
for i, e in enumerate(events['events']):
events['events'][i]["location"] = location
# convert it to json # convert it to json
json_events = json.dumps(events, default=str) json_events = json.dumps(events, default=str)
@ -256,23 +247,6 @@ def daily_imports(self):
run_recurrent_imports_from_list([imp.pk for imp in imports]) 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) @app.task(bind=True)
def run_all_recurrent_imports(self): def run_all_recurrent_imports(self):
from agenda_culturel.models import RecurrentImport from agenda_culturel.models import RecurrentImport
@ -314,7 +288,7 @@ def weekly_imports(self):
run_recurrent_imports_from_list([imp.pk for imp in imports]) run_recurrent_imports_from_list([imp.pk for imp in imports])
@app.task(base=ChromiumTask, bind=True) @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 .db_importer import DBImporterEvents
from agenda_culturel.models import RecurrentImport, BatchImportation from agenda_culturel.models import RecurrentImport, BatchImportation
from agenda_culturel.models import Event, Category from agenda_culturel.models import Event, Category
@ -348,7 +322,7 @@ def import_events_from_url(self, url, cat, tags, force=False, user_id=None):
# set default values # set default values
values = {} values = {}
if cat is not None: if cat is not None:
values = {"category": cat, "tags": tags} values = {"category": cat}
# get event # get event
events = u2e.process( events = u2e.process(
@ -360,7 +334,7 @@ def import_events_from_url(self, url, cat, tags, force=False, user_id=None):
json_events = json.dumps(events, default=str) json_events = json.dumps(events, default=str)
# import events (from json) # 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 # finally, close task
close_import_task(self.request.id, success, error_message, importer) close_import_task(self.request.id, success, error_message, importer)
@ -377,14 +351,13 @@ def import_events_from_url(self, url, cat, tags, force=False, user_id=None):
@app.task(base=ChromiumTask, bind=True) @app.task(base=ChromiumTask, bind=True)
def import_events_from_urls(self, urls_cat_tags, user_id=None): def import_events_from_urls(self, urls_and_cats):
for ucat in urls_cat_tags: for ucat in urls_and_cats:
if ucat is not None: if ucat is not None:
url = ucat[0] url = ucat[0]
cat = ucat[1] 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 = { app.conf.beat_schedule = {
@ -393,10 +366,6 @@ app.conf.beat_schedule = {
# Daily imports at 3:14 a.m. # Daily imports at 3:14 a.m.
"schedule": crontab(hour=3, minute=14), "schedule": crontab(hour=3, minute=14),
}, },
"daily_screenshot": {
"task": "agenda_culturel.celery.screenshot",
"schedule": crontab(hour=3, minute=3),
},
"weekly_imports": { "weekly_imports": {
"task": "agenda_culturel.celery.weekly_imports", "task": "agenda_culturel.celery.weekly_imports",
# Daily imports on Mondays at 2:22 a.m. # Daily imports on Mondays at 2:22 a.m.

View File

@ -11,7 +11,6 @@ class DBImporterEvents:
def __init__(self, celery_id): def __init__(self, celery_id):
self.celery_id = celery_id self.celery_id = celery_id
self.error_message = "" self.error_message = ""
self.user_id = None
self.init_result_properties() self.init_result_properties()
self.today = timezone.now().date().isoformat() self.today = timezone.now().date().isoformat()
@ -35,10 +34,9 @@ class DBImporterEvents:
def get_nb_removed_events(self): def get_nb_removed_events(self):
return self.nb_removed return self.nb_removed
def import_events(self, json_structure, user_id=None): def import_events(self, json_structure):
print(json_structure) print(json_structure)
self.init_result_properties() self.init_result_properties()
self.user_id = user_id
try: try:
structure = json.loads(json_structure) structure = json.loads(json_structure)
@ -97,7 +95,7 @@ class DBImporterEvents:
def save_imported(self): def save_imported(self):
self.db_event_objects, self.nb_updated, self.nb_removed = Event.import_events( 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): 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 django_better_admin_arrayfield.forms.widgets import DynamicArrayWidget
from .utils import PlaceGuesser
from .models import ( from .models import (
Event, Event,
RecurrentImport, RecurrentImport,
CategorisationRule, CategorisationRule,
ModerationAnswer,
ModerationQuestion,
Place, Place,
Category, Category,
Tag,
Message
) )
from django.conf import settings from django.conf import settings
from django.core.files import File 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 string import ascii_uppercase as auc
from .templatetags.utils_extra import int_to_abc from .templatetags.utils_extra import int_to_abc
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.timezone import localtime
from django.utils.formats import localize from django.utils.formats import localize
from .templatetags.event_extra import event_field_verbose_name, field_to_html from .templatetags.event_extra import event_field_verbose_name, field_to_html
import os
import logging import logging
logger = logging.getLogger(__name__) 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): class URLSubmissionForm(Form):
required_css_class = 'required'
url = URLField(max_length=512) url = URLField(max_length=512)
category = ModelChoiceField( category = ModelChoiceField(
label=_("Category"), label=_("Category"),
queryset=Category.objects.all().order_by("name"), queryset=Category.objects.all().order_by("name"),
initial=None, initial=None,
help_text=_('Optional. If you don''t specify a category, we''ll find it for you.'),
required=False, 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,46 +62,22 @@ class DynamicArrayWidgetTags(DynamicArrayWidget):
class RecurrentImportForm(ModelForm): class RecurrentImportForm(ModelForm):
required_css_class = 'required'
defaultTags = MultipleChoiceField(
label=_("Tags"),
initial=None,
choices=[],
required=False
)
class Meta: class Meta:
model = RecurrentImport model = RecurrentImport
fields = "__all__" fields = "__all__"
widgets = {
def __init__(self, *args, **kwargs): "defaultTags": DynamicArrayWidgetTags(),
super().__init__(*args, **kwargs) }
self.fields["defaultTags"].choices = Tag.get_tag_groups(all=True)
class CategorisationRuleImportForm(ModelForm): class CategorisationRuleImportForm(ModelForm):
required_css_class = 'required'
class Meta: class Meta:
model = CategorisationRule model = CategorisationRule
fields = "__all__" fields = "__all__"
class EventForm(GroupFormMixin, ModelForm): class EventForm(ModelForm):
required_css_class = 'required'
old_local_image = CharField(widget=HiddenInput(), required=False) old_local_image = CharField(widget=HiddenInput(), required=False)
simple_cloning = CharField(widget=HiddenInput(), required=False)
cloning = CharField(widget=HiddenInput(), required=False)
tags = MultipleChoiceField(
label=_("Tags"),
initial=None,
choices=[],
required=False
)
class Meta: class Meta:
model = Event model = Event
@ -206,11 +86,7 @@ class EventForm(GroupFormMixin, ModelForm):
"modified_date", "modified_date",
"moderated_date", "moderated_date",
"import_sources", "import_sources",
"image", "image"
"moderated_by_user",
"modified_by_user",
"created_by_user",
"imported_by_user"
] ]
widgets = { widgets = {
"start_day": TextInput( "start_day": TextInput(
@ -232,73 +108,22 @@ class EventForm(GroupFormMixin, ModelForm):
"other_versions": HiddenInput(), "other_versions": HiddenInput(),
"uuids": MultipleHiddenInput(), "uuids": MultipleHiddenInput(),
"reference_urls": DynamicArrayWidgetURLs(), "reference_urls": DynamicArrayWidgetURLs(),
"tags": DynamicArrayWidgetTags(),
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
is_authenticated = kwargs.pop("is_authenticated", False) is_authenticated = kwargs.pop("is_authenticated", False)
self.cloning = kwargs.pop("is_cloning", False) self.cloning = kwargs.pop("is_cloning", False)
self.simple_cloning = kwargs.pop("is_simple_cloning", False)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if not is_authenticated: if not is_authenticated:
del self.fields["status"] del self.fields["status"]
del self.fields["organisers"]
self.fields['category'].queryset = self.fields['category'].queryset.order_by('name') self.fields['category'].queryset = self.fields['category'].queryset.order_by('name')
self.fields['category'].empty_label = None self.fields['category'].empty_label = None
self.fields['category'].initial = Category.get_default_category() 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): def is_clone_from_url(self):
return self.cloning return self.cloning
def is_simple_clone_from_url(self):
return self.simple_cloning
def clean_end_day(self): def clean_end_day(self):
start_day = self.cleaned_data.get("start_day") start_day = self.cleaned_data.get("start_day")
end_day = self.cleaned_data.get("end_day") end_day = self.cleaned_data.get("end_day")
@ -334,69 +159,11 @@ class EventForm(GroupFormMixin, ModelForm):
self.cleaned_data['old_local_image'] != "": self.cleaned_data['old_local_image'] != "":
basename = self.cleaned_data['old_local_image'] basename = self.cleaned_data['old_local_image']
old = settings.MEDIA_ROOT + "/" + basename 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): class BatchImportationForm(Form):
required_css_class = 'required'
json = CharField( json = CharField(
label="JSON", label="JSON",
widget=Textarea(attrs={"rows": "10"}), widget=Textarea(attrs={"rows": "10"}),
@ -406,8 +173,6 @@ class BatchImportationForm(Form):
class FixDuplicates(Form): class FixDuplicates(Form):
required_css_class = 'required'
action = ChoiceField() action = ChoiceField()
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -485,6 +250,7 @@ class FixDuplicates(Form):
def get_selected_event(self, edup): def get_selected_event(self, edup):
selected = self.get_selected_event_code() selected = self.get_selected_event_code()
logger.warning("selected " + str(selected))
for e in edup.get_duplicated(): for e in edup.get_duplicated():
if e.pk == selected: if e.pk == selected:
return e return e
@ -492,9 +258,7 @@ class FixDuplicates(Form):
class SelectEventInList(Form): class SelectEventInList(Form):
required_css_class = 'required' event = ChoiceField()
event = ChoiceField(label=_('Event'))
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
events = kwargs.pop("events", None) events = kwargs.pop("events", None)
@ -506,9 +270,7 @@ class SelectEventInList(Form):
class MergeDuplicates(Form): class MergeDuplicates(Form):
required_css_class = 'required' checkboxes_fields = ["reference_urls", "description"]
checkboxes_fields = ["reference_urls", "description", "tags"]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.duplicates = kwargs.pop("duplicates", None) self.duplicates = kwargs.pop("duplicates", None)
@ -549,17 +311,17 @@ class MergeDuplicates(Form):
'<li><a href="' + e.get_absolute_url() + '">' + e.title + "</a></li>" '<li><a href="' + e.get_absolute_url() + '">' + e.title + "</a></li>"
) )
result += ( result += (
"<li>Création&nbsp;: " + localize(e.created_date) + "</li>" "<li>Création&nbsp;: " + localize(localtime(e.created_date)) + "</li>"
) )
result += ( result += (
"<li>Dernière modification&nbsp;: " "<li>Dernière modification&nbsp;: "
+ localize(e.modified_date) + localize(localtime(e.modified_date))
+ "</li>" + "</li>"
) )
if e.imported_date: if e.imported_date:
result += ( result += (
"<li>Dernière importation&nbsp;: " "<li>Dernière importation&nbsp;: "
+ localize(e.imported_date) + localize(localtime(e.imported_date))
+ "</li>" + "</li>"
) )
result += "</ul>" result += "</ul>"
@ -610,7 +372,7 @@ class MergeDuplicates(Form):
result += '<input id="' + id + '" name="' + key + '"' result += '<input id="' + id + '" name="' + key + '"'
if key in MergeDuplicates.checkboxes_fields: if key in MergeDuplicates.checkboxes_fields:
result += ' type="checkbox"' result += ' type="checkbox"'
if checked and value in checked: if value in checked:
result += " checked" result += " checked"
else: else:
result += ' type="radio"' result += ' type="radio"'
@ -642,7 +404,7 @@ class MergeDuplicates(Form):
result = [] result = []
for s in selected: for s in selected:
for e in self.duplicates.get_duplicated(): for e in self.duplicates.get_duplicated():
if e.pk == s: if e.pk == selected:
result.append(e) result.append(e)
break break
return result return result
@ -655,9 +417,47 @@ class MergeDuplicates(Form):
return None return None
class CategorisationForm(Form): class ModerationQuestionForm(ModelForm):
required_css_class = 'required' 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): def __init__(self, *args, **kwargs):
if "events" in kwargs: if "events" in kwargs:
events = kwargs.pop("events", None) events = kwargs.pop("events", None)
@ -688,8 +488,6 @@ class CategorisationForm(Form):
class EventAddPlaceForm(Form): class EventAddPlaceForm(Form):
required_css_class = 'required'
place = ModelChoiceField( place = ModelChoiceField(
label=_("Place"), label=_("Place"),
queryset=Place.objects.all().order_by("name"), queryset=Place.objects.all().order_by("name"),
@ -726,9 +524,7 @@ class EventAddPlaceForm(Form):
return self.instance return self.instance
class PlaceForm(GroupFormMixin, ModelForm): class PlaceForm(ModelForm):
required_css_class = 'required'
apply_to_all = BooleanField( apply_to_all = BooleanField(
initial=True, initial=True,
label=_( label=_(
@ -742,70 +538,13 @@ class PlaceForm(GroupFormMixin, ModelForm):
fields = "__all__" fields = "__all__"
widgets = {"location": TextInput()} 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): def as_grid(self):
result = ('<div class="grid"><div>' return mark_safe(
'<div class="grid"><div>'
+ super().as_p() + super().as_p()
+ '''</div><div><div class="map-widget"> + '</div><div><div class="map-widget">'
<div id="map_location" style="width: 100%; aspect-ratio: 16/9"></div> + '<div id="map_location" style="width: 100%; aspect-ratio: 16/9"></div><p>Cliquez pour ajuster la position GPS</p></div></div></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)
def apply(self): def apply(self):
return self.cleaned_data.get("apply_to_all") 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 = [] tags = []
if first_cat in ["grand spectacle"]: if first_cat in ["grand spectacle"]:
category = "Spectacles" category = "Spectacles"
tags.append("💃 danse") tags.append("danse")
elif first_cat in ["theatre", "humour / one man show"]: elif first_cat in ["theatre", "humour / one man show"]:
category = "Spectacles" 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"]: elif first_cat in ["chanson francaise", "musique du monde", "pop / rock", "rap", "rnb", "raggae", "variete"]:
category = "Fêtes & Concerts" category = "Fêtes & Concerts"
tags.append("🎵 concert") tags.append("concert")
elif first_cat in ["comedie musicale", "humour / one man show", "spectacle equestre"]: elif first_cat in ["comedie musicale", "humour / one man show", "spectacle equestre"]:
category = "Spectacles" category = "Spectacles"
elif first_cat in ["spectacle pour enfant"]: elif first_cat in ["spectacle pour enfant"]:
tags = ["🎈 jeune public"] tags = ["jeune public"]
category = None category = None
else: else:
category = None category = None

View File

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

View File

@ -3,12 +3,6 @@ from ..extractor_facebook import FacebookEvent
import json5 import json5
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import json 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 # 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): def build_event_url_list(self, content):
soup = BeautifulSoup(content, "html.parser") soup = BeautifulSoup(content, "html.parser")
debug = False
found = False
links = soup.find_all("a") links = soup.find_all("a")
for link in links: for link in links:
if link.get("href").startswith('https://www.facebook.com/events/'): if link.get("href").startswith('https://www.facebook.com/events/'):
self.add_event_url(link.get('href').split('?')[0]) 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( def add_event_from_content(
@ -65,7 +42,4 @@ class CExtractor(TwoStepsExtractor):
event["published"] = published event["published"] = published
self.add_event(default_values, **event) 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" nom_lieu = "La Comédie de Clermont"
url_referer = "https://lacomediedeclermont.com/saison24-25/" 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): def category_comedie2agenda(self, category):
mapping = { mapping = {
"Théâtre": "Spectacles", "Théâtre": "Spectacles",
@ -24,8 +18,8 @@ class CExtractor(TwoStepsExtractor):
"PopCorn Live": "Sans catégorie", "PopCorn Live": "Sans catégorie",
} }
mapping_tag = { mapping_tag = {
"Théâtre": "🎭 théâtre", "Théâtre": "théâtre",
"Danse": "💃 danse", "Danse": "danse",
"Rencontre": None, "Rencontre": None,
"Sortie de résidence": "sortie de résidence", "Sortie de résidence": "sortie de résidence",
"PopCorn Live": None, "PopCorn Live": None,
@ -56,35 +50,33 @@ class CExtractor(TwoStepsExtractor):
e_url = ( e_url = (
e.select("a")[0]["href"] + "#" + d e.select("a")[0]["href"] + "#" + d
) # a "fake" url specific for each day of this show ) # a "fake" url specific for each day of this show
self.add_event_url(e_url)
if self.is_to_import_from_url(e_url): self.add_event_start_day(e_url, d)
self.add_event_url(e_url) t = (
self.add_event_start_day(e_url, d) str(e.select("div#datecal")[0])
t = ( .split(" ")[-1]
str(e.select("div#datecal")[0]) .split("<")[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) if category:
title = e.select("a")[0].contents[0] self.add_event_category(e_url, category)
self.add_event_title(e_url, title) if tag:
category = e.select("div#lieuevtcal span") self.add_event_tag(e_url, tag)
if len(category) > 0: location = (
category, tag = self.category_comedie2agenda( e.select("div#lieuevtcal")[0]
category[-1].contents[0] .contents[-1]
) .split("")[-1]
if category: )
self.add_event_category(e_url, category) if location.replace(" ", "") == "":
if tag: location = self.nom_lieu
self.add_event_tag(e_url, tag) self.add_event_location(e_url, location)
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( def add_event_from_content(
self, self,
@ -106,15 +98,6 @@ class CExtractor(TwoStepsExtractor):
description = soup.select("#descspec") description = soup.select("#descspec")
if description and len(description) > 0: if description and len(description) > 0:
description = description[0].get_text().replace("Lire plus...", "") 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: else:
description = None description = None

View File

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

View File

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

View File

@ -10,7 +10,7 @@ class CExtractor(TwoStepsExtractor):
if not category: if not category:
return None return None
mapping = {"Concerts": "Fêtes & Concerts"} mapping = {"Concerts": "Fêtes & Concerts"}
mapping_tag = {"Concerts": "🎵 concert"} mapping_tag = {"Concerts": "concert"}
if category in mapping: if category in mapping:
return mapping[category], mapping_tag[category] return mapping[category], mapping_tag[category]
else: 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): class ChromiumHeadlessDownloader(Downloader):
def __init__(self, pause=True, noimage=True): def __init__(self, pause=True):
super().__init__() super().__init__()
self.pause = pause self.pause = pause
self.options = Options() self.options = Options()
@ -78,31 +78,17 @@ class ChromiumHeadlessDownloader(Downloader):
self.options.add_argument("--disable-dev-shm-usage") self.options.add_argument("--disable-dev-shm-usage")
self.options.add_argument("--disable-browser-side-navigation") self.options.add_argument("--disable-browser-side-navigation")
self.options.add_argument("--disable-gpu") self.options.add_argument("--disable-gpu")
if noimage: self.options.add_experimental_option(
self.options.add_experimental_option( "prefs", {
"prefs", { # block image loading
# block image loading "profile.managed_default_content_settings.images": 2,
"profile.managed_default_content_settings.images": 2, }
} )
)
self.service = Service("/usr/bin/chromedriver") self.service = Service("/usr/bin/chromedriver")
self.driver = webdriver.Chrome(service=self.service, options=self.options) 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): def download(self, url, referer=None, post=None):
if post: if post:
raise Exception("POST method with Chromium headless not yet implemented") raise Exception("POST method with Chromium headless not yet implemented")

View File

@ -2,7 +2,7 @@ from abc import ABC, abstractmethod
from datetime import datetime, time, date, timedelta from datetime import datetime, time, date, timedelta
import re import re
import unicodedata import unicodedata
from django.utils import timezone
@ -49,7 +49,7 @@ class Extractor(ABC):
return i + 1 return i + 1
return None return None
def parse_french_date(text, default_year=None): def parse_french_date(text):
# format NomJour Numero Mois Année # format NomJour Numero Mois Année
m = re.search( m = re.search(
"[a-zA-ZéÉûÛ:.]+[ ]*([0-9]+)[er]*[ ]*([a-zA-ZéÉûÛ:.]+)[ ]*([0-9]+)", text "[a-zA-ZéÉûÛ:.]+[ ]*([0-9]+)[er]*[ ]*([a-zA-ZéÉûÛ:.]+)[ ]*([0-9]+)", text
@ -73,15 +73,8 @@ class Extractor(ABC):
month = int(m.group(2)) month = int(m.group(2))
year = m.group(3) year = m.group(3)
else: else:
# format Numero Mois Annee # TODO: consolider les cas non satisfaits
m = re.search("([0-9]+)[er]*[ ]*([a-zA-ZéÉûÛ:.]+)", text) return None
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
if month is None: if month is None:
return None return None
@ -194,7 +187,6 @@ class Extractor(ABC):
"start_day": start_day, "start_day": start_day,
"uuids": uuids, "uuids": uuids,
"location": location if location else self.default_value_if_exists(default_values, "location"), "location": location if location else self.default_value_if_exists(default_values, "location"),
"organisers": self.default_value_if_exists(default_values, "organisers"),
"description": description, "description": description,
"tags": tags + tags_default, "tags": tags + tags_default,
"published": published, "published": published,
@ -247,28 +239,6 @@ class Extractor(ABC):
from .extractor_ggcal_link import GoogleCalendarLinkEventExtractor from .extractor_ggcal_link import GoogleCalendarLinkEventExtractor
if single_event: if single_event:
return [FacebookEventExtractor(), GoogleCalendarLinkEventExtractor(), EventNotFoundExtractor()] return [FacebookEventExtractor(), GoogleCalendarLinkEventExtractor()]
else: else:
return [ICALExtractor(), FacebookEventExtractor(), GoogleCalendarLinkEventExtractor(), EventNotFoundExtractor()] return [ICALExtractor(), FacebookEventExtractor(), GoogleCalendarLinkEventExtractor()]
# 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

View File

@ -239,7 +239,7 @@ class FacebookEventExtractor(Extractor):
result = "https://www.facebook.com" + u.path result = "https://www.facebook.com" + u.path
# remove name in the url # 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: if match:
result = match[1] + "/" + match[3] result = match[1] + "/" + match[3]

View File

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

View File

@ -1,11 +1,6 @@
from .downloader import * from .downloader import *
from .extractor import * from .extractor import *
import logging
logger = logging.getLogger(__name__)
class URL2Events: class URL2Events:
def __init__( def __init__(
@ -34,9 +29,8 @@ class URL2Events:
else: else:
# if the extractor is not defined, use a list of default extractors # if the extractor is not defined, use a list of default extractors
for e in Extractor.get_default_extractors(self.single_event): for e in Extractor.get_default_extractors(self.single_event):
logger.warning('Extractor::' + type(e).__name__)
e.set_downloader(self.downloader) e.set_downloader(self.downloader)
events = e.extract(content, url, url_human, default_values, published) 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 events
return None 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 # Generated by Django 4.2.9 on 2024-10-10 20:35
from django.db import migrations from django.db import migrations
from agenda_culturel.models import Place
from django.contrib.gis.geos import Point from django.contrib.gis.geos import Point
def change_coord_format(apps, schema_editor): def change_coord_format(apps, schema_editor):
Place = apps.get_model("agenda_culturel", "Place") places = Place.objects.all()
places = Place.objects.values("location", "location_pt").all()
for p in places: for p in places:
l = p.location.split(',') 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])) p.location_pt = Point(float(l[1]), float(l[0]))
else: else:
p.location_pt = Point(3.08333, 45.783329) p.location_pt = Point(3.08333, 45.783329)
p.save(update_fields=["location_pt"]) p.save()
def reverse_coord_format(apps, schema_editor): def reverse_coord_format(apps, schema_editor):
Place = apps.get_model("agenda_culturel", "Place") places = Place.objects.all()
places = Place.objects.values("location", "location_pt").all()
for p in places: for p in places:
p.location = ','.join([p.location_pt[1], p.location_pt[0]]) 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): def update_database_old(apps, schema_editor):
update_database(apps, new_cats) 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): def do_nothing(apps, schema_editor):
pass pass
@ -197,6 +206,7 @@ class Migration(migrations.Migration):
migrations.RunPython(create_new_categories, reverse_code=delete_new_categories), 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_preserved_categories_new, reverse_code=update_preserved_categories_old),
migrations.RunPython(update_database_new, reverse_code=update_database_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'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 4.2.9 on 2024-12-22 15:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('agenda_culturel', '0129_batchimportation_agenda_cult_created_a23990_idx_and_more'),
]
operations = [
migrations.AddField(
model_name='recurrentimport',
name='forceLocation',
field=models.BooleanField(default=False, help_text='force location even if another is detected.', verbose_name='Force location'),
),
]

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"] DEBUG = os_getenv("DEBUG", "true").lower() in ["True", "true", "1", "yes", "y"]
ALLOWED_HOSTS = os_getenv("ALLOWED_HOSTS", "localhost").split(",") ALLOWED_HOSTS = os_getenv("ALLOWED_HOSTS", "localhost").split(",")
if DEBUG:
ALLOWED_HOSTS = ALLOWED_HOSTS + ['testserver']
if DEBUG: if DEBUG:
CSRF_TRUSTED_ORIGINS = os_getenv("CSRF_TRUSTED_ORIGINS", "http://localhost").split( CSRF_TRUSTED_ORIGINS = os_getenv("CSRF_TRUSTED_ORIGINS", "http://localhost").split(
@ -56,10 +55,9 @@ INSTALLED_APPS = [
"robots", "robots",
"debug_toolbar", "debug_toolbar",
"cache_cleaner", "cache_cleaner",
"honeypot",
] ]
HONEYPOT_FIELD_NAME = "alias_name" SITE_ID = 1
MIDDLEWARE = [ MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware", "django.middleware.security.SecurityMiddleware",
@ -73,7 +71,6 @@ MIDDLEWARE = [
"django.contrib.messages.middleware.MessageMiddleware", "django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",
"debug_toolbar.middleware.DebugToolbarMiddleware", "debug_toolbar.middleware.DebugToolbarMiddleware",
'django.contrib.sites.middleware.CurrentSiteMiddleware',
# "django.middleware.cache.UpdateCacheMiddleware", # "django.middleware.cache.UpdateCacheMiddleware",
# "django.middleware.common.CommonMiddleware", # "django.middleware.common.CommonMiddleware",
# "django.middleware.cache.FetchFromCacheMiddleware", # "django.middleware.cache.FetchFromCacheMiddleware",
@ -147,9 +144,10 @@ TIME_ZONE = "Europe/Paris"
USE_I18N = True USE_I18N = True
USE_TZ = False USE_TZ = True
LANGUAGES = ( LANGUAGES = (
("en-us", _("English")),
("fr", _("French")), ("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; start_day.oldvalue = start_day.value;
} }
else {
new_date = new Date(start_day.value);
end_day.value = formatDate(new_date);
}
} }
else { else {
if (end_day.value && end_time.value && start_day.value) { if (end_day.value && end_time.value && start_day.value) {

View File

@ -34,17 +34,11 @@ const openModal = (modal, back=true) => {
} }
setTimeout(function() { setTimeout(function() {
visibleModal = modal; visibleModal = modal;
}, 500);
console.log("ici");
const mask = visibleModal.querySelector(".h-mask");
mask.classList.add("visible");
}, 350);
}; };
const hideModal = (modal) => { const hideModal = (modal) => {
if (modal != null) { if (modal != null) {
const mask = visibleModal.querySelector(".h-mask");
mask.classList.remove("visible");
visibleModal = null; visibleModal = null;
document.documentElement.style.removeProperty("--scrollbar-width"); document.documentElement.style.removeProperty("--scrollbar-width");
modal.removeAttribute("open"); modal.removeAttribute("open");

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

@ -44,9 +44,6 @@ $enable-responsive-typography: true;
// Modal (<dialog>) // Modal (<dialog>)
--modal-overlay-backdrop-filter: blur(0.05rem); --modal-overlay-backdrop-filter: blur(0.05rem);
--background-color-transparent: color-mix(in srgb, var(--background-color), transparent 30%);
--background-color-transparent-light: color-mix(in srgb, var(--background-color), transparent 80%);
} }
@ -150,7 +147,7 @@ details[role="list"] summary + ul li.selected>a:hover {
} }
} }
.suggestions { .suggested-tags {
font-size: 80%; font-size: 80%;
} }
} }
@ -201,18 +198,8 @@ details[role="list"] summary + ul li.selected>a:hover {
@extend .outline; @extend .outline;
font-size: 90%; font-size: 90%;
padding: 0.15em 0.4em 0.3em 0.4em; 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 { .circ-cat.circ-large {
height: 2.6em; height: 2.6em;
@ -280,12 +267,6 @@ svg {
} }
} }
@media only screen and (min-width: 600px) {
.details-entete {
padding-left: 28%;
}
}
.ephemeris-hour { .ephemeris-hour {
@extend .ephemeris; @extend .ephemeris;
padding: 1.5em 0.1em; padding: 1.5em 0.1em;
@ -332,7 +313,6 @@ footer [data-tooltip] {
scroll-behavior: smooth; scroll-behavior: smooth;
transition-duration: 200ms; transition-duration: 200ms;
.cat { .cat {
margin-right: 0; margin-right: 0;
} }
@ -569,12 +549,6 @@ article#filters {
font-size: 100%; font-size: 100%;
} }
} }
.helptext {
display: block;
margin-top: 0;
}
header .remarque { header .remarque {
font-style: italic; font-style: italic;
} }
@ -593,8 +567,6 @@ header .remarque {
.slide-buttons { .slide-buttons {
float: right; float: right;
max-width: 30em;
text-align: right;
} }
.left-buttons { .left-buttons {
display: inline-block; display: inline-block;
@ -891,7 +863,6 @@ nav>div {
} }
.badge.error { .badge.error {
background: #b71c1c; background: #b71c1c;
border-color: #cb1b1b; border-color: #cb1b1b;
@ -903,25 +874,6 @@ nav>div {
color: var(--secondary-inverse); 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 { form [role="button"], form button {
margin: var(--spacing) 0 var(--spacing) 0; margin: var(--spacing) 0 var(--spacing) 0;
} }
@ -934,10 +886,6 @@ form .buttons [role="button"] {
margin: 2em auto; margin: 2em auto;
} }
.min-y-grid {
min-height: 4em;
}
@media (min-width: 992px) { @media (min-width: 992px) {
.grid.two-columns { .grid.two-columns {
grid-column-gap: var(--nav-element-spacing-vertical); grid-column-gap: var(--nav-element-spacing-vertical);
@ -982,7 +930,7 @@ aside nav.paragraph li a, aside .no-breakline li a {
} }
/* mise en forme pour les récurrences */ /* mise en forme pour les récurrences */
article form div .recurrence-widget { article form p .recurrence-widget {
width: 100%; width: 100%;
border: 0; border: 0;
@ -1162,17 +1110,6 @@ table .buttons {
} }
} }
.image-flottante {
width: 30%;
float: right;
margin: 0 1em;
img {
width: 100%;
margin: -4em 0 0 0;
}
}
img.preview { img.preview {
height: 100px; height: 100px;
} }
@ -1315,31 +1252,15 @@ img.preview {
} }
} }
.choices__list--dropdown { .choices__list--dropdown {
font-size: 80%; display: none;
display: block;
will-change: display; 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; position: relative;
max-height: 300px; max-height: 300px;
overflow: auto; overflow: auto;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
will-change: scroll-position; will-change: scroll-position;
} }
.choices__list--dropdown .choices__group, .choices__list--dropdown.is-active {
.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 {
display: block; display: block;
} }
} }
@ -1347,15 +1268,17 @@ img.preview {
.stick-bottom { article {
background: var(--card-sectionning-background-color); .stick-bottom {
margin-right: calc(var(--block-spacing-horizontal) * -1); background: var(--card-sectionning-background-color);
margin-left: calc(var(--block-spacing-horizontal) * -1); margin-right: calc(var(--block-spacing-horizontal) * -1);
padding: calc(var(--block-spacing-vertical) * 0.66) var(--block-spacing-horizontal); margin-left: calc(var(--block-spacing-horizontal) * -1);
margin-bottom: calc(var(--block-spacing-vertical) * -1); padding: calc(var(--block-spacing-vertical) * 0.66) var(--block-spacing-horizontal);
position: sticky; margin-bottom: calc(var(--block-spacing-vertical) * -1);
bottom: 0; position: sticky;
box-shadow: 0 -1px 0 rgba(115, 130, 140, 0.2); bottom: 0;
box-shadow: 0 -1px 0 rgba(115, 130, 140, 0.2);
}
} }
/*#menu-rechercher, #menu-ajouter, #menu-configurer { /*#menu-rechercher, #menu-ajouter, #menu-configurer {
@ -1397,11 +1320,7 @@ img.preview {
} }
.a-venir article.single-event { .a-venir, .place, .tag, .tag-descriptions {
scroll-margin-top: 7em;
}
.a-venir, .place, .tag, .tag-descriptions, .organisation {
article#filters { article#filters {
margin: 2em 0; margin: 2em 0;
} }
@ -1438,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 { form.messages div, form.moderation-events {
@media only screen and (min-width: 992px) { @media only screen and (min-width: 992px) {
display: grid; display: grid;
@ -1463,287 +1371,3 @@ form.messages div, form.moderation-events {
float: left; 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%;
}
}
}
dialog {
.h-image {
background-repeat: no-repeat;
background-size: cover;
background-position: center, center;
}
.h-mask {
background-color: var(--background-color);
margin: calc(var(--spacing) * -1.5);
padding: calc(var(--spacing) * 1.5);
}
.h-mask.visible {
background-color: var(--background-color-transparent);
transition: background-color .8s ease-in;
}
.h-mask.visible:hover {
background-color: var(--background-color-transparent-light);
}
}
.visible-link {
text-decoration: underline;
}
.detail-link {
text-align: right;
padding-right: 0.4em;
.visible-link {
color: var(--contrast);
}
}
.week-in-month {
article {
.visible-link {
color: var(--contrast);
}
}
}

View File

@ -17,52 +17,8 @@
{% block content %} {% block content %}
<div class="grid two-columns"> <div class="grid two-columns">
<div id="contenu-principal"> <div id="contenu-principal">
<div>
<article>
<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>
<header> <header>
<h2>Activité des derniers jours</h2> <h2>Activité des derniers jours</h2>
</header> </header>
@ -76,7 +32,6 @@
{% include "agenda_culturel/rimports-info-inc.html" with all=1 %}</p> {% include "agenda_culturel/rimports-info-inc.html" with all=1 %}</p>
</article> </article>
</div>
<article> <article>
<header> <header>

View File

@ -47,21 +47,15 @@
<p> <p>
{% picto_from_name "calendar" %} {% picto_from_name "calendar" %}
{% if event.end_day and event.end_day != event.start_day %}du{% else %}le{% endif %} {% if event.end_day and event.end_day != event.start_day %}du{% else %}le{% endif %}
{{ event.start_day|date|frdate }} {% include "agenda_culturel/date-times-inc.html" with event=event %}
{% if event.start_time %} {% if not event.end_day or event.end_day == event.start_day %}{% if event.end_time %}de{% else %}à{% endif %}{% endif %} </p>
{{ 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> </header>
<div class="body-fixed">{{ event.description |linebreaks }}</div> <div class="body-fixed">{{ event.description |linebreaks }}</div>
<p> <p>
{% for tag in event.sorted_tags %} {% for tag in event.tags %}
{{ tag | tag_button }} {{ tag | tag_button }}
{% endfor %} {% endfor %}
</p> </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> <article>
<header> <header>
<div class="slide-buttons"> <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> </div>
<h1>Modération du message «&nbsp;{{ object.subject }}&nbsp;»</h1> <h1>Modération du message «&nbsp;{{ object.subject }}&nbsp;»</h1>
<ul> <ul>
<li>Date&nbsp;: {{ object.date.date }} à {{ object.date.time }}</li> <li>Date&nbsp;: {{ object.date }}</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> <li>Auteur&nbsp;: {{ object.name }} <a href="mailto:{{ object.email }}">{{ object.email }}</a></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 %}
</ul> </ul>
</header> </header>
<div> <div>
{{ object.message | safe }} {{ object.message }}
</div> </div>
</article> </article>
@ -47,7 +46,7 @@
</article> </article>
</div> </div>
{% include "agenda_culturel/side-nav.html" with current="messages" %} {% include "agenda_culturel/side-nav.html" with current="contactmessages" %}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -26,8 +26,7 @@
<h1>Derniers messages de contact reçus</h1> <h1>Derniers messages de contact reçus</h1>
<form method="get" class="form django-form recent messages"> <form method="get" class="form django-form recent messages">
{{ filter.form.as_div }}<br /> {{ filter.form.as_div }}<br />
<button type="submit">Filtrer</button> <button type="submit">Filtrer</button><br />
</form>
</header> </header>
<table role="grid"> <table role="grid">
@ -36,7 +35,6 @@
<th>Date</th> <th>Date</th>
<th>Sujet</th> <th>Sujet</th>
<th>Auteur</th> <th>Auteur</th>
<th>Événement</th>
<th>Fermé</th> <th>Fermé</th>
<th>Spam</th> <th>Spam</th>
</tr> </tr>
@ -45,9 +43,8 @@
{% for obj in paginator_filter %} {% for obj in paginator_filter %}
<tr> <tr>
<td>{{ obj.date }}</td> <td>{{ obj.date }}</td>
<td><a href="{% url 'message' obj.pk %}">{{ obj.subject }}</a></td> <td><a href="{% url 'contactmessage' obj.pk %}">{{ obj.subject }}</a></td>
<td>{% if obj.user %}<em>{{ obj.user }}</em>{% else %}{{ obj.name }}{% endif %}</td> <td>{{ obj.name }}</td>
<td>{% if obj.related_event %}<a href="{{ obj.related_event.get_absolute_url }}">{{ obj.related_event.pk }}</a>{% else %}/{% endif %}</td>
<td>{% if obj.closed %}{% picto_from_name "check-square" "fermé" %}{% else %}{% picto_from_name "square" "ouvert" %}{% endif %}</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> <td>{% if obj.spam %}{% picto_from_name "check-square" "spam" %}{% else %}{% picto_from_name "square" "non spam" %}{% endif %}</td>
</tr> </tr>
@ -59,7 +56,7 @@
</footer> </footer>
</article> </article>
{% include "agenda_culturel/side-nav.html" with current="messages" %} {% include "agenda_culturel/side-nav.html" with current="contactmessages" %}
</div> </div>
{% endblock %} {% 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 %} {% load utils_extra %}
{% if event.moderated_date %} <!-- a href="{% url 'moderate_event' event.id %}" role="button">modérer {% picto_from_name "edit" %}</a-->
<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 %}
{% if event.pure_import %} {% if event.pure_import %}
{% with event.get_local_version as local %} {% with event.get_local_version as local %}
{% if local %} {% if local %}
<a href="{{ local.get_absolute_url }}" role="button">voir la version locale {% picto_from_name "eye" %}</a> <a href="{{ local.get_absolute_url }}" role="button">voir la version locale {% picto_from_name "eye" %}</a>
{% else %} {% 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 %} {% endif %}
{% endwith %} {% endwith %}
{% else %} {% else %}
@ -41,6 +36,3 @@
<a href="{% url 'delete_event' event.id %}" role="button">supprimer définitivement {% picto_from_name "x-circle" %}</a> <a href="{% url 'delete_event' event.id %}" role="button">supprimer définitivement {% picto_from_name "x-circle" %}</a>
{% endif %} {% 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 %} {% 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 %} {% 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 %} {% endif %}
{% if event.imported_date %} {% 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 %} {% endif %}
{% if event.moderated_date %} {% 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 %} {% endif %}
{% if event.pure_import %} {% if event.pure_import %}
<strong>version importée</strong> <strong>version importée</strong>

View File

@ -1,12 +1,10 @@
<footer class="remarque"> <footer class="remarque">
<strong>Informations complémentaires non éditables</strong> Informations complémentaires non éditables&nbsp;:
<ul> <ul>
{% if not allbutdates %} {% if object.created_date %}<li>Création&nbsp;: {{ object.created_date }}</li>{% endif %}
{% 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 }}</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 }}</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 }}</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.uuids %} {% if object.uuids %}
{% if object.uuids|length > 0 %} {% if object.uuids|length > 0 %}
<li>UUIDs (identifiants uniques d'événements dans les sources)&nbsp;: <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 %} {% 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> <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 %} <form method="post">{% csrf_token %}
<p class="large">Êtes-vous sûr·e de vouloir <p class="large">Êtes-vous sûr·e de vouloir
{% if status == "published" %} {% if status == "published" %}

View File

@ -4,11 +4,7 @@
{% block title %}{% block og_title %} {% block title %}{% block og_title %}
{% if object %}{% if form.is_clone_from_url %} {% if object %}{% if form.is_clone_from_url %}
Création d'une copie locale de {% else %} Création d'une copie de {% else %}
Édition de l'événement {{ object.title }} ({{ object.start_day }})
{% endif %}
{% if form.is_simple_clone_from_url %}
Duplication de {% else %}
Édition de l'événement {{ object.title }} ({{ object.start_day }}) Édition de l'événement {{ object.title }} ({{ object.start_day }})
{% endif %} {% endif %}
{% else %} {% else %}
@ -48,21 +44,13 @@ Duplication de {% else %}
{% block content %} {% block content %}
{% load static_content_extra %} {% load static_content_extra %}
<article id="event_form"> <article>
<header> <header>
{% if object %} {% if object %}
<h1>{% if form.is_clone_from_url %} <h1>{% if form.is_clone_from_url %}
Création d'une copie locale de {% else %} Création d'une copie de {% else %}
{% if form.is_simple_clone_from_url %} Édition de l'événement{% endif %} {{ object.title }} ({{ object.start_day }})</h1>
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 %}
{% else %} {% else %}
{% if from_import %} {% if from_import %}
<h1>Ajuster l'événement importé</h1> <h1>Ajuster l'événement importé</h1>
{% else %} {% else %}
@ -72,16 +60,16 @@ Duplication de {% else %}
{% endif %} {% endif %}
</header> </header>
<div id="container"></div>
{% if form.is_clone_from_url or form.is_simple_clone_from_url %} {% if form.is_clone_from_url %}
{% url "add_event_details" as urlparam %} {% url "add_event_details" as urlparam %}
{% endif %} {% endif %}
<form method="post" action="{{ urlparam }}" enctype="multipart/form-data">{% csrf_token %} <form method="post" action="{{ urlparam }}" enctype="multipart/form-data">{% csrf_token %}
{{ form.media }} {{ form.media }}
{{ form }} {{ form.as_p }}
<div class="grid buttons stick-bottom"> <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> <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{% if form.is_clone_from_url %} et modérer{% endif %}"> <input type="submit" value="Enregistrer">
</div> </div>
</form> </form>
@ -94,44 +82,5 @@ Duplication de {% else %}
const element = document.querySelector('#id_exact_location'); const element = document.querySelector('#id_exact_location');
const choices = new Choices(element); 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> </script>
{% endblock %} {% 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 tag_extra %}
{% load utils_extra %} {% load utils_extra %}
{% load static %} {% load static %}
{% load locations_extra %}
{% if noarticle == 0 %} {% if noarticle == 0 %}
<article id="filters"> <article id="filters">
@ -14,7 +13,6 @@
<div class="filtres"> <div class="filtres">
<details {% if filter.is_active %}class="active"{% endif %} {% if filter.form and filter.form.errors %}open=""{% endif %}> <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"> <summary role="button" class="outline secondary">
{% if filter.is_active %} {% if filter.is_active %}
<strong>Filtres&nbsp;: </strong> <strong>Filtres&nbsp;: </strong>
@ -22,7 +20,7 @@
{{ t | tag_button:"true" }} {{ t | tag_button:"true" }}
{% endfor %} {% endfor %}
{% for t in filter.get_exclude_tags %} {% for t in filter.get_exclude_tags %}
{{ t | tag_button_strike:"true" }} {{ t | tag_button_strike }}
{% endfor %} {% endfor %}
{% for s in filter.get_status_names %} {% for s in filter.get_status_names %}
{{ s }} {{ s }}
@ -41,6 +39,7 @@
<form method="get" class="form django-form main-filter"> <form method="get" class="form django-form main-filter">
<div class="grid two-columns-equal"> <div class="grid two-columns-equal">
<article> <article>
<header>Localisation</header>
{% for f in filter.form.visible_fields %} {% for f in filter.form.visible_fields %}
{% if f.id_for_label and f.id_for_label in "id_position,id_radius" %} {% if f.id_for_label and f.id_for_label in "id_position,id_radius" %}
<div> <div>
@ -52,6 +51,7 @@
{% endfor %} {% endfor %}
</article> </article>
<article> <article>
<header>Autres filtres</header>
{% for f in filter.form.visible_fields %} {% for f in filter.form.visible_fields %}
{% if not f.id_for_label or not f.id_for_label in "id_position,id_radius" %} {% if not f.id_for_label or not f.id_for_label in "id_position,id_radius" %}
<p> <p>
@ -66,50 +66,25 @@
<button type="submit">Appliquer le filtre</button> <button type="submit">Appliquer le filtre</button>
</form> </form>
</details> </details>
<div class="suggestions"> <div class="suggested-tags">
Suggestion&nbsp;: {% show_suggested_tags filter=filter %}
{% show_suggested_positions filter=filter %}
{% show_suggested_tags filter=filter %}
</div> </div>
</div> </div>
<div class="clear"></div> <div class="clear"></div>
</div> </div>
<script src="{% static 'choicejs/choices.min.js' %}"></script> <script src="{% static 'choicejs/choices.min.js' %}"></script>
<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 position = document.querySelector('#id_position');
const choices_position = new Choices(position, const choices_position = new Choices(position);
{
shouldSort: false,
}
);
const tags = document.querySelector('#id_tags'); const tags = document.querySelector('#id_tags');
const choices_tags = new Choices(tags, const choices_tags = new Choices(tags,
{ {
placeholderValue: 'Chercher les étiquettes à inclure', placeholderValue: 'Chercher les étiquettes à inclure',
allowHTML: true, allowHTML: true,
delimiter: ',', delimiter: ',',
editItems: true,
removeItemButton: true, removeItemButton: true,
shouldSort: false, }
callbackOnCreateTemplates: () => (show_firstgroup)
}
); );
const exclude_tags = document.querySelector('#id_exclude_tags'); const exclude_tags = document.querySelector('#id_exclude_tags');
const choices_exclude_tags = new Choices(exclude_tags, const choices_exclude_tags = new Choices(exclude_tags,
@ -117,12 +92,15 @@
placeholderValue: 'Chercher les étiquettes à exclure', placeholderValue: 'Chercher les étiquettes à exclure',
allowHTML: true, allowHTML: true,
delimiter: ',', delimiter: ',',
editItems: true,
removeItemButton: 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> </script>
{% if noarticle == 0 %} {% 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> 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> </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 %} {% endblock %}

View File

@ -18,62 +18,27 @@
{% url 'event_import_urls' as local_url %} {% url 'event_import_urls' as local_url %}
{% include "agenda_culturel/static_content.html" with name="import_set" url_path=local_url %} {% include "agenda_culturel/static_content.html" with name="import_set" url_path=local_url %}
</header> </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"> <form method="post">
{{ formset.management_form }} {{ formset.management_form }}
{% csrf_token %} {% csrf_token %}
{% for form in formset %} {% for form in formset %}
<article> <h2>Nouvel événement #{{ forloop.counter }}</h2>
<header>
<h2>Nouvel événement #{{ forloop.counter }}</h2>
</header>
{{ form.as_p }} {{ form.as_p }}
</article>
{% endfor %} {% endfor %}
<div class="stick-bottom"> <div class="stick-bottom">
<input type="submit"value="Lancer l'import" id="import-button"> <input type="submit"value="Lancer l'import" id="import-button">
</div> </div>
</form> </form>
<footer>
<script src="{% static 'choicejs/choices.min.js' %}"></script> <p>Si l'import automatique ne marche pas, ou si l'événement n'est pas en ligne, vous pouvez
<script> 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>
show_firstgroup = { </footer>
choice(classes, choice) { </article>
const i = Choices.defaults.templates.choice.call(this, classes, choice); <dialog id="modal-import">
if (this.first_group !== null && choice.groupId == this.first_group) <article aria-busy="true">
i.classList.add("visible"); Veuillez patienter, lien en cours d'importation...
return i; </article>
}, </dialog>
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>
{% endblock %} {% 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> <a href="?page={{ page_obj.previous_page_number }}" role="button">précédent</a>
{% endif %} {% endif %}
{% if page_obj.paginator.num_pages != 1 %}
<span> <span>
Page {{ page_obj.number }} sur {{ page_obj.paginator.num_pages }} Page {{ page_obj.number }} sur {{ page_obj.paginator.num_pages }}
</span> </span>
{% endif %}
{% if page_obj.has_next %} {% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}" role="button">suivant</a> <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 cat_extra %}
{% load utils_extra %} {% load utils_extra %}
{% load event_extra %} {% load event_extra %}
{% load cache %}
{% block title %}{% block og_title %}{{ event.title }}{% endblock %}{% endblock %} {% 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 og_description %}{% if event.description %}{{ event.description |truncatewords:20|linebreaks }}{% else %}{{ block.super }}{% endif %}{% endblock %}
{% block entete_header %} {% block entete_header %}
@ -17,59 +16,21 @@
{% block content %} {% block content %}
<div class="grid two-columns"> <div class="grid two-columns">
<div> <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 %} {% include "agenda_culturel/single-event/event-single-inc.html" with event=event filter=filter %}
{% endcache %}
{% endwith %}
{% if user.is_authenticated %} {% if user.is_authenticated %}
<article id="chronology"> <article>
<header> <header>
<h2>Chronologie</h2> <h2>Informations internes</h2>
</header> </header>
{% for step in event.chronology %} {% include "agenda_culturel/event-info-inc.html" with object=event %}
{% 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 }}{% if step.data.email %} (<a href="mailto: {{ step.data.email }}">{{ step.data.email }}</a>){% endif %}{% 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 %}
</article> </article>
{% endif %} {% endif %}
</div> </div>
{% with cache_timeout=user.is_authenticated|yesno:"30,600" %}
{% cache cache_timeout event_aside user.is_authenticated event %}
<aside> <aside>
{% with event.get_concurrent_events as concurrent_events %} {% with event.get_concurrent_events as concurrent_events %}
{% if concurrent_events %} {% if concurrent_events %}
@ -90,7 +51,39 @@
</article> </article>
{% endif %} {% endif %}
{% endwith %} {% 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 %} {% if event.other_versions and not event.other_versions.fixed %}
{% with poss_dup=event.get_other_versions|only_allowed:user.is_authenticated %} {% with poss_dup=event.get_other_versions|only_allowed:user.is_authenticated %}
{% if poss_dup|length > 0 %} {% if poss_dup|length > 0 %}
@ -129,15 +122,12 @@
{% else %} {% else %}
Signaler comme doublon Signaler comme doublon
{% endif %}</a> {% endif %}</a>
<a role="button" href="{% url 'message_for_event' event.pk %}">Signaler cet événement</a> </article>
</article>
</aside> </aside>
{% endcache %}
{% endwith %}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,6 +1,9 @@
{% extends "agenda_culturel/page.html" %} {% extends "agenda_culturel/page.html" %}
{% load i18n %}
{% load cat_extra %} {% load cat_extra %}
{% load event_extra %} {% load event_extra %}
{% load utils_extra %} {% load utils_extra %}
@ -22,11 +25,12 @@
{% block content %} {% 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 %} {% 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> <article>
<header> <header>
@ -60,76 +64,12 @@
{% endif %} {% endif %}
<div class="grid week-in-month"> <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 forloop.counter0|divisibleby:7 %}
{% if not forloop.first %}</div><div class="grid week-in-month">{% endif %} {% 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 %} {% endif %}
{% include "agenda_culturel/day-inc.html" with day=d resume=1 fixed_style=calendar.all_in_past filter=filter headers="h3" %}
{% 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 }}" class="visible-link">{{ 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 %}
<li class="detail-link"><a href="{{ day.date | url_day }}?{{ filter.get_url }}" class="visible-link">voir en détail {% picto_from_name "chevrons-right" %}</a></li>
</ul>
{% endif %}
</ul>
</article>
{% endwith %}
{% endwith %}
{% endfor %} {% endfor %}
</div> </div>
{% if calendar.lastdate|shift_day:+1|not_after_last %} {% if calendar.lastdate|shift_day:+1|not_after_last %}

View File

@ -4,7 +4,6 @@
{% load cat_extra %} {% load cat_extra %}
{% load utils_extra %} {% load utils_extra %}
{% load tag_extra %}
{% block entete_header %} {% block entete_header %}
{% css_categories %} {% css_categories %}
@ -39,13 +38,12 @@
<li><strong>Adresse naviguable&nbsp;:</strong> <a href="{{ object.browsable_url }}">{{ object.browsable_url }}</a></li> <li><strong>Adresse naviguable&nbsp;:</strong> <a href="{{ object.browsable_url }}">{{ object.browsable_url }}</a></li>
<li><strong>Valeurs par défaut&nbsp;:</strong> <li><strong>Valeurs par défaut&nbsp;:</strong>
<ul> <ul>
<li><strong>Publié&nbsp;:</strong> {{ object.defaultPublished|yesno:"Oui,Non" }}</li> <li><strong>Publié&nbsp;:</strong> {{ object.defaultPublished }}</li>
{% if object.defaultLocation %}<li><strong>Localisation{% if object.forceLocation %} (forcée){% endif %}&nbsp;:</strong> {{ object.defaultLocation }}</li>{% endif %} <li><strong>Localisation&nbsp;:</strong> {{ object.defaultLocation }}</li>
<li><strong>Catégorie&nbsp;:</strong> {{ object.defaultCategory }}</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> <li><strong>Étiquettes&nbsp;:</strong>
{% for tag in object.defaultTags %} {% 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 %} {% endfor %}
</li> </li>
</ul> </ul>

View File

@ -1,12 +1,10 @@
{% extends "agenda_culturel/page-single.html" %} {% extends "agenda_culturel/page-single.html" %}
{% load static %}
{% block content %} {% block content %}
<article> <article>
<header> <header>
<h1>{{ title }}</h1> <h1>{{ title }}</h1>
</header> </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 %} {% include "agenda_culturel/static_content.html" with name=static_content url_path=url_path %}
<ul> <ul>
{% for rimport in rimports %} {% for rimport in rimports %}

View File

@ -1,6 +1,7 @@
{% extends "agenda_culturel/page.html" %} {% extends "agenda_culturel/page.html" %}
{% load i18n %}
{% load cache %} {% load cache %}
{% load cat_extra %} {% load cat_extra %}
{% load event_extra %} {% load event_extra %}
@ -20,6 +21,9 @@
{% block content %} {% 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"> <article id="filters">
<header><h1 id="index-avenir">{% block title %}{% block og_title %} <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 %} {% 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> <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"> <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" %} {% 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> <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> <ul>
{% for event in ti.events %} {% 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 }} <li>{{ event.category | circle_cat:event.has_recurrences }}
{% if event.start_time %} {% if event.start_time %}
{{ event.start_time }} {{ event.start_time }}
{% endif %} {% 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> </li>
{% endfor %} {% 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> </ul>
{% endif %} {% endif %}
{% endfor %} {% endfor %}

View File

@ -1,12 +1,13 @@
{% extends "agenda_culturel/page.html" %} {% extends "agenda_culturel/page.html" %}
{% load i18n %}
{% load cache %} {% load cache %}
{% load cat_extra %} {% load cat_extra %}
{% load event_extra %} {% load event_extra %}
{% load utils_extra %} {% load utils_extra %}
{% load static %} {% load static %}
{% load tag_extra %} {% load i18n %}
{% block entete_header %} {% block entete_header %}
{% css_categories %} {% css_categories %}
@ -20,10 +21,11 @@
{% block content %} {% 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" %} {% 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> <article>
<header> <header>
@ -37,7 +39,7 @@
<div> <div>
{% if calendar.firstdate|shift_day:-1|not_before_first %} {% if calendar.firstdate|shift_day:-1|not_before_first %}
{% if calendar.lastdate|not_after_last %} {% 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> {% picto_from_name "chevron-left" %} précédente</a>
{% endif %} {% endif %}
{% endif %} {% endif %}
@ -45,7 +47,7 @@
{% if calendar.lastdate|shift_day:+1|not_after_last %} {% if calendar.lastdate|shift_day:+1|not_after_last %}
{% if calendar.lastdate|not_before_first %} {% if calendar.lastdate|not_before_first %}
<div class="right"> <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" %} {% picto_from_name "chevron-right" %}
</a> </a>
</div> </div>
@ -57,123 +59,19 @@
<div class="slider-button slider-button-inside button-left hidden">{% picto_from_name "arrow-left" %}</div> <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.firstdate|shift_day:-1|not_before_first %}
{% if calendar.lastdate|not_after_last %} {% 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 %}
{% endif %} {% endif %}
<div class="grid"> <div class="grid">
{% for day in calendar.calendar_days_list %} {% 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="" %}
{% 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 class="visible-link" 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>
{% if event.has_image_url %}
<header style="background-image: url({{ event.get_image_url }});" class="h-image">
{% else %}
<header class="cat-{{ event.category.pk }}">
{% endif %}
<div class="h-mask">
<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>
</div>
</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 %}
<li class="detail-link"><a href="{{ day.date | url_day }}?{{ filter.get_url }}" class="visible-link">voir en détail {% picto_from_name "chevrons-right" %}</a></li>
</ul>
{% endif %}
</ul>
</article>
{% endwith %}
{% endwith %}
{% endfor %} {% endfor %}
</div> </div>
<div class="slider-button slider-button-inside button-right hidden">{% picto_from_name "arrow-right" %}</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|shift_day:+1|not_after_last %}
{% if calendar.lastdate|not_before_first %} {% 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 %}
{% endif %} {% endif %}

View File

@ -1,11 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
{% load event_extra %}
{% load cache %}
{% load messages_extra %}
{% load utils_extra %}
{% load duplicated_extra %}
{% load rimports_extra %}
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
@ -15,7 +9,7 @@
{% load static %} {% load static %}
<meta property="og:title" content="Pommes de lune — {% block og_title %}{% endblock %}" /> <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: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 }}" /> <meta property="og:url" content="{{ request.build_absolute_uri }}" />
{% if debug %} {% if debug %}
@ -33,7 +27,12 @@
{% block entete_header %} {% block entete_header %}
{% endblock %} {% endblock %}
</head> </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 %}"> <body class="{% block body-class %}contenu{% endblock %}">
<div id="boutons-fixes"> <div id="boutons-fixes">
<ul> <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 %} {% 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 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 %} {% 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 '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> <li><a href="{% url 'ce_mois_ci' %}{% block ce_mois_ci_parameters %}{% endblock %}">Ce mois-ci</a></li>
</ul> </ul>
@ -66,7 +65,8 @@
<li> <li>
<div> <div>
{% if perms.agenda_culturel.view_recurrentimport %} {% if perms.agenda_culturel.view_recurrentimport %}
{% show_badges_rimports "bottom" %} {% show_badge_rimports "bottom" "failed" %}
{% show_badge_rimports "bottom" "running" %}
{% endif %} {% endif %}
{% if perms.agenda_culturel.change_event %} {% if perms.agenda_culturel.change_event %}
{% show_badges_events "bottom" %} {% show_badges_events "bottom" %}
@ -77,8 +77,8 @@
{% if perms.agenda_culturel.change_place and perms.agenda_culturel.change_event %} {% if perms.agenda_culturel.change_place and perms.agenda_culturel.change_event %}
{% show_badge_unknown_places "bottom" %} {% show_badge_unknown_places "bottom" %}
{% endif %} {% endif %}
{% if perms.agenda_culturel.view_message %} {% if perms.agenda_culturel.view_contactmessage %}
{% show_badge_messages "bottom" %} {% show_badge_contactmessages "bottom" %}
{% endif %} {% endif %}
{% if user.is_authenticated %} {% if user.is_authenticated %}
{{ user.username }} @ {{ user.username }} @
@ -108,9 +108,6 @@
<div> <div>
<a href="{% url 'view_places' %}">{% picto_from_name "map-pin" %} lieux</a> <a href="{% url 'view_places' %}">{% picto_from_name "map-pin" %} lieux</a>
</div> </div>
<div>
<a href="{% url 'view_organisations' %}">{% picto_from_name "users" %} organisateurs</a>
</div>
<div> <div>
<a href="{% url 'view_all_tags' %}">{% picto_from_name "tag" %} étiquettes</a> <a href="{% url 'view_all_tags' %}">{% picto_from_name "tag" %} étiquettes</a>
</div> </div>

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