Compare commits
No commits in common. "main" and "filter-import-comedie" have entirely different histories.
main
...
filter-imp
@ -36,7 +36,7 @@ Pour ajouter une nouvelle source custom:
|
||||
### Récupérer un dump du prod sur un serveur dev
|
||||
|
||||
* sur le serveur de dev:
|
||||
* ```docker exec -i agenda_culturel-backend python3 manage.py dumpdata --natural-foreign --natural-primary --format=json --exclude=admin.logentry --indent=2 > fixtures/postgres-backup-20241101.json``` (à noter qu'ici on oublie les comptes, qu'il faudra recréer)
|
||||
* ```docker exec -i agenda_culturel-backend python3 manage.py dumpdata --format=json --exclude=admin.logentry --exclude=auth.group --exclude=auth.permission --exclude=auth.user --exclude=contenttypes --indent=2 > fixtures/postgres-backup-20241101.json``` (à noter qu'ici on oublie les comptes, qu'il faudra recréer)
|
||||
* sur le serveur de prod:
|
||||
* On récupère le dump json ```scp $SERVEUR:$PATH/fixtures/postgres-backup-20241101.json src/fixtures/```
|
||||
* ```scripts/reset-database.sh FIXTURE COMMIT``` où ```FIXTURE``` est le timestamp dans le nom de la fixture, et ```COMMIT``` est l'ID du commit git correspondant à celle en prod sur le serveur au moment de la création de la fixture
|
||||
|
@ -5,11 +5,10 @@ WORKDIR /usr/src/app
|
||||
|
||||
RUN --mount=type=cache,target=/var/cache/apt \
|
||||
apt-get update && \
|
||||
apt-get install --no-install-recommends -y build-essential libpq-dev gettext chromium-driver gdal-bin fonts-symbola \
|
||||
apt-get install --no-install-recommends -y build-essential libpq-dev gettext chromium-driver gdal-bin \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
|
||||
COPY src/requirements.txt ./requirements.txt
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||
|
@ -1,43 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
# coding: utf-8
|
||||
|
||||
import os
|
||||
import json
|
||||
import sys
|
||||
|
||||
# getting the name of the directory
|
||||
# where the this file is present.
|
||||
current = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
# Getting the parent directory name
|
||||
# where the current directory is present.
|
||||
parent = os.path.dirname(current)
|
||||
|
||||
# adding the parent directory to
|
||||
# the sys.path.
|
||||
sys.path.append(parent)
|
||||
|
||||
from src.agenda_culturel.import_tasks.downloader import *
|
||||
from src.agenda_culturel.import_tasks.extractor import *
|
||||
from src.agenda_culturel.import_tasks.importer import *
|
||||
from src.agenda_culturel.import_tasks.custom_extractors import *
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
u2e = URL2Events(SimpleDownloader(), lerio.CExtractor())
|
||||
url = "https://www.cinemalerio.com/evenements/"
|
||||
url_human = "https://www.cinemalerio.com/evenements/"
|
||||
|
||||
try:
|
||||
events = u2e.process(url, url_human, cache = "cache-le-rio.html", default_values = {"location": "Cinéma le Rio", "category": "Cinéma"}, published = True)
|
||||
|
||||
exportfile = "events-le-roi.json"
|
||||
print("Saving events to file {}".format(exportfile))
|
||||
with open(exportfile, "w") as f:
|
||||
json.dump(events, f, indent=4, default=str)
|
||||
except Exception as e:
|
||||
print("Exception: " + str(e))
|
@ -73,10 +73,6 @@ git checkout $COMMIT
|
||||
echobold "Setup database stucture according to the selected commit"
|
||||
docker exec -i agenda_culturel-backend python3 manage.py migrate agenda_culturel
|
||||
|
||||
# remove all elements in database
|
||||
echobold "Flush database"
|
||||
docker exec -i agenda_culturel-backend python3 manage.py flush --no-input
|
||||
|
||||
# import data
|
||||
echobold "Import data"
|
||||
docker exec -i agenda_culturel-backend python3 manage.py loaddata --format=json $FFILE
|
||||
@ -89,4 +85,7 @@ git checkout main
|
||||
echobold "Update database"
|
||||
docker exec -i agenda_culturel-backend python3 manage.py migrate agenda_culturel
|
||||
|
||||
# create superuser
|
||||
echobold "Create superuser"
|
||||
docker exec -ti agenda_culturel-backend python3 manage.py createsuperuser
|
||||
|
||||
|
@ -9,7 +9,7 @@ from .models import (
|
||||
BatchImportation,
|
||||
RecurrentImport,
|
||||
Place,
|
||||
Message,
|
||||
ContactMessage,
|
||||
ReferenceLocation,
|
||||
Organisation
|
||||
)
|
||||
@ -25,7 +25,7 @@ admin.site.register(DuplicatedEvents)
|
||||
admin.site.register(BatchImportation)
|
||||
admin.site.register(RecurrentImport)
|
||||
admin.site.register(Place)
|
||||
admin.site.register(Message)
|
||||
admin.site.register(ContactMessage)
|
||||
admin.site.register(ReferenceLocation)
|
||||
admin.site.register(Organisation)
|
||||
|
||||
|
@ -117,23 +117,6 @@ class DayInCalendar:
|
||||
if e.start_time is None
|
||||
else e.start_time
|
||||
)
|
||||
self.today_night = False
|
||||
if self.is_today():
|
||||
self.today_night = True
|
||||
now = timezone.now()
|
||||
nday = now.date()
|
||||
ntime = now.time()
|
||||
found = False
|
||||
for idx,e in enumerate(self.events):
|
||||
if (nday < e.start_day) or (nday == e.start_day and e.start_time and ntime <= e.start_time):
|
||||
self.events[idx].is_first_after_now = True
|
||||
found = True
|
||||
break
|
||||
if found:
|
||||
self.today_night = False
|
||||
|
||||
def is_today_after_events(self):
|
||||
return self.is_today() and self.today_night
|
||||
|
||||
def events_by_category_ordered(self):
|
||||
from .models import Category
|
||||
@ -192,13 +175,12 @@ class IntervalInDay(DayInCalendar):
|
||||
self.id = self.id + '-' + str(id)
|
||||
|
||||
class CalendarList:
|
||||
def __init__(self, firstdate, lastdate, filter=None, exact=False, ignore_dup=None, qs=None):
|
||||
def __init__(self, firstdate, lastdate, filter=None, exact=False, ignore_dup=None):
|
||||
self.firstdate = firstdate
|
||||
self.lastdate = lastdate
|
||||
self.now = date.today()
|
||||
self.filter = filter
|
||||
self.ignore_dup = ignore_dup
|
||||
self.qs = qs
|
||||
|
||||
if exact:
|
||||
self.c_firstdate = self.firstdate
|
||||
@ -237,12 +219,9 @@ class CalendarList:
|
||||
|
||||
def fill_calendar_days(self):
|
||||
if self.filter is None:
|
||||
if self.qs is None:
|
||||
from .models import Event
|
||||
from .models import Event
|
||||
|
||||
qs = Event.objects.all()
|
||||
else:
|
||||
qs = self.qs
|
||||
qs = Event.objects.all()
|
||||
else:
|
||||
qs = self.filter.qs
|
||||
|
||||
@ -250,7 +229,7 @@ class CalendarList:
|
||||
qs = qs.exclude(other_versions=self.ignore_dup)
|
||||
startdatetime = timezone.make_aware(datetime.combine(self.c_firstdate, time.min), timezone.get_default_timezone())
|
||||
lastdatetime = timezone.make_aware(datetime.combine(self.c_lastdate, time.max), timezone.get_default_timezone())
|
||||
qs = qs.filter(
|
||||
self.events = qs.filter(
|
||||
(Q(recurrence_dtend__isnull=True) & Q(recurrence_dtstart__lte=lastdatetime))
|
||||
| (
|
||||
Q(recurrence_dtend__isnull=False)
|
||||
@ -264,10 +243,7 @@ class CalendarList:
|
||||
Q(other_versions__isnull=True) |
|
||||
Q(other_versions__representative=F('pk')) |
|
||||
Q(other_versions__representative__isnull=True)
|
||||
).order_by("start_time", "title__unaccent__lower")
|
||||
|
||||
qs = qs.select_related("exact_location").select_related("category").select_related("other_versions").select_related("other_versions__representative")
|
||||
self.events = qs
|
||||
).order_by("start_time", "title__unaccent__lower").select_related("exact_location").select_related("category").select_related("other_versions").select_related("other_versions__representative")
|
||||
|
||||
firstdate = datetime.fromordinal(self.c_firstdate.toordinal())
|
||||
if firstdate.tzinfo is None or firstdate.tzinfo.utcoffset(firstdate) is None:
|
||||
@ -315,14 +291,14 @@ class CalendarList:
|
||||
def time_intervals_list_first(self):
|
||||
return self.time_intervals_list(True)
|
||||
|
||||
def export_to_ics(self, request):
|
||||
def export_to_ics(self):
|
||||
from .models import Event
|
||||
events = [event for day in self.get_calendar_days().values() for event in day.events]
|
||||
return Event.export_to_ics(events, request)
|
||||
return Event.export_to_ics(events)
|
||||
|
||||
|
||||
class CalendarMonth(CalendarList):
|
||||
def __init__(self, year, month, filter, qs=None):
|
||||
def __init__(self, year, month, filter):
|
||||
self.year = year
|
||||
self.month = month
|
||||
r = calendar.monthrange(year, month)
|
||||
@ -330,7 +306,7 @@ class CalendarMonth(CalendarList):
|
||||
first = date(year, month, 1)
|
||||
last = date(year, month, r[1])
|
||||
|
||||
super().__init__(first, last, filter, qs)
|
||||
super().__init__(first, last, filter)
|
||||
|
||||
def get_month_name(self):
|
||||
return self.firstdate.strftime("%B")
|
||||
@ -343,14 +319,14 @@ class CalendarMonth(CalendarList):
|
||||
|
||||
|
||||
class CalendarWeek(CalendarList):
|
||||
def __init__(self, year, week, filter, qs=None):
|
||||
def __init__(self, year, week, filter):
|
||||
self.year = year
|
||||
self.week = week
|
||||
|
||||
first = date.fromisocalendar(self.year, self.week, 1)
|
||||
last = date.fromisocalendar(self.year, self.week, 7)
|
||||
|
||||
super().__init__(first, last, filter, qs)
|
||||
super().__init__(first, last, filter)
|
||||
|
||||
def next_week(self):
|
||||
return self.firstdate + timedelta(days=7)
|
||||
@ -360,8 +336,8 @@ class CalendarWeek(CalendarList):
|
||||
|
||||
|
||||
class CalendarDay(CalendarList):
|
||||
def __init__(self, date, filter=None, qs=None):
|
||||
super().__init__(date, date, filter=filter, qs=qs, exact=True)
|
||||
def __init__(self, date, filter=None):
|
||||
super().__init__(date, date, filter, exact=True)
|
||||
|
||||
def get_events(self):
|
||||
return self.calendar_days_list()[0].events
|
||||
|
@ -6,8 +6,7 @@ from celery.schedules import crontab
|
||||
from celery.utils.log import get_task_logger
|
||||
from celery.exceptions import MaxRetriesExceededError
|
||||
import time as time_
|
||||
from django.conf import settings
|
||||
from celery.signals import worker_ready
|
||||
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
@ -148,8 +147,6 @@ def run_recurrent_import_internal(rimport, downloader, req_id):
|
||||
extractor = c3c.CExtractor()
|
||||
elif rimport.processor == RecurrentImport.PROCESSOR.ARACHNEE:
|
||||
extractor = arachnee.CExtractor()
|
||||
elif rimport.processor == RecurrentImport.PROCESSOR.LERIO:
|
||||
extractor = lerio.CExtractor()
|
||||
else:
|
||||
extractor = None
|
||||
|
||||
@ -176,11 +173,6 @@ def run_recurrent_import_internal(rimport, downloader, req_id):
|
||||
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
|
||||
json_events = json.dumps(events, default=str)
|
||||
|
||||
@ -256,23 +248,6 @@ def daily_imports(self):
|
||||
run_recurrent_imports_from_list([imp.pk for imp in imports])
|
||||
|
||||
|
||||
SCREENSHOT_FILE = settings.MEDIA_ROOT + '/screenshot.png'
|
||||
|
||||
@app.task(bind=True)
|
||||
def screenshot(self):
|
||||
downloader = ChromiumHeadlessDownloader(noimage=False)
|
||||
downloader.screenshot("https://pommesdelune.fr", SCREENSHOT_FILE)
|
||||
|
||||
@worker_ready.connect
|
||||
def at_start(sender, **k):
|
||||
if not os.path.isfile(SCREENSHOT_FILE):
|
||||
logger.info("Init screenshot file")
|
||||
with sender.app.connection() as conn:
|
||||
sender.app.send_task('agenda_culturel.celery.screenshot', None, connection=conn)
|
||||
else:
|
||||
logger.info("Screenshot file already exists")
|
||||
|
||||
|
||||
@app.task(bind=True)
|
||||
def run_all_recurrent_imports(self):
|
||||
from agenda_culturel.models import RecurrentImport
|
||||
@ -314,7 +289,7 @@ def weekly_imports(self):
|
||||
run_recurrent_imports_from_list([imp.pk for imp in imports])
|
||||
|
||||
@app.task(base=ChromiumTask, bind=True)
|
||||
def import_events_from_url(self, url, cat, tags, force=False, user_id=None, email=None, comments=None):
|
||||
def import_events_from_url(self, url, cat, tags, force=False):
|
||||
from .db_importer import DBImporterEvents
|
||||
from agenda_culturel.models import RecurrentImport, BatchImportation
|
||||
from agenda_culturel.models import Event, Category
|
||||
@ -323,7 +298,7 @@ def import_events_from_url(self, url, cat, tags, force=False, user_id=None, emai
|
||||
if acquired:
|
||||
|
||||
|
||||
logger.info("URL import: {}".format(self.request.id) + " force " + str(force))
|
||||
logger.info("URL import: {}".format(self.request.id))
|
||||
|
||||
|
||||
# clean url
|
||||
@ -348,13 +323,7 @@ def import_events_from_url(self, url, cat, tags, force=False, user_id=None, emai
|
||||
# set default values
|
||||
values = {}
|
||||
if cat is not None:
|
||||
values["category"] = cat
|
||||
if tags is not None:
|
||||
values["tags"] = tags
|
||||
if email is not None:
|
||||
values["email"] = email
|
||||
if comments is not None:
|
||||
values["comments"] = comments
|
||||
values = {"category": cat, "tags": tags}
|
||||
|
||||
# get event
|
||||
events = u2e.process(
|
||||
@ -366,7 +335,7 @@ def import_events_from_url(self, url, cat, tags, force=False, user_id=None, emai
|
||||
json_events = json.dumps(events, default=str)
|
||||
|
||||
# import events (from json)
|
||||
success, error_message = importer.import_events(json_events, user_id)
|
||||
success, error_message = importer.import_events(json_events)
|
||||
|
||||
# finally, close task
|
||||
close_import_task(self.request.id, success, error_message, importer)
|
||||
@ -383,14 +352,14 @@ def import_events_from_url(self, url, cat, tags, force=False, user_id=None, emai
|
||||
|
||||
|
||||
@app.task(base=ChromiumTask, bind=True)
|
||||
def import_events_from_urls(self, urls_cat_tags, user_id=None, email=None, comments=None):
|
||||
def import_events_from_urls(self, urls_cat_tags):
|
||||
for ucat in urls_cat_tags:
|
||||
if ucat is not None:
|
||||
url = ucat[0]
|
||||
cat = ucat[1]
|
||||
tags = ucat[2]
|
||||
|
||||
import_events_from_url.delay(url, cat, tags, user_id=user_id, email=email, comments=comments)
|
||||
import_events_from_url.delay(url, cat, tags)
|
||||
|
||||
|
||||
app.conf.beat_schedule = {
|
||||
@ -399,10 +368,6 @@ app.conf.beat_schedule = {
|
||||
# Daily imports at 3:14 a.m.
|
||||
"schedule": crontab(hour=3, minute=14),
|
||||
},
|
||||
"daily_screenshot": {
|
||||
"task": "agenda_culturel.celery.screenshot",
|
||||
"schedule": crontab(hour=3, minute=3),
|
||||
},
|
||||
"weekly_imports": {
|
||||
"task": "agenda_culturel.celery.weekly_imports",
|
||||
# Daily imports on Mondays at 2:22 a.m.
|
||||
|
@ -11,7 +11,6 @@ class DBImporterEvents:
|
||||
def __init__(self, celery_id):
|
||||
self.celery_id = celery_id
|
||||
self.error_message = ""
|
||||
self.user_id = None
|
||||
self.init_result_properties()
|
||||
self.today = timezone.now().date().isoformat()
|
||||
|
||||
@ -35,10 +34,9 @@ class DBImporterEvents:
|
||||
def get_nb_removed_events(self):
|
||||
return self.nb_removed
|
||||
|
||||
def import_events(self, json_structure, user_id=None):
|
||||
def import_events(self, json_structure):
|
||||
print(json_structure)
|
||||
self.init_result_properties()
|
||||
self.user_id = user_id
|
||||
|
||||
try:
|
||||
structure = json.loads(json_structure)
|
||||
@ -97,7 +95,7 @@ class DBImporterEvents:
|
||||
|
||||
def save_imported(self):
|
||||
self.db_event_objects, self.nb_updated, self.nb_removed = Event.import_events(
|
||||
self.event_objects, remove_missing_from_source=self.url, user_id=self.user_id
|
||||
self.event_objects, remove_missing_from_source=self.url
|
||||
)
|
||||
|
||||
def is_valid_event_structure(self, event):
|
||||
|
@ -44,7 +44,7 @@ from .models import (
|
||||
Tag,
|
||||
Event,
|
||||
Category,
|
||||
Message,
|
||||
ContactMessage,
|
||||
DuplicatedEvents
|
||||
)
|
||||
|
||||
@ -137,11 +137,7 @@ class EventFilter(django_filters.FilterSet):
|
||||
if self.get_cleaned_data("position") is None or self.get_cleaned_data("radius") is None:
|
||||
return parent
|
||||
d = self.get_cleaned_data("radius")
|
||||
p = self.get_cleaned_data("position")
|
||||
if not isinstance(d, str) or not isinstance(p, ReferenceLocation):
|
||||
return parent
|
||||
p = p.location
|
||||
|
||||
p = self.get_cleaned_data("position").location
|
||||
return parent.exclude(exact_location=False).filter(exact_location__location__distance_lt=(p, D(km=d)))
|
||||
|
||||
def get_url(self):
|
||||
@ -192,7 +188,6 @@ class EventFilter(django_filters.FilterSet):
|
||||
|
||||
|
||||
def get_cleaned_data(self, name):
|
||||
|
||||
try:
|
||||
return self.form.cleaned_data[name]
|
||||
except AttributeError:
|
||||
@ -314,13 +309,6 @@ class EventFilter(django_filters.FilterSet):
|
||||
else:
|
||||
return str(self.get_cleaned_data("position")) + ' (' + str(self.get_cleaned_data("radius")) + ' km)'
|
||||
|
||||
def is_filtered_by_position_radius(self):
|
||||
return not self.get_cleaned_data("position") is None and not self.get_cleaned_data("radius") is None
|
||||
|
||||
def get_url_add_suggested_position(self, location):
|
||||
result = self.request.get_full_path()
|
||||
return result + ('&' if '?' in result else '?') + 'position=' + str(location.pk) + "&radius=" + str(location.suggested_distance)
|
||||
|
||||
|
||||
class EventFilterAdmin(django_filters.FilterSet):
|
||||
status = django_filters.MultipleChoiceFilter(
|
||||
@ -361,27 +349,21 @@ class EventFilterAdmin(django_filters.FilterSet):
|
||||
fields = ["status"]
|
||||
|
||||
|
||||
class MessagesFilterAdmin(django_filters.FilterSet):
|
||||
class ContactMessagesFilterAdmin(django_filters.FilterSet):
|
||||
closed = django_filters.MultipleChoiceFilter(
|
||||
label=_("Status"),
|
||||
label="Status",
|
||||
choices=((True, _("Closed")), (False, _("Open"))),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
)
|
||||
spam = django_filters.MultipleChoiceFilter(
|
||||
label=_("Spam"),
|
||||
label="Spam",
|
||||
choices=((True, _("Spam")), (False, _("Non spam"))),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
)
|
||||
|
||||
message_type = django_filters.MultipleChoiceFilter(
|
||||
label=_("Type"),
|
||||
choices=Message.TYPE.choices,
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Message
|
||||
fields = ["closed", "spam", "message_type"]
|
||||
model = ContactMessage
|
||||
fields = ["closed", "spam"]
|
||||
|
||||
|
||||
class SimpleSearchEventFilter(django_filters.FilterSet):
|
||||
|
@ -13,21 +13,16 @@ from django.forms import (
|
||||
BooleanField,
|
||||
HiddenInput,
|
||||
ModelChoiceField,
|
||||
EmailField
|
||||
)
|
||||
from django.forms import formset_factory
|
||||
|
||||
from django_better_admin_arrayfield.forms.widgets import DynamicArrayWidget
|
||||
|
||||
from .utils import PlaceGuesser
|
||||
from .models import (
|
||||
Event,
|
||||
RecurrentImport,
|
||||
CategorisationRule,
|
||||
Place,
|
||||
Category,
|
||||
Tag,
|
||||
Message
|
||||
Tag
|
||||
)
|
||||
from django.conf import settings
|
||||
from django.core.files import File
|
||||
@ -36,6 +31,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
from string import ascii_uppercase as auc
|
||||
from .templatetags.utils_extra import int_to_abc
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.timezone import localtime
|
||||
from django.utils.formats import localize
|
||||
from .templatetags.event_extra import event_field_verbose_name, field_to_html
|
||||
import os
|
||||
@ -77,7 +73,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)]
|
||||
|
||||
def fields_by_group(self):
|
||||
return [(g, self.get_fields_in_group(g)) for g in self.groups] + [(GroupFormMixin.FieldGroup("other", _("Other")), self.get_no_group_fields())]
|
||||
return [(g, self.get_fields_in_group(g)) for g in self.groups] + [(None, self.get_no_group_fields())]
|
||||
|
||||
def clean(self):
|
||||
result = super().clean()
|
||||
@ -128,42 +124,7 @@ class TagRenameForm(Form):
|
||||
def is_force(self):
|
||||
return "force" in self.fields and self.cleaned_data["force"] == True
|
||||
|
||||
|
||||
class SimpleContactForm(GroupFormMixin, Form):
|
||||
email = EmailField(
|
||||
label=_("Your email"),
|
||||
help_text=_("Your email address"),
|
||||
max_length=254,
|
||||
required=False
|
||||
)
|
||||
|
||||
comments = CharField(
|
||||
label=_("Comments"),
|
||||
help_text=_("Your message for the moderation team (comments, clarifications, requests...)"),
|
||||
widget=Textarea,
|
||||
max_length=2048,
|
||||
required=False
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
is_authenticated = "is_authenticated" in kwargs and kwargs["is_authenticated"]
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if not is_authenticated:
|
||||
self.add_group('communication',
|
||||
_('Receive notification of publication or leave a message for moderation'),
|
||||
maskable=True,
|
||||
default_masked=True)
|
||||
self.fields["email"].group_id = 'communication'
|
||||
self.fields["comments"].group_id = 'communication'
|
||||
else:
|
||||
del self.fields["email"]
|
||||
del self.fields["comments"]
|
||||
|
||||
|
||||
|
||||
|
||||
class URLSubmissionForm(GroupFormMixin, Form):
|
||||
class URLSubmissionForm(Form):
|
||||
required_css_class = 'required'
|
||||
|
||||
url = URLField(max_length=512)
|
||||
@ -181,20 +142,11 @@ class URLSubmissionForm(GroupFormMixin, Form):
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
is_authenticated = kwargs.pop("is_authenticated", False)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["tags"].choices = Tag.get_tag_groups(all=True)
|
||||
|
||||
self.add_group('event', _('Event'))
|
||||
self.fields["url"].group_id = 'event'
|
||||
self.fields["category"].group_id = 'event'
|
||||
self.fields["tags"].group_id = 'event'
|
||||
|
||||
|
||||
class URLSubmissionFormWithContact(SimpleContactForm, URLSubmissionForm):
|
||||
pass
|
||||
|
||||
URLSubmissionFormSet = formset_factory(URLSubmissionForm, extra=9, min_num=1)
|
||||
|
||||
class DynamicArrayWidgetURLs(DynamicArrayWidget):
|
||||
template_name = "agenda_culturel/widgets/widget-urls.html"
|
||||
@ -207,20 +159,12 @@ class DynamicArrayWidgetTags(DynamicArrayWidget):
|
||||
class RecurrentImportForm(ModelForm):
|
||||
required_css_class = 'required'
|
||||
|
||||
defaultTags = MultipleChoiceField(
|
||||
label=_("Tags"),
|
||||
initial=None,
|
||||
choices=[],
|
||||
required=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = RecurrentImport
|
||||
fields = "__all__"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["defaultTags"].choices = Tag.get_tag_groups(all=True)
|
||||
widgets = {
|
||||
"defaultTags": DynamicArrayWidgetTags(),
|
||||
}
|
||||
|
||||
|
||||
class CategorisationRuleImportForm(ModelForm):
|
||||
@ -236,7 +180,6 @@ class EventForm(GroupFormMixin, ModelForm):
|
||||
|
||||
old_local_image = CharField(widget=HiddenInput(), required=False)
|
||||
simple_cloning = CharField(widget=HiddenInput(), required=False)
|
||||
cloning = CharField(widget=HiddenInput(), required=False)
|
||||
|
||||
tags = MultipleChoiceField(
|
||||
label=_("Tags"),
|
||||
@ -253,11 +196,7 @@ class EventForm(GroupFormMixin, ModelForm):
|
||||
"modified_date",
|
||||
"moderated_date",
|
||||
"import_sources",
|
||||
"image",
|
||||
"moderated_by_user",
|
||||
"modified_by_user",
|
||||
"created_by_user",
|
||||
"imported_by_user"
|
||||
"image"
|
||||
]
|
||||
widgets = {
|
||||
"start_day": TextInput(
|
||||
@ -306,14 +245,7 @@ class EventForm(GroupFormMixin, ModelForm):
|
||||
self.fields['end_day'].group_id = 'end'
|
||||
self.fields['end_time'].group_id = 'end'
|
||||
|
||||
self.add_group('recurrences',
|
||||
_('This is a recurring event'),
|
||||
maskable=True,
|
||||
default_masked=not (self.instance and
|
||||
self.instance.recurrences and
|
||||
self.instance.recurrences.rrules and
|
||||
len(self.instance.recurrences.rrules) > 0))
|
||||
|
||||
self.add_group('recurrences', _('This is a recurring event'), maskable=True, default_masked=True)
|
||||
self.fields['recurrences'].group_id = 'recurrences'
|
||||
|
||||
self.add_group('details', _('Details'))
|
||||
@ -329,8 +261,6 @@ class EventForm(GroupFormMixin, ModelForm):
|
||||
self.fields['local_image'].group_id = 'illustration'
|
||||
self.fields['image_alt'].group_id = 'illustration'
|
||||
|
||||
self.add_group('urls', _('URLs'))
|
||||
self.fields["reference_urls"].group_id = 'urls'
|
||||
|
||||
if is_authenticated:
|
||||
self.add_group('meta-admin', _('Meta information'))
|
||||
@ -387,9 +317,6 @@ class EventForm(GroupFormMixin, ModelForm):
|
||||
self.cleaned_data['local_image'] = File(name=basename, file=open(old, "rb"))
|
||||
|
||||
|
||||
class EventFormWithContact(SimpleContactForm, EventForm):
|
||||
pass
|
||||
|
||||
class MultipleChoiceFieldAcceptAll(MultipleChoiceField):
|
||||
def validate(self, value):
|
||||
pass
|
||||
@ -420,9 +347,6 @@ class EventModerateForm(ModelForm):
|
||||
"exact_location",
|
||||
"tags"
|
||||
]
|
||||
widgets = {
|
||||
"status": RadioSelect
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
@ -546,14 +470,14 @@ class FixDuplicates(Form):
|
||||
class SelectEventInList(Form):
|
||||
required_css_class = 'required'
|
||||
|
||||
event = ChoiceField(label=_('Event'))
|
||||
event = ChoiceField()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
events = kwargs.pop("events", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields["event"].choices = [
|
||||
(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
|
||||
(e.pk, str(e.start_day) + " " + e.title + ((", " + e.location) if e.location else "")) for e in events
|
||||
]
|
||||
|
||||
|
||||
@ -601,17 +525,17 @@ class MergeDuplicates(Form):
|
||||
'<li><a href="' + e.get_absolute_url() + '">' + e.title + "</a></li>"
|
||||
)
|
||||
result += (
|
||||
"<li>Création : " + localize(e.created_date) + "</li>"
|
||||
"<li>Création : " + localize(localtime(e.created_date)) + "</li>"
|
||||
)
|
||||
result += (
|
||||
"<li>Dernière modification : "
|
||||
+ localize(e.modified_date)
|
||||
+ localize(localtime(e.modified_date))
|
||||
+ "</li>"
|
||||
)
|
||||
if e.imported_date:
|
||||
result += (
|
||||
"<li>Dernière importation : "
|
||||
+ localize(e.imported_date)
|
||||
+ localize(localtime(e.imported_date))
|
||||
+ "</li>"
|
||||
)
|
||||
result += "</ul>"
|
||||
@ -662,7 +586,7 @@ class MergeDuplicates(Form):
|
||||
result += '<input id="' + id + '" name="' + key + '"'
|
||||
if key in MergeDuplicates.checkboxes_fields:
|
||||
result += ' type="checkbox"'
|
||||
if checked and value in checked:
|
||||
if value in checked:
|
||||
result += " checked"
|
||||
else:
|
||||
result += ' type="radio"'
|
||||
@ -694,7 +618,7 @@ class MergeDuplicates(Form):
|
||||
result = []
|
||||
for s in selected:
|
||||
for e in self.duplicates.get_duplicated():
|
||||
if e.pk == s:
|
||||
if e.pk == selected:
|
||||
result.append(e)
|
||||
break
|
||||
return result
|
||||
@ -778,7 +702,7 @@ class EventAddPlaceForm(Form):
|
||||
return self.instance
|
||||
|
||||
|
||||
class PlaceForm(GroupFormMixin, ModelForm):
|
||||
class PlaceForm(ModelForm):
|
||||
required_css_class = 'required'
|
||||
|
||||
apply_to_all = BooleanField(
|
||||
@ -794,70 +718,13 @@ class PlaceForm(GroupFormMixin, ModelForm):
|
||||
fields = "__all__"
|
||||
widgets = {"location": TextInput()}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.add_group('header', _('Header'))
|
||||
self.fields['name'].group_id = 'header'
|
||||
|
||||
|
||||
self.add_group('address', _('Address'))
|
||||
self.fields['address'].group_id = 'address'
|
||||
self.fields['postcode'].group_id = 'address'
|
||||
self.fields['city'].group_id = 'address'
|
||||
self.fields['location'].group_id = 'address'
|
||||
|
||||
self.add_group('meta', _('Meta'))
|
||||
self.fields['aliases'].group_id = 'meta'
|
||||
|
||||
self.add_group('information', _('Information'))
|
||||
self.fields['description'].group_id = 'information'
|
||||
|
||||
def as_grid(self):
|
||||
result = ('<div class="grid"><div>'
|
||||
return mark_safe(
|
||||
'<div class="grid"><div>'
|
||||
+ super().as_p()
|
||||
+ '''</div><div><div class="map-widget">
|
||||
<div id="map_location" style="width: 100%; aspect-ratio: 16/9"></div>
|
||||
<p>Cliquez pour ajuster la position GPS</p></div>
|
||||
<input type="checkbox" role="switch" id="lock_position">Verrouiller la position</lock>
|
||||
<script>
|
||||
document.getElementById("lock_position").onclick = function() {
|
||||
const field = document.getElementById("id_location");
|
||||
if (this.checked)
|
||||
field.setAttribute("readonly", true);
|
||||
else
|
||||
field.removeAttribute("readonly");
|
||||
}
|
||||
</script>
|
||||
</div></div>''')
|
||||
|
||||
return mark_safe(result)
|
||||
+ '</div><div><div class="map-widget">'
|
||||
+ '<div id="map_location" style="width: 100%; aspect-ratio: 16/9"></div><p>Cliquez pour ajuster la position GPS</p></div></div></div>'
|
||||
)
|
||||
|
||||
def apply(self):
|
||||
return self.cleaned_data.get("apply_to_all")
|
||||
|
||||
class MessageForm(ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Message
|
||||
fields = ["subject", "name", "email", "message", "related_event"]
|
||||
widgets = {"related_event": HiddenInput(), "user": HiddenInput() }
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.event = kwargs.pop("event", False)
|
||||
self.internal = kwargs.pop("internal", False)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['related_event'].required = False
|
||||
if self.internal:
|
||||
self.fields.pop("name")
|
||||
self.fields.pop("email")
|
||||
|
||||
class MessageEventForm(ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Message
|
||||
fields = ["message"]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["message"].label = _("Add a comment")
|
@ -3,12 +3,6 @@ from ..extractor_facebook import FacebookEvent
|
||||
import json5
|
||||
from bs4 import BeautifulSoup
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# A class dedicated to get events from a facebook events page
|
||||
@ -19,27 +13,10 @@ class CExtractor(TwoStepsExtractor):
|
||||
def build_event_url_list(self, content):
|
||||
soup = BeautifulSoup(content, "html.parser")
|
||||
|
||||
debug = False
|
||||
|
||||
found = False
|
||||
links = soup.find_all("a")
|
||||
for link in links:
|
||||
if link.get("href").startswith('https://www.facebook.com/events/'):
|
||||
self.add_event_url(link.get('href').split('?')[0])
|
||||
found = True
|
||||
|
||||
if not found and debug:
|
||||
directory = "errors/"
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
now = datetime.now()
|
||||
filename = directory + now.strftime("%Y%m%d_%H%M%S") + ".html"
|
||||
logger.warning("cannot find any event link in events page. Save content page in " + filename)
|
||||
with open(filename, "w") as text_file:
|
||||
text_file.write("<!-- " + self.url + " -->\n\n")
|
||||
text_file.write(content)
|
||||
|
||||
|
||||
|
||||
|
||||
def add_event_from_content(
|
||||
@ -65,7 +42,4 @@ class CExtractor(TwoStepsExtractor):
|
||||
event["published"] = published
|
||||
|
||||
self.add_event(default_values, **event)
|
||||
else:
|
||||
logger.warning("cannot find any event in page")
|
||||
|
||||
|
||||
|
@ -106,16 +106,6 @@ class CExtractor(TwoStepsExtractor):
|
||||
description = soup.select("#descspec")
|
||||
if description and len(description) > 0:
|
||||
description = description[0].get_text().replace("Lire plus...", "")
|
||||
# on ajoute éventuellement les informations complémentaires
|
||||
|
||||
d_suite = ""
|
||||
for d in ["#typespec", "#dureespec", "#lieuspec", ".lkuncontdroitespec"]:
|
||||
comp_desc = soup.select(d)
|
||||
if comp_desc and len(comp_desc) > 0:
|
||||
for desc in comp_desc:
|
||||
d_suite += "\n\n" + desc.get_text()
|
||||
if d_suite != "":
|
||||
description += "\n\n> Informations complémentaires:" + d_suite
|
||||
else:
|
||||
description = None
|
||||
|
||||
|
@ -1,91 +0,0 @@
|
||||
from ..generic_extractors import *
|
||||
from bs4 import BeautifulSoup
|
||||
from datetime import datetime
|
||||
|
||||
# A class dedicated to get events from Cinéma Le Rio (Clermont-Ferrand)
|
||||
# URL: https://www.cinemalerio.com/evenements/
|
||||
class CExtractor(TwoStepsExtractorNoPause):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.possible_dates = {}
|
||||
self.theater = None
|
||||
|
||||
def build_event_url_list(self, content, infuture_days=180):
|
||||
|
||||
soup = BeautifulSoup(content, "html.parser")
|
||||
|
||||
links = soup.select("td.seance_link a")
|
||||
if links:
|
||||
for l in links:
|
||||
print(l["href"])
|
||||
self.add_event_url(l["href"])
|
||||
|
||||
def to_text_select_one(soup, filter):
|
||||
e = soup.select_one(filter)
|
||||
if e is None:
|
||||
return None
|
||||
else:
|
||||
return e.text
|
||||
|
||||
def add_event_from_content(
|
||||
self,
|
||||
event_content,
|
||||
event_url,
|
||||
url_human=None,
|
||||
default_values=None,
|
||||
published=False,
|
||||
):
|
||||
|
||||
soup = BeautifulSoup(event_content, "html.parser")
|
||||
|
||||
title = soup.select_one("h1").text
|
||||
|
||||
alerte_date = CExtractor.to_text_select_one(soup, ".alerte_date")
|
||||
if alerte_date is None:
|
||||
return
|
||||
dh = alerte_date.split("à")
|
||||
# if date is not found, we skip
|
||||
if len(dh) != 2:
|
||||
return
|
||||
|
||||
date = Extractor.parse_french_date(dh[0], default_year=datetime.now().year)
|
||||
time = Extractor.parse_french_time(dh[1])
|
||||
|
||||
synopsis = CExtractor.to_text_select_one(soup, ".synopsis_bloc")
|
||||
special_titre = CExtractor.to_text_select_one(soup, ".alerte_titre")
|
||||
special = CExtractor.to_text_select_one(soup, ".alerte_text")
|
||||
|
||||
# it's not a specific event: we skip it
|
||||
special_lines = None if special is None else special.split('\n')
|
||||
if special is None or len(special_lines) == 0 or \
|
||||
(len(special_lines) == 1 and special_lines[0].strip().startswith('En partenariat')):
|
||||
return
|
||||
|
||||
description = "\n\n".join([x for x in [synopsis, special_titre, special] if not x is None])
|
||||
|
||||
image = soup.select_one(".col1 img")
|
||||
image_alt = None
|
||||
if not image is None:
|
||||
image_alt = image["alt"]
|
||||
image = image["src"]
|
||||
|
||||
self.add_event_with_props(
|
||||
default_values,
|
||||
event_url,
|
||||
title,
|
||||
None,
|
||||
date,
|
||||
None,
|
||||
description,
|
||||
[],
|
||||
recurrences=None,
|
||||
uuids=[event_url],
|
||||
url_human=event_url,
|
||||
start_time=time,
|
||||
end_day=None,
|
||||
end_time=None,
|
||||
published=published,
|
||||
image=image,
|
||||
image_alt=image_alt
|
||||
)
|
@ -66,7 +66,7 @@ class SimpleDownloader(Downloader):
|
||||
|
||||
|
||||
class ChromiumHeadlessDownloader(Downloader):
|
||||
def __init__(self, pause=True, noimage=True):
|
||||
def __init__(self, pause=True):
|
||||
super().__init__()
|
||||
self.pause = pause
|
||||
self.options = Options()
|
||||
@ -78,31 +78,17 @@ class ChromiumHeadlessDownloader(Downloader):
|
||||
self.options.add_argument("--disable-dev-shm-usage")
|
||||
self.options.add_argument("--disable-browser-side-navigation")
|
||||
self.options.add_argument("--disable-gpu")
|
||||
if noimage:
|
||||
self.options.add_experimental_option(
|
||||
"prefs", {
|
||||
# block image loading
|
||||
"profile.managed_default_content_settings.images": 2,
|
||||
}
|
||||
)
|
||||
self.options.add_experimental_option(
|
||||
"prefs", {
|
||||
# block image loading
|
||||
"profile.managed_default_content_settings.images": 2,
|
||||
}
|
||||
)
|
||||
|
||||
self.service = Service("/usr/bin/chromedriver")
|
||||
self.driver = webdriver.Chrome(service=self.service, options=self.options)
|
||||
|
||||
|
||||
def screenshot(self, url, path_image):
|
||||
print("Screenshot {}".format(url))
|
||||
try:
|
||||
self.driver.get(url)
|
||||
if self.pause:
|
||||
time.sleep(2)
|
||||
self.driver.save_screenshot(path_image)
|
||||
except:
|
||||
print(f">> Exception: {URL}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def download(self, url, referer=None, post=None):
|
||||
if post:
|
||||
raise Exception("POST method with Chromium headless not yet implemented")
|
||||
|
@ -2,7 +2,7 @@ from abc import ABC, abstractmethod
|
||||
from datetime import datetime, time, date, timedelta
|
||||
import re
|
||||
import unicodedata
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
|
||||
|
||||
@ -49,7 +49,7 @@ class Extractor(ABC):
|
||||
return i + 1
|
||||
return None
|
||||
|
||||
def parse_french_date(text, default_year=None):
|
||||
def parse_french_date(text):
|
||||
# format NomJour Numero Mois Année
|
||||
m = re.search(
|
||||
"[a-zA-ZéÉûÛ:.]+[ ]*([0-9]+)[er]*[ ]*([a-zA-ZéÉûÛ:.]+)[ ]*([0-9]+)", text
|
||||
@ -73,15 +73,8 @@ class Extractor(ABC):
|
||||
month = int(m.group(2))
|
||||
year = m.group(3)
|
||||
else:
|
||||
# format Numero Mois Annee
|
||||
m = re.search("([0-9]+)[er]*[ ]*([a-zA-ZéÉûÛ:.]+)", text)
|
||||
if m:
|
||||
day = m.group(1)
|
||||
month = Extractor.guess_month(m.group(2))
|
||||
year = default_year
|
||||
else:
|
||||
# TODO: consolider les cas non satisfaits
|
||||
return None
|
||||
# TODO: consolider les cas non satisfaits
|
||||
return None
|
||||
|
||||
if month is None:
|
||||
return None
|
||||
@ -200,8 +193,6 @@ class Extractor(ABC):
|
||||
"published": published,
|
||||
"image": image,
|
||||
"image_alt": image_alt,
|
||||
"email": self.default_value_if_exists(default_values, "email"),
|
||||
"comments": self.default_value_if_exists(default_values, "comments"),
|
||||
}
|
||||
# TODO: pourquoi url_human et non reference_url
|
||||
if url_human is not None:
|
||||
@ -249,28 +240,6 @@ class Extractor(ABC):
|
||||
from .extractor_ggcal_link import GoogleCalendarLinkEventExtractor
|
||||
|
||||
if single_event:
|
||||
return [FacebookEventExtractor(), GoogleCalendarLinkEventExtractor(), EventNotFoundExtractor()]
|
||||
return [FacebookEventExtractor(), GoogleCalendarLinkEventExtractor()]
|
||||
else:
|
||||
return [ICALExtractor(), FacebookEventExtractor(), GoogleCalendarLinkEventExtractor(), EventNotFoundExtractor()]
|
||||
|
||||
|
||||
# A class that only produce a not found event
|
||||
class EventNotFoundExtractor(Extractor):
|
||||
|
||||
def extract(
|
||||
self, content, url, url_human=None, default_values=None, published=False
|
||||
):
|
||||
self.set_header(url)
|
||||
self.clear_events()
|
||||
|
||||
self.add_event(default_values, "événement sans titre depuis " + url,
|
||||
None, timezone.now().date(), None,
|
||||
"l'import a échoué, la saisie doit se faire manuellement à partir de l'url source " + url,
|
||||
[], [url], published=False, url_human=url)
|
||||
|
||||
return self.get_structure()
|
||||
|
||||
|
||||
def clean_url(url):
|
||||
return url
|
||||
|
||||
return [ICALExtractor(), FacebookEventExtractor(), GoogleCalendarLinkEventExtractor()]
|
||||
|
@ -239,7 +239,7 @@ class FacebookEventExtractor(Extractor):
|
||||
result = "https://www.facebook.com" + u.path
|
||||
|
||||
# remove name in the url
|
||||
match = re.match(r"(.*/events)/s/([a-zA-Z-][a-zA-Z-0-9-]+)/([0-9/]*)", result)
|
||||
match = re.match(r"(.*/events)/s/([a-zA-Z-][a-zA-Z-0-9]+)/([0-9/]*)", result)
|
||||
if match:
|
||||
result = match[1] + "/" + match[3]
|
||||
|
||||
|
@ -264,13 +264,9 @@ class TwoStepsExtractorNoPause(TwoStepsExtractor):
|
||||
only_future=True,
|
||||
ignore_404=True
|
||||
):
|
||||
if hasattr(self.downloader, "pause"):
|
||||
pause = self.downloader.pause
|
||||
else:
|
||||
pause = False
|
||||
pause = self.downloader.pause
|
||||
self.downloader.pause = False
|
||||
result = super().extract(content, url, url_human, default_values, published, only_future, ignore_404)
|
||||
self.downloader.pause = pause
|
||||
|
||||
return result
|
||||
|
||||
return result
|
@ -1,11 +1,6 @@
|
||||
from .downloader import *
|
||||
from .extractor import *
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
class URL2Events:
|
||||
def __init__(
|
||||
@ -34,9 +29,8 @@ class URL2Events:
|
||||
else:
|
||||
# if the extractor is not defined, use a list of default extractors
|
||||
for e in Extractor.get_default_extractors(self.single_event):
|
||||
logger.warning('Extractor::' + type(e).__name__)
|
||||
e.set_downloader(self.downloader)
|
||||
events = e.extract(content, url, url_human, default_values, published)
|
||||
if events is not None and len(events) > 0:
|
||||
if events is not None:
|
||||
return events
|
||||
return None
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,11 @@
|
||||
# Generated by Django 4.2.9 on 2024-10-10 20:35
|
||||
|
||||
from django.db import migrations
|
||||
from agenda_culturel.models import Place
|
||||
from django.contrib.gis.geos import Point
|
||||
|
||||
def change_coord_format(apps, schema_editor):
|
||||
Place = apps.get_model("agenda_culturel", "Place")
|
||||
places = Place.objects.values("location", "location_pt").all()
|
||||
places = Place.objects.all()
|
||||
|
||||
for p in places:
|
||||
l = p.location.split(',')
|
||||
@ -13,15 +13,14 @@ def change_coord_format(apps, schema_editor):
|
||||
p.location_pt = Point(float(l[1]), float(l[0]))
|
||||
else:
|
||||
p.location_pt = Point(3.08333, 45.783329)
|
||||
p.save(update_fields=["location_pt"])
|
||||
p.save()
|
||||
|
||||
def reverse_coord_format(apps, schema_editor):
|
||||
Place = apps.get_model("agenda_culturel", "Place")
|
||||
places = Place.objects.values("location", "location_pt").all()
|
||||
places = Place.objects.all()
|
||||
|
||||
for p in places:
|
||||
p.location = ','.join([p.location_pt[1], p.location_pt[0]])
|
||||
p.save(update_fields=["location"])
|
||||
p.save()
|
||||
|
||||
|
||||
|
||||
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 4.2.9 on 2024-11-27 18:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agenda_culturel', '0119_alter_tag_options_alter_event_category_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='referencelocation',
|
||||
name='suggested_distance',
|
||||
field=models.IntegerField(default=None, help_text='If this distance is given, this location is part of the suggested filters.', null=True, verbose_name='Suggested distance (km)'),
|
||||
),
|
||||
]
|
@ -1,19 +0,0 @@
|
||||
# Generated by Django 4.2.9 on 2024-11-27 22:13
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agenda_culturel', '0120_referencelocation_suggested_distance'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='contactmessage',
|
||||
name='related_event',
|
||||
field=models.ForeignKey(default=None, help_text='The message is associated with this event.', null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='agenda_culturel.event', verbose_name='Related event'),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 4.2.9 on 2024-11-29 13:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agenda_culturel', '0121_contactmessage_related_event'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='recurrentimport',
|
||||
name='processor',
|
||||
field=models.CharField(choices=[('ical', 'ical'), ('icalnobusy', 'ical no busy'), ('icalnovc', 'ical no VC'), ('lacoope', 'lacoope.org'), ('lacomedie', 'la comédie'), ('lefotomat', 'le fotomat'), ('lapucealoreille', "la puce à l'oreille"), ('Plugin wordpress MEC', 'Plugin wordpress MEC'), ('Facebook events', "Événements d'une page FB"), ('cour3coquins', 'la cour des 3 coquins'), ('arachnee', 'Arachnée concert'), ('rio', 'Le Rio')], default='ical', max_length=20, verbose_name='Processor'),
|
||||
),
|
||||
]
|
@ -1,36 +0,0 @@
|
||||
# Generated by Django 4.2.9 on 2024-11-29 18:18
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('agenda_culturel', '0122_alter_recurrentimport_processor'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='created_by_user',
|
||||
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='created_events', to=settings.AUTH_USER_MODEL, verbose_name='Author of the event creation'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='imported_by_user',
|
||||
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='imported_events', to=settings.AUTH_USER_MODEL, verbose_name='Author of the last importation'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='moderated_by_user',
|
||||
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='moderated_events', to=settings.AUTH_USER_MODEL, verbose_name='Author of the last moderation'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='modified_by_user',
|
||||
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='modified_events', to=settings.AUTH_USER_MODEL, verbose_name='Author of the last modification'),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 4.2.9 on 2024-12-06 21:37
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agenda_culturel', '0123_event_created_by_user_event_imported_by_user_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='place',
|
||||
name='postcode',
|
||||
field=models.CharField(blank=True, help_text='The post code is not displayed, but makes it easier to find an address when you enter it.', null=True, verbose_name='Postcode'),
|
||||
),
|
||||
]
|
@ -1,21 +0,0 @@
|
||||
# Generated by Django 4.2.9 on 2024-12-11 11:33
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agenda_culturel', '0124_place_postcode'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name='ContactMessage',
|
||||
new_name='Message',
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='message',
|
||||
options={'verbose_name': 'Message', 'verbose_name_plural': 'Messages'},
|
||||
),
|
||||
]
|
@ -1,21 +0,0 @@
|
||||
# Generated by Django 4.2.9 on 2024-12-11 11:56
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('agenda_culturel', '0125_rename_contactmessage_message_alter_message_options'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='message',
|
||||
name='user',
|
||||
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to=settings.AUTH_USER_MODEL, verbose_name='Author of the message'),
|
||||
),
|
||||
]
|
@ -1,25 +0,0 @@
|
||||
# Generated by Django 4.2.9 on 2024-12-11 19:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agenda_culturel', '0126_message_user'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name='event',
|
||||
index=models.Index(fields=['end_day', 'end_time'], name='agenda_cult_end_day_4660a5_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='event',
|
||||
index=models.Index(fields=['status'], name='agenda_cult_status_893243_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='event',
|
||||
index=models.Index(fields=['recurrence_dtstart', 'recurrence_dtend'], name='agenda_cult_recurre_a8911c_idx'),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 4.2.9 on 2024-12-11 19:12
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.functions.text
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agenda_culturel', '0127_event_agenda_cult_end_day_4660a5_idx_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name='event',
|
||||
index=models.Index(models.F('start_time'), models.F('start_day'), models.F('end_day'), models.F('end_time'), django.db.models.functions.text.Lower('title'), name='datetimes title'),
|
||||
),
|
||||
]
|
@ -1,57 +0,0 @@
|
||||
# Generated by Django 4.2.9 on 2024-12-11 19:58
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agenda_culturel', '0128_event_datetimes_title'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name='batchimportation',
|
||||
index=models.Index(fields=['created_date'], name='agenda_cult_created_a23990_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='batchimportation',
|
||||
index=models.Index(fields=['status'], name='agenda_cult_status_54b205_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='batchimportation',
|
||||
index=models.Index(fields=['created_date', 'recurrentImport'], name='agenda_cult_created_0296e4_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='duplicatedevents',
|
||||
index=models.Index(fields=['representative'], name='agenda_cult_represe_9a4fa2_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='message',
|
||||
index=models.Index(fields=['related_event'], name='agenda_cult_related_79de3c_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='message',
|
||||
index=models.Index(fields=['user'], name='agenda_cult_user_id_42dc88_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='message',
|
||||
index=models.Index(fields=['date'], name='agenda_cult_date_049c71_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='message',
|
||||
index=models.Index(fields=['spam', 'closed'], name='agenda_cult_spam_22f9b3_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='place',
|
||||
index=models.Index(fields=['name'], name='agenda_cult_name_222846_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='place',
|
||||
index=models.Index(fields=['city'], name='agenda_cult_city_156dc7_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='place',
|
||||
index=models.Index(fields=['location'], name='agenda_cult_locatio_6f3c05_idx'),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 4.2.9 on 2024-12-22 15:19
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agenda_culturel', '0129_batchimportation_agenda_cult_created_a23990_idx_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='recurrentimport',
|
||||
name='forceLocation',
|
||||
field=models.BooleanField(default=False, help_text='force location even if another is detected.', verbose_name='Force location'),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 4.2.9 on 2024-12-29 11:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agenda_culturel', '0130_recurrentimport_forcelocation'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='message',
|
||||
name='message_type',
|
||||
field=models.CharField(choices=[('from_contributor', 'From contributor'), ('import_process', 'Import process'), ('contact_form', 'Contact form'), ('event_report', 'Event report')], default=None, max_length=20, null=True, verbose_name='Type'),
|
||||
),
|
||||
]
|
@ -1,18 +0,0 @@
|
||||
# Generated by Django 4.2.9 on 2024-12-29 16:26
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agenda_culturel', '0131_message_message_type'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='message',
|
||||
name='message_type',
|
||||
field=models.CharField(choices=[('from_contributor', 'From contributor'), ('import_process', 'Import process'), ('contact_form', 'Contact form'), ('event_report', 'Event report'), ('from_contrib_no_msg', 'From contributor (without message)')], default=None, max_length=20, null=True, verbose_name='Type'),
|
||||
),
|
||||
]
|
@ -10,16 +10,7 @@ from colorfield.fields import ColorField
|
||||
from django_ckeditor_5.fields import CKEditor5Field
|
||||
from urllib.parse import urlparse
|
||||
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
|
||||
from django.core.files.storage import default_storage
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.mail import send_mail
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
|
||||
import uuid
|
||||
|
||||
import hashlib
|
||||
import urllib.request
|
||||
@ -29,7 +20,6 @@ from django.utils import timezone
|
||||
from django.contrib.postgres.search import TrigramSimilarity
|
||||
from django.db.models import Q, Count, F, Subquery, OuterRef, Func
|
||||
from django.db.models.functions import Lower
|
||||
from django.contrib.postgres.lookups import Unaccent
|
||||
import recurrence.fields
|
||||
import recurrence
|
||||
import copy
|
||||
@ -294,10 +284,6 @@ class DuplicatedEvents(models.Model):
|
||||
class Meta:
|
||||
verbose_name = _("Duplicated events")
|
||||
verbose_name_plural = _("Duplicated events")
|
||||
indexes = [
|
||||
models.Index(fields=['representative']),
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.events = None
|
||||
@ -412,7 +398,6 @@ class DuplicatedEvents(models.Model):
|
||||
|
||||
|
||||
class ReferenceLocation(models.Model):
|
||||
|
||||
name = models.CharField(verbose_name=_("Name"), help_text=_("Name of the location"), unique=True, null=False)
|
||||
location = LocationField(based_fields=["name"], zoom=12, default=Point(3.08333, 45.783329), srid=4326)
|
||||
main = models.IntegerField(
|
||||
@ -420,12 +405,6 @@ class ReferenceLocation(models.Model):
|
||||
help_text=_("This location is one of the main locations (shown first higher values)."),
|
||||
default=0,
|
||||
)
|
||||
suggested_distance = models.IntegerField(
|
||||
verbose_name=_("Suggested distance (km)"),
|
||||
help_text=_("If this distance is given, this location is part of the suggested filters."),
|
||||
null=True,
|
||||
default=None
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Reference location")
|
||||
@ -448,9 +427,8 @@ class Place(models.Model):
|
||||
blank=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"))
|
||||
location = LocationField(based_fields=["name", "address", "postcode", "city"], zoom=12, default=Point(3.08333, 45.783329))
|
||||
location = LocationField(based_fields=["name", "address", "city"], zoom=12, default=Point(3.08333, 45.783329))
|
||||
|
||||
description = CKEditor5Field(
|
||||
verbose_name=_("Description"),
|
||||
@ -473,11 +451,6 @@ class Place(models.Model):
|
||||
verbose_name = _("Place")
|
||||
verbose_name_plural = _("Places")
|
||||
ordering = ["name"]
|
||||
indexes = [
|
||||
models.Index(fields=['name']),
|
||||
models.Index(fields=['city']),
|
||||
models.Index(fields=['location']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
if self.address:
|
||||
@ -563,7 +536,7 @@ class Organisation(models.Model):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("view_organisation", kwargs={'pk': self.pk, "extra": self.name})
|
||||
return reverse("view_organisation", kwargs={'pk': self.pk})
|
||||
|
||||
|
||||
|
||||
@ -578,39 +551,6 @@ class Event(models.Model):
|
||||
modified_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_dtend = models.DateTimeField(editable=False, blank=True, null=True)
|
||||
|
||||
@ -745,10 +685,6 @@ class Event(models.Model):
|
||||
blank=True,
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.processing_user = None
|
||||
|
||||
def get_consolidated_end_day(self, intuitive=True):
|
||||
if intuitive:
|
||||
end_day = self.get_consolidated_end_day(False)
|
||||
@ -766,6 +702,15 @@ class Event(models.Model):
|
||||
last = self.get_consolidated_end_day()
|
||||
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):
|
||||
return self.start_day == self.get_consolidated_end_day(intuitive)
|
||||
|
||||
@ -775,15 +720,6 @@ class Event(models.Model):
|
||||
end_date = parse_date(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):
|
||||
return d >= self.start_day and d <= self.get_consolidated_end_day(intuitive)
|
||||
|
||||
@ -821,36 +757,9 @@ class Event(models.Model):
|
||||
permissions = [("set_duplicated_event", "Can set an event as duplicated")]
|
||||
indexes = [
|
||||
models.Index(fields=["start_day", "start_time"]),
|
||||
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")
|
||||
models.Index("start_time", Lower("title"), name="start_time 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):
|
||||
if self.tags is None:
|
||||
return []
|
||||
@ -942,27 +851,19 @@ class Event(models.Model):
|
||||
def is_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):
|
||||
self.download_image()
|
||||
self.save(update_fields=["local_image"])
|
||||
|
||||
def download_image(self):
|
||||
# first download file
|
||||
|
||||
a = urlparse(self.image)
|
||||
basename = os.path.basename(a.path)
|
||||
|
||||
ext = basename.split('.')[-1]
|
||||
filename = "%s.%s" % (uuid.uuid4(), ext)
|
||||
|
||||
try:
|
||||
tmpfile, _ = urllib.request.urlretrieve(self.image)
|
||||
except:
|
||||
return None
|
||||
|
||||
# if the download is ok, then create the corresponding file object
|
||||
self.local_image = File(name=filename, file=open(tmpfile, "rb"))
|
||||
self.local_image = File(name=basename, file=open(tmpfile, "rb"))
|
||||
|
||||
def add_pending_organisers(self, organisers):
|
||||
self.pending_organisers = organisers
|
||||
@ -988,12 +889,6 @@ class Event(models.Model):
|
||||
def set_no_modification_date_changed(self):
|
||||
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):
|
||||
self.in_moderation_process = True
|
||||
|
||||
@ -1004,16 +899,12 @@ class Event(models.Model):
|
||||
now = timezone.now()
|
||||
if not self.id:
|
||||
self.created_date = now
|
||||
self.created_by_user = self.processing_user
|
||||
if self.is_in_importation_process():
|
||||
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():
|
||||
self.modified_date = now
|
||||
self.modified_by_user = self.processing_user
|
||||
if self.is_in_moderation_process():
|
||||
self.moderated_date = now
|
||||
self.moderated_by_user = self.processing_user
|
||||
|
||||
def get_recurrence_at_date(self, year, month, day):
|
||||
dtstart = timezone.make_aware(
|
||||
@ -1025,13 +916,10 @@ class Event(models.Model):
|
||||
else:
|
||||
return recurrences[0]
|
||||
|
||||
def get_image_url(self, request=None):
|
||||
def get_image_url(self):
|
||||
if self.local_image and hasattr(self.local_image, "url"):
|
||||
try:
|
||||
if request:
|
||||
return request.build_absolute_uri(self.local_image.url)
|
||||
else:
|
||||
return self.local_image.url
|
||||
return self.local_image.url
|
||||
except:
|
||||
pass
|
||||
if self.image:
|
||||
@ -1129,7 +1017,7 @@ class Event(models.Model):
|
||||
self.update_recurrence_dtstartend()
|
||||
|
||||
# if the image is defined but not locally downloaded
|
||||
if self.image and (not self.local_image or not default_storage.exists(self.local_image.name)):
|
||||
if self.image and not self.local_image:
|
||||
self.download_image()
|
||||
|
||||
# remove "/" from tags
|
||||
@ -1148,44 +1036,6 @@ class Event(models.Model):
|
||||
if not self.category or self.category.name == Category.default_name:
|
||||
CategorisationRule.apply_rules(self)
|
||||
|
||||
|
||||
def get_contributor_message(self):
|
||||
types = [Message.TYPE.FROM_CONTRIBUTOR, Message.TYPE.FROM_CONTRIBUTOR_NO_MSG]
|
||||
if self.other_versions is None or self.other_versions.representative is None:
|
||||
logger.warning("cas single")
|
||||
return Message.objects.filter(related_event=self.pk, message_type__in=types, closed=False)
|
||||
else:
|
||||
logger.warning("cas multiple")
|
||||
return Message.objects.filter(related_event__in=self.other_versions.get_duplicated(), message_type__in=types, closed=False).first()
|
||||
|
||||
|
||||
def notify_if_required(self, request):
|
||||
notif = False
|
||||
if self.status != Event.STATUS.DRAFT:
|
||||
messages = self.get_contributor_message()
|
||||
logger.warning("messages: ")
|
||||
logger.warning(messages)
|
||||
if messages:
|
||||
for message in messages:
|
||||
if message and not message.closed and message.email:
|
||||
# send email
|
||||
context = {"sitename": Site.objects.get_current(request).name, 'event_title': self.title }
|
||||
if self.status == Event.STATUS.PUBLISHED:
|
||||
context["url"] = request.build_absolute_uri(self.get_absolute_url())
|
||||
subject = _('Your event has been published')
|
||||
body = render_to_string("agenda_culturel/emails/published.txt", context)
|
||||
else:
|
||||
subject = _('Your message has not been retained')
|
||||
body = render_to_string("agenda_culturel/emails/retained.txt", context)
|
||||
|
||||
send_mail(subject, body, None, [message.email])
|
||||
message.closed = True
|
||||
message.save()
|
||||
notif = True
|
||||
|
||||
return notif
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.prepare_save()
|
||||
|
||||
@ -1219,21 +1069,6 @@ class Event(models.Model):
|
||||
# first save the current object
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
# notify only if required (and request is known)
|
||||
if "request" in kwargs:
|
||||
self.notify_if_required(kwargs.get("request"))
|
||||
|
||||
# clear cache
|
||||
for is_auth in [False, True]:
|
||||
key = make_template_fragment_key("event_body", [is_auth, self])
|
||||
cache.delete(key)
|
||||
|
||||
# save message if required
|
||||
if self.has_message():
|
||||
msg = self.get_message()
|
||||
msg.related_event = self
|
||||
msg.save()
|
||||
|
||||
# then if its a clone, update the representative
|
||||
if clone:
|
||||
self.other_versions.representative = self
|
||||
@ -1250,8 +1085,6 @@ class Event(models.Model):
|
||||
def from_structure(event_structure, import_source=None):
|
||||
# organisers is a manytomany relation thus cannot be initialised before creation of the event
|
||||
organisers = event_structure.pop('organisers', None)
|
||||
email = event_structure.pop('email', None)
|
||||
comments = event_structure.pop('comments', None)
|
||||
|
||||
if "category" in event_structure and event_structure["category"] is not None:
|
||||
try:
|
||||
@ -1281,6 +1114,8 @@ class Event(models.Model):
|
||||
and event_structure["last_modified"] is not None
|
||||
):
|
||||
d = datetime.fromisoformat(event_structure["last_modified"])
|
||||
if d.year == 2024 and d.month > 2:
|
||||
logger.warning("last modified {}".format(d))
|
||||
if d.tzinfo is None or d.tzinfo.utcoffset(d) is None:
|
||||
d = timezone.make_aware(d, timezone.get_default_timezone())
|
||||
event_structure["modified_date"] = d
|
||||
@ -1328,13 +1163,7 @@ class Event(models.Model):
|
||||
|
||||
result = Event(**event_structure)
|
||||
result.add_pending_organisers(organisers)
|
||||
if email or comments:
|
||||
has_comments = not comments in ["", None]
|
||||
result.set_message(Message(subject=_('during import process'),
|
||||
email=email,
|
||||
message=comments,
|
||||
closed=False,
|
||||
message_type=Message.TYPE.FROM_CONTRIBUTOR if has_comments else Message.TYPE.FROM_CONTRIBUTOR_NO_MSG))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@ -1486,10 +1315,9 @@ class Event(models.Model):
|
||||
# otherwise merge existing groups
|
||||
group = DuplicatedEvents.merge_groups(groups)
|
||||
|
||||
group.save()
|
||||
|
||||
if force_non_fixed:
|
||||
group.representative = None
|
||||
group.save()
|
||||
|
||||
# set the possibly duplicated group for the current object
|
||||
self.other_versions = group
|
||||
@ -1510,8 +1338,6 @@ class Event(models.Model):
|
||||
"category",
|
||||
"tags",
|
||||
]
|
||||
if not no_m2m:
|
||||
result += ["organisers"]
|
||||
|
||||
result += [
|
||||
"title",
|
||||
@ -1523,6 +1349,8 @@ class Event(models.Model):
|
||||
"description",
|
||||
"image",
|
||||
]
|
||||
if not no_m2m:
|
||||
result += ["organisers"]
|
||||
if all and local_img:
|
||||
result += ["local_image"]
|
||||
if all and exact_location:
|
||||
@ -1569,19 +1397,12 @@ class Event(models.Model):
|
||||
self.uuids.append(uuid)
|
||||
|
||||
# add possible missing sources
|
||||
if other.import_sources:
|
||||
if not self.import_sources:
|
||||
self.import_sources = []
|
||||
for source in other.import_sources:
|
||||
if source not in self.import_sources:
|
||||
self.import_sources.append(source)
|
||||
for source in other.import_sources:
|
||||
if source not in self.import_sources:
|
||||
self.import_sources.append(source)
|
||||
|
||||
# Limitation: the given events should not be considered similar one to another...
|
||||
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()
|
||||
def import_events(events, remove_missing_from_source=None):
|
||||
|
||||
to_import = []
|
||||
to_update = []
|
||||
@ -1608,7 +1429,6 @@ class Event(models.Model):
|
||||
|
||||
# imported events should be updated
|
||||
event.set_in_importation_process()
|
||||
event.set_processing_user(user)
|
||||
event.prepare_save()
|
||||
|
||||
# check if the event has already be imported (using uuid)
|
||||
@ -1635,14 +1455,9 @@ class Event(models.Model):
|
||||
same_imported.other_versions.representative = None
|
||||
same_imported.other_versions.save()
|
||||
# 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.set_in_importation_process()
|
||||
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)
|
||||
else:
|
||||
# otherwise, the new event possibly a duplication of the remaining others.
|
||||
@ -1670,23 +1485,13 @@ class Event(models.Model):
|
||||
for e in to_import:
|
||||
if e.is_event_long_duration():
|
||||
e.status = Event.STATUS.DRAFT
|
||||
e.set_message(
|
||||
Message(subject=_('Import'),
|
||||
name=_('import process'),
|
||||
message=_("The duration of the event is a little too long for direct publication. Moderators can choose to publish it or not."),
|
||||
message_type=Message.TYPE.IMPORT_PROCESS)
|
||||
)
|
||||
|
||||
# then import all the new events
|
||||
imported = Event.objects.bulk_create(to_import)
|
||||
# update organisers (m2m relation)
|
||||
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():
|
||||
i.organisers.set(ti.pending_organisers)
|
||||
if ti.has_message():
|
||||
msg = ti.get_message()
|
||||
msg.related_event = i
|
||||
msg.save()
|
||||
|
||||
nb_updated = Event.objects.bulk_update(
|
||||
to_update,
|
||||
@ -1760,12 +1565,13 @@ class Event(models.Model):
|
||||
|
||||
def get_concurrent_events(self, remove_same_dup=True):
|
||||
day = self.current_date if hasattr(self, "current_date") else self.start_day
|
||||
day_events = CalendarDay(day, qs = Event.objects.filter(status=Event.STATUS.PUBLISHED)).get_events()
|
||||
day_events = CalendarDay(self.start_day).get_events()
|
||||
return [
|
||||
e
|
||||
for e in day_events
|
||||
if e != self
|
||||
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)
|
||||
]
|
||||
|
||||
@ -1775,10 +1581,10 @@ class Event(models.Model):
|
||||
|
||||
return (dtstart <= e_dtstart <= dtend) or (e_dtstart <= dtstart <= e_dtend)
|
||||
|
||||
def export_to_ics(events, request):
|
||||
def export_to_ics(events):
|
||||
cal = icalCal()
|
||||
# Some properties are required to be compliant
|
||||
cal.add("prodid", "-//Pommes de lune//pommesdelune.fr//")
|
||||
cal.add("prodid", "-//My calendar product//example.com//")
|
||||
cal.add("version", "2.0")
|
||||
|
||||
for event in events:
|
||||
@ -1829,12 +1635,9 @@ class Event(models.Model):
|
||||
eventIcal.add("summary", event.title)
|
||||
eventIcal.add("name", event.title)
|
||||
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(
|
||||
"description", description + url
|
||||
"description", event.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)
|
||||
|
||||
cal.add_component(eventIcal)
|
||||
@ -1870,50 +1673,16 @@ class Event(models.Model):
|
||||
return [Event.get_count_modification(w) for w in when_list]
|
||||
|
||||
|
||||
class Message(models.Model):
|
||||
|
||||
class TYPE(models.TextChoices):
|
||||
FROM_CONTRIBUTOR = "from_contributor", _("From contributor")
|
||||
IMPORT_PROCESS = "import_process", _("Import process")
|
||||
CONTACT_FORM = "contact_form", _("Contact form")
|
||||
EVENT_REPORT = "event_report", _("Event report")
|
||||
FROM_CONTRIBUTOR_NO_MSG = "from_contrib_no_msg", _("From contributor (without message)")
|
||||
|
||||
class ContactMessage(models.Model):
|
||||
class Meta:
|
||||
verbose_name = _("Message")
|
||||
verbose_name_plural = _("Messages")
|
||||
indexes = [
|
||||
models.Index(fields=['related_event']),
|
||||
models.Index(fields=['user']),
|
||||
models.Index(fields=['date']),
|
||||
models.Index(fields=['spam', 'closed']),
|
||||
]
|
||||
|
||||
|
||||
verbose_name = _("Contact message")
|
||||
verbose_name_plural = _("Contact messages")
|
||||
|
||||
subject = models.CharField(
|
||||
verbose_name=_("Subject"),
|
||||
help_text=_("The subject of your message"),
|
||||
max_length=512,
|
||||
)
|
||||
|
||||
related_event = models.ForeignKey(
|
||||
Event,
|
||||
verbose_name=_("Related event"),
|
||||
help_text=_("The message is associated with this event."),
|
||||
null=True,
|
||||
default=None,
|
||||
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(
|
||||
verbose_name=_("Name"),
|
||||
help_text=_("Your name"),
|
||||
@ -1953,18 +1722,8 @@ class Message(models.Model):
|
||||
null=True,
|
||||
)
|
||||
|
||||
message_type = models.CharField(
|
||||
verbose_name=_("Type"),
|
||||
max_length=20,
|
||||
choices=TYPE.choices,
|
||||
default=None, null=True
|
||||
)
|
||||
|
||||
def nb_open_messages():
|
||||
return Message.objects.filter(Q(closed=False)&Q(spam=False)&Q(message_type__in=[Message.TYPE.CONTACT_FORM, Message.TYPE.EVENT_REPORT, Message.TYPE.FROM_CONTRIBUTOR])).count()
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("message", kwargs={"pk": self.pk})
|
||||
def nb_open_contactmessages():
|
||||
return ContactMessage.objects.filter(closed=False).count()
|
||||
|
||||
|
||||
class RecurrentImport(models.Model):
|
||||
@ -1985,7 +1744,6 @@ class RecurrentImport(models.Model):
|
||||
FBEVENTS = "Facebook events", _("Événements d'une page FB")
|
||||
C3C = "cour3coquins", _("la cour des 3 coquins")
|
||||
ARACHNEE = "arachnee", _("Arachnée concert")
|
||||
LERIO = "rio", _('Le Rio')
|
||||
|
||||
class DOWNLOADER(models.TextChoices):
|
||||
SIMPLE = "simple", _("simple")
|
||||
@ -2053,12 +1811,6 @@ class RecurrentImport(models.Model):
|
||||
blank=True,
|
||||
)
|
||||
|
||||
forceLocation = models.BooleanField(
|
||||
verbose_name=_("Force location"),
|
||||
help_text=_("force location even if another is detected."),
|
||||
default=False
|
||||
)
|
||||
|
||||
defaultOrganiser = models.ForeignKey(
|
||||
Organisation,
|
||||
verbose_name=_("Organiser"),
|
||||
@ -2119,11 +1871,6 @@ class BatchImportation(models.Model):
|
||||
verbose_name = _("Batch importation")
|
||||
verbose_name_plural = _("Batch importations")
|
||||
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)
|
||||
|
||||
|
@ -56,10 +56,9 @@ INSTALLED_APPS = [
|
||||
"robots",
|
||||
"debug_toolbar",
|
||||
"cache_cleaner",
|
||||
"honeypot",
|
||||
]
|
||||
|
||||
HONEYPOT_FIELD_NAME = "alias_name"
|
||||
SITE_ID = 1
|
||||
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
@ -73,7 +72,6 @@ MIDDLEWARE = [
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
||||
'django.contrib.sites.middleware.CurrentSiteMiddleware',
|
||||
# "django.middleware.cache.UpdateCacheMiddleware",
|
||||
# "django.middleware.common.CommonMiddleware",
|
||||
# "django.middleware.cache.FetchFromCacheMiddleware",
|
||||
@ -147,7 +145,7 @@ TIME_ZONE = "Europe/Paris"
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = False
|
||||
USE_TZ = True
|
||||
|
||||
LANGUAGES = (
|
||||
("fr", _("French")),
|
||||
@ -250,7 +248,6 @@ LOCATION_FIELD = {
|
||||
# stop robots
|
||||
|
||||
ROBOTS_USE_SITEMAP = False
|
||||
ROBOTS_SITE_BY_REQUEST = 'cached-sitemap'
|
||||
|
||||
# debug
|
||||
if DEBUG:
|
||||
|
@ -1,13 +0,0 @@
|
||||
from django.contrib import sitemaps
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
class StaticViewSitemap(sitemaps.Sitemap):
|
||||
priority = 0.5
|
||||
changefreq = "daily"
|
||||
|
||||
def items(self):
|
||||
return ["home", "cette_semaine", "ce_mois_ci", "aujourdhui", "a_venir", "about", "contact"]
|
||||
|
||||
def location(self, item):
|
||||
return reverse(item)
|
BIN
src/agenda_culturel/static/images/capture.png
Normal file
BIN
src/agenda_culturel/static/images/capture.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 237 KiB |
@ -34,17 +34,11 @@ const openModal = (modal, back=true) => {
|
||||
}
|
||||
setTimeout(function() {
|
||||
visibleModal = modal;
|
||||
|
||||
console.log("ici");
|
||||
const mask = visibleModal.querySelector(".h-mask");
|
||||
mask.classList.add("visible");
|
||||
}, 350);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const hideModal = (modal) => {
|
||||
if (modal != null) {
|
||||
const mask = visibleModal.querySelector(".h-mask");
|
||||
mask.classList.remove("visible");
|
||||
visibleModal = null;
|
||||
document.documentElement.style.removeProperty("--scrollbar-width");
|
||||
modal.removeAttribute("open");
|
||||
|
@ -1,673 +0,0 @@
|
||||
var SequentialLoader = function() {
|
||||
var SL = {
|
||||
loadJS: function(src, onload) {
|
||||
//console.log(src);
|
||||
// add to pending list
|
||||
this._load_pending.push({'src': src, 'onload': onload});
|
||||
// check if not already loading
|
||||
if ( ! this._loading) {
|
||||
this._loading = true;
|
||||
// load first
|
||||
this.loadNextJS();
|
||||
}
|
||||
},
|
||||
|
||||
loadNextJS: function() {
|
||||
// get next
|
||||
var next = this._load_pending.shift();
|
||||
if (next == undefined) {
|
||||
// nothing to load
|
||||
this._loading = false;
|
||||
return;
|
||||
}
|
||||
// check not loaded
|
||||
if (this._load_cache[next.src] != undefined) {
|
||||
next.onload();
|
||||
this.loadNextJS();
|
||||
return; // already loaded
|
||||
}
|
||||
else {
|
||||
this._load_cache[next.src] = 1;
|
||||
}
|
||||
// load
|
||||
var el = document.createElement('script');
|
||||
el.type = 'application/javascript';
|
||||
el.src = next.src;
|
||||
// onload callback
|
||||
var self = this;
|
||||
el.onload = function(){
|
||||
//console.log('Loaded: ' + next.src);
|
||||
// trigger onload
|
||||
next.onload();
|
||||
// try to load next
|
||||
self.loadNextJS();
|
||||
};
|
||||
document.body.appendChild(el);
|
||||
},
|
||||
|
||||
_loading: false,
|
||||
_load_pending: [],
|
||||
_load_cache: {}
|
||||
};
|
||||
|
||||
return {
|
||||
loadJS: SL.loadJS.bind(SL)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
!function($){
|
||||
var LocationFieldCache = {
|
||||
load: [],
|
||||
onload: {},
|
||||
|
||||
isLoading: false
|
||||
};
|
||||
|
||||
var LocationFieldResourceLoader;
|
||||
|
||||
$.locationField = function(options) {
|
||||
var LocationField = {
|
||||
options: $.extend({
|
||||
provider: 'google',
|
||||
providerOptions: {
|
||||
google: {
|
||||
api: '//maps.google.com/maps/api/js',
|
||||
mapType: 'ROADMAP'
|
||||
}
|
||||
},
|
||||
searchProvider: 'google',
|
||||
id: 'map',
|
||||
latLng: '0,0',
|
||||
mapOptions: {
|
||||
zoom: 9
|
||||
},
|
||||
basedFields: $(),
|
||||
inputField: $(),
|
||||
suffix: '',
|
||||
path: '',
|
||||
fixMarker: true
|
||||
}, options),
|
||||
|
||||
providers: /google|openstreetmap|mapbox/,
|
||||
searchProviders: /google|yandex|nominatim|addok/,
|
||||
|
||||
render: function() {
|
||||
this.$id = $('#' + this.options.id);
|
||||
|
||||
if ( ! this.providers.test(this.options.provider)) {
|
||||
this.error('render failed, invalid map provider: ' + this.options.provider);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! this.searchProviders.test(this.options.searchProvider)) {
|
||||
this.error('render failed, invalid search provider: ' + this.options.searchProvider);
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
this.loadAll(function(){
|
||||
var mapOptions = self._getMapOptions(),
|
||||
map = self._getMap(mapOptions);
|
||||
|
||||
var marker = self._getMarker(map, mapOptions.center);
|
||||
|
||||
// fix issue w/ marker not appearing
|
||||
if (self.options.provider == 'google' && self.options.fixMarker)
|
||||
self.__fixMarker();
|
||||
|
||||
// watch based fields
|
||||
self._watchBasedFields(map, marker);
|
||||
});
|
||||
},
|
||||
|
||||
fill: function(latLng) {
|
||||
this.options.inputField.val(latLng.lat + ',' + latLng.lng);
|
||||
},
|
||||
|
||||
search: function(map, marker, address) {
|
||||
if (this.options.searchProvider === 'google') {
|
||||
var provider = new GeoSearch.GoogleProvider({ apiKey: this.options.providerOptions.google.apiKey });
|
||||
provider.search({query: address}).then(data => {
|
||||
if (data.length > 0) {
|
||||
var result = data[0],
|
||||
latLng = new L.LatLng(result.y, result.x);
|
||||
|
||||
marker.setLatLng(latLng);
|
||||
map.panTo(latLng);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
else if (this.options.searchProvider === 'yandex') {
|
||||
// https://yandex.com/dev/maps/geocoder/doc/desc/concepts/input_params.html
|
||||
var url = 'https://geocode-maps.yandex.ru/1.x/?format=json&geocode=' + address;
|
||||
|
||||
if (typeof this.options.providerOptions.yandex.apiKey !== 'undefined') {
|
||||
url += '&apikey=' + this.options.providerOptions.yandex.apiKey;
|
||||
}
|
||||
|
||||
var request = new XMLHttpRequest();
|
||||
request.open('GET', url, true);
|
||||
|
||||
request.onload = function () {
|
||||
if (request.status >= 200 && request.status < 400) {
|
||||
var data = JSON.parse(request.responseText);
|
||||
var pos = data.response.GeoObjectCollection.featureMember[0].GeoObject.Point.pos.split(' ');
|
||||
var latLng = new L.LatLng(pos[1], pos[0]);
|
||||
marker.setLatLng(latLng);
|
||||
map.panTo(latLng);
|
||||
} else {
|
||||
console.error('Yandex geocoder error response');
|
||||
}
|
||||
};
|
||||
|
||||
request.onerror = function () {
|
||||
console.error('Check connection to Yandex geocoder');
|
||||
};
|
||||
|
||||
request.send();
|
||||
}
|
||||
|
||||
else if (this.options.searchProvider === 'addok') {
|
||||
var url = 'https://api-adresse.data.gouv.fr/search/?limit=1&q=' + address;
|
||||
|
||||
var request = new XMLHttpRequest();
|
||||
request.open('GET', url, true);
|
||||
|
||||
request.onload = function () {
|
||||
if (request.status >= 200 && request.status < 400) {
|
||||
var data = JSON.parse(request.responseText);
|
||||
var pos = data.features[0].geometry.coordinates;
|
||||
var latLng = new L.LatLng(pos[1], pos[0]);
|
||||
marker.setLatLng(latLng);
|
||||
map.panTo(latLng);
|
||||
} else {
|
||||
console.error('Addok geocoder error response');
|
||||
}
|
||||
};
|
||||
|
||||
request.onerror = function () {
|
||||
console.error('Check connection to Addok geocoder');
|
||||
};
|
||||
|
||||
request.send();
|
||||
}
|
||||
|
||||
else if (this.options.searchProvider === 'nominatim') {
|
||||
var url = '//nominatim.openstreetmap.org/search?format=json&q=' + address;
|
||||
|
||||
var request = new XMLHttpRequest();
|
||||
request.open('GET', url, true);
|
||||
|
||||
request.onload = function () {
|
||||
if (request.status >= 200 && request.status < 400) {
|
||||
var data = JSON.parse(request.responseText);
|
||||
if (data.length > 0) {
|
||||
var pos = data[0];
|
||||
var latLng = new L.LatLng(pos.lat, pos.lon);
|
||||
marker.setLatLng(latLng);
|
||||
map.panTo(latLng);
|
||||
} else {
|
||||
console.error(address + ': not found via Nominatim');
|
||||
}
|
||||
} else {
|
||||
console.error('Nominatim geocoder error response');
|
||||
}
|
||||
};
|
||||
|
||||
request.onerror = function () {
|
||||
console.error('Check connection to Nominatim geocoder');
|
||||
};
|
||||
|
||||
request.send();
|
||||
}
|
||||
},
|
||||
|
||||
loadAll: function(onload) {
|
||||
this.$id.html('Loading...');
|
||||
|
||||
// resource loader
|
||||
if (LocationFieldResourceLoader == undefined)
|
||||
LocationFieldResourceLoader = SequentialLoader();
|
||||
|
||||
this.load.loader = LocationFieldResourceLoader;
|
||||
this.load.path = this.options.path;
|
||||
|
||||
var self = this;
|
||||
|
||||
this.load.common(function(){
|
||||
var mapProvider = self.options.provider,
|
||||
onLoadMapProvider = function() {
|
||||
var searchProvider = self.options.searchProvider + 'SearchProvider',
|
||||
onLoadSearchProvider = function() {
|
||||
self.$id.html('');
|
||||
onload();
|
||||
};
|
||||
|
||||
if (self.load[searchProvider] != undefined) {
|
||||
self.load[searchProvider](self.options.providerOptions[self.options.searchProvider] || {}, onLoadSearchProvider);
|
||||
}
|
||||
else {
|
||||
onLoadSearchProvider();
|
||||
}
|
||||
};
|
||||
|
||||
if (self.load[mapProvider] != undefined) {
|
||||
self.load[mapProvider](self.options.providerOptions[mapProvider] || {}, onLoadMapProvider);
|
||||
}
|
||||
else {
|
||||
onLoadMapProvider();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
load: {
|
||||
google: function(options, onload) {
|
||||
var js = [
|
||||
this.path + '/@googlemaps/js-api-loader/index.min.js',
|
||||
this.path + '/Leaflet.GoogleMutant.js',
|
||||
];
|
||||
|
||||
this._loadJSList(js, function(){
|
||||
const loader = new google.maps.plugins.loader.Loader({
|
||||
apiKey: options.apiKey,
|
||||
version: "weekly",
|
||||
});
|
||||
loader.load().then(() => onload());
|
||||
});
|
||||
},
|
||||
|
||||
googleSearchProvider: function(options, onload) {
|
||||
onload();
|
||||
//var url = options.api;
|
||||
|
||||
//if (typeof options.apiKey !== 'undefined') {
|
||||
// url += url.indexOf('?') === -1 ? '?' : '&';
|
||||
// url += 'key=' + options.apiKey;
|
||||
//}
|
||||
|
||||
//var js = [
|
||||
// url,
|
||||
// this.path + '/l.geosearch.provider.google.js'
|
||||
// ];
|
||||
|
||||
//this._loadJSList(js, function(){
|
||||
// // https://github.com/smeijer/L.GeoSearch/issues/57#issuecomment-148393974
|
||||
// L.GeoSearch.Provider.Google.Geocoder = new google.maps.Geocoder();
|
||||
|
||||
// onload();
|
||||
//});
|
||||
},
|
||||
|
||||
yandexSearchProvider: function (options, onload) {
|
||||
onload();
|
||||
},
|
||||
|
||||
mapbox: function(options, onload) {
|
||||
onload();
|
||||
},
|
||||
|
||||
openstreetmap: function(options, onload) {
|
||||
onload();
|
||||
},
|
||||
|
||||
common: function(onload) {
|
||||
var self = this,
|
||||
js = [
|
||||
// map providers
|
||||
this.path + '/leaflet/leaflet.js',
|
||||
// search providers
|
||||
this.path + '/leaflet-geosearch/geosearch.umd.js',
|
||||
],
|
||||
css = [
|
||||
// map providers
|
||||
this.path + '/leaflet/leaflet.css'
|
||||
];
|
||||
|
||||
// Leaflet docs note:
|
||||
// Include Leaflet JavaScript file *after* 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,9 +44,6 @@ $enable-responsive-typography: true;
|
||||
// Modal (<dialog>)
|
||||
--modal-overlay-backdrop-filter: blur(0.05rem);
|
||||
|
||||
--background-color-transparent: color-mix(in srgb, var(--background-color), transparent 30%);
|
||||
|
||||
--background-color-transparent-light: color-mix(in srgb, var(--background-color), transparent 80%);
|
||||
}
|
||||
|
||||
|
||||
@ -150,7 +147,7 @@ details[role="list"] summary + ul li.selected>a:hover {
|
||||
}
|
||||
|
||||
}
|
||||
.suggestions {
|
||||
.suggested-tags {
|
||||
font-size: 80%;
|
||||
}
|
||||
}
|
||||
@ -204,16 +201,10 @@ details[role="list"] summary + ul li.selected>a:hover {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.suggestions .small-cat {
|
||||
.suggested-tags .small-cat {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.small-location {
|
||||
@extend .small-cat;
|
||||
border-color: var(--contrast);
|
||||
color: var(--contrast);
|
||||
}
|
||||
|
||||
.circ-cat.circ-large {
|
||||
height: 2.6em;
|
||||
width: 2.6em;
|
||||
@ -298,7 +289,15 @@ svg {
|
||||
width: 100%;
|
||||
padding: 0.3em;
|
||||
margin: 0 0 0.5em 0;
|
||||
float: right;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 550px) {
|
||||
.illustration {
|
||||
width: 40%;
|
||||
float: right;
|
||||
margin: 0 0 0.5em .5em;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 992px) {
|
||||
@ -324,7 +323,6 @@ footer [data-tooltip] {
|
||||
scroll-behavior: smooth;
|
||||
transition-duration: 200ms;
|
||||
|
||||
|
||||
.cat {
|
||||
margin-right: 0;
|
||||
}
|
||||
@ -497,15 +495,6 @@ body > main {
|
||||
padding-top: 0.2em;
|
||||
}
|
||||
|
||||
body.authenticated > main {
|
||||
padding-top: 0.8em;
|
||||
}
|
||||
@media only screen and (min-width: 700px) {
|
||||
body.authenticated > main {
|
||||
padding-top: 0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
article {
|
||||
margin: 1em 0;
|
||||
}
|
||||
@ -614,7 +603,7 @@ header .remarque {
|
||||
}
|
||||
|
||||
.form.recent, .form.main-filter, .search .form {
|
||||
#id_status>div, #id_representative>div {
|
||||
#id_status>div {
|
||||
display: inline-block;
|
||||
margin-right: 2em;
|
||||
}
|
||||
@ -862,7 +851,7 @@ nav>div {
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (min-width: 1400px) {
|
||||
@media only screen and (min-width: 992px) {
|
||||
.header li {
|
||||
float: left;
|
||||
}
|
||||
@ -904,39 +893,6 @@ nav>div {
|
||||
color: var(--secondary-inverse);
|
||||
}
|
||||
|
||||
#badges {
|
||||
position: absolute;
|
||||
font-size: 70%;
|
||||
top:3.5em;
|
||||
left: 0;
|
||||
padding: 0.2em .5em 0.2em 0.2em;
|
||||
background: var(--card-sectionning-background-color);
|
||||
display: inline-block;
|
||||
border-radius: 0 var(--border-radius) var(--border-radius) 0;
|
||||
|
||||
.badge {
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
|
||||
}
|
||||
.link {
|
||||
margin-left: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 700px) {
|
||||
#badges {
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
left: 50%;
|
||||
top: 0;
|
||||
transform: translate(-50%, 0);
|
||||
padding: 0 .5em .2em .5em;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.tw-badge {
|
||||
background: black;
|
||||
border-color: black;
|
||||
@ -1444,28 +1400,17 @@ img.preview {
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 600px) {
|
||||
.single-event, .tag-description {
|
||||
display: grid;
|
||||
grid-template-columns: 60% auto;
|
||||
grid-column-gap: 1em;
|
||||
}
|
||||
header {
|
||||
grid-column: 1 / 3;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 992px) {
|
||||
.resume {
|
||||
column-count: 4;
|
||||
}
|
||||
.single-event, .tag-description {
|
||||
display: grid;
|
||||
grid-template-columns: 30% auto 14em;
|
||||
grid-column-gap: 1em;
|
||||
|
||||
header {
|
||||
margin: 0;
|
||||
grid-column: 1 / 2;
|
||||
}
|
||||
.illustration {
|
||||
width: auto;
|
||||
@ -1483,38 +1428,19 @@ img.preview {
|
||||
}
|
||||
}
|
||||
|
||||
.header-complement {
|
||||
float: none;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 992px) {
|
||||
.header-complement {
|
||||
float: left;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
form.messages {
|
||||
div {
|
||||
width: 100%;
|
||||
display: block;
|
||||
fieldset div {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width: 800px) {
|
||||
form.messages div, form.moderation-events {
|
||||
@media only screen and (min-width: 992px) {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
:last-child {
|
||||
grid-column: 1 / 4;
|
||||
}
|
||||
div fieldset div {
|
||||
display: block;
|
||||
}
|
||||
grid-template-columns: repeat(2, 50%);
|
||||
}
|
||||
fieldset {
|
||||
float: left;
|
||||
margin-right: 1em;
|
||||
}
|
||||
label {
|
||||
clear: both;
|
||||
float: left;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.moderate-preview .event-body {
|
||||
@ -1522,11 +1448,6 @@ form.messages {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#moderate-form #id_status {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
label.required::after {
|
||||
content: ' *';
|
||||
color: red;
|
||||
@ -1572,234 +1493,6 @@ label.required::after {
|
||||
}
|
||||
}
|
||||
|
||||
.maskable_group {
|
||||
margin: 0.5em 0;
|
||||
.body_group.closed {
|
||||
display: none;
|
||||
}
|
||||
.maskable_group .body_group.closed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.form-place {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
row-gap: .5em;
|
||||
margin-bottom: 0.5em;
|
||||
.map-widget {
|
||||
grid-row: 3;
|
||||
}
|
||||
#group_address .body_group {
|
||||
display: grid;
|
||||
grid-template-columns: repear(2, 1fr);
|
||||
|
||||
column-gap: .5em;
|
||||
#div_id_address, #div_id_location {
|
||||
grid-column: 1 / 3;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width: 992px) {
|
||||
.form-place {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
|
||||
.map-widget {
|
||||
grid-column: 2 / 3;
|
||||
grid-row: 1 / 3;
|
||||
}
|
||||
|
||||
#group_other {
|
||||
grid-column: 1 / 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.line-now {
|
||||
font-size: 60%;
|
||||
div {
|
||||
display: grid;
|
||||
grid-template-columns: fit-content(2em) auto;
|
||||
column-gap: .2em;
|
||||
color: red;
|
||||
.line {
|
||||
margin-top: .7em;
|
||||
border-top: 1px solid red;
|
||||
}
|
||||
}
|
||||
margin-bottom: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.a-venir .line-now {
|
||||
margin-left: -2em;
|
||||
|
||||
}
|
||||
|
||||
#chronology {
|
||||
.entree {
|
||||
display: grid;
|
||||
grid-template-columns: fit-content(2em) auto;
|
||||
column-gap: .7em;
|
||||
.texte {
|
||||
background: var(--background-color);
|
||||
padding: 0.1em 0.8em;
|
||||
border-radius: var(--border-radius);
|
||||
p {
|
||||
font-size: 100%;
|
||||
}
|
||||
p:last-child {
|
||||
margin-bottom: 0.1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
font-size: 85%;
|
||||
footer {
|
||||
margin-top: 1.8em;
|
||||
padding: 0.2em .8em;
|
||||
}
|
||||
.ts {
|
||||
@extend .badge-small;
|
||||
border-radius: var(--border-radius);
|
||||
display: inline-block;
|
||||
width: 14em;
|
||||
margin-right: 1.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.moderation_heatmap {
|
||||
overflow-x: auto;
|
||||
table {
|
||||
max-width: 600px;
|
||||
margin: auto;
|
||||
.total, .month {
|
||||
display: none;
|
||||
}
|
||||
.label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
th {
|
||||
font-size: 90%;
|
||||
text-align: center;
|
||||
}
|
||||
td {
|
||||
font-size: 80%;
|
||||
text-align: center;
|
||||
}
|
||||
tbody th {
|
||||
text-align: right;
|
||||
}
|
||||
.ratio {
|
||||
padding: 0.1em;
|
||||
a, .a {
|
||||
margin: auto;
|
||||
border-radius: var(--border-radius);
|
||||
color: black;
|
||||
padding: 0;
|
||||
display: block;
|
||||
max-width: 6em;
|
||||
width: 3.2em;
|
||||
height: 2em;
|
||||
line-height: 2em;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
.score_0 {
|
||||
a, .a {
|
||||
background: rgb(0, 128, 0);
|
||||
}
|
||||
a:hover {
|
||||
background: rgb(0, 176, 0);
|
||||
}
|
||||
}
|
||||
.score_1 {
|
||||
a, .a {
|
||||
background: rgb(255, 255, 0);
|
||||
}
|
||||
a:hover {
|
||||
background: rgb(248, 248, 121);
|
||||
}
|
||||
}
|
||||
.score_2 {
|
||||
a, .a {
|
||||
background: rgb(255, 166, 0);
|
||||
}
|
||||
a:hover {
|
||||
background: rgb(255, 182, 47);
|
||||
}
|
||||
}
|
||||
.score_3 {
|
||||
a, .a {
|
||||
background: rgb(255, 0, 0);
|
||||
}
|
||||
a:hover {
|
||||
background: rgb(255, 91, 91);
|
||||
}
|
||||
}
|
||||
.score_4 {
|
||||
a, .a {
|
||||
background: rgb(128, 0, 128);
|
||||
color: white;
|
||||
}
|
||||
a:hover {
|
||||
background: rgb(178, 0, 178);
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width: 1800px) {
|
||||
.total, .month {
|
||||
display: inline;
|
||||
opacity: .35;
|
||||
}
|
||||
.label {
|
||||
display: table-cell;
|
||||
}
|
||||
}
|
||||
@media only screen and (min-width: 1600px) {
|
||||
.ratio {
|
||||
a, .a {
|
||||
width: 5em;
|
||||
height: 3.2em;
|
||||
line-height: 3.2em;
|
||||
}
|
||||
}
|
||||
font-size: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dialog {
|
||||
.h-image {
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center, center;
|
||||
}
|
||||
|
||||
.h-mask {
|
||||
background-color: var(--background-color);
|
||||
margin: calc(var(--spacing) * -1.5);
|
||||
padding: calc(var(--spacing) * 1.5);
|
||||
}
|
||||
.h-mask.visible {
|
||||
background-color: var(--background-color-transparent);
|
||||
transition: background-color .8s ease-in;
|
||||
}
|
||||
.h-mask.visible:hover {
|
||||
background-color: var(--background-color-transparent-light);
|
||||
}
|
||||
}
|
||||
|
||||
.visible-link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.detail-link {
|
||||
text-align: right;
|
||||
padding-right: 0.4em;
|
||||
.visible-link {
|
||||
color: var(--contrast);
|
||||
}
|
||||
}
|
||||
.week-in-month {
|
||||
article {
|
||||
.visible-link {
|
||||
color: var(--contrast);
|
||||
}
|
||||
}
|
||||
}
|
@ -17,53 +17,13 @@
|
||||
{% block content %}
|
||||
<div class="grid two-columns">
|
||||
<div id="contenu-principal">
|
||||
<div>
|
||||
|
||||
|
||||
<article>
|
||||
<header>
|
||||
<div class="slide-buttons">
|
||||
<a href="{% url 'moderate' %}" role="button">Modérer {% picto_from_name "check-square" %}</a>
|
||||
</div>
|
||||
<h2>Modération à venir</h2>
|
||||
</header>
|
||||
<div class="grid">
|
||||
<div>
|
||||
{% url 'administration' as local_url %}
|
||||
{% include "agenda_culturel/static_content.html" with name="administration" url_path=local_url %}
|
||||
</div>
|
||||
<div class="moderation_heatmap">
|
||||
{% for w in nb_not_moderated %}
|
||||
<table>
|
||||
<thead>
|
||||
<th class="label"></th>
|
||||
{% for m in w %}
|
||||
<th><a href="{% url 'day_view' m.start_day.year m.start_day.month m.start_day.day %}">{{ m.start_day|date:"d" }}<span class="month"> {{ m.start_day|date:"M"|lower }}</span></a></th>
|
||||
{% endfor %}
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th class="label">reste à modérer</h>
|
||||
{% for m in w %}
|
||||
<td class="ratio score_{{ m.note }}">
|
||||
<{% if m.not_moderated > 0 %}a href="{% if m.is_today %}
|
||||
{% url 'moderate' %}
|
||||
{% else %}
|
||||
{% url 'moderate_from_date' m.start_day.year m.start_day.month m.start_day.day %}
|
||||
{% endif %}"{% else %}span class="a"{% endif %}>
|
||||
{{ m.not_moderated }}<span class="total"> / {{ m.nb_events }}</span></{% if m.not_moderated > 0 %}a{% else %}span{% endif %}>
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<article>
|
||||
<header>
|
||||
<div class="slide-buttons">
|
||||
<a href="{% url 'moderate' %}" role="button">Modérer {% picto_from_name "check-square" %}</a>
|
||||
</div>
|
||||
|
||||
<h2>Activité des derniers jours</h2>
|
||||
</header>
|
||||
<h3>Résumé des activités</h3>
|
||||
@ -76,8 +36,7 @@
|
||||
{% include "agenda_culturel/rimports-info-inc.html" with all=1 %}</p>
|
||||
|
||||
</article>
|
||||
</div>
|
||||
|
||||
|
||||
<article>
|
||||
<header>
|
||||
<div class="slide-buttons">
|
||||
|
@ -1,27 +0,0 @@
|
||||
{% extends "agenda_culturel/page-admin.html" %}
|
||||
|
||||
|
||||
{% block fluid %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<article>
|
||||
<header>
|
||||
<h1>{% block title %}{% block og_title %}Vider le cache{% endblock %}{% endblock %}</h1>
|
||||
</header>
|
||||
<form method="post">{% csrf_token %}
|
||||
<p>Êtes-vous sûr·e de vouloir vider le cache ? 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 %}
|
@ -0,0 +1,37 @@
|
||||
{% extends "agenda_culturel/page-admin.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{% block og_title %}Contact{% endblock %}{% endblock %}
|
||||
|
||||
{% block entete_header %}
|
||||
<script>window.CKEDITOR_BASEPATH = '/static/ckeditor/ckeditor/';</script>
|
||||
<script src="/static/admin/js/vendor/jquery/jquery.js"></script>
|
||||
<script src="/static/admin/js/jquery.init.js"></script>
|
||||
<link href="{% static 'css/django_better_admin_arrayfield.min.css' %}" type="text/css" media="all" rel="stylesheet">
|
||||
<script src="{% static 'js/django_better_admin_arrayfield.min.js' %}"></script>
|
||||
<script src="{% static 'js/adjust_datetimes.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block fluid %}{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>Contact</h1>
|
||||
|
||||
<article>
|
||||
{% url 'contact' as local_url %}
|
||||
{% include "agenda_culturel/static_content.html" with name="contact" url_path=local_url %}
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<header>
|
||||
<p class="message warning"><strong>Attention :</strong> n'utilisez pas le formulaire ci-dessous pour proposer un événement, il sera ignoré. Utilisez plutôt la page <a href="{% url 'add_event' %}">ajouter un événement</a>.</p>
|
||||
</header>
|
||||
<form method="post">{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" value="Envoyer">
|
||||
</form>
|
||||
</article>
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,52 @@
|
||||
{% extends "agenda_culturel/page-admin.html" %}
|
||||
{% load static %}
|
||||
{% load utils_extra %}
|
||||
|
||||
{% block title %}{% block og_title %}Message de contact : {{ obj.subject }}{% endblock %}{% endblock %}
|
||||
|
||||
{% block entete_header %}
|
||||
<script>window.CKEDITOR_BASEPATH = '/static/ckeditor/ckeditor/';</script>
|
||||
<script src="/static/admin/js/vendor/jquery/jquery.js"></script>
|
||||
<script src="/static/admin/js/jquery.init.js"></script>
|
||||
<link href="{% static 'css/django_better_admin_arrayfield.min.css' %}" type="text/css" media="all" rel="stylesheet">
|
||||
<script src="{% static 'js/django_better_admin_arrayfield.min.js' %}"></script>
|
||||
<script src="{% static 'js/adjust_datetimes.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block sidemenu-bouton %}
|
||||
<li><a href="#contenu-principal" aria-label="Aller au contenu">{% picto_from_name "chevron-up" %}</a></li>
|
||||
<li><a href="#sidebar" aria-label="Aller au menu latéral">{% picto_from_name "chevron-down" %}</a></li>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="grid two-columns">
|
||||
<div>
|
||||
<article>
|
||||
<header>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<h1>Modération du message « {{ object.subject }} »</h1>
|
||||
<ul>
|
||||
<li>Date : {{ object.date }}</li>
|
||||
<li>Auteur : {{ object.name }} <a href="mailto:{{ object.email }}">{{ object.email }}</a></li>
|
||||
</ul>
|
||||
</header>
|
||||
<div>
|
||||
{{ object.message }}
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<form method="post">{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" value="Enregistrer">
|
||||
</form>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
{% include "agenda_culturel/side-nav.html" with current="contactmessages" %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -34,10 +34,8 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Type</th>
|
||||
<th>Sujet</th>
|
||||
<th>Auteur</th>
|
||||
<th>Événement</th>
|
||||
<th>Fermé</th>
|
||||
<th>Spam</th>
|
||||
</tr>
|
||||
@ -46,10 +44,8 @@
|
||||
{% for obj in paginator_filter %}
|
||||
<tr>
|
||||
<td>{{ obj.date }}</td>
|
||||
<td>{% if obj.message_type %}{{ obj.get_message_type_display }}{% else %}-{% endif %}</td>
|
||||
<td><a href="{% url 'message' obj.pk %}">{{ obj.subject }}</a></td>
|
||||
<td>{% if obj.user %}<em>{{ obj.user }}</em>{% else %}{% if obj.name %}{{ obj.name }}{% else %}-{% endif %}{% endif %}</td>
|
||||
<td>{% if obj.related_event %}<a href="{{ obj.related_event.get_absolute_url }}">{{ obj.related_event.pk }}</a>{% else %}-{% endif %}</td>
|
||||
<td><a href="{% url 'contactmessage' obj.pk %}">{{ obj.subject }}</a></td>
|
||||
<td>{{ obj.name }}</td>
|
||||
<td>{% if obj.closed %}{% picto_from_name "check-square" "fermé" %}{% else %}{% picto_from_name "square" "ouvert" %}{% endif %}</td>
|
||||
<td>{% if obj.spam %}{% picto_from_name "check-square" "spam" %}{% else %}{% picto_from_name "square" "non spam" %}{% endif %}</td>
|
||||
</tr>
|
||||
@ -61,7 +57,7 @@
|
||||
</footer>
|
||||
</article>
|
||||
|
||||
{% include "agenda_culturel/side-nav.html" with current="messages" %}
|
||||
{% include "agenda_culturel/side-nav.html" with current="contactmessages" %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -12,7 +12,7 @@
|
||||
{% if local %}
|
||||
<a href="{{ local.get_absolute_url }}" role="button">voir la version locale {% picto_from_name "eye" %}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'clone_edit' event.id %}" role="button">modifier en copie locale {% picto_from_name "plus-circle" %}</a>
|
||||
<a href="{% url 'clone_edit' event.id %}" role="button">créer une copie locale {% picto_from_name "plus-circle" %}</a>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% else %}
|
||||
|
@ -1,9 +0,0 @@
|
||||
Bonjour,
|
||||
|
||||
Nous avons le plaisir de t'informer que l'événement « {{ event_title }} » que tu as proposé sur {{ sitename }} a été validé et publié par l'équipe de modération.
|
||||
Tu peux dès maintenant le retrouver à l'adresse suivante :
|
||||
- {{ url }}
|
||||
|
||||
Merci de participer à l'amélioration de {{ sitename }}. N'hésites pas à continuer à contribuer en ajoutant de nouveaux événements, ça nous fait bien plaisir.
|
||||
|
||||
L'équipe de modération.
|
@ -1,8 +0,0 @@
|
||||
Bonjour,
|
||||
|
||||
Nous avons la dure tâche de t'informer que l'événement « {{ event_title }} » que tu avais proposé sur {{ sitename }} n'a pas été retenu par l'équipe de modération.
|
||||
|
||||
Nous te remercions pour cette proposition, et espérons qu'une prochaine fois, ta proposition correspondra à la ligne portée par {{ sitename }}.
|
||||
|
||||
L'équipe de modération.
|
||||
|
@ -1,13 +1,13 @@
|
||||
{% if user.is_authenticated %}
|
||||
<p class="footer">Création : {{ event.created_date }}{% if event.created_by_user %} par <em>{{ event.created_by_user.username }}</em>{% endif %}
|
||||
<p class="footer">Création : {{ event.created_date }}
|
||||
{% if event.modified %}
|
||||
— dernière modification : {{ event.modified_date }}{% if event.modified_by_user %} par <em>{{ event.modified_by_user.username }}</em>{% endif %}
|
||||
— dernière modification : {{ event.modified_date }}
|
||||
{% endif %}
|
||||
{% if event.imported_date %}
|
||||
— dernière importation : {{ event.imported_date }}{% if event.imported_by_user %} par <em>{{ event.imported_by_user.username }}</em>{% endif %}
|
||||
— dernière importation : {{ event.imported_date }}
|
||||
{% endif %}
|
||||
{% if event.moderated_date %}
|
||||
— dernière modération : {{ event.moderated_date }}{% if event.moderated_by_user %} par <em>{{ event.moderated_by_user.username }}</em>{% endif %}
|
||||
— dernière modération : {{ event.moderated_date }}
|
||||
{% endif %}
|
||||
{% if event.pure_import %}
|
||||
— <strong>version importée</strong>
|
||||
|
@ -1,12 +1,10 @@
|
||||
<footer class="remarque">
|
||||
<strong>Informations complémentaires non éditables</strong>
|
||||
Informations complémentaires non éditables :
|
||||
<ul>
|
||||
{% if not allbutdates %}
|
||||
{% 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.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.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.created_date %}<li>Création : {{ object.created_date }}</li>{% endif %}
|
||||
{% if object.modified_date %}<li>Dernière modification : {{ object.modified_date }}</li>{% endif %}
|
||||
{% if object.moderated_date %}<li>Dernière modération : {{ object.moderated_date }}</li>{% endif %}
|
||||
{% if object.imported_date %}<li>Dernière importation : {{ object.imported_date }}</li>{% endif %}
|
||||
{% if object.uuids %}
|
||||
{% if object.uuids|length > 0 %}
|
||||
<li>UUIDs (identifiants uniques d'événements dans les sources) :
|
||||
|
@ -81,7 +81,7 @@ Duplication de {% else %}
|
||||
{{ form }}
|
||||
<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>
|
||||
<input type="submit" value="Enregistrer{% if form.is_clone_from_url %} et modérer{% endif %}">
|
||||
<input type="submit" value="Enregistrer">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
@ -33,39 +33,31 @@
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" id="moderate-form">{% csrf_token %}
|
||||
<form method="post" enctype="multipart/form-data">{% csrf_token %}
|
||||
|
||||
<div class="grid moderate-preview">
|
||||
|
||||
<div>
|
||||
{% include "agenda_culturel/single-event/event-single-inc.html" with event=event onlyedit=1 %}
|
||||
|
||||
{% with event.get_concurrent_events as concurrent_events %}
|
||||
{% if concurrent_events %}
|
||||
<article>
|
||||
<header>
|
||||
<h2>En même temps</h2>
|
||||
<p class="remarque">{% if concurrent_events|length > 1 %}Plusieurs événements se déroulent en même temps.{% else %}Un autre événement se déroule en même temps.{% endif %}</p>
|
||||
</header>
|
||||
<ul>
|
||||
{% for e in concurrent_events %}
|
||||
<li>
|
||||
{{ e.category|circle_cat }} {% if e.start_time %}{{ e.start_time }}{% else %}<em>toute la journée</em>{% endif %} <a href="{{ e.get_absolute_url }}">{{ e.title }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</article>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
</div>
|
||||
{% include "agenda_culturel/single-event/event-single-inc.html" with event=event noedit=1 %}
|
||||
|
||||
<article>
|
||||
<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>
|
||||
{% if event.moderated_date %}
|
||||
<p class="message info">Cet événement a déjà été modéré {% if event.moderation_by_user %}par {<em>{ event.moderation_by_user.username }}</em> {% endif %}le {{ event.moderated_date }}.
|
||||
<p class="message info">Cet événement a déjà été modéré par le {{ event.moderated_date }}.
|
||||
Vous pouvez bien sûr modifier de nouveau ces méta-informations en utilisant
|
||||
le formulaire ci-après.
|
||||
</p>
|
||||
@ -77,13 +69,23 @@
|
||||
</div>
|
||||
<div class="grid buttons">
|
||||
{% if pred %}
|
||||
<a href="{% url 'moderate_event' pred %}" class="secondary" role="button">< Revenir au précédent</a>
|
||||
<a href="{% url 'moderate_event' pred %}" role="button">🠄 Revenir au précédent</a>
|
||||
{% else %}
|
||||
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{{ object.get_absolute_url }}{% endif %}" role="button" class="secondary">Annuler</a>
|
||||
{% endif %}
|
||||
<input type="submit" value="Enregistrer" name="save">
|
||||
<input type="submit" value="Enregistrer et passer au suivant >" name="save_and_next">
|
||||
<a href="{% url 'moderate_event_next' event.pk %}" class="secondary" role="button">Passer au suivant sans enregistrer ></a>
|
||||
{% with event.get_local_version as local %}
|
||||
{% if local %}
|
||||
{% 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>
|
||||
</form>
|
||||
</article>
|
||||
|
@ -2,7 +2,6 @@
|
||||
{% load tag_extra %}
|
||||
{% load utils_extra %}
|
||||
{% load static %}
|
||||
{% load locations_extra %}
|
||||
|
||||
{% if noarticle == 0 %}
|
||||
<article id="filters">
|
||||
@ -66,10 +65,8 @@
|
||||
<button type="submit">Appliquer le filtre</button>
|
||||
</form>
|
||||
</details>
|
||||
<div class="suggestions">
|
||||
Suggestion :
|
||||
{% show_suggested_positions filter=filter %}
|
||||
{% show_suggested_tags filter=filter %}
|
||||
<div class="suggested-tags">
|
||||
{% show_suggested_tags filter=filter %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
|
@ -6,13 +6,7 @@
|
||||
{% for group, fields in form.fields_by_group %}
|
||||
<div {% if group.maskable %}class="maskable_group"{% endif %} id="group_{{ group.id }}">
|
||||
{% if group.maskable %}
|
||||
<input
|
||||
class="toggle_body"
|
||||
type="checkbox"
|
||||
id="maskable_group_{{ group.id }}"
|
||||
name="group_{{ group.id }}"
|
||||
{% if not group.default_masked %}checked{% endif %}
|
||||
><label for="maskable_group_{{ group.id }}">{{ group.label }}</label>
|
||||
<input class="toggle_body" type="checkbox" id="maskable_group_{{ group.id }}" name="group_{{ group.id }}"><label for="maskable_group_{{ group.id }}">{{ group.label }}</label>
|
||||
{% endif %}
|
||||
<div class="error_group">
|
||||
{% for field in fields %}
|
||||
@ -42,9 +36,7 @@
|
||||
<script>
|
||||
const maskables = document.querySelectorAll('.maskable_group');
|
||||
maskables.forEach(function (item) {
|
||||
if (!item.checked) {
|
||||
item.querySelector('.body_group').classList.add('closed');
|
||||
}
|
||||
item.querySelector('.body_group').classList.add('closed');
|
||||
console.log('item ' + item);
|
||||
|
||||
item.querySelector('.toggle_body').addEventListener('change', (event) => {
|
||||
|
@ -21,7 +21,7 @@
|
||||
<form method="post" action="">
|
||||
{% csrf_token %}
|
||||
{{ form.media }}
|
||||
{{ form }}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" value="Lancer l'import" id="import-button">
|
||||
</form>
|
||||
<p>Si tu as plein d'événements à ajouter, tu peux les <a href="{% url 'add_event_urls' %}" >ajouter par lots</a>.</p>
|
||||
|
@ -28,12 +28,6 @@
|
||||
|
||||
{{ formset.management_form }}
|
||||
{% csrf_token %}
|
||||
{% if contactform %}
|
||||
<article>
|
||||
{{ contactform }}
|
||||
</article>
|
||||
{% endif %}
|
||||
|
||||
{% for form in formset %}
|
||||
<article>
|
||||
<header>
|
||||
|
@ -1,51 +0,0 @@
|
||||
{% extends "agenda_culturel/page-admin.html" %}
|
||||
{% load static %}
|
||||
{% load honeypot %}
|
||||
|
||||
{% block title %}{% block og_title %}{% if form.event %}Contact au sujet de l'événement {{ form.event.title }}{% else %}
|
||||
Contact{% endif %}{% endblock %}{% endblock %}
|
||||
|
||||
{% block entete_header %}
|
||||
<script>window.CKEDITOR_BASEPATH = '/static/ckeditor/ckeditor/';</script>
|
||||
<script src="/static/admin/js/vendor/jquery/jquery.js"></script>
|
||||
<script src="/static/admin/js/jquery.init.js"></script>
|
||||
<link href="{% static 'css/django_better_admin_arrayfield.min.css' %}" type="text/css" media="all" rel="stylesheet">
|
||||
<script src="{% static 'js/django_better_admin_arrayfield.min.js' %}"></script>
|
||||
<script src="{% static 'js/adjust_datetimes.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block fluid %}{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
<article>
|
||||
<header>
|
||||
<h1>{% if form.event %}Contact au sujet de l'événement « {{ form.event.title }} »{% else %}
|
||||
Contact{% endif %}</h1>
|
||||
|
||||
{% if not form.event %}
|
||||
<article>
|
||||
{% url 'contact' as local_url %}
|
||||
{% include "agenda_culturel/static_content.html" with name="contact" url_path=local_url %}
|
||||
</article>
|
||||
{% endif %}
|
||||
|
||||
<p class="message warning"><strong>Attention :</strong> n'utilisez pas le formulaire ci-dessous pour proposer un événement, il sera ignoré. Utilisez plutôt la page <a href="{% url 'add_event' %}">ajouter un événement</a>.</p>
|
||||
{% if form.event %}
|
||||
<p>Tu nous contactes au sujet de l'événement « {{ form.event.title }} » du {{ form.event.start_day }}.
|
||||
N'hésites pas à nous indiquer le maximum de contexte et à nous laisser ton adresse
|
||||
afin que l'on puisse répondre à tes demandes ou remarques.
|
||||
</p>
|
||||
{% endif %}
|
||||
</header>
|
||||
<form method="post">{% csrf_token %}
|
||||
{% render_honeypot_field "alias_name" %}
|
||||
{{ form.media }}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" value="Envoyer">
|
||||
</form>
|
||||
</article>
|
||||
|
||||
{% endblock %}
|
@ -1,63 +0,0 @@
|
||||
{% extends "agenda_culturel/page-admin.html" %}
|
||||
{% load static %}
|
||||
{% load utils_extra %}
|
||||
|
||||
{% block title %}{% block og_title %}Message {{ obj.subject }}{% endblock %}{% endblock %}
|
||||
|
||||
{% block entete_header %}
|
||||
<script>window.CKEDITOR_BASEPATH = '/static/ckeditor/ckeditor/';</script>
|
||||
<script src="/static/admin/js/vendor/jquery/jquery.js"></script>
|
||||
<script src="/static/admin/js/jquery.init.js"></script>
|
||||
<link href="{% static 'css/django_better_admin_arrayfield.min.css' %}" type="text/css" media="all" rel="stylesheet">
|
||||
<script src="{% static 'js/django_better_admin_arrayfield.min.js' %}"></script>
|
||||
<script src="{% static 'js/adjust_datetimes.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block sidemenu-bouton %}
|
||||
<li><a href="#contenu-principal" aria-label="Aller au contenu">{% picto_from_name "chevron-up" %}</a></li>
|
||||
<li><a href="#sidebar" aria-label="Aller au menu latéral">{% picto_from_name "chevron-down" %}</a></li>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="grid two-columns">
|
||||
<div>
|
||||
<article>
|
||||
<header>
|
||||
<div class="slide-buttons">
|
||||
<a href="{% url 'delete_message' object.id %}" role="button" data-tooltip="Supprimer le message">Supprimer {% picto_from_name "trash-2" %}</a>
|
||||
</div>
|
||||
|
||||
<h1>Message « {{ object.subject }} »</h1>
|
||||
<ul>
|
||||
<li>Date : {{ object.date.date }} à {{ object.date.time }}</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 %}
|
||||
<li>Type : {% if object.message_type %}{{ object.get_message_type_display }}{% else %}-{% endif %}</li>
|
||||
</ul>
|
||||
</header>
|
||||
<div>
|
||||
{{ object.message | safe }}
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
{% if object.message_type == "from_contributor" or object.message_type == "from_contrib_no_msg" %}<p class="message info">Ce message a été envoyé par une personne lors
|
||||
de l'ajout d'un événement.
|
||||
{% if object.closed %}
|
||||
En décochant fermé, vous modifiez manuellement son statut, et cela pourra entraîner l'envoi d'un message de notification
|
||||
lors de la modification future de l'événement associé.{% else %}
|
||||
En cochant fermé, vous modifiez manuellement son statut, et cela empêchera l'envoi d'un message de notification
|
||||
lors de la modification future de l'événement associé.
|
||||
{% endif %}
|
||||
</p>{% endif %}
|
||||
<form method="post">{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" value="Enregistrer">
|
||||
</form>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
{% include "agenda_culturel/side-nav.html" with current="messages" %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -35,8 +35,12 @@
|
||||
const places = document.querySelector('#id_principal_place');
|
||||
const choices_places = new Choices(places,
|
||||
{
|
||||
placeholderValue: 'Sélectionner le lieu principal ',
|
||||
allowHTML: true,
|
||||
delimiter: ',',
|
||||
removeItemButton: true,
|
||||
shouldSort: false,
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
|
@ -3,11 +3,10 @@
|
||||
{% load cat_extra %}
|
||||
{% load utils_extra %}
|
||||
{% load event_extra %}
|
||||
{% load cache %}
|
||||
|
||||
|
||||
{% block title %}{% block og_title %}{{ event.title }}{% endblock %}{% endblock %}
|
||||
{% block og_image %}{% if event.has_image_url %}{{ event|get_image_uri:request }}{% else %}{{ block.super }}{% endif %}{% endblock %}
|
||||
{% block og_image %}{% if event.has_image_url %}{{ event.get_image_url }}{% else %}{{ block.super }}{% endif %}{% endblock %}
|
||||
{% block og_description %}{% if event.description %}{{ event.description |truncatewords:20|linebreaks }}{% else %}{{ block.super }}{% endif %}{% endblock %}
|
||||
|
||||
{% block entete_header %}
|
||||
@ -17,62 +16,21 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
|
||||
<div class="grid two-columns">
|
||||
<div>
|
||||
{% with cache_timeout=user.is_authenticated|yesno:"30,600" %}
|
||||
{% cache cache_timeout event_body user.is_authenticated event %}
|
||||
{% include "agenda_culturel/single-event/event-single-inc.html" with event=event filter=filter %}
|
||||
{% endcache %}
|
||||
{% endwith %}
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
<article id="chronology">
|
||||
<article>
|
||||
<header>
|
||||
<h2>Chronologie</h2>
|
||||
<h2>Informations internes</h2>
|
||||
</header>
|
||||
{% for step in event.chronology %}
|
||||
{% if step.is_date %}
|
||||
<div class="entree dateline">
|
||||
<div><span class="ts">{{ step.timestamp }}</span></div>
|
||||
<div>
|
||||
{% if step.data == "created_date" %}<em>création</em>{% if event.created_by_user %} par {{ event.created_by_user.username }}{% endif %}{% endif %}
|
||||
{% if step.data == "modified_date" %}<em>dernière modification</em>{% if event.modified_by_user %} par {{ event.modified_by_user.username }}{% endif %}{% endif %}
|
||||
{% if step.data == "moderated_date" %}<em>dernière modération</em>{% if event.moderated_by_user %} par {{ event.moderated_by_user.username }}{% endif %}{% endif %}
|
||||
{% if step.data == "imported_date" %}<em>dernière importation</em>{% if event.imported_by_user %} par {{ event.imported_by_user.username }}{% endif %}{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="entree">
|
||||
<div><span class="ts">{{ step.timestamp }}</span></div>
|
||||
<div>
|
||||
<header><strong>Message{% if not step.data.closed %} (ouvert){% endif %}</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 {% if step.data.name %}{{ step.data.name }}{% if step.data.email %} (<a href="mailto: {{ step.data.email }}">{{ step.data.email }}</a>){% endif %}{% else %} <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 %}
|
||||
{% include "agenda_culturel/event-info-inc.html" with object=event %}
|
||||
</article>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
{% with cache_timeout=user.is_authenticated|yesno:"30,600" %}
|
||||
{% cache cache_timeout event_aside user.is_authenticated event %}
|
||||
<aside>
|
||||
{% with event.get_concurrent_events as concurrent_events %}
|
||||
{% if concurrent_events %}
|
||||
@ -93,7 +51,39 @@
|
||||
</article>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<article>
|
||||
{% with event.get_nb_events_same_dates as nb_events_same_dates %}
|
||||
{% with nb_events_same_dates|length as c_dates %}
|
||||
<header>
|
||||
<h2>Voir aussi</h2>
|
||||
{% if c_dates != 1 %}
|
||||
<p class="remarque">
|
||||
Retrouvez ci-dessous tous les événements
|
||||
{% if event.is_single_day %}
|
||||
à la même date
|
||||
{% else %}
|
||||
aux mêmes dates
|
||||
{% endif %}
|
||||
que l'événement affiché.
|
||||
</p>
|
||||
{% endif %}
|
||||
</header>
|
||||
<nav>
|
||||
{% if c_dates == 1 %}
|
||||
<a role="button" href="{% url 'day_view' nb_events_same_dates.0.1.year nb_events_same_dates.0.1.month nb_events_same_dates.0.1.day %}">Toute la journée</a>
|
||||
{% else %}
|
||||
<ul>
|
||||
{% for nbevents_date in nb_events_same_dates %}
|
||||
<li>
|
||||
<a href="{% url 'day_view' nbevents_date.1.year nbevents_date.1.month nbevents_date.1.day %}">{{ nbevents_date.0 }} événement{{ nbevents_date.0 | pluralize }} le {{ nbevents_date.1 }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</nav>
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
</article>
|
||||
{% if event.other_versions and not event.other_versions.fixed %}
|
||||
{% with poss_dup=event.get_other_versions|only_allowed:user.is_authenticated %}
|
||||
{% if poss_dup|length > 0 %}
|
||||
@ -132,15 +122,12 @@
|
||||
{% else %}
|
||||
Signaler comme doublon
|
||||
{% endif %}</a>
|
||||
<a role="button" href="{% url 'message_for_event' event.pk %}">Signaler cet événement</a>
|
||||
</article>
|
||||
</article>
|
||||
|
||||
|
||||
</aside>
|
||||
{% endcache %}
|
||||
{% endwith %}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
@ -84,7 +84,7 @@
|
||||
</script>
|
||||
{% endif %}
|
||||
<header{% if day.is_today %} id="today"{% endif %}>
|
||||
<h3><a href="{{ day.date | url_day }}?{{ filter.get_url }}" class="visible-link">{{ day.date | date:"l j" }}</a></h3}>
|
||||
<h3><a href="{{ day.date | url_day }}?{{ filter.get_url }}">{{ day.date | date:"l j" }}</a></h3}>
|
||||
</header>
|
||||
{% if day.events %}
|
||||
<ul>
|
||||
@ -107,7 +107,7 @@
|
||||
{% if event.start_day == day.date and event.start_time %}
|
||||
{{ event.start_time }}
|
||||
{% endif %}
|
||||
<a href="{{ event.get_absolute_url }}">{{ event|picto_status }} {{ event.title|no_emoji }} {{ event|tw_badge }}</a>
|
||||
<a href="{{ event.get_absolute_url }}">{{ event|picto_status }} {{ event.title }} {{ event|tw_badge }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
@ -121,8 +121,7 @@
|
||||
</article>
|
||||
</dialog>
|
||||
{% endfor %}
|
||||
<li class="detail-link"><a href="{{ day.date | url_day }}?{{ filter.get_url }}" class="visible-link">voir en détail {% picto_from_name "chevrons-right" %}</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</article>
|
||||
|
@ -39,13 +39,13 @@
|
||||
<li><strong>Adresse naviguable :</strong> <a href="{{ object.browsable_url }}">{{ object.browsable_url }}</a></li>
|
||||
<li><strong>Valeurs par défaut :</strong>
|
||||
<ul>
|
||||
<li><strong>Publié :</strong> {{ object.defaultPublished|yesno:"Oui,Non" }}</li>
|
||||
{% if object.defaultLocation %}<li><strong>Localisation{% if object.forceLocation %} (forcée){% endif %} :</strong> {{ object.defaultLocation }}</li>{% endif %}
|
||||
<li><strong>Publié :</strong> {{ object.defaultPublished }}</li>
|
||||
<li><strong>Localisation :</strong> {{ object.defaultLocation }}</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 %}
|
||||
<li><strong>Organisateur :</strong> <a href="{{ object.defaultOrganiser.get_absolute_url }}">{{ object.defaultOrganiser }}</a></li>
|
||||
<li><strong>Étiquettes :</strong>
|
||||
{% for tag in object.defaultTags %}
|
||||
<a href="{% url 'view_tag' tag %}">{{ tag|tw_highlight }}</a>{% if not forloop.last %}, {% endif %}
|
||||
{{ tag|tw_highlight }}{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -95,19 +95,13 @@
|
||||
<h3>{{ ti.short_name }} <a class="badge simple" href="#{{ ti.id }}" data-tooltip="Aller à {{ ti.name }}">{{ ti.events|length }} {% picto_from_name "chevrons-down" %}</a></h3>
|
||||
<ul>
|
||||
{% for event in ti.events %}
|
||||
{% if event.is_first_after_now %}
|
||||
<li class="line-now"><div><div>{% now "H:i" %}</div><div class="line"></div></div></li>
|
||||
{% endif %}
|
||||
<li>{{ event.category | circle_cat:event.has_recurrences }}
|
||||
{% if event.start_time %}
|
||||
{{ event.start_time }}
|
||||
{% endif %}
|
||||
{{ event|picto_status }} <a href="#event-{{ event.id }}">{{ event.title|no_emoji }}</a> {{ event|tw_badge }}
|
||||
{{ event|picto_status }} <a href="#event-{{ event.id }}">{{ event.title }}</a> {{ event|tw_badge }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if forloop.last and cd.is_today_after_events %}
|
||||
<li class="line-now"><div><div>{% now "H:i" %}</div><div class="line"></div></div></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
@ -14,7 +14,7 @@
|
||||
<script src="{% static 'js/calendar-buttons.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}{% block og_title %}{% if calendar.today_in_calendar %}Sorties culturelles cette semaine à Clermont-Ferrand et aux environs{% else %}Semaine du {{ calendar.firstdate|date|frdate }}{% endif %}{% endblock %}{% endblock %}
|
||||
{% block title %}{% block og_title %}Semaine du {{ calendar.firstdate|date|frdate }}{% endblock %}{% endblock %}
|
||||
|
||||
{% block ce_mois_ci_parameters %}{% block cette_semaine_parameters %}{% block a_venir_parameters %}?{{ filter.get_url }}{% endblock %}{% endblock %}{% endblock %}
|
||||
|
||||
@ -37,7 +37,7 @@
|
||||
<div>
|
||||
{% if calendar.firstdate|shift_day:-1|not_before_first %}
|
||||
{% if calendar.lastdate|not_after_last %}
|
||||
<a role="button" href="{% url 'week_view' calendar.previous_week|weekyear calendar.previous_week|week %}?{{ filter.get_url }}">
|
||||
<a role="button" href="{% url 'week_view' calendar.previous_week.year calendar.previous_week|week %}?{{ filter.get_url }}">
|
||||
{% picto_from_name "chevron-left" %} précédente</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@ -45,7 +45,7 @@
|
||||
{% if calendar.lastdate|shift_day:+1|not_after_last %}
|
||||
{% if calendar.lastdate|not_before_first %}
|
||||
<div class="right">
|
||||
<a role="button" href="{% url 'week_view' calendar.next_week|weekyear calendar.next_week|week %}?{{ filter.get_url }}">suivante
|
||||
<a role="button" href="{% url 'week_view' calendar.next_week.year calendar.next_week|week %}?{{ filter.get_url }}">suivante
|
||||
{% picto_from_name "chevron-right" %}
|
||||
</a>
|
||||
</div>
|
||||
@ -57,7 +57,7 @@
|
||||
<div class="slider-button slider-button-inside button-left hidden">{% picto_from_name "arrow-left" %}</div>
|
||||
{% if calendar.firstdate|shift_day:-1|not_before_first %}
|
||||
{% if calendar.lastdate|not_after_last %}
|
||||
<div class="slider-button slider-button-page button-left hidden"><a href="{% url 'week_view' calendar.previous_week|weekyear calendar.previous_week|week %}?{{ filter.get_url }}">{% picto_from_name "chevrons-left" %}</a></div>
|
||||
<div class="slider-button slider-button-page button-left hidden"><a href="{% url 'week_view' calendar.previous_week.year calendar.previous_week|week %}?{{ filter.get_url }}">{% picto_from_name "chevrons-left" %}</a></div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
@ -80,28 +80,20 @@
|
||||
</script>
|
||||
{% endif %}
|
||||
<header{% if day.is_today %} id="today"{% endif %}>
|
||||
<h2><a class="visible-link" href="{{ day.date | url_day }}?{{ filter.get_url }}">{{ day.date | date:"l j" }}</a></h2>
|
||||
<h2><a href="{{ day.date | url_day }}?{{ filter.get_url }}">{{ day.date | date:"l j" }}</a></h2>
|
||||
</header>
|
||||
{% if day.events %}
|
||||
<ul>
|
||||
{% for event in day.events %}
|
||||
{% if event.is_first_after_now %}
|
||||
<li class="line-now"><div><div>{% now "H:i" %}</div><div class="line"></div></div></li>
|
||||
{% endif %}
|
||||
<li>{{ event.category | circle_cat:event.has_recurrences }}
|
||||
{% if event.start_day == day.date and event.start_time %}
|
||||
{{ event.start_time }}
|
||||
{% endif %}
|
||||
{{ event|picto_status }} <a href="{{ event.get_absolute_url }}" data-target="event-{{ event.id }}" onClick="toggleModal(event)">{{ event.title|no_emoji }}</a>
|
||||
{{ event|picto_status }} <a href="{{ event.get_absolute_url }}" data-target="event-{{ event.id }}" onClick="toggleModal(event)">{{ event.title }}</a>
|
||||
{{ event|tw_badge }}
|
||||
<dialog id="event-{{ event.id }}">
|
||||
<article>
|
||||
{% if event.has_image_url %}
|
||||
<header style="background-image: url({{ event.get_image_url }});" class="h-image">
|
||||
{% else %}
|
||||
<header class="cat-{{ event.category.pk }}">
|
||||
{% endif %}
|
||||
<div class="h-mask">
|
||||
<header>
|
||||
<a href="#event-{{ event.id }}"
|
||||
aria-label="Fermer"
|
||||
class="close"
|
||||
@ -133,7 +125,6 @@
|
||||
{% endif %}
|
||||
{% if event.end_time %} {% if not event.end_day|date|frdate or event.end_day == event.start_day %}jusqu'à{% endif %} {{ event.end_time }}{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="body-fixed">{{ event.description |linebreaks }}</div>
|
||||
@ -156,10 +147,6 @@
|
||||
</dialog>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if day.is_today_after_events %}
|
||||
<li class="line-now"><div><div>{% now "H:i" %}</div><div class="line"></div></div></li>
|
||||
{% endif %}
|
||||
<li class="detail-link"><a href="{{ day.date | url_day }}?{{ filter.get_url }}" class="visible-link">voir en détail {% picto_from_name "chevrons-right" %}</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
</ul>
|
||||
@ -173,7 +160,7 @@
|
||||
<div class="slider-button slider-button-inside button-right hidden">{% picto_from_name "arrow-right" %}</div>
|
||||
{% if calendar.lastdate|shift_day:+1|not_after_last %}
|
||||
{% if calendar.lastdate|not_before_first %}
|
||||
<div class="slider-button slider-button-page button-right hidden"><a href="{% url 'week_view' calendar.next_week|weekyear calendar.next_week|week %}?{{ filter.get_url }}">{% picto_from_name "chevrons-right" %}</a></div>
|
||||
<div class="slider-button slider-button-page button-right hidden"><a href="{% url 'week_view' calendar.next_week.year calendar.next_week|week %}?{{ filter.get_url }}">{% picto_from_name "chevrons-right" %}</a></div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
|
@ -1,23 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
{% load event_extra %}
|
||||
{% load cache %}
|
||||
{% load messages_extra %}
|
||||
{% load utils_extra %}
|
||||
{% load duplicated_extra %}
|
||||
{% load rimports_extra %}
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Pommes de lune — {% block title %}{% endblock %}</title>
|
||||
<meta name="google-site-verification" content="pvRD0rc_xIE-1IYmbao0kj5ngGo1IWxJqKwoxrQwxuA" />
|
||||
<meta name="keywords" content="Clermont-Ferrand, Puy-de-Dôme, agenda culturel, agenda participatif, sortir à clermont, sorties, concerts, théâtre, danse, animations, ateliers, lectures">
|
||||
|
||||
{% load static %}
|
||||
<meta property="og:title" content="Pommes de lune — {% block og_title %}{% endblock %}" />
|
||||
<meta property="og:description" content="{% block og_description %}Où sortir à Clermont-Ferrand? Retrouve tous les bons plans sur l'agenda participatif des événements culturels à Clermont-Ferrand et dans le Puy-de-Dôme{% endblock %}" />
|
||||
<meta property="og:image" content="{% block og_image %}https://{{ request.get_host }}{% get_media_prefix %}screenshot.png{% endblock %}" />
|
||||
<meta property="og:locale" content="fr_FR" />
|
||||
<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:url" content="{{ request.build_absolute_uri }}" />
|
||||
|
||||
{% if debug %}
|
||||
@ -35,8 +27,13 @@
|
||||
{% block entete_header %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
|
||||
<body class="{% block body-class %}contenu{% endblock %} {% if user.is_authenticated %}authenticated{% endif %}">
|
||||
{% load event_extra %}
|
||||
{% load cache %}
|
||||
{% load contactmessages_extra %}
|
||||
{% load utils_extra %}
|
||||
{% load duplicated_extra %}
|
||||
{% load rimports_extra %}
|
||||
<body class="{% block body-class %}contenu{% endblock %}">
|
||||
<div id="boutons-fixes">
|
||||
<ul>
|
||||
{% block sidemenu-bouton %}{% endblock %}
|
||||
@ -50,9 +47,10 @@
|
||||
<input class="menu-btn" type="checkbox" id="menu-btn" />
|
||||
<label class="menu-icon" for="menu-btn">{% picto_from_name "menu" %}</label>
|
||||
<ul class="menu">
|
||||
{% if user.is_authenticated %}{% block configurer-menu %}<li id="menu-configurer" class="configurer-bouton"><a href="{% url 'administration' %}">Administrer {% picto_from_name "settings" %}</a></li>{% endblock %}{% endif %}
|
||||
{% block ajouter-menu %}<li id="menu-ajouter" class="ajouter-bouton"><a href="{% url 'add_event' %}">Ajouter un événement {% picto_from_name "plus-circle" %}</a></li>{% endblock %}
|
||||
{% block rechercher-menu %}<li id="menu-rechercher" class="rechercher-bouton"><a href="{% url 'event_search' %}">Rechercher {% picto_from_name "search" %}</a></li>{% endblock %}
|
||||
<li><a href="{% url 'a_venir' %}{% block a_venir_parameters %}{% endblock %}">Maintenant</a></li>
|
||||
<li><a href="{% url 'a_venir' %}{% block a_venir_parameters %}{% endblock %}">À venir</a></li>
|
||||
<li><a href="{% url 'cette_semaine' %}{% block cette_semaine_parameters %}{% endblock %}">Cette semaine</a></li>
|
||||
<li><a href="{% url 'ce_mois_ci' %}{% block ce_mois_ci_parameters %}{% endblock %}">Ce mois-ci</a></li>
|
||||
</ul>
|
||||
@ -66,35 +64,30 @@
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
{% if perms.agenda_culturel.view_recurrentimport %}
|
||||
{% show_badges_rimports "bottom" %}
|
||||
{% endif %}
|
||||
{% if perms.agenda_culturel.change_event %}
|
||||
{% show_badges_events "bottom" %}
|
||||
{% endif %}
|
||||
{% if perms.agenda_culturel.change_duplicatedevents %}
|
||||
{% show_badge_duplicated "bottom" %}
|
||||
{% endif %}
|
||||
{% if perms.agenda_culturel.change_place and perms.agenda_culturel.change_event %}
|
||||
{% show_badge_unknown_places "bottom" %}
|
||||
{% endif %}
|
||||
{% if perms.agenda_culturel.view_contactmessage %}
|
||||
{% show_badge_contactmessages "bottom" %}
|
||||
{% endif %}
|
||||
{% if user.is_authenticated %}
|
||||
{{ user.username }} @
|
||||
{% endif %}
|
||||
<a href="{% url 'home' %}" aria-label="Retour accueil">Pommes de lune</a>
|
||||
</div>
|
||||
<div class="soustitre">Agenda participatif des sorties culturelles à Clermont-Ferrand et aux environs</div>
|
||||
<div class="soustitre">Événements culturels à Clermont-Ferrand et aux environs</div>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% if user.is_authenticated %}
|
||||
<div id="badges">
|
||||
{% if perms.agenda_culturel.view_recurrentimport %}
|
||||
{% show_badges_rimports "bottom" %}
|
||||
{% endif %}
|
||||
{% if perms.agenda_culturel.change_event %}
|
||||
{% show_badges_events "bottom" %}
|
||||
{% endif %}
|
||||
{% if perms.agenda_culturel.change_duplicatedevents %}
|
||||
{% show_badge_duplicated "bottom" %}
|
||||
{% endif %}
|
||||
{% if perms.agenda_culturel.change_place and perms.agenda_culturel.change_event %}
|
||||
{% show_badge_unknown_places "bottom" %}
|
||||
{% endif %}
|
||||
{% if perms.agenda_culturel.view_message %}
|
||||
{% show_badge_messages "bottom" %}
|
||||
{% endif %}
|
||||
<a class="link" href="{% url 'administration' %}">Administrer {% picto_from_name "settings" %}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<main class="container{% block fluid %}-fluid{% endblock %}">
|
||||
{% if messages %}
|
||||
|
@ -22,26 +22,9 @@
|
||||
<article>
|
||||
{% if event %}
|
||||
<p>Création d'un lieu depuis l'événement « {{ event }} » (voir en bas de page le détail de l'événement).</p>
|
||||
<p><strong>Remarque :</strong> les champs ont été pré-remplis à partir de la description sous forme libre et n'est probablement pas parfaite.</p>
|
||||
{% endif %}
|
||||
<form method="post">{% csrf_token %}
|
||||
<div class="grid form-place">
|
||||
{{ form }}
|
||||
<div class="map-widget">
|
||||
<div id="map_location" style="width: 100%; aspect-ratio: 16/9"></div>
|
||||
<p>Cliquez pour ajuster la position GPS</p>
|
||||
<input type="checkbox" role="switch" id="lock_position">Verrouiller la position</input>
|
||||
<script>
|
||||
document.getElementById("lock_position").onclick = function() {
|
||||
const field = document.getElementById("id_location");
|
||||
if (this.checked)
|
||||
field.setAttribute("readonly", true);
|
||||
else
|
||||
field.removeAttribute("readonly");
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
{{ form.as_grid }}
|
||||
<div class="grid buttons">
|
||||
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{{ object.get_absolute_url }}{% endif %}" role="button" class="secondary">Annuler</a>
|
||||
<input type="submit" value="Envoyer">
|
||||
|
@ -6,8 +6,6 @@
|
||||
{% block configurer-bouton %}{% endblock %}
|
||||
|
||||
{% block entete_header %}
|
||||
<script src="{% static 'choicejs/choices.min.js' %}"></script>
|
||||
|
||||
<script src="/static/admin/js/vendor/jquery/jquery.js"></script>
|
||||
<script src="/static/admin/js/jquery.init.js"></script>
|
||||
<link href="{% static 'css/django_better_admin_arrayfield.min.css' %}" type="text/css" media="all" rel="stylesheet">
|
||||
@ -28,35 +26,4 @@
|
||||
</form>
|
||||
</article>
|
||||
|
||||
<script>
|
||||
show_firstgroup = {
|
||||
choice(classes, choice) {
|
||||
const i = Choices.defaults.templates.choice.call(this, classes, choice);
|
||||
if (this.first_group !== null && choice.groupId == this.first_group)
|
||||
i.classList.add("visible");
|
||||
return i;
|
||||
},
|
||||
choiceGroup(classes, group) {
|
||||
const g = Choices.defaults.templates.choiceGroup.call(this, classes, group);
|
||||
if (this.first_group === undefined && group.value == "Suggestions")
|
||||
this.first_group = group.id;
|
||||
if (this.first_group !== null && group.id == this.first_group)
|
||||
g.classList.add("visible");
|
||||
return g;
|
||||
}
|
||||
};
|
||||
|
||||
const tags = document.querySelector('#id_defaultTags');
|
||||
const choices_tags = new Choices(tags,
|
||||
{
|
||||
placeholderValue: 'Sélectionner les étiquettes par défaut',
|
||||
allowHTML: true,
|
||||
delimiter: ',',
|
||||
removeItemButton: true,
|
||||
shouldSort: false,
|
||||
callbackOnCreateTemplates: () => (show_firstgroup)
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
@ -1,5 +1,5 @@
|
||||
{% load event_extra %}
|
||||
{% load messages_extra %}
|
||||
{% load contactmessages_extra %}
|
||||
{% load duplicated_extra %}
|
||||
{% load utils_extra %}
|
||||
<aside id="sidebar">
|
||||
@ -56,11 +56,11 @@
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% if perms.agenda_culturel.view_message %}
|
||||
{% if perms.agenda_culturel.view_contactmessage %}
|
||||
<h3>Messages</h3>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a {% if current == "messages" %}class="selected" {% endif %}href="{% url 'messages' %}">Messages de contact</a>{% show_badge_messages "left" %}</li>
|
||||
<li><a {% if current == "contactmessages" %}class="selected" {% endif %}href="{% url 'contactmessages' %}">Messages de contact</a>{% show_badge_contactmessages "left" %}</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
@ -68,7 +68,6 @@
|
||||
<h3>Configuration interne</h3>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="{% url 'clear_cache' %}">Vider le cache</a></li>
|
||||
<li><a href="{% url 'admin:index' %}">Administration de django</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
@ -6,11 +6,7 @@
|
||||
|
||||
<article id="event-{{ event.pk}}" class="single-event {% if not event.image and not event.local_image %}no-image{% endif %}">
|
||||
<header class="head">
|
||||
{% if day == 0 %}
|
||||
<div class="small-ephemeride">
|
||||
{% include "agenda_culturel/ephemeris-inc.html" with event=event filter=filter %}
|
||||
</div>
|
||||
{% else %}
|
||||
{% if day != 0 %}
|
||||
{% if event|can_show_start_time:day %}
|
||||
{% if event.start_time %}
|
||||
<article class='ephemeris-hour'>
|
||||
@ -26,7 +22,7 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<div class="header-complement">
|
||||
|
||||
{{ event.category | small_cat_recurrent:event.has_recurrences }}
|
||||
|
||||
{% if event.location or event.exact_location %}<hgroup>{% endif %}
|
||||
@ -54,7 +50,11 @@
|
||||
</hgroup>
|
||||
{% 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>
|
||||
{% picto_from_name "calendar" %}
|
||||
@ -86,13 +86,12 @@
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="buttons" style="clear: both">
|
||||
{% if perms.agenda_culturel.change_event %}
|
||||
{% include "agenda_culturel/edit-buttons-inc.html" with event=event %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</header>
|
||||
|
||||
<div class="description">
|
||||
|
@ -22,17 +22,16 @@
|
||||
{% 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>
|
||||
{% picto_from_name "map-pin" %}
|
||||
|
||||
{% if event.exact_location %}
|
||||
<p>{% picto_from_name "map-pin" %}
|
||||
<a href="{{ event.exact_location.get_absolute_url }}">{{ event.exact_location.name }}, {{ event.exact_location.city }}</a></p>
|
||||
<a href="{{ event.exact_location.get_absolute_url }}">{{ event.exact_location.name }}, {{ event.exact_location.city }}</a>
|
||||
{% else %}
|
||||
{% if perms.agenda_culturel.change_event and perms.agenda_culturel.change_place %}
|
||||
<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>
|
||||
<a href="{% url 'add_place_to_event' event.pk %}" class="missing-data">{{ event.location }}</a>
|
||||
{% else %}
|
||||
{% if event.location %}<p>{% picto_from_name "map-pin" %} {{ event.location }}</p>{% endif %}
|
||||
{{ event.location }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</p>
|
||||
@ -76,15 +75,6 @@
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if perms.agenda_culturel.change_message %}
|
||||
{% if event.message_set.all.count > 0 %}
|
||||
<p class="remarque">Cet événement a été l'objet {% if event.message_set.all.count == 1 %}d'un message{% else %}de messages{% endif %}
|
||||
{% 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 %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</header>
|
||||
<div class="event-body">
|
||||
@ -134,24 +124,9 @@
|
||||
{% include "agenda_culturel/event-date-info-inc.html" %}
|
||||
</div>
|
||||
<div class="buttons">
|
||||
{% if onlyedit %}
|
||||
{% if event.pure_import %}
|
||||
{% with event.get_local_version as local %}
|
||||
{% if local %}
|
||||
<a href="{{ local.get_absolute_url }}" role="button">voir la version locale {% picto_from_name "eye" %}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'clone_edit' event.id %}" role="button">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 %}
|
||||
<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 %}
|
||||
</div>
|
||||
</footer>
|
||||
|
@ -62,12 +62,6 @@
|
||||
{% endif %}
|
||||
|
||||
<footer>
|
||||
{% if user.is_authenticated and rimports %}
|
||||
<p>Cette étiquette est ajoutée par défaut {% if rimports.count == 1 %}à l'import récurrent{% else %}aux imports récurrents :{% endif %}
|
||||
{% for ri in rimports %}
|
||||
<a href="{{ ri.get_absolute_url }}">{{ ri.name }}</a>{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}</p>
|
||||
{% endif %}
|
||||
{% include 'agenda_culturel/paginator.html' %}
|
||||
</footer>
|
||||
</article>
|
||||
|
@ -20,12 +20,6 @@
|
||||
{% else %} sera bien sûr conservé, mais perdra cette étiquette.
|
||||
{% endif %}</p>
|
||||
{% endif %}
|
||||
{% if nbi > 0 %}
|
||||
<p>Remarquez qu'elle est associée à {{ nbi }} import{{ nbs|pluralize }} récurrent{{ nbs|pluralize }}, qui
|
||||
{% if nbi > 1 %} seront bien sûr conservés, mais perdront cette étiquette.
|
||||
{% else %} sera bien sûr conservé, mais perdra cette étiquette.
|
||||
{% endif %}</p>
|
||||
{% endif %}
|
||||
{% if obj %}
|
||||
<p>Différentes informations sont associées à cette étiquette (description, suggestion d'inclusion, etc)
|
||||
seront également perdues lors de cette suppression.</p>
|
||||
|
@ -17,7 +17,7 @@
|
||||
<article>
|
||||
<header>
|
||||
<h1>Renommer l'étiquette {{ tag }}</em></h1>
|
||||
<p>En renommant l'étiquette {{ tag }}, vous remplacerez cette étiquette par la nouvelle pour tous les événements et tous les imports récurrents concernés.</p>
|
||||
<p>En renommant l'étiquette {{ tag }}, vous remplacerez cette étiquette par la nouvelle pour tous les événements concernés.</p>
|
||||
</header>
|
||||
|
||||
|
||||
|
@ -86,6 +86,12 @@ def css_categories():
|
||||
)
|
||||
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 += "form ." + c["css_class"] + ", "
|
||||
result += ".selected ." + c["css_class"] + " {"
|
||||
|
@ -4,7 +4,7 @@ from django.urls import reverse_lazy
|
||||
from django.template.defaultfilters import pluralize
|
||||
|
||||
|
||||
from agenda_culturel.models import Message
|
||||
from agenda_culturel.models import ContactMessage
|
||||
|
||||
from .utils_extra import picto_from_name
|
||||
|
||||
@ -12,15 +12,15 @@ register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def show_badge_messages(placement="top"):
|
||||
nb_open = Message.nb_open_messages()
|
||||
def show_badge_contactmessages(placement="top"):
|
||||
nb_open = ContactMessage.nb_open_contactmessages()
|
||||
if nb_open != 0:
|
||||
return mark_safe(
|
||||
'<a href="'
|
||||
+ reverse_lazy("messages")
|
||||
+ '?closed=False&message_type=contact_form&message_type=event_report&message_type=from_contributor" class="badge" data-placement="'
|
||||
+ reverse_lazy("contactmessages")
|
||||
+ '?closed=False" class="badge" data-placement="'
|
||||
+ placement
|
||||
+ '" data-tooltip="'
|
||||
+ '"data-tooltip="'
|
||||
+ str(nb_open)
|
||||
+ " message"
|
||||
+ pluralize(nb_open)
|
@ -179,8 +179,4 @@ def tw_badge(event):
|
||||
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>')
|
||||
else:
|
||||
return ""
|
||||
|
||||
@register.filter
|
||||
def get_image_uri(event, request):
|
||||
return event.get_image_url(request)
|
||||
return ""
|
@ -1,25 +0,0 @@
|
||||
from django import template
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.core.cache import cache
|
||||
from .utils_extra import picto_from_name
|
||||
|
||||
from agenda_culturel.models import ReferenceLocation
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def show_suggested_positions(filter):
|
||||
filter.form.full_clean()
|
||||
|
||||
if filter.is_filtered_by_position_radius():
|
||||
return ""
|
||||
|
||||
|
||||
locations = ReferenceLocation.objects.all().filter(suggested_distance__isnull=False).order_by("main", "name")
|
||||
|
||||
result = ''
|
||||
for l in locations:
|
||||
result += ' <a class="small-location" role="button" href="' + filter.get_url_add_suggested_position(l) + '">' + picto_from_name("map-pin") + ' ' + l.name + ' ' + str(l.suggested_distance) + 'km</a>'
|
||||
|
||||
return mark_safe(result)
|
@ -54,7 +54,7 @@ def tag_not_in_db(tag, tags):
|
||||
def show_suggested_tags(filter):
|
||||
filter.form.full_clean()
|
||||
tags = Tag.objects.all().filter(principal=True).order_by("name")
|
||||
result = ""
|
||||
result = "Suggestion :"
|
||||
|
||||
for t in tags:
|
||||
if filter.tag_exists(t.name) and not filter.is_selected_tag(t.name):
|
||||
|
@ -9,7 +9,6 @@ from django.urls import reverse_lazy
|
||||
from django.templatetags.static import static
|
||||
from string import ascii_uppercase as auc
|
||||
from django.utils.html import strip_tags
|
||||
import emoji
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@ -29,9 +28,6 @@ def add_de(txt):
|
||||
def week(d):
|
||||
return d.isocalendar()[1]
|
||||
|
||||
@register.filter
|
||||
def weekyear(d):
|
||||
return d.isocalendar()[0]
|
||||
|
||||
@register.filter
|
||||
def not_before_first(d):
|
||||
@ -141,6 +137,3 @@ def is_string(val):
|
||||
def html_vide(val):
|
||||
return len(strip_tags(val).replace(" ", "").strip()) == 0
|
||||
|
||||
@register.filter
|
||||
def no_emoji(text):
|
||||
return emoji.replace_emoji(text, replace='')
|
||||
|
@ -4,32 +4,10 @@ from django.contrib import admin
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
from django.urls import path, include, re_path
|
||||
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 *
|
||||
|
||||
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 = [
|
||||
path("", home, name="home"),
|
||||
path("semaine/<int:year>/<int:week>/", week_view, name="week_view"),
|
||||
@ -57,13 +35,11 @@ urlpatterns = [
|
||||
),
|
||||
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/after/<int:pred>", EventModerateView.as_view(), name="moderate_event_step"),
|
||||
path("event/<int:pk>/moderate-next", moderate_event_next, name="moderate_event_next"),
|
||||
path("event/<int:pk>/moderate-next", EventModerateView.as_view(), name="moderate_event_next"),
|
||||
path("event/<int:pk>/moderate-next/error", error_next_event, name="error_next_event"),
|
||||
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>/clone/edit", EventUpdateView.as_view(), name="clone_edit"),
|
||||
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>/change-status/<status>",
|
||||
@ -98,18 +74,18 @@ urlpatterns = [
|
||||
path("mentions-legales", mentions_legales, name="mentions_legales"),
|
||||
path("a-propos", about, name="about"),
|
||||
path("merci", thank_you, name="thank_you"),
|
||||
path("contact", MessageCreateView.as_view(), name="contact"),
|
||||
path("messages", view_messages, name="messages"),
|
||||
path("messages/spams/delete", delete_cm_spam, name="delete_cm_spam"),
|
||||
path("contact", ContactMessageCreateView.as_view(), name="contact"),
|
||||
path("contactmessages", contactmessages, name="contactmessages"),
|
||||
path("contactmessages/spams/delete", delete_cm_spam, name="delete_cm_spam"),
|
||||
path(
|
||||
"message/<int:pk>",
|
||||
MessageUpdateView.as_view(),
|
||||
name="message",
|
||||
"contactmessage/<int:pk>",
|
||||
ContactMessageUpdateView.as_view(),
|
||||
name="contactmessage",
|
||||
),
|
||||
path(
|
||||
"message/<int:pk>/delete",
|
||||
MessageDeleteView.as_view(),
|
||||
name="delete_message",
|
||||
"contactmessage/<int:pk>/delete",
|
||||
ContactMessageDeleteView.as_view(),
|
||||
name="delete_contactmessage",
|
||||
),
|
||||
path("imports/", imports, name="imports"),
|
||||
path("imports/add", add_import, name="add_import"),
|
||||
@ -157,8 +133,7 @@ urlpatterns = [
|
||||
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>", OrganisationDetailView.as_view(), name="view_organisation_shortname"),
|
||||
path("organisme/<int:pk>-<extra>", OrganisationDetailView.as_view(), name="view_organisation"),
|
||||
path("organisme/<int:pk>", 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>", OrganisationDetailView.as_view(), name="view_organisation_fullname"),
|
||||
path("organisme/<int:pk>/edit", OrganisationUpdateView.as_view(), name="edit_organisation"),
|
||||
@ -202,14 +177,6 @@ urlpatterns = [
|
||||
re_path(r'^robots\.txt', include('robots.urls')),
|
||||
path("__debug__/", include("debug_toolbar.urls")),
|
||||
path("ckeditor5/", include('django_ckeditor_5.urls')),
|
||||
path(
|
||||
"sitemap.xml",
|
||||
cache_page(86400)(sitemap),
|
||||
{"sitemaps": sitemaps},
|
||||
name="cached-sitemap",
|
||||
),
|
||||
path("cache/clear", clear_cache, name="clear_cache"),
|
||||
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
|
@ -1,114 +0,0 @@
|
||||
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.views.generic import ListView, DetailView
|
||||
from django.views.generic.edit import CreateView, UpdateView, DeleteView, ModelFormMixin
|
||||
from django.views.generic.edit import CreateView, UpdateView, DeleteView
|
||||
from django.contrib.auth.mixins import (
|
||||
LoginRequiredMixin,
|
||||
UserPassesTestMixin,
|
||||
@ -10,12 +10,6 @@ from django import forms
|
||||
from django.http import Http404
|
||||
from django.contrib.postgres.search import SearchQuery, SearchHeadline
|
||||
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.measure import D
|
||||
@ -25,9 +19,11 @@ from django.urls import reverse
|
||||
from collections import Counter
|
||||
import emoji
|
||||
|
||||
from django.forms import formset_factory
|
||||
|
||||
from .forms import (
|
||||
URLSubmissionForm,
|
||||
EventForm,
|
||||
EventFormWithContact,
|
||||
BatchImportationForm,
|
||||
FixDuplicates,
|
||||
SelectEventInList,
|
||||
@ -40,18 +36,13 @@ from .forms import (
|
||||
MultipleHiddenInput,
|
||||
EventModerateForm,
|
||||
TagForm,
|
||||
TagRenameForm,
|
||||
MessageForm,
|
||||
MessageEventForm,
|
||||
URLSubmissionFormWithContact,
|
||||
URLSubmissionFormSet,
|
||||
SimpleContactForm,
|
||||
TagRenameForm
|
||||
)
|
||||
|
||||
from .filters import (
|
||||
EventFilter,
|
||||
EventFilterAdmin,
|
||||
MessagesFilterAdmin,
|
||||
ContactMessagesFilterAdmin,
|
||||
SimpleSearchEventFilter,
|
||||
SearchEventFilter,
|
||||
DuplicatedEventsFilter,
|
||||
@ -63,7 +54,7 @@ from .models import (
|
||||
Category,
|
||||
Tag,
|
||||
StaticContent,
|
||||
Message,
|
||||
ContactMessage,
|
||||
BatchImportation,
|
||||
DuplicatedEvents,
|
||||
RecurrentImport,
|
||||
@ -77,7 +68,7 @@ from django.utils import timezone
|
||||
from django.utils.html import escape
|
||||
from datetime import date, timedelta
|
||||
from django.utils.timezone import datetime
|
||||
from django.db.models import Q, Subquery, OuterRef, Count, F, Func, BooleanField, ExpressionWrapper
|
||||
from django.db.models import Q, Subquery, OuterRef, Count, F, Func
|
||||
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@ -226,7 +217,7 @@ def month_view(request, year=None, month=None):
|
||||
def week_view(request, year=None, week=None, home=False):
|
||||
now = date.today()
|
||||
if year is None:
|
||||
year = now.isocalendar()[0]
|
||||
year = now.year
|
||||
if week is None:
|
||||
week = now.isocalendar()[1]
|
||||
|
||||
@ -301,7 +292,7 @@ def update_from_source(request, pk):
|
||||
if url is None:
|
||||
messages.warning(request, _("The event cannot be updated because the import process is not available for the referenced sources."))
|
||||
else:
|
||||
import_events_from_url.delay(url, None, None, True, user_id=request.user.pk if request.user else None)
|
||||
import_events_from_url.delay(url, None, True)
|
||||
messages.success(request, _("The event update has been queued and will be completed shortly."))
|
||||
|
||||
return HttpResponseRedirect(event.get_absolute_url())
|
||||
@ -312,6 +303,7 @@ class EventUpdateView(
|
||||
model = Event
|
||||
permission_required = "agenda_culturel.change_event"
|
||||
form_class = EventForm
|
||||
success_message = _("The event has been successfully modified.")
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
@ -320,16 +312,6 @@ class EventUpdateView(
|
||||
kwargs["is_simple_cloning"] = self.is_simple_cloning
|
||||
return kwargs
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
txt = _(" A message has been sent to the person who proposed the event.") if hasattr(self, "with_msg") else ""
|
||||
return mark_safe(_('The event has been successfully modified.') + txt)
|
||||
|
||||
|
||||
def form_valid(self, form):
|
||||
form.instance.set_processing_user(self.request.user)
|
||||
self.with_message = form.instance.notify_if_required(self.request)
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_initial(self):
|
||||
self.is_cloning = "clone" in self.request.path.split('/')
|
||||
self.is_simple_cloning = "simple-clone" in self.request.path.split('/')
|
||||
@ -345,7 +327,6 @@ class EventUpdateView(
|
||||
obj.save()
|
||||
result["other_versions"] = obj.other_versions
|
||||
result["status"] = Event.STATUS.PUBLISHED
|
||||
result["cloning"] = True
|
||||
|
||||
if self.is_simple_cloning:
|
||||
result["other_versions"] = None
|
||||
@ -367,28 +348,21 @@ class EventModerateView(
|
||||
permission_required = "agenda_culturel.change_event"
|
||||
template_name = "agenda_culturel/event_form_moderate.html"
|
||||
form_class = EventModerateForm
|
||||
|
||||
def get_success_message(self, cleaned_data):
|
||||
txt = _(" A message has been sent to the person who proposed the event.") if hasattr(self, "with_msg") else ""
|
||||
return mark_safe(_('The event <a href="{}">{}</a> has been moderated with success.').format(self.object.get_absolute_url(), self.object.title) + txt)
|
||||
|
||||
success_message = _("The event has been successfully moderated.")
|
||||
|
||||
def is_moderate_next(self):
|
||||
return "after" in self.request.path.split('/')
|
||||
return "moderate-next" in self.request.path.split('/')
|
||||
|
||||
def is_starting_moderation(self):
|
||||
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(start_day, start_time):
|
||||
def get_next_event(self, start_day, start_time):
|
||||
# select non moderated events
|
||||
qs = Event.objects.filter(moderated_date__isnull=True)
|
||||
|
||||
# select events after the current one
|
||||
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__gt=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))))
|
||||
else:
|
||||
qs = qs.filter(start_day__gte=start_day)
|
||||
|
||||
@ -409,15 +383,19 @@ class EventModerateView(
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
if self.is_moderate_next():
|
||||
context['pred'] = self.kwargs["pred"]
|
||||
context['pred'] = self.kwargs["pk"]
|
||||
return context
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
if self.is_starting_moderation():
|
||||
now = datetime.now()
|
||||
return EventModerateView.get_next_event(now.date(), now.time())
|
||||
return self.get_next_event(now.date(), now.time())
|
||||
else:
|
||||
return super().get_object(queryset)
|
||||
result = 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):
|
||||
try:
|
||||
@ -429,13 +407,15 @@ class EventModerateView(
|
||||
def form_valid(self, form):
|
||||
form.instance.set_no_modification_date_changed()
|
||||
form.instance.set_in_moderation_process()
|
||||
form.instance.set_processing_user(self.request.user)
|
||||
self.with_msg = form.instance.notify_if_required(self.request)
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
if 'save_and_next' in self.request.POST:
|
||||
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:
|
||||
return reverse_lazy("edit_event", args=[self.object.get_local_version().pk])
|
||||
else:
|
||||
@ -454,30 +434,6 @@ def error_next_event(request, pk):
|
||||
{"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(
|
||||
@ -489,12 +445,9 @@ class EventDeleteView(
|
||||
success_message = _("The event has been successfully deleted.")
|
||||
|
||||
|
||||
class EventDetailView(UserPassesTestMixin, DetailView, ModelFormMixin):
|
||||
class EventDetailView(UserPassesTestMixin, DetailView):
|
||||
model = Event
|
||||
form_class = MessageEventForm
|
||||
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):
|
||||
return (
|
||||
@ -504,7 +457,6 @@ class EventDetailView(UserPassesTestMixin, DetailView, ModelFormMixin):
|
||||
|
||||
def get_object(self):
|
||||
o = super().get_object()
|
||||
o.download_missing_image()
|
||||
y = self.kwargs["year"]
|
||||
m = self.kwargs["month"]
|
||||
d = self.kwargs["day"]
|
||||
@ -512,30 +464,6 @@ class EventDetailView(UserPassesTestMixin, DetailView, ModelFormMixin):
|
||||
obj.set_current_date(date(y, m, d))
|
||||
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/")
|
||||
@ -546,11 +474,7 @@ def change_status_event(request, pk, status):
|
||||
if request.method == "POST":
|
||||
event.status = Event.STATUS(status)
|
||||
event.save(update_fields=["status"])
|
||||
with_msg = event.notify_if_required(request)
|
||||
if with_msg:
|
||||
messages.success(request, _("The status has been successfully modified and a message has been sent to the person who proposed the event."))
|
||||
else:
|
||||
messages.success(request, _("The status has been successfully modified."))
|
||||
messages.success(request, _("The status has been successfully modified."))
|
||||
|
||||
return HttpResponseRedirect(event.get_absolute_url())
|
||||
|
||||
@ -569,7 +493,7 @@ def import_event_proxy(request):
|
||||
|
||||
class EventCreateView(SuccessMessageMixin, CreateView):
|
||||
model = Event
|
||||
form_class = EventFormWithContact
|
||||
form_class = EventForm
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
@ -593,32 +517,31 @@ class EventCreateView(SuccessMessageMixin, CreateView):
|
||||
|
||||
if form.cleaned_data['simple_cloning']:
|
||||
form.instance.set_skip_duplicate_check()
|
||||
|
||||
if form.cleaned_data['cloning']:
|
||||
form.instance.set_in_moderation_process()
|
||||
|
||||
if form.cleaned_data.get("email") or form.cleaned_data.get("comments"):
|
||||
has_comments = not form.cleaned_data.get("comments") in ["", None]
|
||||
form.instance.set_message(
|
||||
Message(subject=_('during the creation process'),
|
||||
message=form.cleaned_data.get("comments"),
|
||||
email=form.cleaned_data.get("email"),
|
||||
closed=False,
|
||||
message_type=Message.TYPE.FROM_CONTRIBUTOR if has_comments else Message.TYPE.FROM_CONTRIBUTOR_NO_MSG))
|
||||
|
||||
form.instance.import_sources = None
|
||||
form.instance.set_processing_user(self.request.user)
|
||||
|
||||
result = super().form_valid(form)
|
||||
|
||||
if form.cleaned_data['cloning']:
|
||||
with_msg = form.instance.notify_if_required(self.request)
|
||||
if with_msg:
|
||||
messages.success(self.request, _("A message has been sent to the person who proposed the initial event."))
|
||||
|
||||
return result
|
||||
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
|
||||
# level of the user
|
||||
@ -685,14 +608,11 @@ class URLEventEvaluation:
|
||||
|
||||
|
||||
def import_from_urls(request):
|
||||
|
||||
URLSubmissionFormSet = formset_factory(URLSubmissionForm, extra=9, min_num=1)
|
||||
|
||||
if request.method == "POST":
|
||||
formset = URLSubmissionFormSet(request.POST, request.FILES)
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
contactform = SimpleContactForm(request.POST)
|
||||
if formset.is_valid() and (request.user.is_authenticated or contactform.is_valid()):
|
||||
if formset.is_valid():
|
||||
# evaluate all the forms
|
||||
ucat = [URLEventEvaluation(form, request.user.is_authenticated) for form in formset.forms]
|
||||
|
||||
@ -719,32 +639,20 @@ def import_from_urls(request):
|
||||
request,
|
||||
_('Integrating {} url(s) into our import process.').format(len(ucat))
|
||||
)
|
||||
email = None
|
||||
comments = None
|
||||
if not request.user.is_authenticated:
|
||||
email = contactform.cleaned_data["email"]
|
||||
comments = contactform.cleaned_data["comments"]
|
||||
import_events_from_urls.delay(ucat,
|
||||
user_id=request.user.pk if request.user else None,
|
||||
email=email, comments=comments)
|
||||
import_events_from_urls.delay(ucat)
|
||||
return HttpResponseRedirect(reverse("thank_you"))
|
||||
else:
|
||||
return HttpResponseRedirect(reverse("home"))
|
||||
|
||||
else:
|
||||
formset = URLSubmissionFormSet()
|
||||
if not request.user.is_authenticated:
|
||||
contactform = SimpleContactForm()
|
||||
|
||||
context = {"formset": formset}
|
||||
if not request.user.is_authenticated:
|
||||
context["contactform"] = contactform
|
||||
return render(request, "agenda_culturel/import_set.html", context=context)
|
||||
return render(request, "agenda_culturel/import_set.html", context={"formset": formset})
|
||||
|
||||
|
||||
def import_from_url(request):
|
||||
|
||||
form = URLSubmissionFormWithContact(is_authenticated=request.user.is_authenticated)
|
||||
form = URLSubmissionForm()
|
||||
|
||||
initial = {
|
||||
"start_day": date.today() + timedelta(days=1),
|
||||
@ -756,7 +664,7 @@ def import_from_url(request):
|
||||
|
||||
# if the form has been sent
|
||||
if request.method == "POST":
|
||||
form = URLSubmissionFormWithContact(request.POST, is_authenticated=request.user.is_authenticated)
|
||||
form = URLSubmissionForm(request.POST)
|
||||
|
||||
# if the form is valid
|
||||
if form.is_valid():
|
||||
@ -781,7 +689,7 @@ def import_from_url(request):
|
||||
request,
|
||||
_('Integrating {} into our import process.').format(uc.url)
|
||||
)
|
||||
import_events_from_url.delay(uc.url, uc.cat, uc.tags, user_id=request.user.pk if request.user else None, email=form.cleaned_data.get("email"), comments=form.cleaned_data.get("comments"))
|
||||
import_events_from_url.delay(uc.url, uc.cat, uc.tags)
|
||||
return HttpResponseRedirect(reverse("thank_you"))
|
||||
|
||||
|
||||
@ -799,7 +707,7 @@ def export_event_ical(request, year, month, day, pk):
|
||||
events = list()
|
||||
events.append(event)
|
||||
|
||||
cal = Event.export_to_ics(events, request)
|
||||
cal = Event.export_to_ics(events)
|
||||
|
||||
response = HttpResponse(content_type="text/calendar")
|
||||
response.content = cal.to_ical().decode("utf-8").replace("\r\n", "\n")
|
||||
@ -809,17 +717,14 @@ def export_event_ical(request, year, month, day, pk):
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def export_ical(request):
|
||||
now = date.today()
|
||||
|
||||
request = EventFilter.set_default_values(request)
|
||||
filter = EventFilter(request.GET, queryset=get_event_qs(request), request=request)
|
||||
id_cache = hashlib.md5(filter.get_url().encode("utf8")).hexdigest()
|
||||
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
|
||||
calendar = CalendarList(now + timedelta(days=-7), now + timedelta(days=+60), filter)
|
||||
ical = calendar.export_to_ics()
|
||||
|
||||
response = HttpResponse(content_type="text/calendar")
|
||||
response.content = ical.to_ical().decode("utf-8").replace("\r\n", "\n")
|
||||
@ -830,71 +735,41 @@ def export_ical(request):
|
||||
return response
|
||||
|
||||
|
||||
@method_decorator(check_honeypot, name='post')
|
||||
class MessageCreateView(SuccessMessageMixin, CreateView):
|
||||
model = Message
|
||||
template_name = "agenda_culturel/message_create_form.html"
|
||||
form_class = MessageForm
|
||||
|
||||
class ContactMessageCreateView(SuccessMessageMixin, CreateView):
|
||||
model = ContactMessage
|
||||
template_name = "agenda_culturel/contactmessage_create_form.html"
|
||||
fields = ["subject", "name", "email", "message"]
|
||||
|
||||
success_url = reverse_lazy("home")
|
||||
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):
|
||||
if form_class is None:
|
||||
form_class = self.get_form_class()
|
||||
return form_class(**self.get_form_kwargs())
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs["event"] = self.event
|
||||
if self.request.user.is_authenticated:
|
||||
kwargs["internal"] = True
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, form):
|
||||
if self.request.user.is_authenticated:
|
||||
form.instance.user = self.request.user
|
||||
form.instance.message_type = Message.TYPE.EVENT_REPORT if "pk" in self.kwargs else Message.TYPE.CONTACT_FORM
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
def get_initial(self):
|
||||
result = super().get_initial()
|
||||
if "pk" in self.kwargs:
|
||||
self.event = get_object_or_404(Event, pk=self.kwargs["pk"])
|
||||
result["related_event"] = self.event
|
||||
result["subject"] = _('Reporting the event {} on {}').format(self.event.title, self.event.start_day)
|
||||
else:
|
||||
result["related_event"] = None
|
||||
return result
|
||||
|
||||
|
||||
|
||||
class MessageDeleteView(SuccessMessageMixin, DeleteView):
|
||||
model = Message
|
||||
class ContactMessageDeleteView(SuccessMessageMixin, DeleteView):
|
||||
model = ContactMessage
|
||||
success_message = _(
|
||||
"The contact message has been successfully deleted."
|
||||
)
|
||||
success_url = reverse_lazy("messages")
|
||||
success_url = reverse_lazy("contactmessages")
|
||||
|
||||
|
||||
class MessageUpdateView(
|
||||
class ContactMessageUpdateView(
|
||||
SuccessMessageMixin, PermissionRequiredMixin, LoginRequiredMixin, UpdateView
|
||||
):
|
||||
model = Message
|
||||
permission_required = "agenda_culturel.change_message"
|
||||
template_name = "agenda_culturel/message_moderation_form.html"
|
||||
model = ContactMessage
|
||||
permission_required = "agenda_culturel.change_contactmessage"
|
||||
template_name = "agenda_culturel/contactmessage_moderation_form.html"
|
||||
fields = ("spam", "closed", "comments")
|
||||
|
||||
success_message = _(
|
||||
"The contact message properties has been successfully modified."
|
||||
)
|
||||
|
||||
success_url = reverse_lazy("messages")
|
||||
success_url = reverse_lazy("contactmessages")
|
||||
|
||||
def get_form_kwargs(self):
|
||||
"""Return the keyword arguments for instantiating the form."""
|
||||
@ -931,27 +806,23 @@ def activite(request):
|
||||
@login_required(login_url="/accounts/login/")
|
||||
@permission_required("agenda_culturel.view_event")
|
||||
def administration(request):
|
||||
nb_mod_days = 21
|
||||
nb_classes = 4
|
||||
today = date.today()
|
||||
start_time = datetime.now().time()
|
||||
|
||||
# get information about recent modifications
|
||||
days = [today]
|
||||
days = [date.today()]
|
||||
for i in range(0, 2):
|
||||
days.append(days[-1] + timedelta(days=-1))
|
||||
daily_modifications = Event.get_count_modifications([(d, 1) for d in days])
|
||||
|
||||
# get last created events
|
||||
events = Event.objects.all().order_by("-created_date").select_related("exact_location", "category")[:5]
|
||||
events = Event.objects.all().order_by("-created_date")[:5]
|
||||
|
||||
# get last batch imports
|
||||
batch_imports = BatchImportation.objects.all().select_related("recurrentImport").order_by("-created_date")[:5]
|
||||
batch_imports = BatchImportation.objects.all().order_by("-created_date")[:5]
|
||||
|
||||
# get info about batch information
|
||||
newest = BatchImportation.objects.filter(recurrentImport=OuterRef("pk")).order_by(
|
||||
"-created_date"
|
||||
).select_related("recurrentImport")
|
||||
)
|
||||
imported_events = RecurrentImport.objects.annotate(
|
||||
last_run_status=Subquery(newest.values("status")[:1])
|
||||
)
|
||||
@ -967,41 +838,13 @@ def administration(request):
|
||||
.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(
|
||||
request,
|
||||
"agenda_culturel/administration.html",
|
||||
{"daily_modifications": daily_modifications,
|
||||
"events": events, "batch_imports": batch_imports,
|
||||
"nb_failed": nb_failed, "nb_canceled": nb_canceled,
|
||||
"nb_running": nb_running, "nb_all": nb_all,
|
||||
"nb_not_moderated": nb_not_moderated},
|
||||
"nb_running": nb_running, "nb_all": nb_all},
|
||||
)
|
||||
|
||||
|
||||
@ -1030,15 +873,15 @@ def recent(request):
|
||||
|
||||
|
||||
@login_required(login_url="/accounts/login/")
|
||||
@permission_required("agenda_culturel.view_message")
|
||||
def view_messages(request):
|
||||
filter = MessagesFilterAdmin(
|
||||
request.GET, queryset=Message.objects.all().order_by("-date")
|
||||
@permission_required("agenda_culturel.view_contactmessage")
|
||||
def contactmessages(request):
|
||||
filter = ContactMessagesFilterAdmin(
|
||||
request.GET, queryset=ContactMessage.objects.all().order_by("-date")
|
||||
)
|
||||
paginator = PaginatorFilter(filter, 10, request)
|
||||
page = request.GET.get("page")
|
||||
|
||||
nb_spams = Message.objects.filter(spam=True).count()
|
||||
nb_spams = ContactMessage.objects.filter(spam=True).count()
|
||||
|
||||
try:
|
||||
response = paginator.page(page)
|
||||
@ -1049,24 +892,24 @@ def view_messages(request):
|
||||
|
||||
return render(
|
||||
request,
|
||||
"agenda_culturel/messages.html",
|
||||
"agenda_culturel/contactmessages.html",
|
||||
{"filter": filter, "nb_spams": nb_spams, "paginator_filter": response},
|
||||
)
|
||||
|
||||
@login_required(login_url="/accounts/login/")
|
||||
@permission_required("agenda_culturel.view_message")
|
||||
@permission_required("agenda_culturel.view_contactmessage")
|
||||
def delete_cm_spam(request):
|
||||
|
||||
if request.method == "POST":
|
||||
Message.objects.filter(spam=True).delete()
|
||||
ContactMessage.objects.filter(spam=True).delete()
|
||||
|
||||
messages.success(request, _("Spam has been successfully deleted."))
|
||||
return HttpResponseRedirect(reverse_lazy("messages"))
|
||||
return HttpResponseRedirect(reverse_lazy("contactmessages"))
|
||||
else:
|
||||
nb_msgs = Message.objects.values('spam').annotate(total=Count('spam'))
|
||||
nb_msgs = ContactMessage.objects.values('spam').annotate(total=Count('spam'))
|
||||
nb_total = sum([nb["total"] for nb in nb_msgs])
|
||||
nb_spams = sum([nb["total"] for nb in nb_msgs if nb["spam"]])
|
||||
cancel_url = reverse_lazy("messages")
|
||||
cancel_url = reverse_lazy("contactmessages")
|
||||
return render(
|
||||
request,
|
||||
"agenda_culturel/delete_spams_confirm.html",
|
||||
@ -1622,7 +1465,6 @@ def set_duplicate(request, year, month, day, pk):
|
||||
event.other_versions is None
|
||||
or event.other_versions != e.other_versions
|
||||
)
|
||||
and e.status != Event.STATUS.TRASH
|
||||
]
|
||||
|
||||
form = SelectEventInList(events=others)
|
||||
@ -1998,7 +1840,6 @@ class UnknownPlaceAddView(PermissionRequiredMixin, SuccessMessageMixin, UpdateVi
|
||||
|
||||
|
||||
class PlaceFromEventCreateView(PlaceCreateView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["event"] = self.event
|
||||
@ -2009,14 +1850,6 @@ class PlaceFromEventCreateView(PlaceCreateView):
|
||||
self.event = get_object_or_404(Event, pk=self.kwargs["pk"])
|
||||
if self.event.location and "add" in self.request.GET:
|
||||
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
|
||||
|
||||
def form_valid(self, form):
|
||||
@ -2138,10 +1971,10 @@ def view_tag(request, t):
|
||||
except EmptyPage:
|
||||
response = paginator.page(paginator.num_pages)
|
||||
|
||||
rimports = RecurrentImport.objects.filter(defaultTags__contains=[t])
|
||||
|
||||
|
||||
tag = Tag.objects.filter(name=t).first()
|
||||
context = {"tag": t, "paginator_filter": response, "object": tag, "rimports": rimports}
|
||||
context = {"tag": t, "paginator_filter": response, "object": tag}
|
||||
return render(request, "agenda_culturel/tag.html", context)
|
||||
|
||||
|
||||
@ -2204,21 +2037,11 @@ def rename_tag(request, t):
|
||||
e.tags += [new_name]
|
||||
Event.objects.bulk_update(events, fields=["tags"])
|
||||
|
||||
# find all recurrent imports and fix them
|
||||
rimports = RecurrentImport.objects.filter(defaultTags__contains=[t])
|
||||
for ri in rimports:
|
||||
ri.tags = [te for te in ri.defaultTags if te != t]
|
||||
if not new_name in ri.tags:
|
||||
ri.tags += [new_name]
|
||||
RecurrentImport.objects.bulk_update(rimports, fields=["defaultTags"])
|
||||
|
||||
# find tag object
|
||||
tag_object = Tag.objects.filter(name=t).first()
|
||||
if tag_object:
|
||||
tag_object.name = new_name
|
||||
tag_object.save()
|
||||
|
||||
|
||||
messages.success(
|
||||
request,
|
||||
(_(
|
||||
@ -2237,6 +2060,8 @@ def rename_tag(request, t):
|
||||
@permission_required("agenda_culturel.delete_tag")
|
||||
def delete_tag(request, t):
|
||||
respage = reverse_lazy("view_all_tags")
|
||||
nb = Event.objects.filter(tags__contains=[t]).count()
|
||||
obj = Tag.objects.filter(name=t).first()
|
||||
|
||||
if request.method == "POST":
|
||||
|
||||
@ -2246,12 +2071,6 @@ def delete_tag(request, t):
|
||||
e.tags = [te for te in e.tags if te != t]
|
||||
Event.objects.bulk_update(events, fields=["tags"])
|
||||
|
||||
# remove tag from recurrent imports
|
||||
rimports = RecurrentImport.objects.filter(defaultTags__contains=[t])
|
||||
for ri in rimports:
|
||||
ri.tags = [te for te in ri.defaultTags if te != t]
|
||||
RecurrentImport.objects.bulk_update(rimports, fields=["defaultTags"])
|
||||
|
||||
# find tag object
|
||||
tag_object = Tag.objects.filter(name=t).first()
|
||||
if tag_object:
|
||||
@ -2265,26 +2084,11 @@ def delete_tag(request, t):
|
||||
)
|
||||
return HttpResponseRedirect(respage)
|
||||
else:
|
||||
nb = Event.objects.filter(tags__contains=[t]).count()
|
||||
obj = Tag.objects.filter(name=t).first()
|
||||
nbi = RecurrentImport.objects.filter(defaultTags__contains=[t]).count()
|
||||
cancel_url = request.META.get("HTTP_REFERER", "")
|
||||
if cancel_url == "":
|
||||
cancel_url = respage
|
||||
return render(
|
||||
request,
|
||||
"agenda_culturel/tag_confirm_delete_by_name.html",
|
||||
{"tag": t, "nb": nb, "nbi": nbi, "cancel_url": cancel_url, "obj": obj},
|
||||
{"tag": t, "nb": nb, "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,5 +42,4 @@ django-location-field==2.7.3
|
||||
django-robots==6.1
|
||||
django-debug-toolbar==4.4.6
|
||||
django-cache-cleaner==0.1.0
|
||||
emoji==2.14.0
|
||||
django-honeypot==1.2.1
|
||||
emoji==2.14.0
|
@ -289,7 +289,6 @@
|
||||
{
|
||||
"com_name": "Clermont-Ferrand",
|
||||
"main": 10,
|
||||
"suggested": 10,
|
||||
"geo_point_2d": {
|
||||
"lon": 3.1153994509459313,
|
||||
"lat": 45.78590931605406
|
||||
@ -340,7 +339,6 @@
|
||||
{
|
||||
"com_name": "Riom",
|
||||
"main": 3,
|
||||
"suggested": 10,
|
||||
"geo_point_2d": {
|
||||
"lon": 3.13259085594027,
|
||||
"lat": 45.89435053196184
|
||||
|
@ -13,10 +13,7 @@ def run():
|
||||
# remove all locations
|
||||
ReferenceLocation.objects.all().delete()
|
||||
|
||||
objs = [ReferenceLocation(location=Point(c["geo_point_2d"]["lon"], c["geo_point_2d"]["lat"]),
|
||||
name=c["com_name"],
|
||||
main=c["main"] if "main" in c else 0,
|
||||
suggested_distance=c["suggested"] if "suggested" in c else None) for c in data]
|
||||
objs = [ReferenceLocation(location=Point(c["geo_point_2d"]["lon"], c["geo_point_2d"]["lat"]), name=c["com_name"], main=c["main"] if "main" in c else 0) for c in data]
|
||||
|
||||
objs = ReferenceLocation.objects.bulk_create(objs, ignore_conflicts=True)
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
from agenda_culturel.models import Message
|
||||
from agenda_culturel.models import ContactMessage
|
||||
|
||||
|
||||
def run():
|
||||
Message.objects.all().update(spam=True)
|
||||
ContactMessage.objects.all().update(spam=True)
|
||||
|
Loading…
Reference in New Issue
Block a user