Merge branch 'main' into correction-script-import-comedie
This commit is contained in:
commit
cc0ae8b582
@ -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 --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)
|
* ```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)
|
||||||
* 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
|
||||||
|
@ -5,10 +5,11 @@ 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 \
|
apt-get install --no-install-recommends -y build-essential libpq-dev gettext chromium-driver gdal-bin fonts-symbola \
|
||||||
&& 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 \
|
||||||
|
43
experimentations/get_le_rio.py
Executable file
43
experimentations/get_le_rio.py
Executable file
@ -0,0 +1,43 @@
|
|||||||
|
#!/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))
|
@ -73,6 +73,10 @@ 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
|
||||||
@ -85,7 +89,4 @@ 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
|
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ from .models import (
|
|||||||
BatchImportation,
|
BatchImportation,
|
||||||
RecurrentImport,
|
RecurrentImport,
|
||||||
Place,
|
Place,
|
||||||
ContactMessage,
|
Message,
|
||||||
ReferenceLocation,
|
ReferenceLocation,
|
||||||
Organisation
|
Organisation
|
||||||
)
|
)
|
||||||
@ -25,7 +25,7 @@ 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(ContactMessage)
|
admin.site.register(Message)
|
||||||
admin.site.register(ReferenceLocation)
|
admin.site.register(ReferenceLocation)
|
||||||
admin.site.register(Organisation)
|
admin.site.register(Organisation)
|
||||||
|
|
||||||
|
@ -117,6 +117,23 @@ 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
|
||||||
@ -175,12 +192,13 @@ 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):
|
def __init__(self, firstdate, lastdate, filter=None, exact=False, ignore_dup=None, qs=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
|
||||||
@ -219,9 +237,12 @@ class CalendarList:
|
|||||||
|
|
||||||
def fill_calendar_days(self):
|
def fill_calendar_days(self):
|
||||||
if self.filter is None:
|
if self.filter is None:
|
||||||
from .models import Event
|
if self.qs is None:
|
||||||
|
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
|
||||||
|
|
||||||
@ -229,7 +250,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())
|
||||||
self.events = qs.filter(
|
qs = 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)
|
||||||
@ -243,7 +264,10 @@ class CalendarList:
|
|||||||
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").select_related("exact_location").select_related("category").select_related("other_versions").select_related("other_versions__representative")
|
).order_by("start_time", "title__unaccent__lower")
|
||||||
|
|
||||||
|
qs = qs.select_related("exact_location").select_related("category").select_related("other_versions").select_related("other_versions__representative")
|
||||||
|
self.events = qs
|
||||||
|
|
||||||
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:
|
||||||
@ -291,14 +315,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):
|
def export_to_ics(self, request):
|
||||||
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)
|
return Event.export_to_ics(events, request)
|
||||||
|
|
||||||
|
|
||||||
class CalendarMonth(CalendarList):
|
class CalendarMonth(CalendarList):
|
||||||
def __init__(self, year, month, filter):
|
def __init__(self, year, month, filter, qs=None):
|
||||||
self.year = year
|
self.year = year
|
||||||
self.month = month
|
self.month = month
|
||||||
r = calendar.monthrange(year, month)
|
r = calendar.monthrange(year, month)
|
||||||
@ -306,7 +330,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)
|
super().__init__(first, last, filter, qs)
|
||||||
|
|
||||||
def get_month_name(self):
|
def get_month_name(self):
|
||||||
return self.firstdate.strftime("%B")
|
return self.firstdate.strftime("%B")
|
||||||
@ -319,14 +343,14 @@ class CalendarMonth(CalendarList):
|
|||||||
|
|
||||||
|
|
||||||
class CalendarWeek(CalendarList):
|
class CalendarWeek(CalendarList):
|
||||||
def __init__(self, year, week, filter):
|
def __init__(self, year, week, filter, qs=None):
|
||||||
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)
|
super().__init__(first, last, filter, qs)
|
||||||
|
|
||||||
def next_week(self):
|
def next_week(self):
|
||||||
return self.firstdate + timedelta(days=7)
|
return self.firstdate + timedelta(days=7)
|
||||||
@ -336,8 +360,8 @@ class CalendarWeek(CalendarList):
|
|||||||
|
|
||||||
|
|
||||||
class CalendarDay(CalendarList):
|
class CalendarDay(CalendarList):
|
||||||
def __init__(self, date, filter=None):
|
def __init__(self, date, filter=None, qs=None):
|
||||||
super().__init__(date, date, filter, exact=True)
|
super().__init__(date, date, filter=filter, qs=qs, exact=True)
|
||||||
|
|
||||||
def get_events(self):
|
def get_events(self):
|
||||||
return self.calendar_days_list()[0].events
|
return self.calendar_days_list()[0].events
|
||||||
|
@ -6,7 +6,8 @@ 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
|
||||||
|
|
||||||
@ -147,6 +148,8 @@ 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
|
||||||
|
|
||||||
@ -173,6 +176,11 @@ def run_recurrent_import_internal(rimport, downloader, req_id):
|
|||||||
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)
|
||||||
|
|
||||||
@ -248,6 +256,23 @@ 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
|
||||||
@ -289,7 +314,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):
|
def import_events_from_url(self, url, cat, tags, force=False, user_id=None):
|
||||||
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
|
||||||
@ -298,7 +323,7 @@ def import_events_from_url(self, url, cat, tags, force=False):
|
|||||||
if acquired:
|
if acquired:
|
||||||
|
|
||||||
|
|
||||||
logger.info("URL import: {}".format(self.request.id))
|
logger.info("URL import: {}".format(self.request.id) + " force " + str(force))
|
||||||
|
|
||||||
|
|
||||||
# clean url
|
# clean url
|
||||||
@ -335,7 +360,7 @@ def import_events_from_url(self, url, cat, tags, force=False):
|
|||||||
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)
|
success, error_message = importer.import_events(json_events, user_id)
|
||||||
|
|
||||||
# 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)
|
||||||
@ -352,14 +377,14 @@ def import_events_from_url(self, url, cat, tags, force=False):
|
|||||||
|
|
||||||
|
|
||||||
@app.task(base=ChromiumTask, bind=True)
|
@app.task(base=ChromiumTask, bind=True)
|
||||||
def import_events_from_urls(self, urls_cat_tags):
|
def import_events_from_urls(self, urls_cat_tags, user_id=None):
|
||||||
for ucat in urls_cat_tags:
|
for ucat in urls_cat_tags:
|
||||||
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]
|
tags = ucat[2]
|
||||||
|
|
||||||
import_events_from_url.delay(url, cat, tags)
|
import_events_from_url.delay(url, cat, tags, user_id=user_id)
|
||||||
|
|
||||||
|
|
||||||
app.conf.beat_schedule = {
|
app.conf.beat_schedule = {
|
||||||
@ -368,6 +393,10 @@ 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.
|
||||||
|
@ -11,6 +11,7 @@ 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()
|
||||||
|
|
||||||
@ -34,9 +35,10 @@ 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):
|
def import_events(self, json_structure, user_id=None):
|
||||||
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)
|
||||||
@ -95,7 +97,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
|
self.event_objects, remove_missing_from_source=self.url, user_id=self.user_id
|
||||||
)
|
)
|
||||||
|
|
||||||
def is_valid_event_structure(self, event):
|
def is_valid_event_structure(self, event):
|
||||||
|
@ -44,7 +44,7 @@ from .models import (
|
|||||||
Tag,
|
Tag,
|
||||||
Event,
|
Event,
|
||||||
Category,
|
Category,
|
||||||
ContactMessage,
|
Message,
|
||||||
DuplicatedEvents
|
DuplicatedEvents
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -137,7 +137,11 @@ class EventFilter(django_filters.FilterSet):
|
|||||||
if self.get_cleaned_data("position") is None or self.get_cleaned_data("radius") is None:
|
if self.get_cleaned_data("position") is None or self.get_cleaned_data("radius") is None:
|
||||||
return parent
|
return parent
|
||||||
d = self.get_cleaned_data("radius")
|
d = self.get_cleaned_data("radius")
|
||||||
p = self.get_cleaned_data("position").location
|
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)))
|
return parent.exclude(exact_location=False).filter(exact_location__location__distance_lt=(p, D(km=d)))
|
||||||
|
|
||||||
def get_url(self):
|
def get_url(self):
|
||||||
@ -188,6 +192,7 @@ class EventFilter(django_filters.FilterSet):
|
|||||||
|
|
||||||
|
|
||||||
def get_cleaned_data(self, name):
|
def get_cleaned_data(self, name):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self.form.cleaned_data[name]
|
return self.form.cleaned_data[name]
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@ -356,7 +361,7 @@ class EventFilterAdmin(django_filters.FilterSet):
|
|||||||
fields = ["status"]
|
fields = ["status"]
|
||||||
|
|
||||||
|
|
||||||
class ContactMessagesFilterAdmin(django_filters.FilterSet):
|
class MessagesFilterAdmin(django_filters.FilterSet):
|
||||||
closed = django_filters.MultipleChoiceFilter(
|
closed = django_filters.MultipleChoiceFilter(
|
||||||
label="Status",
|
label="Status",
|
||||||
choices=((True, _("Closed")), (False, _("Open"))),
|
choices=((True, _("Closed")), (False, _("Open"))),
|
||||||
@ -369,7 +374,7 @@ class ContactMessagesFilterAdmin(django_filters.FilterSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ContactMessage
|
model = Message
|
||||||
fields = ["closed", "spam"]
|
fields = ["closed", "spam"]
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ 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,
|
||||||
@ -23,7 +24,7 @@ from .models import (
|
|||||||
Place,
|
Place,
|
||||||
Category,
|
Category,
|
||||||
Tag,
|
Tag,
|
||||||
ContactMessage
|
Message
|
||||||
)
|
)
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.files import File
|
from django.core.files import File
|
||||||
@ -32,7 +33,6 @@ 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 os
|
||||||
@ -74,7 +74,7 @@ class GroupFormMixin:
|
|||||||
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)]
|
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):
|
def fields_by_group(self):
|
||||||
return [(g, self.get_fields_in_group(g)) for g in self.groups] + [(None, self.get_no_group_fields())]
|
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):
|
def clean(self):
|
||||||
result = super().clean()
|
result = super().clean()
|
||||||
@ -189,6 +189,7 @@ class EventForm(GroupFormMixin, ModelForm):
|
|||||||
|
|
||||||
old_local_image = CharField(widget=HiddenInput(), required=False)
|
old_local_image = CharField(widget=HiddenInput(), required=False)
|
||||||
simple_cloning = CharField(widget=HiddenInput(), required=False)
|
simple_cloning = CharField(widget=HiddenInput(), required=False)
|
||||||
|
cloning = CharField(widget=HiddenInput(), required=False)
|
||||||
|
|
||||||
tags = MultipleChoiceField(
|
tags = MultipleChoiceField(
|
||||||
label=_("Tags"),
|
label=_("Tags"),
|
||||||
@ -205,7 +206,11 @@ 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(
|
||||||
@ -363,6 +368,9 @@ class EventModerateForm(ModelForm):
|
|||||||
"exact_location",
|
"exact_location",
|
||||||
"tags"
|
"tags"
|
||||||
]
|
]
|
||||||
|
widgets = {
|
||||||
|
"status": RadioSelect
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@ -493,7 +501,7 @@ class SelectEventInList(Form):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.fields["event"].choices = [
|
self.fields["event"].choices = [
|
||||||
(e.pk, str(e.start_day) + " " + e.title + ((", " + e.location) if e.location else "")) for e in events
|
(e.pk, (e.start_time.strftime('%H:%M') + " : " if e.start_time else "") + e.title + ((", " + e.location) if e.location else "")) for e in events
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -541,17 +549,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 : " + localize(localtime(e.created_date)) + "</li>"
|
"<li>Création : " + localize(e.created_date) + "</li>"
|
||||||
)
|
)
|
||||||
result += (
|
result += (
|
||||||
"<li>Dernière modification : "
|
"<li>Dernière modification : "
|
||||||
+ localize(localtime(e.modified_date))
|
+ localize(e.modified_date)
|
||||||
+ "</li>"
|
+ "</li>"
|
||||||
)
|
)
|
||||||
if e.imported_date:
|
if e.imported_date:
|
||||||
result += (
|
result += (
|
||||||
"<li>Dernière importation : "
|
"<li>Dernière importation : "
|
||||||
+ localize(localtime(e.imported_date))
|
+ localize(e.imported_date)
|
||||||
+ "</li>"
|
+ "</li>"
|
||||||
)
|
)
|
||||||
result += "</ul>"
|
result += "</ul>"
|
||||||
@ -602,7 +610,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 value in checked:
|
if checked and value in checked:
|
||||||
result += " checked"
|
result += " checked"
|
||||||
else:
|
else:
|
||||||
result += ' type="radio"'
|
result += ' type="radio"'
|
||||||
@ -634,7 +642,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 == selected:
|
if e.pk == s:
|
||||||
result.append(e)
|
result.append(e)
|
||||||
break
|
break
|
||||||
return result
|
return result
|
||||||
@ -718,7 +726,7 @@ class EventAddPlaceForm(Form):
|
|||||||
return self.instance
|
return self.instance
|
||||||
|
|
||||||
|
|
||||||
class PlaceForm(ModelForm):
|
class PlaceForm(GroupFormMixin, ModelForm):
|
||||||
required_css_class = 'required'
|
required_css_class = 'required'
|
||||||
|
|
||||||
apply_to_all = BooleanField(
|
apply_to_all = BooleanField(
|
||||||
@ -734,24 +742,70 @@ class PlaceForm(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):
|
||||||
return mark_safe(
|
result = ('<div class="grid"><div>'
|
||||||
'<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><p>Cliquez pour ajuster la position GPS</p></div></div></div>'
|
<div id="map_location" style="width: 100%; aspect-ratio: 16/9"></div>
|
||||||
)
|
<p>Cliquez pour ajuster la position GPS</p></div>
|
||||||
|
<input type="checkbox" role="switch" id="lock_position">Verrouiller la position</lock>
|
||||||
|
<script>
|
||||||
|
document.getElementById("lock_position").onclick = function() {
|
||||||
|
const field = document.getElementById("id_location");
|
||||||
|
if (this.checked)
|
||||||
|
field.setAttribute("readonly", true);
|
||||||
|
else
|
||||||
|
field.removeAttribute("readonly");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</div></div>''')
|
||||||
|
|
||||||
|
return mark_safe(result)
|
||||||
|
|
||||||
def apply(self):
|
def apply(self):
|
||||||
return self.cleaned_data.get("apply_to_all")
|
return self.cleaned_data.get("apply_to_all")
|
||||||
|
|
||||||
class ContactMessageForm(ModelForm):
|
class MessageForm(ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ContactMessage
|
model = Message
|
||||||
fields = ["subject", "name", "email", "message", "related_event"]
|
fields = ["subject", "name", "email", "message", "related_event"]
|
||||||
widgets = {"related_event": HiddenInput()}
|
widgets = {"related_event": HiddenInput(), "user": HiddenInput() }
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.event = kwargs.pop("event", False)
|
self.event = kwargs.pop("event", False)
|
||||||
|
self.internal = kwargs.pop("internal", False)
|
||||||
super().__init__(*args, **kwargs)
|
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")
|
@ -3,6 +3,12 @@ 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
|
||||||
@ -13,10 +19,27 @@ 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(
|
||||||
@ -42,4 +65,7 @@ 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")
|
||||||
|
|
||||||
|
|
||||||
|
91
src/agenda_culturel/import_tasks/custom_extractors/lerio.py
Normal file
91
src/agenda_culturel/import_tasks/custom_extractors/lerio.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
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
|
||||||
|
)
|
@ -66,7 +66,7 @@ class SimpleDownloader(Downloader):
|
|||||||
|
|
||||||
|
|
||||||
class ChromiumHeadlessDownloader(Downloader):
|
class ChromiumHeadlessDownloader(Downloader):
|
||||||
def __init__(self, pause=True):
|
def __init__(self, pause=True, noimage=True):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.pause = pause
|
self.pause = pause
|
||||||
self.options = Options()
|
self.options = Options()
|
||||||
@ -78,17 +78,31 @@ 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")
|
||||||
self.options.add_experimental_option(
|
if noimage:
|
||||||
"prefs", {
|
self.options.add_experimental_option(
|
||||||
# block image loading
|
"prefs", {
|
||||||
"profile.managed_default_content_settings.images": 2,
|
# block image loading
|
||||||
}
|
"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")
|
||||||
|
@ -49,7 +49,7 @@ class Extractor(ABC):
|
|||||||
return i + 1
|
return i + 1
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def parse_french_date(text):
|
def parse_french_date(text, default_year=None):
|
||||||
# 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,8 +73,15 @@ class Extractor(ABC):
|
|||||||
month = int(m.group(2))
|
month = int(m.group(2))
|
||||||
year = m.group(3)
|
year = m.group(3)
|
||||||
else:
|
else:
|
||||||
# TODO: consolider les cas non satisfaits
|
# format Numero Mois Annee
|
||||||
return None
|
m = re.search("([0-9]+)[er]*[ ]*([a-zA-ZéÉûÛ:.]+)", text)
|
||||||
|
if m:
|
||||||
|
day = m.group(1)
|
||||||
|
month = Extractor.guess_month(m.group(2))
|
||||||
|
year = default_year
|
||||||
|
else:
|
||||||
|
# TODO: consolider les cas non satisfaits
|
||||||
|
return None
|
||||||
|
|
||||||
if month is None:
|
if month is None:
|
||||||
return None
|
return None
|
||||||
@ -254,9 +261,9 @@ class EventNotFoundExtractor(Extractor):
|
|||||||
self.set_header(url)
|
self.set_header(url)
|
||||||
self.clear_events()
|
self.clear_events()
|
||||||
|
|
||||||
self.add_event(default_values, "événement sans titre",
|
self.add_event(default_values, "événement sans titre depuis " + url,
|
||||||
None, timezone.now().date(), None,
|
None, timezone.now().date(), None,
|
||||||
"l'import a échoué, la saisie doit se faire manuellement à partir de l'url source",
|
"l'import a échoué, la saisie doit se faire manuellement à partir de l'url source " + url,
|
||||||
[], [url], published=False, url_human=url)
|
[], [url], published=False, url_human=url)
|
||||||
|
|
||||||
return self.get_structure()
|
return self.get_structure()
|
||||||
|
@ -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]
|
||||||
|
|
||||||
|
@ -264,7 +264,10 @@ class TwoStepsExtractorNoPause(TwoStepsExtractor):
|
|||||||
only_future=True,
|
only_future=True,
|
||||||
ignore_404=True
|
ignore_404=True
|
||||||
):
|
):
|
||||||
pause = self.downloader.pause
|
if hasattr(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
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -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):
|
||||||
places = Place.objects.all()
|
Place = apps.get_model("agenda_culturel", "Place")
|
||||||
|
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,14 +13,15 @@ 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()
|
p.save(update_fields=["location_pt"])
|
||||||
|
|
||||||
def reverse_coord_format(apps, schema_editor):
|
def reverse_coord_format(apps, schema_editor):
|
||||||
places = Place.objects.all()
|
Place = apps.get_model("agenda_culturel", "Place")
|
||||||
|
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()
|
p.save(update_fields=["location"])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,36 @@
|
|||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
18
src/agenda_culturel/migrations/0124_place_postcode.py
Normal file
18
src/agenda_culturel/migrations/0124_place_postcode.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,21 @@
|
|||||||
|
# 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'},
|
||||||
|
),
|
||||||
|
]
|
21
src/agenda_culturel/migrations/0126_message_user.py
Normal file
21
src/agenda_culturel/migrations/0126_message_user.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,25 @@
|
|||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
18
src/agenda_culturel/migrations/0128_event_datetimes_title.py
Normal file
18
src/agenda_culturel/migrations/0128_event_datetimes_title.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,57 @@
|
|||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,18 @@
|
|||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
@ -10,7 +10,11 @@ from colorfield.fields import ColorField
|
|||||||
from django_ckeditor_5.fields import CKEditor5Field
|
from django_ckeditor_5.fields import CKEditor5Field
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
from django.core.cache.utils import make_template_fragment_key
|
||||||
|
from django.contrib.auth.models import User, AnonymousUser
|
||||||
import emoji
|
import emoji
|
||||||
|
from django.core.files.storage import default_storage
|
||||||
|
import uuid
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import urllib.request
|
import urllib.request
|
||||||
@ -20,6 +24,7 @@ from django.utils import timezone
|
|||||||
from django.contrib.postgres.search import TrigramSimilarity
|
from django.contrib.postgres.search import TrigramSimilarity
|
||||||
from django.db.models import Q, Count, F, Subquery, OuterRef, Func
|
from django.db.models import Q, Count, F, Subquery, OuterRef, Func
|
||||||
from django.db.models.functions import Lower
|
from django.db.models.functions import Lower
|
||||||
|
from django.contrib.postgres.lookups import Unaccent
|
||||||
import recurrence.fields
|
import recurrence.fields
|
||||||
import recurrence
|
import recurrence
|
||||||
import copy
|
import copy
|
||||||
@ -284,6 +289,10 @@ class DuplicatedEvents(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Duplicated events")
|
verbose_name = _("Duplicated events")
|
||||||
verbose_name_plural = _("Duplicated events")
|
verbose_name_plural = _("Duplicated events")
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=['representative']),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.events = None
|
self.events = None
|
||||||
@ -434,8 +443,9 @@ class Place(models.Model):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
postcode = models.CharField(verbose_name=_("Postcode"), help_text=_("The post code is not displayed, but makes it easier to find an address when you enter it."), blank=True, null=True)
|
||||||
city = models.CharField(verbose_name=_("City"), help_text=_("City name"))
|
city = models.CharField(verbose_name=_("City"), help_text=_("City name"))
|
||||||
location = LocationField(based_fields=["name", "address", "city"], zoom=12, default=Point(3.08333, 45.783329))
|
location = LocationField(based_fields=["name", "address", "postcode", "city"], zoom=12, default=Point(3.08333, 45.783329))
|
||||||
|
|
||||||
description = CKEditor5Field(
|
description = CKEditor5Field(
|
||||||
verbose_name=_("Description"),
|
verbose_name=_("Description"),
|
||||||
@ -458,6 +468,11 @@ class Place(models.Model):
|
|||||||
verbose_name = _("Place")
|
verbose_name = _("Place")
|
||||||
verbose_name_plural = _("Places")
|
verbose_name_plural = _("Places")
|
||||||
ordering = ["name"]
|
ordering = ["name"]
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=['name']),
|
||||||
|
models.Index(fields=['city']),
|
||||||
|
models.Index(fields=['location']),
|
||||||
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.address:
|
if self.address:
|
||||||
@ -543,7 +558,7 @@ class Organisation(models.Model):
|
|||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("view_organisation", kwargs={'pk': self.pk})
|
return reverse("view_organisation", kwargs={'pk': self.pk, "extra": self.name})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -558,6 +573,39 @@ class Event(models.Model):
|
|||||||
modified_date = models.DateTimeField(blank=True, null=True)
|
modified_date = models.DateTimeField(blank=True, null=True)
|
||||||
moderated_date = models.DateTimeField(blank=True, null=True)
|
moderated_date = models.DateTimeField(blank=True, null=True)
|
||||||
|
|
||||||
|
created_by_user = models.ForeignKey(
|
||||||
|
User,
|
||||||
|
verbose_name=_("Author of the event creation"),
|
||||||
|
null=True,
|
||||||
|
default=None,
|
||||||
|
on_delete=models.SET_DEFAULT,
|
||||||
|
related_name="created_events"
|
||||||
|
)
|
||||||
|
imported_by_user = models.ForeignKey(
|
||||||
|
User,
|
||||||
|
verbose_name=_("Author of the last importation"),
|
||||||
|
null=True,
|
||||||
|
default=None,
|
||||||
|
on_delete=models.SET_DEFAULT,
|
||||||
|
related_name="imported_events"
|
||||||
|
)
|
||||||
|
modified_by_user = models.ForeignKey(
|
||||||
|
User,
|
||||||
|
verbose_name=_("Author of the last modification"),
|
||||||
|
null=True,
|
||||||
|
default=None,
|
||||||
|
on_delete=models.SET_DEFAULT,
|
||||||
|
related_name="modified_events"
|
||||||
|
)
|
||||||
|
moderated_by_user = models.ForeignKey(
|
||||||
|
User,
|
||||||
|
verbose_name=_("Author of the last moderation"),
|
||||||
|
null=True,
|
||||||
|
default=None,
|
||||||
|
on_delete=models.SET_DEFAULT,
|
||||||
|
related_name="moderated_events"
|
||||||
|
)
|
||||||
|
|
||||||
recurrence_dtstart = models.DateTimeField(editable=False, blank=True, null=True)
|
recurrence_dtstart = models.DateTimeField(editable=False, blank=True, null=True)
|
||||||
recurrence_dtend = models.DateTimeField(editable=False, blank=True, null=True)
|
recurrence_dtend = models.DateTimeField(editable=False, blank=True, null=True)
|
||||||
|
|
||||||
@ -692,6 +740,10 @@ class Event(models.Model):
|
|||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.processing_user = None
|
||||||
|
|
||||||
def get_consolidated_end_day(self, intuitive=True):
|
def get_consolidated_end_day(self, intuitive=True):
|
||||||
if intuitive:
|
if intuitive:
|
||||||
end_day = self.get_consolidated_end_day(False)
|
end_day = self.get_consolidated_end_day(False)
|
||||||
@ -709,15 +761,6 @@ class Event(models.Model):
|
|||||||
last = self.get_consolidated_end_day()
|
last = self.get_consolidated_end_day()
|
||||||
return [first + timedelta(n) for n in range(int((last - first).days) + 1)]
|
return [first + timedelta(n) for n in range(int((last - first).days) + 1)]
|
||||||
|
|
||||||
def get_nb_events_same_dates(self, remove_same_dup=True):
|
|
||||||
first = self.start_day
|
|
||||||
last = self.get_consolidated_end_day()
|
|
||||||
ignore_dup = None
|
|
||||||
if remove_same_dup:
|
|
||||||
ignore_dup = self.other_versions
|
|
||||||
calendar = CalendarList(first, last, exact=True, ignore_dup=ignore_dup)
|
|
||||||
return [(len(d.events), d.date) for dstr, d in calendar.get_calendar_days().items()]
|
|
||||||
|
|
||||||
def is_single_day(self, intuitive=True):
|
def is_single_day(self, intuitive=True):
|
||||||
return self.start_day == self.get_consolidated_end_day(intuitive)
|
return self.start_day == self.get_consolidated_end_day(intuitive)
|
||||||
|
|
||||||
@ -727,6 +770,15 @@ class Event(models.Model):
|
|||||||
end_date = parse_date(end_date)
|
end_date = parse_date(end_date)
|
||||||
return parse_date(self.start_day) + timedelta(days=min_days) < end_date
|
return parse_date(self.start_day) + timedelta(days=min_days) < end_date
|
||||||
|
|
||||||
|
def set_message(self, msg):
|
||||||
|
self._message = msg
|
||||||
|
|
||||||
|
def get_message(self):
|
||||||
|
return self._message
|
||||||
|
|
||||||
|
def has_message(self):
|
||||||
|
return hasattr(self, '_message')
|
||||||
|
|
||||||
def contains_date(self, d, intuitive=True):
|
def contains_date(self, d, intuitive=True):
|
||||||
return d >= self.start_day and d <= self.get_consolidated_end_day(intuitive)
|
return d >= self.start_day and d <= self.get_consolidated_end_day(intuitive)
|
||||||
|
|
||||||
@ -764,9 +816,36 @@ class Event(models.Model):
|
|||||||
permissions = [("set_duplicated_event", "Can set an event as duplicated")]
|
permissions = [("set_duplicated_event", "Can set an event as duplicated")]
|
||||||
indexes = [
|
indexes = [
|
||||||
models.Index(fields=["start_day", "start_time"]),
|
models.Index(fields=["start_day", "start_time"]),
|
||||||
models.Index("start_time", Lower("title"), name="start_time title")
|
models.Index(fields=["end_day", "end_time"]),
|
||||||
|
models.Index(fields=["status"]),
|
||||||
|
models.Index(fields=["recurrence_dtstart", "recurrence_dtend"]),
|
||||||
|
models.Index("start_time", Lower("title"), name="start_time title"),
|
||||||
|
models.Index("start_time", "start_day", "end_day", "end_time", Lower("title"), name="datetimes title")
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def chronology(self):
|
||||||
|
c = []
|
||||||
|
if self.modified_date:
|
||||||
|
c.append({ "timestamp": self.modified_date, "data": "modified_date", "user": self.modified_by_user, "is_date": True })
|
||||||
|
if self.moderated_date:
|
||||||
|
c.append({ "timestamp": self.moderated_date, "data": "moderated_date", "user" : self.moderated_by_user, "is_date": True})
|
||||||
|
if self.imported_date:
|
||||||
|
c.append({ "timestamp": self.imported_date, "data": "imported_date", "user": self.imported_by_user, "is_date": True })
|
||||||
|
if self.created_date:
|
||||||
|
c.append({ "timestamp": self.created_date + timedelta(milliseconds=-1), "data": "created_date", "user": self.created_by_user, "is_date": True})
|
||||||
|
|
||||||
|
c += [{ "timestamp": m.date, "data": m, "user": m.user, "is_date": False} for m in self.message_set.filter(spam=False)]
|
||||||
|
|
||||||
|
if self.other_versions:
|
||||||
|
for o in self.other_versions.get_duplicated():
|
||||||
|
if o != self:
|
||||||
|
c += [{ "timestamp": m.date, "data": m, "user": m.user, "is_date": False} for m in o.message_set.filter(spam=False)]
|
||||||
|
|
||||||
|
|
||||||
|
c.sort(key=lambda x: x["timestamp"])
|
||||||
|
|
||||||
|
return c
|
||||||
|
|
||||||
def sorted_tags(self):
|
def sorted_tags(self):
|
||||||
if self.tags is None:
|
if self.tags is None:
|
||||||
return []
|
return []
|
||||||
@ -858,19 +937,28 @@ class Event(models.Model):
|
|||||||
def is_representative(self):
|
def is_representative(self):
|
||||||
return self.other_versions is None or self.other_versions.representative == self
|
return self.other_versions is None or self.other_versions.representative == self
|
||||||
|
|
||||||
|
def download_missing_image(self):
|
||||||
|
if self.local_image and not default_storage.exists(self.local_image.name):
|
||||||
|
logger.warning("on dl")
|
||||||
|
self.download_image()
|
||||||
|
self.save(update_fields=["local_image"])
|
||||||
|
|
||||||
def download_image(self):
|
def download_image(self):
|
||||||
# first download file
|
# first download file
|
||||||
|
|
||||||
a = urlparse(self.image)
|
a = urlparse(self.image)
|
||||||
basename = os.path.basename(a.path)
|
basename = os.path.basename(a.path)
|
||||||
|
|
||||||
|
ext = basename.split('.')[-1]
|
||||||
|
filename = "%s.%s" % (uuid.uuid4(), ext)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tmpfile, _ = urllib.request.urlretrieve(self.image)
|
tmpfile, _ = urllib.request.urlretrieve(self.image)
|
||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# if the download is ok, then create the corresponding file object
|
# if the download is ok, then create the corresponding file object
|
||||||
self.local_image = File(name=basename, file=open(tmpfile, "rb"))
|
self.local_image = File(name=filename, file=open(tmpfile, "rb"))
|
||||||
|
|
||||||
def add_pending_organisers(self, organisers):
|
def add_pending_organisers(self, organisers):
|
||||||
self.pending_organisers = organisers
|
self.pending_organisers = organisers
|
||||||
@ -896,6 +984,12 @@ class Event(models.Model):
|
|||||||
def set_no_modification_date_changed(self):
|
def set_no_modification_date_changed(self):
|
||||||
self.no_modification_date_changed = True
|
self.no_modification_date_changed = True
|
||||||
|
|
||||||
|
def set_processing_user(self, user):
|
||||||
|
if user is None or user.is_anonymous:
|
||||||
|
self.processing_user = None
|
||||||
|
else:
|
||||||
|
self.processing_user = user
|
||||||
|
|
||||||
def set_in_moderation_process(self):
|
def set_in_moderation_process(self):
|
||||||
self.in_moderation_process = True
|
self.in_moderation_process = True
|
||||||
|
|
||||||
@ -906,12 +1000,16 @@ class Event(models.Model):
|
|||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
if not self.id:
|
if not self.id:
|
||||||
self.created_date = now
|
self.created_date = now
|
||||||
|
self.created_by_user = self.processing_user
|
||||||
if self.is_in_importation_process():
|
if self.is_in_importation_process():
|
||||||
self.imported_date = now
|
self.imported_date = now
|
||||||
|
self.imported_by_user = self.processing_user
|
||||||
if self.modified_date is None or not self.is_no_modification_date_changed():
|
if self.modified_date is None or not self.is_no_modification_date_changed():
|
||||||
self.modified_date = now
|
self.modified_date = now
|
||||||
|
self.modified_by_user = self.processing_user
|
||||||
if self.is_in_moderation_process():
|
if self.is_in_moderation_process():
|
||||||
self.moderated_date = now
|
self.moderated_date = now
|
||||||
|
self.moderated_by_user = self.processing_user
|
||||||
|
|
||||||
def get_recurrence_at_date(self, year, month, day):
|
def get_recurrence_at_date(self, year, month, day):
|
||||||
dtstart = timezone.make_aware(
|
dtstart = timezone.make_aware(
|
||||||
@ -923,10 +1021,13 @@ class Event(models.Model):
|
|||||||
else:
|
else:
|
||||||
return recurrences[0]
|
return recurrences[0]
|
||||||
|
|
||||||
def get_image_url(self):
|
def get_image_url(self, request=None):
|
||||||
if self.local_image and hasattr(self.local_image, "url"):
|
if self.local_image and hasattr(self.local_image, "url"):
|
||||||
try:
|
try:
|
||||||
return self.local_image.url
|
if request:
|
||||||
|
return request.build_absolute_uri(self.local_image.url)
|
||||||
|
else:
|
||||||
|
return self.local_image.url
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
if self.image:
|
if self.image:
|
||||||
@ -1024,7 +1125,7 @@ class Event(models.Model):
|
|||||||
self.update_recurrence_dtstartend()
|
self.update_recurrence_dtstartend()
|
||||||
|
|
||||||
# if the image is defined but not locally downloaded
|
# if the image is defined but not locally downloaded
|
||||||
if self.image and not self.local_image:
|
if self.image and (not self.local_image or not default_storage.exists(self.local_image.name)):
|
||||||
self.download_image()
|
self.download_image()
|
||||||
|
|
||||||
# remove "/" from tags
|
# remove "/" from tags
|
||||||
@ -1076,6 +1177,11 @@ class Event(models.Model):
|
|||||||
# first save the current object
|
# first save the current object
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
# clear cache
|
||||||
|
for is_auth in [False, True]:
|
||||||
|
key = make_template_fragment_key("event_body", [is_auth, self])
|
||||||
|
cache.delete(key)
|
||||||
|
|
||||||
# then if its a clone, update the representative
|
# then if its a clone, update the representative
|
||||||
if clone:
|
if clone:
|
||||||
self.other_versions.representative = self
|
self.other_versions.representative = self
|
||||||
@ -1322,9 +1428,10 @@ class Event(models.Model):
|
|||||||
# otherwise merge existing groups
|
# otherwise merge existing groups
|
||||||
group = DuplicatedEvents.merge_groups(groups)
|
group = DuplicatedEvents.merge_groups(groups)
|
||||||
|
|
||||||
|
group.save()
|
||||||
|
|
||||||
if force_non_fixed:
|
if force_non_fixed:
|
||||||
group.representative = None
|
group.representative = None
|
||||||
group.save()
|
|
||||||
|
|
||||||
# set the possibly duplicated group for the current object
|
# set the possibly duplicated group for the current object
|
||||||
self.other_versions = group
|
self.other_versions = group
|
||||||
@ -1345,6 +1452,8 @@ class Event(models.Model):
|
|||||||
"category",
|
"category",
|
||||||
"tags",
|
"tags",
|
||||||
]
|
]
|
||||||
|
if not no_m2m:
|
||||||
|
result += ["organisers"]
|
||||||
|
|
||||||
result += [
|
result += [
|
||||||
"title",
|
"title",
|
||||||
@ -1356,8 +1465,6 @@ class Event(models.Model):
|
|||||||
"description",
|
"description",
|
||||||
"image",
|
"image",
|
||||||
]
|
]
|
||||||
if not no_m2m:
|
|
||||||
result += ["organisers"]
|
|
||||||
if all and local_img:
|
if all and local_img:
|
||||||
result += ["local_image"]
|
result += ["local_image"]
|
||||||
if all and exact_location:
|
if all and exact_location:
|
||||||
@ -1409,7 +1516,11 @@ class Event(models.Model):
|
|||||||
self.import_sources.append(source)
|
self.import_sources.append(source)
|
||||||
|
|
||||||
# Limitation: the given events should not be considered similar one to another...
|
# Limitation: the given events should not be considered similar one to another...
|
||||||
def import_events(events, remove_missing_from_source=None):
|
def import_events(events, remove_missing_from_source=None, user_id=None):
|
||||||
|
|
||||||
|
user = None
|
||||||
|
if user_id:
|
||||||
|
user = User.objects.filter(pk=user_id).first()
|
||||||
|
|
||||||
to_import = []
|
to_import = []
|
||||||
to_update = []
|
to_update = []
|
||||||
@ -1436,6 +1547,7 @@ class Event(models.Model):
|
|||||||
|
|
||||||
# imported events should be updated
|
# imported events should be updated
|
||||||
event.set_in_importation_process()
|
event.set_in_importation_process()
|
||||||
|
event.set_processing_user(user)
|
||||||
event.prepare_save()
|
event.prepare_save()
|
||||||
|
|
||||||
# check if the event has already be imported (using uuid)
|
# check if the event has already be imported (using uuid)
|
||||||
@ -1462,9 +1574,14 @@ class Event(models.Model):
|
|||||||
same_imported.other_versions.representative = None
|
same_imported.other_versions.representative = None
|
||||||
same_imported.other_versions.save()
|
same_imported.other_versions.save()
|
||||||
# we only update local information if it's a pure import and has no moderated_date
|
# we only update local information if it's a pure import and has no moderated_date
|
||||||
|
new_image = same_imported.image != event.image
|
||||||
same_imported.update(event, pure and same_imported.moderated_date is None)
|
same_imported.update(event, pure and same_imported.moderated_date is None)
|
||||||
same_imported.set_in_importation_process()
|
same_imported.set_in_importation_process()
|
||||||
same_imported.prepare_save()
|
same_imported.prepare_save()
|
||||||
|
# fix missing or updated files
|
||||||
|
if same_imported.local_image and (not default_storage.exists(same_imported.local_image.name) or new_image):
|
||||||
|
same_imported.download_image()
|
||||||
|
same_imported.save(update_fields=["local_image"])
|
||||||
to_update.append(same_imported)
|
to_update.append(same_imported)
|
||||||
else:
|
else:
|
||||||
# otherwise, the new event possibly a duplication of the remaining others.
|
# otherwise, the new event possibly a duplication of the remaining others.
|
||||||
@ -1492,6 +1609,7 @@ class Event(models.Model):
|
|||||||
for e in to_import:
|
for e in to_import:
|
||||||
if e.is_event_long_duration():
|
if e.is_event_long_duration():
|
||||||
e.status = Event.STATUS.DRAFT
|
e.status = Event.STATUS.DRAFT
|
||||||
|
e.set_message(_("The duration of the event is a little too long for direct publication. Moderators can choose to publish it or not."))
|
||||||
|
|
||||||
# then import all the new events
|
# then import all the new events
|
||||||
imported = Event.objects.bulk_create(to_import)
|
imported = Event.objects.bulk_create(to_import)
|
||||||
@ -1499,6 +1617,9 @@ class Event(models.Model):
|
|||||||
for i, ti in zip(imported, to_import):
|
for i, ti in zip(imported, to_import):
|
||||||
if ti.has_pending_organisers() and ti.pending_organisers is not None:
|
if ti.has_pending_organisers() and ti.pending_organisers is not None:
|
||||||
i.organisers.set(ti.pending_organisers)
|
i.organisers.set(ti.pending_organisers)
|
||||||
|
if ti.has_message():
|
||||||
|
msg = Message(subject=_('Import'), related_event=i, name=_('import process'), message=ti.get_message())
|
||||||
|
msg.save()
|
||||||
|
|
||||||
nb_updated = Event.objects.bulk_update(
|
nb_updated = Event.objects.bulk_update(
|
||||||
to_update,
|
to_update,
|
||||||
@ -1572,13 +1693,12 @@ class Event(models.Model):
|
|||||||
|
|
||||||
def get_concurrent_events(self, remove_same_dup=True):
|
def get_concurrent_events(self, remove_same_dup=True):
|
||||||
day = self.current_date if hasattr(self, "current_date") else self.start_day
|
day = self.current_date if hasattr(self, "current_date") else self.start_day
|
||||||
day_events = CalendarDay(self.start_day).get_events()
|
day_events = CalendarDay(day, qs = Event.objects.filter(status=Event.STATUS.PUBLISHED)).get_events()
|
||||||
return [
|
return [
|
||||||
e
|
e
|
||||||
for e in day_events
|
for e in day_events
|
||||||
if e != self
|
if e != self
|
||||||
and self.is_concurrent_event(e, day)
|
and self.is_concurrent_event(e, day)
|
||||||
and e.status == Event.STATUS.PUBLISHED
|
|
||||||
and (e.other_versions is None or e.other_versions != self.other_versions)
|
and (e.other_versions is None or e.other_versions != self.other_versions)
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1588,10 +1708,10 @@ class Event(models.Model):
|
|||||||
|
|
||||||
return (dtstart <= e_dtstart <= dtend) or (e_dtstart <= dtstart <= e_dtend)
|
return (dtstart <= e_dtstart <= dtend) or (e_dtstart <= dtstart <= e_dtend)
|
||||||
|
|
||||||
def export_to_ics(events):
|
def export_to_ics(events, request):
|
||||||
cal = icalCal()
|
cal = icalCal()
|
||||||
# Some properties are required to be compliant
|
# Some properties are required to be compliant
|
||||||
cal.add("prodid", "-//My calendar product//example.com//")
|
cal.add("prodid", "-//Pommes de lune//pommesdelune.fr//")
|
||||||
cal.add("version", "2.0")
|
cal.add("version", "2.0")
|
||||||
|
|
||||||
for event in events:
|
for event in events:
|
||||||
@ -1642,9 +1762,12 @@ class Event(models.Model):
|
|||||||
eventIcal.add("summary", event.title)
|
eventIcal.add("summary", event.title)
|
||||||
eventIcal.add("name", event.title)
|
eventIcal.add("name", event.title)
|
||||||
url = ("\n" + event.reference_urls[0]) if event.reference_urls and len(event.reference_urls) > 0 else ""
|
url = ("\n" + event.reference_urls[0]) if event.reference_urls and len(event.reference_urls) > 0 else ""
|
||||||
|
description = event.description if event.description else ""
|
||||||
eventIcal.add(
|
eventIcal.add(
|
||||||
"description", event.description + url
|
"description", description + url
|
||||||
)
|
)
|
||||||
|
if not event.local_image is None and event.local_image != "":
|
||||||
|
eventIcal.add('image', request.build_absolute_uri(event.local_image), parameters={'VALUE': 'URI'})
|
||||||
eventIcal.add("location", event.exact_location or event.location)
|
eventIcal.add("location", event.exact_location or event.location)
|
||||||
|
|
||||||
cal.add_component(eventIcal)
|
cal.add_component(eventIcal)
|
||||||
@ -1680,10 +1803,18 @@ class Event(models.Model):
|
|||||||
return [Event.get_count_modification(w) for w in when_list]
|
return [Event.get_count_modification(w) for w in when_list]
|
||||||
|
|
||||||
|
|
||||||
class ContactMessage(models.Model):
|
class Message(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Contact message")
|
verbose_name = _("Message")
|
||||||
verbose_name_plural = _("Contact messages")
|
verbose_name_plural = _("Messages")
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=['related_event']),
|
||||||
|
models.Index(fields=['user']),
|
||||||
|
models.Index(fields=['date']),
|
||||||
|
models.Index(fields=['spam', 'closed']),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
subject = models.CharField(
|
subject = models.CharField(
|
||||||
verbose_name=_("Subject"),
|
verbose_name=_("Subject"),
|
||||||
@ -1700,6 +1831,14 @@ class ContactMessage(models.Model):
|
|||||||
on_delete=models.SET_DEFAULT,
|
on_delete=models.SET_DEFAULT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
user = models.ForeignKey(
|
||||||
|
User,
|
||||||
|
verbose_name=_("Author of the message"),
|
||||||
|
null=True,
|
||||||
|
default=None,
|
||||||
|
on_delete=models.SET_DEFAULT,
|
||||||
|
)
|
||||||
|
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
verbose_name=_("Name"),
|
verbose_name=_("Name"),
|
||||||
help_text=_("Your name"),
|
help_text=_("Your name"),
|
||||||
@ -1739,11 +1878,11 @@ class ContactMessage(models.Model):
|
|||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def nb_open_contactmessages():
|
def nb_open_messages():
|
||||||
return ContactMessage.objects.filter(closed=False).count()
|
return Message.objects.filter(Q(closed=False)&Q(spam=False)).count()
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse("contactmessage", kwargs={"pk": self.pk})
|
return reverse("message", kwargs={"pk": self.pk})
|
||||||
|
|
||||||
|
|
||||||
class RecurrentImport(models.Model):
|
class RecurrentImport(models.Model):
|
||||||
@ -1764,6 +1903,7 @@ class RecurrentImport(models.Model):
|
|||||||
FBEVENTS = "Facebook events", _("Événements d'une page FB")
|
FBEVENTS = "Facebook events", _("Événements d'une page FB")
|
||||||
C3C = "cour3coquins", _("la cour des 3 coquins")
|
C3C = "cour3coquins", _("la cour des 3 coquins")
|
||||||
ARACHNEE = "arachnee", _("Arachnée concert")
|
ARACHNEE = "arachnee", _("Arachnée concert")
|
||||||
|
LERIO = "rio", _('Le Rio')
|
||||||
|
|
||||||
class DOWNLOADER(models.TextChoices):
|
class DOWNLOADER(models.TextChoices):
|
||||||
SIMPLE = "simple", _("simple")
|
SIMPLE = "simple", _("simple")
|
||||||
@ -1831,6 +1971,12 @@ class RecurrentImport(models.Model):
|
|||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
forceLocation = models.BooleanField(
|
||||||
|
verbose_name=_("Force location"),
|
||||||
|
help_text=_("force location even if another is detected."),
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
defaultOrganiser = models.ForeignKey(
|
defaultOrganiser = models.ForeignKey(
|
||||||
Organisation,
|
Organisation,
|
||||||
verbose_name=_("Organiser"),
|
verbose_name=_("Organiser"),
|
||||||
@ -1891,6 +2037,11 @@ class BatchImportation(models.Model):
|
|||||||
verbose_name = _("Batch importation")
|
verbose_name = _("Batch importation")
|
||||||
verbose_name_plural = _("Batch importations")
|
verbose_name_plural = _("Batch importations")
|
||||||
permissions = [("run_batchimportation", "Can run a batch importation")]
|
permissions = [("run_batchimportation", "Can run a batch importation")]
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=['created_date']),
|
||||||
|
models.Index(fields=['status']),
|
||||||
|
models.Index(fields=['created_date', 'recurrentImport']),
|
||||||
|
]
|
||||||
|
|
||||||
created_date = models.DateTimeField(auto_now_add=True)
|
created_date = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
@ -56,9 +56,10 @@ INSTALLED_APPS = [
|
|||||||
"robots",
|
"robots",
|
||||||
"debug_toolbar",
|
"debug_toolbar",
|
||||||
"cache_cleaner",
|
"cache_cleaner",
|
||||||
|
"honeypot",
|
||||||
]
|
]
|
||||||
|
|
||||||
SITE_ID = 1
|
HONEYPOT_FIELD_NAME = "alias_name"
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
"django.middleware.security.SecurityMiddleware",
|
"django.middleware.security.SecurityMiddleware",
|
||||||
@ -72,6 +73,7 @@ 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",
|
||||||
@ -145,7 +147,7 @@ TIME_ZONE = "Europe/Paris"
|
|||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
USE_TZ = True
|
USE_TZ = False
|
||||||
|
|
||||||
LANGUAGES = (
|
LANGUAGES = (
|
||||||
("fr", _("French")),
|
("fr", _("French")),
|
||||||
|
13
src/agenda_culturel/sitemaps.py
Normal file
13
src/agenda_culturel/sitemaps.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
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)
|
Binary file not shown.
Before Width: | Height: | Size: 237 KiB |
@ -34,11 +34,17 @@ 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");
|
||||||
|
673
src/agenda_culturel/static/location_field/js/form.js
Normal file
673
src/agenda_culturel/static/location_field/js/form.js
Normal file
@ -0,0 +1,673 @@
|
|||||||
|
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* Leaflet’s 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 don’t 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 );
|
||||||
|
}
|
||||||
|
}
|
@ -44,6 +44,9 @@ $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%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -329,6 +332,7 @@ footer [data-tooltip] {
|
|||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
transition-duration: 200ms;
|
transition-duration: 200ms;
|
||||||
|
|
||||||
|
|
||||||
.cat {
|
.cat {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
@ -1434,6 +1438,17 @@ 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;
|
||||||
@ -1454,6 +1469,11 @@ form.messages div, form.moderation-events {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#moderate-form #id_status {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
label.required::after {
|
label.required::after {
|
||||||
content: ' *';
|
content: ' *';
|
||||||
color: red;
|
color: red;
|
||||||
@ -1502,3 +1522,228 @@ label.required::after {
|
|||||||
.maskable_group .body_group.closed {
|
.maskable_group .body_group.closed {
|
||||||
display: none;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,13 +17,53 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="grid two-columns">
|
<div class="grid two-columns">
|
||||||
<div id="contenu-principal">
|
<div id="contenu-principal">
|
||||||
|
<div>
|
||||||
|
|
||||||
<article>
|
|
||||||
<header>
|
<article>
|
||||||
<div class="slide-buttons">
|
<header>
|
||||||
<a href="{% url 'moderate' %}" role="button">Modérer {% picto_from_name "check-square" %}</a>
|
<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>
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
<h2>Activité des derniers jours</h2>
|
<h2>Activité des derniers jours</h2>
|
||||||
</header>
|
</header>
|
||||||
<h3>Résumé des activités</h3>
|
<h3>Résumé des activités</h3>
|
||||||
@ -36,7 +76,8 @@
|
|||||||
{% 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>
|
||||||
<div class="slide-buttons">
|
<div class="slide-buttons">
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
{% 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 ? 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 %}
|
@ -12,7 +12,7 @@
|
|||||||
{% 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">créer une copie locale {% picto_from_name "plus-circle" %}</a>
|
<a href="{% url 'clone_edit' event.id %}" role="button">modifier en copie locale {% picto_from_name "plus-circle" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<p class="footer">Création : {{ event.created_date }}
|
<p class="footer">Création : {{ event.created_date }}{% if event.created_by_user %} par <em>{{ event.created_by_user.username }}</em>{% endif %}
|
||||||
{% if event.modified %}
|
{% if event.modified %}
|
||||||
— dernière modification : {{ event.modified_date }}
|
— dernière modification : {{ event.modified_date }}{% if event.modified_by_user %} par <em>{{ event.modified_by_user.username }}</em>{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if event.imported_date %}
|
{% if event.imported_date %}
|
||||||
— dernière importation : {{ event.imported_date }}
|
— dernière importation : {{ event.imported_date }}{% if event.imported_by_user %} par <em>{{ event.imported_by_user.username }}</em>{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if event.moderated_date %}
|
{% if event.moderated_date %}
|
||||||
— dernière modération : {{ event.moderated_date }}
|
— dernière modération : {{ event.moderated_date }}{% if event.moderated_by_user %} par <em>{{ event.moderated_by_user.username }}</em>{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if event.pure_import %}
|
{% if event.pure_import %}
|
||||||
— <strong>version importée</strong>
|
— <strong>version importée</strong>
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
<footer class="remarque">
|
<footer class="remarque">
|
||||||
Informations complémentaires non éditables :
|
<strong>Informations complémentaires non éditables</strong>
|
||||||
<ul>
|
<ul>
|
||||||
{% if object.created_date %}<li>Création : {{ object.created_date }}</li>{% endif %}
|
{% if not allbutdates %}
|
||||||
{% if object.modified_date %}<li>Dernière modification : {{ object.modified_date }}</li>{% endif %}
|
{% if object.created_date %}<li>Création : {{ object.created_date }}{% if object.created_by_user %} par <em>{{ object.created_by_user.username }}</em>{% endif %}</li>{% endif %}
|
||||||
{% if object.moderated_date %}<li>Dernière modération : {{ object.moderated_date }}</li>{% endif %}
|
{% if object.modified_date %}<li>Dernière modification : {{ object.modified_date }}{% if object.modified_by_user %} par <em>{{ object.modified_by_user.username }}</em>{% endif %}</li>{% endif %}
|
||||||
{% if object.imported_date %}<li>Dernière importation : {{ object.imported_date }}</li>{% endif %}
|
{% if object.moderated_date %}<li>Dernière modération : {{ 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 : {{ 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) :
|
<li>UUIDs (identifiants uniques d'événements dans les sources) :
|
||||||
|
@ -81,7 +81,7 @@ Duplication de {% else %}
|
|||||||
{{ form }}
|
{{ form }}
|
||||||
<div class="grid buttons stick-bottom">
|
<div class="grid buttons stick-bottom">
|
||||||
<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">
|
<input type="submit" value="Enregistrer{% if form.is_clone_from_url %} et modérer{% endif %}">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@ -33,31 +33,39 @@
|
|||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<form method="post" enctype="multipart/form-data">{% csrf_token %}
|
<form method="post" enctype="multipart/form-data" id="moderate-form">{% csrf_token %}
|
||||||
|
|
||||||
<div class="grid moderate-preview">
|
<div class="grid moderate-preview">
|
||||||
|
|
||||||
{% include "agenda_culturel/single-event/event-single-inc.html" with event=event noedit=1 %}
|
<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>
|
<article>
|
||||||
<header>
|
<header>
|
||||||
<div class="slide-buttons">
|
|
||||||
{% with event.get_local_version as local %}
|
|
||||||
{% if local %}
|
|
||||||
{% if event != local %}
|
|
||||||
<input type="submit" value="Enregistrer et éditer la version locale" name="save_and_edit_local">
|
|
||||||
{% else %}
|
|
||||||
<input type="submit" value="Enregistrer et éditer" name="save_and_edit">
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<input type="submit" value="Enregistrer et créer une version locale" name="save_and_create_local">
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>Modification des méta-informations</h2>
|
<h2>Modification des méta-informations</h2>
|
||||||
{% if event.moderated_date %}
|
{% if event.moderated_date %}
|
||||||
<p class="message info">Cet événement a déjà été modéré par le {{ 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
|
Vous pouvez bien sûr modifier de nouveau ces méta-informations en utilisant
|
||||||
le formulaire ci-après.
|
le formulaire ci-après.
|
||||||
</p>
|
</p>
|
||||||
@ -69,23 +77,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="grid buttons">
|
<div class="grid buttons">
|
||||||
{% if pred %}
|
{% if pred %}
|
||||||
<a href="{% url 'moderate_event' pred %}" role="button">🠄 Revenir au précédent</a>
|
<a href="{% url 'moderate_event' pred %}" class="secondary" role="button">< Revenir au précédent</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<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>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<input type="submit" value="Enregistrer" name="save">
|
<input type="submit" value="Enregistrer" name="save">
|
||||||
{% with event.get_local_version as local %}
|
<input type="submit" value="Enregistrer et passer au suivant >" name="save_and_next">
|
||||||
{% if local %}
|
<a href="{% url 'moderate_event_next' event.pk %}" class="secondary" role="button">Passer au suivant sans enregistrer ></a>
|
||||||
{% if local == event %}
|
|
||||||
<input type="submit" value="Enregistrer et éditer la version locale" name="save_and_edit_local">
|
|
||||||
{% else %}
|
|
||||||
<input type="submit" value="Enregistrer et éditer" name="save_and_edit">
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<input type="submit" value="Enregistrer et créer une version locale" name="save_and_create_local">
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
<input type="submit" value="Enregistrer et passer au suivant 🠆" name="save_and_next">
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</article>
|
</article>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{% extends "agenda_culturel/page-admin.html" %}
|
{% extends "agenda_culturel/page-admin.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
{% load honeypot %}
|
||||||
|
|
||||||
{% block title %}{% block og_title %}{% if form.event %}Contact au sujet de l'événement {{ form.event.title }}{% else %}
|
{% block title %}{% block og_title %}{% if form.event %}Contact au sujet de l'événement {{ form.event.title }}{% else %}
|
||||||
Contact{% endif %}{% endblock %}{% endblock %}
|
Contact{% endif %}{% endblock %}{% endblock %}
|
||||||
@ -31,7 +32,7 @@ Contact{% endif %}{% endblock %}{% endblock %}
|
|||||||
</article>
|
</article>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<p class="message warning"><strong>Attention :</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>
|
<p class="message warning"><strong>Attention :</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 %}
|
{% if form.event %}
|
||||||
<p>Tu nous contactes au sujet de l'événement « {{ form.event.title }} » du {{ form.event.start_day }}.
|
<p>Tu nous contactes au sujet de l'événement « {{ form.event.title }} » du {{ form.event.start_day }}.
|
||||||
N'hésites pas à nous indiquer le maximum de contexte et à nous laisser ton adresse
|
N'hésites pas à nous indiquer le maximum de contexte et à nous laisser ton adresse
|
||||||
@ -40,6 +41,7 @@ Contact{% endif %}{% endblock %}{% endblock %}
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</header>
|
</header>
|
||||||
<form method="post">{% csrf_token %}
|
<form method="post">{% csrf_token %}
|
||||||
|
{% render_honeypot_field "alias_name" %}
|
||||||
{{ form.media }}
|
{{ form.media }}
|
||||||
{{ form.as_p }}
|
{{ form.as_p }}
|
||||||
<input type="submit" value="Envoyer">
|
<input type="submit" value="Envoyer">
|
@ -24,13 +24,13 @@
|
|||||||
<article>
|
<article>
|
||||||
<header>
|
<header>
|
||||||
<div class="slide-buttons">
|
<div class="slide-buttons">
|
||||||
<a href="{% url 'delete_contactmessage' object.id %}" role="button" data-tooltip="Supprimer le message">Supprimer {% picto_from_name "trash-2" %}</a>
|
<a href="{% url 'delete_message' object.id %}" role="button" data-tooltip="Supprimer le message">Supprimer {% picto_from_name "trash-2" %}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1>Modération du message « {{ object.subject }} »</h1>
|
<h1>Modération du message « {{ object.subject }} »</h1>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Date : {{ object.date.date }} à {{ object.date.time }}</li>
|
<li>Date : {{ object.date.date }} à {{ object.date.time }}</li>
|
||||||
<li>Auteur : {{ object.name }} {% if object.email %}<a href="mailto:{{ object.email }}">{{ object.email }}</a>{% endif %}</li>
|
<li>Auteur : {% if object.user %}<em>{{ object.user }}</em>{% else %}{{ object.name }}{% endif %} {% if object.email %}<a href="mailto:{{ object.email }}">{{ object.email }}</a>{% endif %}</li>
|
||||||
{% if object.related_event %}<li>Événement associé : <a href="{{ object.related_event.get_absolute_url }}">{{ object.related_event.title }}</a> du {{ object.related_event.start_day }}</li>{% endif %}
|
{% if object.related_event %}<li>Événement associé : <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>
|
||||||
@ -47,7 +47,7 @@
|
|||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% include "agenda_culturel/side-nav.html" with current="contactmessages" %}
|
{% include "agenda_culturel/side-nav.html" with current="messages" %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -45,8 +45,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 'contactmessage' obj.pk %}">{{ obj.subject }}</a></td>
|
<td><a href="{% url 'message' obj.pk %}">{{ obj.subject }}</a></td>
|
||||||
<td>{{ obj.name }}</td>
|
<td>{% if obj.user %}<em>{{ obj.user }}</em>{% else %}{{ obj.name }}{% endif %}</td>
|
||||||
<td>{% if obj.related_event %}<a href="{{ obj.related_event.get_absolute_url }}">{{ obj.related_event.pk }}</a>{% else %}/{% endif %}</td>
|
<td>{% 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>
|
||||||
@ -59,7 +59,7 @@
|
|||||||
</footer>
|
</footer>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
{% include "agenda_culturel/side-nav.html" with current="contactmessages" %}
|
{% include "agenda_culturel/side-nav.html" with current="messages" %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -35,12 +35,8 @@
|
|||||||
const places = document.querySelector('#id_principal_place');
|
const places = document.querySelector('#id_principal_place');
|
||||||
const choices_places = new Choices(places,
|
const choices_places = new Choices(places,
|
||||||
{
|
{
|
||||||
placeholderValue: 'Sélectionner le lieu principal ',
|
|
||||||
allowHTML: true,
|
|
||||||
delimiter: ',',
|
|
||||||
removeItemButton: true,
|
|
||||||
shouldSort: false,
|
shouldSort: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -3,10 +3,11 @@
|
|||||||
{% 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_url }}{% else %}{{ block.super }}{% endif %}{% endblock %}
|
{% block og_image %}{% if event.has_image_url %}{{ event|get_image_uri:request }}{% 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 %}
|
||||||
@ -16,21 +17,59 @@
|
|||||||
|
|
||||||
{% 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>
|
<article id="chronology">
|
||||||
<header>
|
<header>
|
||||||
<h2>Informations internes</h2>
|
<h2>Chronologie</h2>
|
||||||
</header>
|
</header>
|
||||||
{% include "agenda_culturel/event-info-inc.html" with object=event %}
|
{% for step in event.chronology %}
|
||||||
|
{% if step.is_date %}
|
||||||
|
<div class="entree dateline">
|
||||||
|
<div><span class="ts">{{ step.timestamp }}</span></div>
|
||||||
|
<div>
|
||||||
|
{% if step.data == "created_date" %}<em>création</em>{% if event.created_by_user %} par {{ event.created_by_user.username }}{% endif %}{% endif %}
|
||||||
|
{% if step.data == "modified_date" %}<em>dernière modification</em>{% if event.modified_by_user %} par {{ event.modified_by_user.username }}{% endif %}{% endif %}
|
||||||
|
{% if step.data == "moderated_date" %}<em>dernière modération</em>{% if event.moderated_by_user %} par {{ event.moderated_by_user.username }}{% endif %}{% endif %}
|
||||||
|
{% if step.data == "imported_date" %}<em>dernière importation</em>{% if event.imported_by_user %} par {{ event.imported_by_user.username }}{% endif %}{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="entree">
|
||||||
|
<div><span class="ts">{{ step.timestamp }}</span></div>
|
||||||
|
<div>
|
||||||
|
<header><strong>Message</strong>{% if step.data.related_event and event != step.data.related_event %} sur <a href="{{ step.data.related_event.get_absolute_url }}">une autre</a> version{% endif %} : <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 :</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 %}
|
||||||
@ -51,39 +90,7 @@
|
|||||||
</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 %}
|
||||||
@ -127,8 +134,10 @@
|
|||||||
|
|
||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
|
{% endcache %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -84,7 +84,7 @@
|
|||||||
</script>
|
</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<header{% if day.is_today %} id="today"{% endif %}>
|
<header{% if day.is_today %} id="today"{% endif %}>
|
||||||
<h3><a href="{{ day.date | url_day }}?{{ filter.get_url }}">{{ day.date | date:"l j" }}</a></h3}>
|
<h3><a href="{{ day.date | url_day }}?{{ filter.get_url }}" class="visible-link">{{ day.date | date:"l j" }}</a></h3}>
|
||||||
</header>
|
</header>
|
||||||
{% if day.events %}
|
{% if day.events %}
|
||||||
<ul>
|
<ul>
|
||||||
@ -121,7 +121,8 @@
|
|||||||
</article>
|
</article>
|
||||||
</dialog>
|
</dialog>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<ul>
|
<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 %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</article>
|
</article>
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
<li><strong>Valeurs par défaut :</strong>
|
<li><strong>Valeurs par défaut :</strong>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Publié :</strong> {{ object.defaultPublished|yesno:"Oui,Non" }}</li>
|
<li><strong>Publié :</strong> {{ object.defaultPublished|yesno:"Oui,Non" }}</li>
|
||||||
{% if object.defaultLocation %}<li><strong>Localisation :</strong> {{ object.defaultLocation }}</li>{% endif %}
|
{% if object.defaultLocation %}<li><strong>Localisation{% if object.forceLocation %} (forcée){% endif %} :</strong> {{ object.defaultLocation }}</li>{% endif %}
|
||||||
<li><strong>Catégorie :</strong> {{ object.defaultCategory }}</li>
|
<li><strong>Catégorie :</strong> {{ object.defaultCategory }}</li>
|
||||||
{% if object.defaultOrganiser %}<li><strong>Organisateur :</strong> <a href="{{ object.defaultOrganiser.get_absolute_url }}">{{ object.defaultOrganiser }}</a></li>{% endif %}
|
{% if object.defaultOrganiser %}<li><strong>Organisateur :</strong> <a href="{{ object.defaultOrganiser.get_absolute_url }}">{{ object.defaultOrganiser }}</a></li>{% endif %}
|
||||||
<li><strong>Étiquettes :</strong>
|
<li><strong>Étiquettes :</strong>
|
||||||
|
@ -95,6 +95,9 @@
|
|||||||
<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 }}
|
||||||
@ -102,6 +105,9 @@
|
|||||||
{{ 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|no_emoji }}</a> {{ event|tw_badge }}
|
||||||
</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 %}
|
||||||
|
@ -37,7 +37,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.year calendar.previous_week|week %}?{{ filter.get_url }}">
|
<a role="button" href="{% url 'week_view' calendar.previous_week|weekyear 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 +45,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.year calendar.next_week|week %}?{{ filter.get_url }}">suivante
|
<a role="button" href="{% url 'week_view' calendar.next_week|weekyear calendar.next_week|week %}?{{ filter.get_url }}">suivante
|
||||||
{% picto_from_name "chevron-right" %}
|
{% picto_from_name "chevron-right" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -57,7 +57,7 @@
|
|||||||
<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.year 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|weekyear calendar.previous_week|week %}?{{ filter.get_url }}">{% picto_from_name "chevrons-left" %}</a></div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@ -80,11 +80,14 @@
|
|||||||
</script>
|
</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<header{% if day.is_today %} id="today"{% endif %}>
|
<header{% if day.is_today %} id="today"{% endif %}>
|
||||||
<h2><a href="{{ day.date | url_day }}?{{ filter.get_url }}">{{ day.date | date:"l j" }}</a></h2>
|
<h2><a class="visible-link" href="{{ day.date | url_day }}?{{ filter.get_url }}">{{ day.date | date:"l j" }}</a></h2>
|
||||||
</header>
|
</header>
|
||||||
{% if day.events %}
|
{% if day.events %}
|
||||||
<ul>
|
<ul>
|
||||||
{% for event in day.events %}
|
{% 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 }}
|
<li>{{ event.category | circle_cat:event.has_recurrences }}
|
||||||
{% if event.start_day == day.date and event.start_time %}
|
{% if event.start_day == day.date and event.start_time %}
|
||||||
{{ event.start_time }}
|
{{ event.start_time }}
|
||||||
@ -93,7 +96,12 @@
|
|||||||
{{ event|tw_badge }}
|
{{ event|tw_badge }}
|
||||||
<dialog id="event-{{ event.id }}">
|
<dialog id="event-{{ event.id }}">
|
||||||
<article>
|
<article>
|
||||||
<header>
|
{% 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 }}"
|
<a href="#event-{{ event.id }}"
|
||||||
aria-label="Fermer"
|
aria-label="Fermer"
|
||||||
class="close"
|
class="close"
|
||||||
@ -125,6 +133,7 @@
|
|||||||
{% 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 %}
|
{% 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>
|
</p>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="body-fixed">{{ event.description |linebreaks }}</div>
|
<div class="body-fixed">{{ event.description |linebreaks }}</div>
|
||||||
@ -147,6 +156,10 @@
|
|||||||
</dialog>
|
</dialog>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% 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>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
@ -160,7 +173,7 @@
|
|||||||
<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.year 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|weekyear calendar.next_week|week %}?{{ filter.get_url }}">{% picto_from_name "chevrons-right" %}</a></div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
<!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">
|
||||||
@ -9,7 +15,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 %}{% static 'images/capture.png' %}{% endblock %}" />
|
<meta property="og:image" content="{% block og_image %}https://{{ request.get_host }}{% get_media_prefix %}screenshot.png{% endblock %}" />
|
||||||
<meta property="og:url" content="{{ request.build_absolute_uri }}" />
|
<meta property="og:url" content="{{ request.build_absolute_uri }}" />
|
||||||
|
|
||||||
{% if debug %}
|
{% if debug %}
|
||||||
@ -27,12 +33,7 @@
|
|||||||
{% 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>
|
||||||
@ -50,7 +51,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 %}">À venir</a></li>
|
<li><a href="{% url 'a_venir' %}{% block a_venir_parameters %}{% endblock %}">Maintenant</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>
|
||||||
@ -76,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_contactmessage %}
|
{% if perms.agenda_culturel.view_message %}
|
||||||
{% show_badge_contactmessages "bottom" %}
|
{% show_badge_messages "bottom" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
{{ user.username }} @
|
{{ user.username }} @
|
||||||
|
@ -22,9 +22,26 @@
|
|||||||
<article>
|
<article>
|
||||||
{% if event %}
|
{% if event %}
|
||||||
<p>Création d'un lieu depuis l'événement « {{ event }} » (voir en bas de page le détail de l'événement).</p>
|
<p>Création d'un lieu depuis l'événement « {{ event }} » (voir en bas de page le détail de l'événement).</p>
|
||||||
|
<p><strong>Remarque :</strong> les champs ont été pré-remplis à partir de la description sous forme libre et n'est probablement pas parfaite.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form method="post">{% csrf_token %}
|
<form method="post">{% csrf_token %}
|
||||||
{{ form.as_grid }}
|
<div class="grid form-place">
|
||||||
|
{{ form }}
|
||||||
|
<div class="map-widget">
|
||||||
|
<div id="map_location" style="width: 100%; aspect-ratio: 16/9"></div>
|
||||||
|
<p>Cliquez pour ajuster la position GPS</p>
|
||||||
|
<input type="checkbox" role="switch" id="lock_position">Verrouiller la position</input>
|
||||||
|
<script>
|
||||||
|
document.getElementById("lock_position").onclick = function() {
|
||||||
|
const field = document.getElementById("id_location");
|
||||||
|
if (this.checked)
|
||||||
|
field.setAttribute("readonly", true);
|
||||||
|
else
|
||||||
|
field.removeAttribute("readonly");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="grid buttons">
|
<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="Envoyer">
|
<input type="submit" value="Envoyer">
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{% load event_extra %}
|
{% load event_extra %}
|
||||||
{% load contactmessages_extra %}
|
{% load messages_extra %}
|
||||||
{% load duplicated_extra %}
|
{% load duplicated_extra %}
|
||||||
{% load utils_extra %}
|
{% load utils_extra %}
|
||||||
<aside id="sidebar">
|
<aside id="sidebar">
|
||||||
@ -56,11 +56,11 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.agenda_culturel.view_contactmessage %}
|
{% if perms.agenda_culturel.view_message %}
|
||||||
<h3>Messages</h3>
|
<h3>Messages</h3>
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a {% if current == "contactmessages" %}class="selected" {% endif %}href="{% url 'contactmessages' %}">Messages de contact</a>{% show_badge_contactmessages "left" %}</li>
|
<li><a {% if current == "messages" %}class="selected" {% endif %}href="{% url 'messages' %}">Messages de contact</a>{% show_badge_messages "left" %}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -68,6 +68,7 @@
|
|||||||
<h3>Configuration interne</h3>
|
<h3>Configuration interne</h3>
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li><a href="{% url 'clear_cache' %}">Vider le cache</a></li>
|
||||||
<li><a href="{% url 'admin:index' %}">Administration de django</a></li>
|
<li><a href="{% url 'admin:index' %}">Administration de django</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -6,7 +6,11 @@
|
|||||||
|
|
||||||
<article id="event-{{ event.pk}}" class="single-event {% if not event.image and not event.local_image %}no-image{% endif %}">
|
<article id="event-{{ event.pk}}" class="single-event {% if not event.image and not event.local_image %}no-image{% endif %}">
|
||||||
<header class="head">
|
<header class="head">
|
||||||
{% if day != 0 %}
|
{% if day == 0 %}
|
||||||
|
<div class="small-ephemeride">
|
||||||
|
{% include "agenda_culturel/ephemeris-inc.html" with event=event filter=filter %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
{% if event|can_show_start_time:day %}
|
{% if event|can_show_start_time:day %}
|
||||||
{% if event.start_time %}
|
{% if event.start_time %}
|
||||||
<article class='ephemeris-hour'>
|
<article class='ephemeris-hour'>
|
||||||
@ -22,7 +26,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<div class="header-complement">
|
||||||
{{ event.category | small_cat_recurrent:event.has_recurrences }}
|
{{ event.category | small_cat_recurrent:event.has_recurrences }}
|
||||||
|
|
||||||
{% if event.location or event.exact_location %}<hgroup>{% endif %}
|
{% if event.location or event.exact_location %}<hgroup>{% endif %}
|
||||||
@ -50,11 +54,7 @@
|
|||||||
</hgroup>
|
</hgroup>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if day == 0 %}
|
|
||||||
<div class="small-ephemeride">
|
|
||||||
{% include "agenda_culturel/ephemeris-inc.html" with event=event filter=filter %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if event|need_complete_display:True %}<p>
|
{% if event|need_complete_display:True %}<p>
|
||||||
{% picto_from_name "calendar" %}
|
{% picto_from_name "calendar" %}
|
||||||
@ -86,12 +86,13 @@
|
|||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="buttons" style="clear: both">
|
<div class="buttons" style="clear: both">
|
||||||
{% if perms.agenda_culturel.change_event %}
|
{% if perms.agenda_culturel.change_event %}
|
||||||
{% include "agenda_culturel/edit-buttons-inc.html" with event=event %}
|
{% include "agenda_culturel/edit-buttons-inc.html" with event=event %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="description">
|
<div class="description">
|
||||||
|
@ -22,16 +22,17 @@
|
|||||||
{% 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 %}
|
{% 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>
|
</p>
|
||||||
<p>
|
|
||||||
{% picto_from_name "map-pin" %}
|
|
||||||
|
|
||||||
{% if event.exact_location %}
|
{% if event.exact_location %}
|
||||||
<a href="{{ event.exact_location.get_absolute_url }}">{{ event.exact_location.name }}, {{ event.exact_location.city }}</a>
|
<p>{% picto_from_name "map-pin" %}
|
||||||
|
<a href="{{ event.exact_location.get_absolute_url }}">{{ event.exact_location.name }}, {{ event.exact_location.city }}</a></p>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if perms.agenda_culturel.change_event and perms.agenda_culturel.change_place %}
|
{% 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>
|
<p>{% picto_from_name "map-pin" %}
|
||||||
|
<a href="{% url 'add_place_to_event' event.pk %}" class="missing-data">{% if event.location %}{{ event.location }}{% else %}sans lieu{% endif %}</a></p>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ event.location }}
|
{% if event.location %}<p>{% picto_from_name "map-pin" %} {{ event.location }}</p>{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
@ -75,10 +76,10 @@
|
|||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.agenda_culturel.change_contactmessage %}
|
{% if perms.agenda_culturel.change_message %}
|
||||||
{% if event.contactmessage_set.all.count > 0 %}
|
{% if event.message_set.all.count > 0 %}
|
||||||
<p class="remarque">Cet événement a fait l'objet {% if event.contactmessage_set.all.count == 1 %}d'un signalement{% else %}de signalements{% endif %}
|
<p class="remarque">Cet événement a fait l'objet {% if event.message_set.all.count == 1 %}d'un signalement{% else %}de signalements{% endif %}
|
||||||
{% for cm in event.contactmessage_set.all %}
|
{% for cm in event.message_set.all %}
|
||||||
<a href="{{ cm.get_absolute_url }}">le {{ cm.date.date }} à {{ cm.date.time }}</a>{% if not forloop.last %}, {% endif %}
|
<a href="{{ cm.get_absolute_url }}">le {{ cm.date.date }} à {{ cm.date.time }}</a>{% if not forloop.last %}, {% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</p>
|
</p>
|
||||||
@ -133,9 +134,24 @@
|
|||||||
{% include "agenda_culturel/event-date-info-inc.html" %}
|
{% include "agenda_culturel/event-date-info-inc.html" %}
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<a href="{% url 'export_event_ical' event.start_day.year event.start_day.month event.start_day.day event.id %}" role="button">Exporter ical {% picto_from_name "calendar" %}</a>
|
{% if onlyedit %}
|
||||||
{% if perms.agenda_culturel.change_event and not noedit %}
|
{% if event.pure_import %}
|
||||||
{% include "agenda_culturel/edit-buttons-inc.html" with event=event with_clone=1 %}
|
{% with event.get_local_version as local %}
|
||||||
|
{% if local %}
|
||||||
|
<a href="{{ local.get_absolute_url }}" role="button">voir la version locale {% picto_from_name "eye" %}</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'clone_edit' event.id %}" role="button">créer une copie locale {% picto_from_name "plus-circle" %}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'edit_event' event.id %}" role="button">modifier {% picto_from_name "edit-3" %}</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'export_event_ical' event.start_day.year event.start_day.month event.start_day.day event.id %}" role="button">Exporter ical {% picto_from_name "calendar" %}</a>
|
||||||
|
{% if perms.agenda_culturel.change_event and not noedit %}
|
||||||
|
{% include "agenda_culturel/edit-buttons-inc.html" with event=event with_clone=1 %}
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
@ -86,12 +86,6 @@ def css_categories():
|
|||||||
)
|
)
|
||||||
result += "}"
|
result += "}"
|
||||||
|
|
||||||
result += "*:hover>." + c["css_class"] + " {"
|
|
||||||
result += background_color_adjust_color(
|
|
||||||
adjust_lightness_saturation(c["color"], 0.02, 1.0)
|
|
||||||
)
|
|
||||||
result += "}"
|
|
||||||
|
|
||||||
result += "." + c["css_class"] + ".circ-cat, "
|
result += "." + c["css_class"] + ".circ-cat, "
|
||||||
result += "form ." + c["css_class"] + ", "
|
result += "form ." + c["css_class"] + ", "
|
||||||
result += ".selected ." + c["css_class"] + " {"
|
result += ".selected ." + c["css_class"] + " {"
|
||||||
|
@ -179,4 +179,8 @@ def tw_badge(event):
|
|||||||
if event.tags and len([t for t in event.tags if t.startswith("TW:")]) > 0:
|
if event.tags and len([t for t in event.tags if t.startswith("TW:")]) > 0:
|
||||||
return mark_safe('<span class="badge tw-badge">TW</span>')
|
return mark_safe('<span class="badge tw-badge">TW</span>')
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def get_image_uri(event, request):
|
||||||
|
return event.get_image_url(request)
|
@ -4,7 +4,7 @@ from django.urls import reverse_lazy
|
|||||||
from django.template.defaultfilters import pluralize
|
from django.template.defaultfilters import pluralize
|
||||||
|
|
||||||
|
|
||||||
from agenda_culturel.models import ContactMessage
|
from agenda_culturel.models import Message
|
||||||
|
|
||||||
from .utils_extra import picto_from_name
|
from .utils_extra import picto_from_name
|
||||||
|
|
||||||
@ -12,15 +12,15 @@ register = template.Library()
|
|||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def show_badge_contactmessages(placement="top"):
|
def show_badge_messages(placement="top"):
|
||||||
nb_open = ContactMessage.nb_open_contactmessages()
|
nb_open = Message.nb_open_messages()
|
||||||
if nb_open != 0:
|
if nb_open != 0:
|
||||||
return mark_safe(
|
return mark_safe(
|
||||||
'<a href="'
|
'<a href="'
|
||||||
+ reverse_lazy("contactmessages")
|
+ reverse_lazy("messages")
|
||||||
+ '?closed=False" class="badge" data-placement="'
|
+ '?closed=False" class="badge" data-placement="'
|
||||||
+ placement
|
+ placement
|
||||||
+ '"data-tooltip="'
|
+ '" data-tooltip="'
|
||||||
+ str(nb_open)
|
+ str(nb_open)
|
||||||
+ " message"
|
+ " message"
|
||||||
+ pluralize(nb_open)
|
+ pluralize(nb_open)
|
@ -29,6 +29,9 @@ def add_de(txt):
|
|||||||
def week(d):
|
def week(d):
|
||||||
return d.isocalendar()[1]
|
return d.isocalendar()[1]
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def weekyear(d):
|
||||||
|
return d.isocalendar()[0]
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def not_before_first(d):
|
def not_before_first(d):
|
||||||
|
@ -4,10 +4,32 @@ from django.contrib import admin
|
|||||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||||
from django.urls import path, include, re_path
|
from django.urls import path, include, re_path
|
||||||
from django.views.i18n import JavaScriptCatalog
|
from django.views.i18n import JavaScriptCatalog
|
||||||
|
from django.contrib.sitemaps.views import sitemap
|
||||||
|
from django.contrib.sitemaps import GenericSitemap
|
||||||
|
from .sitemaps import StaticViewSitemap
|
||||||
|
from django.views.decorators.cache import cache_page
|
||||||
|
|
||||||
from .views import *
|
from .views import *
|
||||||
|
|
||||||
|
event_dict = {
|
||||||
|
"queryset": Event.objects.all(),
|
||||||
|
"date_field": "modified_date",
|
||||||
|
}
|
||||||
|
place_dict = {
|
||||||
|
"queryset": Place.objects.all(),
|
||||||
|
}
|
||||||
|
organisation_dict = {
|
||||||
|
"queryset": Organisation.objects.all(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sitemaps = {
|
||||||
|
"static": StaticViewSitemap,
|
||||||
|
"events": GenericSitemap(event_dict, priority=0.7),
|
||||||
|
"places": GenericSitemap(place_dict, priority=0.6),
|
||||||
|
"organisations": GenericSitemap(organisation_dict, priority=0.2),
|
||||||
|
}
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", home, name="home"),
|
path("", home, name="home"),
|
||||||
path("semaine/<int:year>/<int:week>/", week_view, name="week_view"),
|
path("semaine/<int:year>/<int:week>/", week_view, name="week_view"),
|
||||||
@ -35,12 +57,13 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path("event/<int:pk>/edit", EventUpdateView.as_view(), name="edit_event"),
|
path("event/<int:pk>/edit", EventUpdateView.as_view(), name="edit_event"),
|
||||||
path("event/<int:pk>/moderate", EventModerateView.as_view(), name="moderate_event"),
|
path("event/<int:pk>/moderate", EventModerateView.as_view(), name="moderate_event"),
|
||||||
path("event/<int:pk>/moderate-next", EventModerateView.as_view(), name="moderate_event_next"),
|
path("event/<int:pk>/moderate/after/<int:pred>", EventModerateView.as_view(), name="moderate_event_step"),
|
||||||
path("event/<int:pk>/moderate-next/error", error_next_event, name="error_next_event"),
|
path("event/<int:pk>/moderate-next", moderate_event_next, name="moderate_event_next"),
|
||||||
path("moderate", EventModerateView.as_view(), name="moderate"),
|
path("moderate", EventModerateView.as_view(), name="moderate"),
|
||||||
|
path("moderate/<int:y>/<int:m>/<int:d>", moderate_from_date, name="moderate_from_date"),
|
||||||
path("event/<int:pk>/simple-clone/edit", EventUpdateView.as_view(), name="simple_clone_edit"),
|
path("event/<int:pk>/simple-clone/edit", EventUpdateView.as_view(), name="simple_clone_edit"),
|
||||||
path("event/<int:pk>/clone/edit", EventUpdateView.as_view(), name="clone_edit"),
|
path("event/<int:pk>/clone/edit", EventUpdateView.as_view(), name="clone_edit"),
|
||||||
path("event/<int:pk>/message", ContactMessageCreateView.as_view(), name="message_for_event"),
|
path("event/<int:pk>/message", MessageCreateView.as_view(), name="message_for_event"),
|
||||||
path("event/<int:pk>/update-from-source", update_from_source, name="update_from_source"),
|
path("event/<int:pk>/update-from-source", update_from_source, name="update_from_source"),
|
||||||
path(
|
path(
|
||||||
"event/<int:pk>/change-status/<status>",
|
"event/<int:pk>/change-status/<status>",
|
||||||
@ -75,18 +98,18 @@ urlpatterns = [
|
|||||||
path("mentions-legales", mentions_legales, name="mentions_legales"),
|
path("mentions-legales", mentions_legales, name="mentions_legales"),
|
||||||
path("a-propos", about, name="about"),
|
path("a-propos", about, name="about"),
|
||||||
path("merci", thank_you, name="thank_you"),
|
path("merci", thank_you, name="thank_you"),
|
||||||
path("contact", ContactMessageCreateView.as_view(), name="contact"),
|
path("contact", MessageCreateView.as_view(), name="contact"),
|
||||||
path("contactmessages", contactmessages, name="contactmessages"),
|
path("messages", view_messages, name="messages"),
|
||||||
path("contactmessages/spams/delete", delete_cm_spam, name="delete_cm_spam"),
|
path("messages/spams/delete", delete_cm_spam, name="delete_cm_spam"),
|
||||||
path(
|
path(
|
||||||
"contactmessage/<int:pk>",
|
"message/<int:pk>",
|
||||||
ContactMessageUpdateView.as_view(),
|
MessageUpdateView.as_view(),
|
||||||
name="contactmessage",
|
name="message",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"contactmessage/<int:pk>/delete",
|
"message/<int:pk>/delete",
|
||||||
ContactMessageDeleteView.as_view(),
|
MessageDeleteView.as_view(),
|
||||||
name="delete_contactmessage",
|
name="delete_message",
|
||||||
),
|
),
|
||||||
path("imports/", imports, name="imports"),
|
path("imports/", imports, name="imports"),
|
||||||
path("imports/add", add_import, name="add_import"),
|
path("imports/add", add_import, name="add_import"),
|
||||||
@ -134,7 +157,8 @@ urlpatterns = [
|
|||||||
path("500/", internal_server_error, name="internal_server_error"),
|
path("500/", internal_server_error, name="internal_server_error"),
|
||||||
|
|
||||||
path("organisme/<int:pk>/past", OrganisationDetailViewPast.as_view(), name="view_organisation_past"),
|
path("organisme/<int:pk>/past", OrganisationDetailViewPast.as_view(), name="view_organisation_past"),
|
||||||
path("organisme/<int:pk>", OrganisationDetailView.as_view(), name="view_organisation"),
|
path("organisme/<int:pk>", OrganisationDetailView.as_view(), name="view_organisation_shortname"),
|
||||||
|
path("organisme/<int:pk>-<extra>", OrganisationDetailView.as_view(), name="view_organisation"),
|
||||||
path("organisme/<int:pk>-<extra>/past", OrganisationDetailViewPast.as_view(), name="view_organisation_past_fullname"),
|
path("organisme/<int:pk>-<extra>/past", OrganisationDetailViewPast.as_view(), name="view_organisation_past_fullname"),
|
||||||
path("organisme/<int:pk>-<extra>", OrganisationDetailView.as_view(), name="view_organisation_fullname"),
|
path("organisme/<int:pk>-<extra>", OrganisationDetailView.as_view(), name="view_organisation_fullname"),
|
||||||
path("organisme/<int:pk>/edit", OrganisationUpdateView.as_view(), name="edit_organisation"),
|
path("organisme/<int:pk>/edit", OrganisationUpdateView.as_view(), name="edit_organisation"),
|
||||||
@ -178,6 +202,14 @@ urlpatterns = [
|
|||||||
re_path(r'^robots\.txt', include('robots.urls')),
|
re_path(r'^robots\.txt', include('robots.urls')),
|
||||||
path("__debug__/", include("debug_toolbar.urls")),
|
path("__debug__/", include("debug_toolbar.urls")),
|
||||||
path("ckeditor5/", include('django_ckeditor_5.urls')),
|
path("ckeditor5/", include('django_ckeditor_5.urls')),
|
||||||
|
path(
|
||||||
|
"sitemap.xml",
|
||||||
|
cache_page(86400)(sitemap),
|
||||||
|
{"sitemaps": sitemaps},
|
||||||
|
name="django.contrib.sitemaps.views.sitemap",
|
||||||
|
),
|
||||||
|
path("cache/clear", clear_cache, name="clear_cache"),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
114
src/agenda_culturel/utils.py
Normal file
114
src/agenda_culturel/utils.py
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
from agenda_culturel.models import ReferenceLocation
|
||||||
|
import re
|
||||||
|
import unicodedata
|
||||||
|
|
||||||
|
|
||||||
|
class PlaceGuesser:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.__citynames = list(ReferenceLocation.objects.values_list("name__lower__unaccent", "name")) + [("clermont-fd", "Clermont-Ferrand"), ("aurillac", "Aurillac"), ("montlucon", "Montluçon"), ("montferrand", "Clermont-Ferrand")]
|
||||||
|
self.__citynames = [(x[0].replace("-", " "), x[1]) for x in self.__citynames]
|
||||||
|
|
||||||
|
def __remove_accents(self, input_str):
|
||||||
|
if input_str is None:
|
||||||
|
return None
|
||||||
|
nfkd_form = unicodedata.normalize("NFKD", input_str)
|
||||||
|
return "".join([c for c in nfkd_form if not unicodedata.combining(c)])
|
||||||
|
|
||||||
|
|
||||||
|
def __guess_is_address(self, part):
|
||||||
|
toponyms = ["bd", "rue", "avenue", "place", "boulevard", "allee", ]
|
||||||
|
part = part.strip()
|
||||||
|
if re.match(r'^[0-9]', part):
|
||||||
|
return True
|
||||||
|
|
||||||
|
elems = part.split(" ")
|
||||||
|
return any([self.__remove_accents(e.lower()) in toponyms for e in elems])
|
||||||
|
|
||||||
|
|
||||||
|
def __clean_address(self, addr):
|
||||||
|
toponyms = ["bd", "rue", "avenue", "place", "boulevard", "allée", "bis", "ter", "ZI"]
|
||||||
|
for t in toponyms:
|
||||||
|
addr = re.sub(" " + t + " ", " " + t + " ", addr, flags=re.IGNORECASE)
|
||||||
|
return addr
|
||||||
|
|
||||||
|
def __guess_city_name(self, part):
|
||||||
|
part = part.strip().replace(" - ", "-")
|
||||||
|
if len(part) == 0:
|
||||||
|
return None
|
||||||
|
part = self.__remove_accents(part.lower()).replace("-", " ")
|
||||||
|
match = [x[1] for x in self.__citynames if x[0] == part]
|
||||||
|
if len(match) > 0:
|
||||||
|
return match[0]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __guess_city_name_postcode(self, part):
|
||||||
|
with_pc = re.search(r'^(.*)(([0-9][ ]*){5})(.*)$', part)
|
||||||
|
if with_pc:
|
||||||
|
p1 = self.__guess_city_name(with_pc.group(1).strip())
|
||||||
|
postcode = with_pc.group(2).replace(" ", "")
|
||||||
|
p2 = self.__guess_city_name(with_pc.group(4).strip())
|
||||||
|
return postcode, p2, p1
|
||||||
|
else:
|
||||||
|
return None, self.__guess_city_name(part), None
|
||||||
|
|
||||||
|
def __guess_name_address(self, part):
|
||||||
|
with_num = re.search(r'^(([^0-9])+)([0-9]+)(.*)', part)
|
||||||
|
if with_num:
|
||||||
|
name = with_num.group(1)
|
||||||
|
return name, part[len(name):]
|
||||||
|
else:
|
||||||
|
return "", part
|
||||||
|
|
||||||
|
def guess_address_elements(self, alias):
|
||||||
|
parts = re.split(r'[,/à]', alias)
|
||||||
|
parts = [p1 for p1 in [p.strip() for p in parts] if p1 != "" and p1.lower() != "france"]
|
||||||
|
|
||||||
|
name = ""
|
||||||
|
address = ""
|
||||||
|
postcode = ""
|
||||||
|
city = ""
|
||||||
|
possible_city = ""
|
||||||
|
|
||||||
|
oparts = []
|
||||||
|
for part in parts:
|
||||||
|
p, c, possible_c = self.__guess_city_name_postcode(part)
|
||||||
|
if not possible_c is None:
|
||||||
|
possible_city = possible_c
|
||||||
|
if not c is None and city == "":
|
||||||
|
city = c
|
||||||
|
if not p is None and postcode == "":
|
||||||
|
postcode = p
|
||||||
|
if p is None and c is None:
|
||||||
|
oparts.append(part)
|
||||||
|
|
||||||
|
if city == "" and possible_city != "":
|
||||||
|
city = possible_city
|
||||||
|
else:
|
||||||
|
if len(oparts) == 0 and not possible_city != "":
|
||||||
|
oparts = [possible_city]
|
||||||
|
|
||||||
|
if city == "":
|
||||||
|
alias_simple = self.__remove_accents(alias.lower()).replace("-", " ")
|
||||||
|
mc = [x[1] for x in self.__citynames if alias_simple.endswith(" " + x[0])]
|
||||||
|
if len(mc) == 1:
|
||||||
|
city = mc[0]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if len(oparts) > 0:
|
||||||
|
if not self.__guess_is_address(oparts[0]):
|
||||||
|
name = oparts[0]
|
||||||
|
address = ", ".join(oparts[1:])
|
||||||
|
else:
|
||||||
|
name, address = self.__guess_name_address(", ".join(oparts))
|
||||||
|
|
||||||
|
address = self.__clean_address(address)
|
||||||
|
|
||||||
|
if name == "" and possible_city != "" and possible_city != city:
|
||||||
|
name = possible_city
|
||||||
|
|
||||||
|
return name, address, postcode, city
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
from django.shortcuts import render, get_object_or_404
|
from django.shortcuts import render, get_object_or_404
|
||||||
from django.views.generic import ListView, DetailView
|
from django.views.generic import ListView, DetailView
|
||||||
from django.views.generic.edit import CreateView, UpdateView, DeleteView
|
from django.views.generic.edit import CreateView, UpdateView, DeleteView, ModelFormMixin
|
||||||
from django.contrib.auth.mixins import (
|
from django.contrib.auth.mixins import (
|
||||||
LoginRequiredMixin,
|
LoginRequiredMixin,
|
||||||
UserPassesTestMixin,
|
UserPassesTestMixin,
|
||||||
@ -10,6 +10,12 @@ from django import forms
|
|||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.contrib.postgres.search import SearchQuery, SearchHeadline
|
from django.contrib.postgres.search import SearchQuery, SearchHeadline
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from honeypot.decorators import check_honeypot
|
||||||
|
from .utils import PlaceGuesser
|
||||||
|
import hashlib
|
||||||
|
from django.core.cache import cache
|
||||||
|
|
||||||
|
|
||||||
from django.contrib.gis.geos import Point
|
from django.contrib.gis.geos import Point
|
||||||
from django.contrib.gis.measure import D
|
from django.contrib.gis.measure import D
|
||||||
@ -37,13 +43,14 @@ from .forms import (
|
|||||||
EventModerateForm,
|
EventModerateForm,
|
||||||
TagForm,
|
TagForm,
|
||||||
TagRenameForm,
|
TagRenameForm,
|
||||||
ContactMessageForm,
|
MessageForm,
|
||||||
|
MessageEventForm,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .filters import (
|
from .filters import (
|
||||||
EventFilter,
|
EventFilter,
|
||||||
EventFilterAdmin,
|
EventFilterAdmin,
|
||||||
ContactMessagesFilterAdmin,
|
MessagesFilterAdmin,
|
||||||
SimpleSearchEventFilter,
|
SimpleSearchEventFilter,
|
||||||
SearchEventFilter,
|
SearchEventFilter,
|
||||||
DuplicatedEventsFilter,
|
DuplicatedEventsFilter,
|
||||||
@ -55,7 +62,7 @@ from .models import (
|
|||||||
Category,
|
Category,
|
||||||
Tag,
|
Tag,
|
||||||
StaticContent,
|
StaticContent,
|
||||||
ContactMessage,
|
Message,
|
||||||
BatchImportation,
|
BatchImportation,
|
||||||
DuplicatedEvents,
|
DuplicatedEvents,
|
||||||
RecurrentImport,
|
RecurrentImport,
|
||||||
@ -69,7 +76,7 @@ from django.utils import timezone
|
|||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
from django.utils.timezone import datetime
|
from django.utils.timezone import datetime
|
||||||
from django.db.models import Q, Subquery, OuterRef, Count, F, Func
|
from django.db.models import Q, Subquery, OuterRef, Count, F, Func, BooleanField, ExpressionWrapper
|
||||||
|
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@ -293,7 +300,7 @@ def update_from_source(request, pk):
|
|||||||
if url is None:
|
if url is None:
|
||||||
messages.warning(request, _("The event cannot be updated because the import process is not available for the referenced sources."))
|
messages.warning(request, _("The event cannot be updated because the import process is not available for the referenced sources."))
|
||||||
else:
|
else:
|
||||||
import_events_from_url.delay(url, None, True)
|
import_events_from_url.delay(url, None, None, True, user_id=request.user.pk if request.user else None)
|
||||||
messages.success(request, _("The event update has been queued and will be completed shortly."))
|
messages.success(request, _("The event update has been queued and will be completed shortly."))
|
||||||
|
|
||||||
return HttpResponseRedirect(event.get_absolute_url())
|
return HttpResponseRedirect(event.get_absolute_url())
|
||||||
@ -313,6 +320,10 @@ class EventUpdateView(
|
|||||||
kwargs["is_simple_cloning"] = self.is_simple_cloning
|
kwargs["is_simple_cloning"] = self.is_simple_cloning
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
form.instance.set_processing_user(self.request.user)
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
self.is_cloning = "clone" in self.request.path.split('/')
|
self.is_cloning = "clone" in self.request.path.split('/')
|
||||||
self.is_simple_cloning = "simple-clone" in self.request.path.split('/')
|
self.is_simple_cloning = "simple-clone" in self.request.path.split('/')
|
||||||
@ -328,6 +339,7 @@ class EventUpdateView(
|
|||||||
obj.save()
|
obj.save()
|
||||||
result["other_versions"] = obj.other_versions
|
result["other_versions"] = obj.other_versions
|
||||||
result["status"] = Event.STATUS.PUBLISHED
|
result["status"] = Event.STATUS.PUBLISHED
|
||||||
|
result["cloning"] = True
|
||||||
|
|
||||||
if self.is_simple_cloning:
|
if self.is_simple_cloning:
|
||||||
result["other_versions"] = None
|
result["other_versions"] = None
|
||||||
@ -349,21 +361,27 @@ class EventModerateView(
|
|||||||
permission_required = "agenda_culturel.change_event"
|
permission_required = "agenda_culturel.change_event"
|
||||||
template_name = "agenda_culturel/event_form_moderate.html"
|
template_name = "agenda_culturel/event_form_moderate.html"
|
||||||
form_class = EventModerateForm
|
form_class = EventModerateForm
|
||||||
success_message = _("The event has been successfully moderated.")
|
|
||||||
|
def get_success_message(self, cleaned_data):
|
||||||
|
return mark_safe(_('The event <a href="{}">{}</a> has been moderated with success.').format(self.object.get_absolute_url(), self.object.title))
|
||||||
|
|
||||||
|
|
||||||
def is_moderate_next(self):
|
def is_moderate_next(self):
|
||||||
return "moderate-next" in self.request.path.split('/')
|
return "after" in self.request.path.split('/')
|
||||||
|
|
||||||
def is_starting_moderation(self):
|
def is_starting_moderation(self):
|
||||||
return not "pk" in self.kwargs
|
return not "pk" in self.kwargs
|
||||||
|
|
||||||
|
def is_moderation_from_date(self):
|
||||||
|
return "m" in self.kwargs and "y" in self.kwargs and "d" in self.kwargs
|
||||||
|
|
||||||
def get_next_event(self, start_day, start_time):
|
def get_next_event(start_day, start_time):
|
||||||
# select non moderated events
|
# select non moderated events
|
||||||
qs = Event.objects.filter(moderated_date__isnull=True)
|
qs = Event.objects.filter(moderated_date__isnull=True)
|
||||||
|
|
||||||
# select events after the current one
|
# select events after the current one
|
||||||
if start_time:
|
if start_time:
|
||||||
qs = qs.filter(Q(start_day__gt=start_day)|(Q(start_day=start_day) & (Q(start_time__isnull=True)|Q(start_time__gte=start_time))))
|
qs = qs.filter(Q(start_day__gt=start_day)|(Q(start_day=start_day) & (Q(start_time__isnull=True)|Q(start_time__gt=start_time))))
|
||||||
else:
|
else:
|
||||||
qs = qs.filter(start_day__gte=start_day)
|
qs = qs.filter(start_day__gte=start_day)
|
||||||
|
|
||||||
@ -384,19 +402,15 @@ class EventModerateView(
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
if self.is_moderate_next():
|
if self.is_moderate_next():
|
||||||
context['pred'] = self.kwargs["pk"]
|
context['pred'] = self.kwargs["pred"]
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_object(self, queryset=None):
|
def get_object(self, queryset=None):
|
||||||
if self.is_starting_moderation():
|
if self.is_starting_moderation():
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
return self.get_next_event(now.date(), now.time())
|
return EventModerateView.get_next_event(now.date(), now.time())
|
||||||
else:
|
else:
|
||||||
result = super().get_object(queryset)
|
return super().get_object(queryset)
|
||||||
if self.is_moderate_next():
|
|
||||||
return self.get_next_event(result.start_day, result.start_time)
|
|
||||||
else:
|
|
||||||
return result
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
@ -408,15 +422,12 @@ class EventModerateView(
|
|||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.set_no_modification_date_changed()
|
form.instance.set_no_modification_date_changed()
|
||||||
form.instance.set_in_moderation_process()
|
form.instance.set_in_moderation_process()
|
||||||
|
form.instance.set_processing_user(self.request.user)
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
if 'save_and_next' in self.request.POST:
|
if 'save_and_next' in self.request.POST:
|
||||||
return reverse_lazy("moderate_event_next", args=[self.object.pk])
|
return reverse_lazy("moderate_event_next", args=[self.object.pk])
|
||||||
elif 'save_and_create_local' in self.request.POST:
|
|
||||||
return reverse_lazy("clone_edit", args=[self.object.pk])
|
|
||||||
elif 'save_and_edit' in self.request.POST:
|
|
||||||
return reverse_lazy("edit_event", args=[self.object.pk])
|
|
||||||
elif 'save_and_edit_local' in self.request.POST:
|
elif 'save_and_edit_local' in self.request.POST:
|
||||||
return reverse_lazy("edit_event", args=[self.object.get_local_version().pk])
|
return reverse_lazy("edit_event", args=[self.object.get_local_version().pk])
|
||||||
else:
|
else:
|
||||||
@ -435,6 +446,30 @@ def error_next_event(request, pk):
|
|||||||
{"pk": pk, "object": obj},
|
{"pk": pk, "object": obj},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@login_required(login_url="/accounts/login/")
|
||||||
|
@permission_required("agenda_culturel.change_event")
|
||||||
|
def moderate_event_next(request, pk):
|
||||||
|
# current event
|
||||||
|
obj = Event.objects.filter(pk=pk).first()
|
||||||
|
start_day = obj.start_day
|
||||||
|
start_time = obj.start_time
|
||||||
|
|
||||||
|
next_obj = EventModerateView.get_next_event(start_day, start_time)
|
||||||
|
if next_obj is None:
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"agenda_culturel/event_next_error_message.html",
|
||||||
|
{"pk": pk, "object": obj},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return HttpResponseRedirect(reverse_lazy("moderate_event_step", args=[next_obj.pk, obj.pk]))
|
||||||
|
|
||||||
|
@login_required(login_url="/accounts/login/")
|
||||||
|
@permission_required("agenda_culturel.change_event")
|
||||||
|
def moderate_from_date(request, y, m, d):
|
||||||
|
d = date(y, m, d)
|
||||||
|
obj = EventModerateView.get_next_event(d, None)
|
||||||
|
return HttpResponseRedirect(reverse_lazy("moderate_event", args=[obj.pk]))
|
||||||
|
|
||||||
|
|
||||||
class EventDeleteView(
|
class EventDeleteView(
|
||||||
@ -446,9 +481,12 @@ class EventDeleteView(
|
|||||||
success_message = _("The event has been successfully deleted.")
|
success_message = _("The event has been successfully deleted.")
|
||||||
|
|
||||||
|
|
||||||
class EventDetailView(UserPassesTestMixin, DetailView):
|
class EventDetailView(UserPassesTestMixin, DetailView, ModelFormMixin):
|
||||||
model = Event
|
model = Event
|
||||||
|
form_class = MessageEventForm
|
||||||
template_name = "agenda_culturel/page-event.html"
|
template_name = "agenda_culturel/page-event.html"
|
||||||
|
queryset = Event.objects.select_related("exact_location").select_related("category").select_related("other_versions").select_related("other_versions__representative").prefetch_related("message_set")
|
||||||
|
|
||||||
|
|
||||||
def test_func(self):
|
def test_func(self):
|
||||||
return (
|
return (
|
||||||
@ -458,6 +496,8 @@ class EventDetailView(UserPassesTestMixin, DetailView):
|
|||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
o = super().get_object()
|
o = super().get_object()
|
||||||
|
logger.warning(">>>> details")
|
||||||
|
o.download_missing_image()
|
||||||
y = self.kwargs["year"]
|
y = self.kwargs["year"]
|
||||||
m = self.kwargs["month"]
|
m = self.kwargs["month"]
|
||||||
d = self.kwargs["day"]
|
d = self.kwargs["day"]
|
||||||
@ -465,6 +505,30 @@ class EventDetailView(UserPassesTestMixin, DetailView):
|
|||||||
obj.set_current_date(date(y, m, d))
|
obj.set_current_date(date(y, m, d))
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return self.get_object().get_absolute_url() + "#chronology"
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
if not request.user.is_authenticated:
|
||||||
|
return HttpResponseForbidden()
|
||||||
|
form = self.get_form()
|
||||||
|
if form.is_valid():
|
||||||
|
return self.form_valid(form)
|
||||||
|
else:
|
||||||
|
return self.form_invalid(form)
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
message = form.save(commit=False)
|
||||||
|
message.user = self.request.user
|
||||||
|
message.related_event = self.get_object()
|
||||||
|
message.subject = _("Comment")
|
||||||
|
message.spam = False
|
||||||
|
message.closed = True
|
||||||
|
message.save()
|
||||||
|
|
||||||
|
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@login_required(login_url="/accounts/login/")
|
@login_required(login_url="/accounts/login/")
|
||||||
@ -518,31 +582,16 @@ class EventCreateView(SuccessMessageMixin, CreateView):
|
|||||||
|
|
||||||
if form.cleaned_data['simple_cloning']:
|
if form.cleaned_data['simple_cloning']:
|
||||||
form.instance.set_skip_duplicate_check()
|
form.instance.set_skip_duplicate_check()
|
||||||
|
|
||||||
|
if form.cleaned_data['cloning']:
|
||||||
|
form.instance.set_in_moderation_process()
|
||||||
|
|
||||||
form.instance.import_sources = None
|
form.instance.import_sources = None
|
||||||
|
form.instance.set_processing_user(self.request.user)
|
||||||
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
def import_from_details(request):
|
|
||||||
form = EventForm(request.POST, is_authenticated=request.user.is_authenticated)
|
|
||||||
if form.is_valid():
|
|
||||||
new_event = form.save()
|
|
||||||
if request.user.is_authenticated:
|
|
||||||
messages.success(request, _("The event is saved."))
|
|
||||||
return HttpResponseRedirect(new_event.get_absolute_url())
|
|
||||||
else:
|
|
||||||
messages.success(
|
|
||||||
request,
|
|
||||||
_(
|
|
||||||
"The event has been submitted and will be published as soon as it has been validated by the moderation team."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return HttpResponseRedirect(reverse("home"))
|
|
||||||
else:
|
|
||||||
return render(
|
|
||||||
request, "agenda_culturel/event_form.html", context={"form": form}
|
|
||||||
)
|
|
||||||
|
|
||||||
# A class to evaluate the URL according to the existing events and the authentification
|
# A class to evaluate the URL according to the existing events and the authentification
|
||||||
# level of the user
|
# level of the user
|
||||||
@ -640,7 +689,7 @@ def import_from_urls(request):
|
|||||||
request,
|
request,
|
||||||
_('Integrating {} url(s) into our import process.').format(len(ucat))
|
_('Integrating {} url(s) into our import process.').format(len(ucat))
|
||||||
)
|
)
|
||||||
import_events_from_urls.delay(ucat)
|
import_events_from_urls.delay(ucat, user_id=request.user.pk if request.user else None)
|
||||||
return HttpResponseRedirect(reverse("thank_you"))
|
return HttpResponseRedirect(reverse("thank_you"))
|
||||||
else:
|
else:
|
||||||
return HttpResponseRedirect(reverse("home"))
|
return HttpResponseRedirect(reverse("home"))
|
||||||
@ -690,7 +739,7 @@ def import_from_url(request):
|
|||||||
request,
|
request,
|
||||||
_('Integrating {} into our import process.').format(uc.url)
|
_('Integrating {} into our import process.').format(uc.url)
|
||||||
)
|
)
|
||||||
import_events_from_url.delay(uc.url, uc.cat, uc.tags)
|
import_events_from_url.delay(uc.url, uc.cat, uc.tags, user_id=request.user.pk if request.user else None)
|
||||||
return HttpResponseRedirect(reverse("thank_you"))
|
return HttpResponseRedirect(reverse("thank_you"))
|
||||||
|
|
||||||
|
|
||||||
@ -708,7 +757,7 @@ def export_event_ical(request, year, month, day, pk):
|
|||||||
events = list()
|
events = list()
|
||||||
events.append(event)
|
events.append(event)
|
||||||
|
|
||||||
cal = Event.export_to_ics(events)
|
cal = Event.export_to_ics(events, request)
|
||||||
|
|
||||||
response = HttpResponse(content_type="text/calendar")
|
response = HttpResponse(content_type="text/calendar")
|
||||||
response.content = cal.to_ical().decode("utf-8").replace("\r\n", "\n")
|
response.content = cal.to_ical().decode("utf-8").replace("\r\n", "\n")
|
||||||
@ -718,14 +767,17 @@ def export_event_ical(request, year, month, day, pk):
|
|||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def export_ical(request):
|
def export_ical(request):
|
||||||
now = date.today()
|
now = date.today()
|
||||||
|
|
||||||
request = EventFilter.set_default_values(request)
|
request = EventFilter.set_default_values(request)
|
||||||
filter = EventFilter(request.GET, queryset=get_event_qs(request), request=request)
|
filter = EventFilter(request.GET, queryset=get_event_qs(request), request=request)
|
||||||
calendar = CalendarList(now + timedelta(days=-7), now + timedelta(days=+60), filter)
|
id_cache = hashlib.md5(filter.get_url().encode("utf8")).hexdigest()
|
||||||
ical = calendar.export_to_ics()
|
ical = cache.get(id_cache)
|
||||||
|
if not ical:
|
||||||
|
calendar = CalendarList(now + timedelta(days=-7), now + timedelta(days=+60), filter)
|
||||||
|
ical = calendar.export_to_ics(request)
|
||||||
|
cache.set(id_cache, ical, 3600) # 1 heure
|
||||||
|
|
||||||
response = HttpResponse(content_type="text/calendar")
|
response = HttpResponse(content_type="text/calendar")
|
||||||
response.content = ical.to_ical().decode("utf-8").replace("\r\n", "\n")
|
response.content = ical.to_ical().decode("utf-8").replace("\r\n", "\n")
|
||||||
@ -736,15 +788,19 @@ def export_ical(request):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(check_honeypot, name='post')
|
||||||
class ContactMessageCreateView(SuccessMessageMixin, CreateView):
|
class MessageCreateView(SuccessMessageMixin, CreateView):
|
||||||
model = ContactMessage
|
model = Message
|
||||||
template_name = "agenda_culturel/contactmessage_create_form.html"
|
template_name = "agenda_culturel/message_create_form.html"
|
||||||
form_class = ContactMessageForm
|
form_class = MessageForm
|
||||||
|
|
||||||
success_url = reverse_lazy("home")
|
success_url = reverse_lazy("home")
|
||||||
success_message = _("Your message has been sent successfully.")
|
success_message = _("Your message has been sent successfully.")
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.event = None
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def get_form(self, form_class=None):
|
def get_form(self, form_class=None):
|
||||||
if form_class is None:
|
if form_class is None:
|
||||||
form_class = self.get_form_class()
|
form_class = self.get_form_class()
|
||||||
@ -753,39 +809,48 @@ class ContactMessageCreateView(SuccessMessageMixin, CreateView):
|
|||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
kwargs["event"] = self.event
|
kwargs["event"] = self.event
|
||||||
|
if self.request.user.is_authenticated:
|
||||||
|
kwargs["internal"] = True
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
form.instance.user = self.request.user
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
result = super().get_initial()
|
result = super().get_initial()
|
||||||
if "pk" in self.kwargs:
|
if "pk" in self.kwargs:
|
||||||
self.event = get_object_or_404(Event, pk=self.kwargs["pk"])
|
self.event = get_object_or_404(Event, pk=self.kwargs["pk"])
|
||||||
result["related_event"] = self.event
|
result["related_event"] = self.event
|
||||||
result["subject"] = _('Reporting the event {} on {}').format(self.event.title, self.event.start_day)
|
result["subject"] = _('Reporting the event {} on {}').format(self.event.title, self.event.start_day)
|
||||||
|
else:
|
||||||
|
result["related_event"] = None
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ContactMessageDeleteView(SuccessMessageMixin, DeleteView):
|
class MessageDeleteView(SuccessMessageMixin, DeleteView):
|
||||||
model = ContactMessage
|
model = Message
|
||||||
success_message = _(
|
success_message = _(
|
||||||
"The contact message has been successfully deleted."
|
"The contact message has been successfully deleted."
|
||||||
)
|
)
|
||||||
success_url = reverse_lazy("contactmessages")
|
success_url = reverse_lazy("messages")
|
||||||
|
|
||||||
|
|
||||||
class ContactMessageUpdateView(
|
class MessageUpdateView(
|
||||||
SuccessMessageMixin, PermissionRequiredMixin, LoginRequiredMixin, UpdateView
|
SuccessMessageMixin, PermissionRequiredMixin, LoginRequiredMixin, UpdateView
|
||||||
):
|
):
|
||||||
model = ContactMessage
|
model = Message
|
||||||
permission_required = "agenda_culturel.change_contactmessage"
|
permission_required = "agenda_culturel.change_message"
|
||||||
template_name = "agenda_culturel/contactmessage_moderation_form.html"
|
template_name = "agenda_culturel/message_moderation_form.html"
|
||||||
fields = ("spam", "closed", "comments")
|
fields = ("spam", "closed", "comments")
|
||||||
|
|
||||||
success_message = _(
|
success_message = _(
|
||||||
"The contact message properties has been successfully modified."
|
"The contact message properties has been successfully modified."
|
||||||
)
|
)
|
||||||
|
|
||||||
success_url = reverse_lazy("contactmessages")
|
success_url = reverse_lazy("messages")
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
"""Return the keyword arguments for instantiating the form."""
|
"""Return the keyword arguments for instantiating the form."""
|
||||||
@ -822,23 +887,27 @@ def activite(request):
|
|||||||
@login_required(login_url="/accounts/login/")
|
@login_required(login_url="/accounts/login/")
|
||||||
@permission_required("agenda_culturel.view_event")
|
@permission_required("agenda_culturel.view_event")
|
||||||
def administration(request):
|
def administration(request):
|
||||||
|
nb_mod_days = 21
|
||||||
|
nb_classes = 4
|
||||||
|
today = date.today()
|
||||||
|
start_time = datetime.now().time()
|
||||||
|
|
||||||
# get information about recent modifications
|
# get information about recent modifications
|
||||||
days = [date.today()]
|
days = [today]
|
||||||
for i in range(0, 2):
|
for i in range(0, 2):
|
||||||
days.append(days[-1] + timedelta(days=-1))
|
days.append(days[-1] + timedelta(days=-1))
|
||||||
daily_modifications = Event.get_count_modifications([(d, 1) for d in days])
|
daily_modifications = Event.get_count_modifications([(d, 1) for d in days])
|
||||||
|
|
||||||
# get last created events
|
# get last created events
|
||||||
events = Event.objects.all().order_by("-created_date")[:5]
|
events = Event.objects.all().order_by("-created_date").select_related("exact_location", "category")[:5]
|
||||||
|
|
||||||
# get last batch imports
|
# get last batch imports
|
||||||
batch_imports = BatchImportation.objects.all().order_by("-created_date")[:5]
|
batch_imports = BatchImportation.objects.all().select_related("recurrentImport").order_by("-created_date")[:5]
|
||||||
|
|
||||||
# get info about batch information
|
# get info about batch information
|
||||||
newest = BatchImportation.objects.filter(recurrentImport=OuterRef("pk")).order_by(
|
newest = BatchImportation.objects.filter(recurrentImport=OuterRef("pk")).order_by(
|
||||||
"-created_date"
|
"-created_date"
|
||||||
)
|
).select_related("recurrentImport")
|
||||||
imported_events = RecurrentImport.objects.annotate(
|
imported_events = RecurrentImport.objects.annotate(
|
||||||
last_run_status=Subquery(newest.values("status")[:1])
|
last_run_status=Subquery(newest.values("status")[:1])
|
||||||
)
|
)
|
||||||
@ -854,13 +923,41 @@ def administration(request):
|
|||||||
.count())
|
.count())
|
||||||
nb_all = imported_events.count()
|
nb_all = imported_events.count()
|
||||||
|
|
||||||
|
window_end = today + timedelta(days=nb_mod_days)
|
||||||
|
# get all non moderated events
|
||||||
|
nb_not_moderated = Event.objects.filter(~Q(status=Event.STATUS.TRASH)). \
|
||||||
|
filter(Q(start_day__gte=today)&Q(start_day__lte=window_end)). \
|
||||||
|
filter(
|
||||||
|
Q(other_versions__isnull=True) |
|
||||||
|
Q(other_versions__representative=F('pk')) |
|
||||||
|
Q(other_versions__representative__isnull=True)).values("start_day").\
|
||||||
|
annotate(not_moderated=Count("start_day", filter=Q(moderated_date__isnull=True))). \
|
||||||
|
annotate(nb_events=Count("start_day")). \
|
||||||
|
order_by("start_day").values("not_moderated", "nb_events", "start_day")
|
||||||
|
|
||||||
|
max_not_moderated = max([x["not_moderated"] for x in nb_not_moderated])
|
||||||
|
if max_not_moderated == 0:
|
||||||
|
max_not_moderated = 1
|
||||||
|
nb_not_moderated_dict = dict([(x["start_day"], (x["not_moderated"], x["nb_events"])) for x in nb_not_moderated])
|
||||||
|
# add missing dates
|
||||||
|
date_list = [today + timedelta(days=x) for x in range(0, nb_mod_days)]
|
||||||
|
nb_not_moderated = [{"start_day": d,
|
||||||
|
"is_today": d == today,
|
||||||
|
"nb_events": nb_not_moderated_dict[d][1] if d in nb_not_moderated_dict else 0,
|
||||||
|
"not_moderated": nb_not_moderated_dict[d][0] if d in nb_not_moderated_dict else 0} for d in date_list]
|
||||||
|
nb_not_moderated = [ x | { "note": 0 if x["not_moderated"] == 0 else int((nb_classes - 1) * x["not_moderated"] / max_not_moderated) + 1 } for x in nb_not_moderated]
|
||||||
|
nb_not_moderated = [nb_not_moderated[x:x + 7] for x in range(0, len(nb_not_moderated), 7)]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"agenda_culturel/administration.html",
|
"agenda_culturel/administration.html",
|
||||||
{"daily_modifications": daily_modifications,
|
{"daily_modifications": daily_modifications,
|
||||||
"events": events, "batch_imports": batch_imports,
|
"events": events, "batch_imports": batch_imports,
|
||||||
"nb_failed": nb_failed, "nb_canceled": nb_canceled,
|
"nb_failed": nb_failed, "nb_canceled": nb_canceled,
|
||||||
"nb_running": nb_running, "nb_all": nb_all},
|
"nb_running": nb_running, "nb_all": nb_all,
|
||||||
|
"nb_not_moderated": nb_not_moderated},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -889,15 +986,15 @@ def recent(request):
|
|||||||
|
|
||||||
|
|
||||||
@login_required(login_url="/accounts/login/")
|
@login_required(login_url="/accounts/login/")
|
||||||
@permission_required("agenda_culturel.view_contactmessage")
|
@permission_required("agenda_culturel.view_message")
|
||||||
def contactmessages(request):
|
def view_messages(request):
|
||||||
filter = ContactMessagesFilterAdmin(
|
filter = MessagesFilterAdmin(
|
||||||
request.GET, queryset=ContactMessage.objects.all().order_by("-date")
|
request.GET, queryset=Message.objects.all().order_by("-date")
|
||||||
)
|
)
|
||||||
paginator = PaginatorFilter(filter, 10, request)
|
paginator = PaginatorFilter(filter, 10, request)
|
||||||
page = request.GET.get("page")
|
page = request.GET.get("page")
|
||||||
|
|
||||||
nb_spams = ContactMessage.objects.filter(spam=True).count()
|
nb_spams = Message.objects.filter(spam=True).count()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = paginator.page(page)
|
response = paginator.page(page)
|
||||||
@ -908,24 +1005,24 @@ def contactmessages(request):
|
|||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"agenda_culturel/contactmessages.html",
|
"agenda_culturel/messages.html",
|
||||||
{"filter": filter, "nb_spams": nb_spams, "paginator_filter": response},
|
{"filter": filter, "nb_spams": nb_spams, "paginator_filter": response},
|
||||||
)
|
)
|
||||||
|
|
||||||
@login_required(login_url="/accounts/login/")
|
@login_required(login_url="/accounts/login/")
|
||||||
@permission_required("agenda_culturel.view_contactmessage")
|
@permission_required("agenda_culturel.view_message")
|
||||||
def delete_cm_spam(request):
|
def delete_cm_spam(request):
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
ContactMessage.objects.filter(spam=True).delete()
|
Message.objects.filter(spam=True).delete()
|
||||||
|
|
||||||
messages.success(request, _("Spam has been successfully deleted."))
|
messages.success(request, _("Spam has been successfully deleted."))
|
||||||
return HttpResponseRedirect(reverse_lazy("contactmessages"))
|
return HttpResponseRedirect(reverse_lazy("messages"))
|
||||||
else:
|
else:
|
||||||
nb_msgs = ContactMessage.objects.values('spam').annotate(total=Count('spam'))
|
nb_msgs = Message.objects.values('spam').annotate(total=Count('spam'))
|
||||||
nb_total = sum([nb["total"] for nb in nb_msgs])
|
nb_total = sum([nb["total"] for nb in nb_msgs])
|
||||||
nb_spams = sum([nb["total"] for nb in nb_msgs if nb["spam"]])
|
nb_spams = sum([nb["total"] for nb in nb_msgs if nb["spam"]])
|
||||||
cancel_url = reverse_lazy("contactmessages")
|
cancel_url = reverse_lazy("messages")
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"agenda_culturel/delete_spams_confirm.html",
|
"agenda_culturel/delete_spams_confirm.html",
|
||||||
@ -1481,6 +1578,7 @@ def set_duplicate(request, year, month, day, pk):
|
|||||||
event.other_versions is None
|
event.other_versions is None
|
||||||
or event.other_versions != e.other_versions
|
or event.other_versions != e.other_versions
|
||||||
)
|
)
|
||||||
|
and e.status != Event.STATUS.TRASH
|
||||||
]
|
]
|
||||||
|
|
||||||
form = SelectEventInList(events=others)
|
form = SelectEventInList(events=others)
|
||||||
@ -1856,6 +1954,7 @@ class UnknownPlaceAddView(PermissionRequiredMixin, SuccessMessageMixin, UpdateVi
|
|||||||
|
|
||||||
|
|
||||||
class PlaceFromEventCreateView(PlaceCreateView):
|
class PlaceFromEventCreateView(PlaceCreateView):
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context["event"] = self.event
|
context["event"] = self.event
|
||||||
@ -1866,6 +1965,14 @@ class PlaceFromEventCreateView(PlaceCreateView):
|
|||||||
self.event = get_object_or_404(Event, pk=self.kwargs["pk"])
|
self.event = get_object_or_404(Event, pk=self.kwargs["pk"])
|
||||||
if self.event.location and "add" in self.request.GET:
|
if self.event.location and "add" in self.request.GET:
|
||||||
initial["aliases"] = [self.event.location]
|
initial["aliases"] = [self.event.location]
|
||||||
|
guesser = PlaceGuesser()
|
||||||
|
name, address, postcode, city = guesser.guess_address_elements(self.event.location)
|
||||||
|
initial["name"] = name
|
||||||
|
initial["address"] = address
|
||||||
|
initial["postcode"] = postcode
|
||||||
|
initial["city"] = city
|
||||||
|
initial["location"] = ""
|
||||||
|
|
||||||
return initial
|
return initial
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
@ -2125,3 +2232,15 @@ def delete_tag(request, t):
|
|||||||
"agenda_culturel/tag_confirm_delete_by_name.html",
|
"agenda_culturel/tag_confirm_delete_by_name.html",
|
||||||
{"tag": t, "nb": nb, "nbi": nbi, "cancel_url": cancel_url, "obj": obj},
|
{"tag": t, "nb": nb, "nbi": nbi, "cancel_url": cancel_url, "obj": obj},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def clear_cache(request):
|
||||||
|
if request.method == "POST":
|
||||||
|
cache.clear()
|
||||||
|
messages.success(request, _("Cache successfully cleared."))
|
||||||
|
return HttpResponseRedirect(reverse_lazy("administration"))
|
||||||
|
else:
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"agenda_culturel/clear_cache.html",
|
||||||
|
)
|
@ -42,4 +42,5 @@ django-location-field==2.7.3
|
|||||||
django-robots==6.1
|
django-robots==6.1
|
||||||
django-debug-toolbar==4.4.6
|
django-debug-toolbar==4.4.6
|
||||||
django-cache-cleaner==0.1.0
|
django-cache-cleaner==0.1.0
|
||||||
emoji==2.14.0
|
emoji==2.14.0
|
||||||
|
django-honeypot==1.2.1
|
@ -1,5 +1,5 @@
|
|||||||
from agenda_culturel.models import ContactMessage
|
from agenda_culturel.models import Message
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
ContactMessage.objects.all().update(spam=True)
|
Message.objects.all().update(spam=True)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user