From 7f7f46a9bbfc6661265cdff68f4fa511d8a51bea Mon Sep 17 00:00:00 2001 From: SebF Date: Tue, 15 Apr 2025 10:38:39 +0200 Subject: [PATCH] ajout de pages aux vues generales --- src/agenda_culturel/urls.py | 62 ++-- src/agenda_culturel/views/calendar_views.py | 51 +++- .../views/general_pages_views.py | 154 +++++++++- src/agenda_culturel/views/oldviews.py | 264 +----------------- src/agenda_culturel/views/utils.py | 60 +++- 5 files changed, 291 insertions(+), 300 deletions(-) diff --git a/src/agenda_culturel/urls.py b/src/agenda_culturel/urls.py index d498780..0e26102 100644 --- a/src/agenda_culturel/urls.py +++ b/src/agenda_culturel/urls.py @@ -20,6 +20,7 @@ from .views import ( # Calendar day_view, month_view, + upcoming_events, week_view, # Categorisation rules CategorisationRuleCreateView, @@ -42,12 +43,17 @@ from .views import ( export_ical, # General pages about, + administration, activite, + clear_cache, home, import_requirements, mentions_legales, moderation_rules, + StaticContentCreateView, + StaticContentUpdateView, statistics, + UserProfileUpdateView, thank_you, # Import batch imports, @@ -105,14 +111,9 @@ from .views import ( delete_tag, TagCreateView, # TODO pas encore triƩ - upcoming_events, recent, - administration, - clear_cache, EventDetailView, EventUpdateView, - StaticContentCreateView, - StaticContentUpdateView, EventCreateView, update_from_source, change_status_event, @@ -120,7 +121,6 @@ from .views import ( import_event_proxy, import_from_url, import_from_urls, - UserProfileUpdateView, ) event_dict = { @@ -170,6 +170,18 @@ urlpatterns = [ path("cat:/ce-mois-ci", month_view, name="ce_mois_ci_category"), path("mois///", month_view, name="month_view"), path("ce-mois-ci", month_view, name="ce_mois_ci"), + path("cat:/a-venir/", upcoming_events, name="a_venir_category"), + path( + "cat:/a-venir////", + upcoming_events, + name="a_venir_jour_category", + ), + path("a-venir/", upcoming_events, name="a_venir"), + path( + "a-venir////", + upcoming_events, + name="a_venir_jour", + ), path( "cat:/semaine///", week_view, @@ -236,11 +248,24 @@ urlpatterns = [ path("duplicates//fix", fix_duplicate, name="fix_duplicate"), # General pages path("a-propos", about, name="about"), + path("administration/", administration, name="administration"), path("activite/", activite, name="activite"), + path("cache/clear", clear_cache, name="clear_cache"), path("", home, name="home"), path("besoin-pour-import", import_requirements, name="import_requirements"), path("mentions-legales", mentions_legales, name="mentions_legales"), path("regles-de-moderation", moderation_rules, name="moderation_rules"), + path( + "static-content/create", + StaticContentCreateView.as_view(), + name="create_static_content", + ), + path( + "static-content//edit", + StaticContentUpdateView.as_view(), + name="edit_static_content", + ), + path("profile/edit", UserProfileUpdateView.as_view(), name="edit_profile"), path("statistiques", statistics, name="statistics"), path("merci", thank_you, name="thank_you"), # Import batch @@ -436,20 +461,7 @@ urlpatterns = [ path("tags/add", TagCreateView.as_view(), name="add_tag"), # TODO pas encore triƩ path("cat:/", home, name="home_category"), - path("cat:/a-venir/", upcoming_events, name="a_venir_category"), - path( - "cat:/a-venir////", - upcoming_events, - name="a_venir_jour_category", - ), - path("a-venir/", upcoming_events, name="a_venir"), - path( - "a-venir////", - upcoming_events, - name="a_venir_jour", - ), path("recent/", recent, name="recent"), - path("administration/", administration, name="administration"), path( "event////-", EventDetailView.as_view(), @@ -488,16 +500,6 @@ urlpatterns = [ path("admin/", admin.site.urls), path("accounts/", include("django.contrib.auth.urls")), path("test_app/", include("test_app.urls")), - path( - "static-content/create", - StaticContentCreateView.as_view(), - name="create_static_content", - ), - path( - "static-content//edit", - StaticContentUpdateView.as_view(), - name="edit_static_content", - ), path("rimports//stats", statistics, name="stats_rimport"), re_path(r"^robots\.txt", include("robots.urls")), path("__debug__/", include("debug_toolbar.urls")), @@ -508,8 +510,6 @@ urlpatterns = [ {"sitemaps": sitemaps}, name="cached-sitemap", ), - path("cache/clear", clear_cache, name="clear_cache"), - path("profile/edit", UserProfileUpdateView.as_view(), name="edit_profile"), ] if settings.DEBUG: diff --git a/src/agenda_culturel/views/calendar_views.py b/src/agenda_culturel/views/calendar_views.py index 3943a31..39bb60f 100644 --- a/src/agenda_culturel/views/calendar_views.py +++ b/src/agenda_culturel/views/calendar_views.py @@ -1,4 +1,4 @@ -from datetime import date +from datetime import date, timedelta from django.http import ( HttpResponseRedirect, @@ -7,9 +7,8 @@ from django.shortcuts import render from django.urls import reverse_lazy from django.utils.timezone import datetime -from . import upcoming_events from .utils import get_event_qs -from ..calendar import CalendarMonth, CalendarWeek +from ..calendar import CalendarMonth, CalendarWeek, CalendarList from ..filters import EventFilter from ..models import Category @@ -141,3 +140,49 @@ def week_view(request, year=None, week=None, home=False, cat=None): if home: context["home"] = 1 return render(request, "agenda_culturel/page-week.html", context) + + +def upcoming_events(request, year=None, month=None, day=None, neighsize=1, cat=None): + now = date.today() + if year is None: + year = now.year + if month is None: + month = now.month + if day is None: + day = now.day + + day = date(year, month, day) + day = day + timedelta(days=neighsize) + + request = EventFilter.set_default_values(request) + qs = get_event_qs(request).select_related("exact_location") + if cat is not None: + category = Category.objects.filter(slug=cat).first() + qs = qs.filter(category=category) + else: + category = None + + filter = EventFilter(request.GET, qs, request=request) + + if filter.has_category_parameters(): + return HttpResponseRedirect(filter.get_new_url()) + + cal = CalendarList( + day + timedelta(days=-neighsize), + day + timedelta(days=neighsize), + filter, + True, + ) + + context = { + "calendar": cal, + "now": now, + "day": day, + "init_date": now if cal.today_in_calendar() else day, + "filter": filter, + "date_pred": day + timedelta(days=-neighsize - 1), + "date_next": day + timedelta(days=neighsize + 1), + "category": category, + } + + return render(request, "agenda_culturel/page-upcoming.html", context) diff --git a/src/agenda_culturel/views/general_pages_views.py b/src/agenda_culturel/views/general_pages_views.py index 685954a..9998f0b 100644 --- a/src/agenda_culturel/views/general_pages_views.py +++ b/src/agenda_culturel/views/general_pages_views.py @@ -1,16 +1,30 @@ import calendar as _calendar from datetime import date, timedelta, datetime +from django.contrib import messages from django.contrib.auth.decorators import login_required, permission_required -from django.db.models import Count, F +from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin +from django.contrib.messages.views import SuccessMessageMixin +from django.core.cache import cache +from django.db.models import Count, F, Subquery, OuterRef from django.db.models import Q, Min, Max, Avg, StdDev from django.db.models.functions import TruncMonth, ExtractDay +from django.http import HttpResponseRedirect from django.shortcuts import render from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ +from django.views.generic import CreateView, UpdateView -from . import Median, week_view -from ..models import RecurrentImport, Event +from . import week_view +from .utils import Median +from ..forms import UserProfileForm +from ..models import ( + RecurrentImport, + Event, + StaticContent, + BatchImportation, + UserProfile, +) def home(request, cat=None): @@ -198,3 +212,137 @@ def activite(request): "weekly_modifications": weekly_modifications, }, ) + + +class StaticContentCreateView(LoginRequiredMixin, CreateView): + model = StaticContent + fields = ["text"] + permission_required = "agenda_culturel.add_staticcontent" + + def form_valid(self, form): + form.instance.name = self.request.GET["name"] + form.instance.url_path = self.request.GET["url_path"] + return super().form_valid(form) + + +class StaticContentUpdateView( + SuccessMessageMixin, + PermissionRequiredMixin, + LoginRequiredMixin, + UpdateView, +): + model = StaticContent + permission_required = "agenda_culturel.change_staticcontent" + fields = ["text"] + success_message = _("The static content has been successfully updated.") + + +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", + ) + + +@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() + + # get information about recent modifications + days = [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] + ) + + # get last batch imports + rel_event = Event.objects.filter( + import_sources__contains=[OuterRef("url_source")] + ).values("pk")[:1] + batch_imports = ( + BatchImportation.objects.all() + .select_related("recurrentImport") + .annotate(event_id=Subquery(rel_event)) + .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]) + ) + + nb_failed = imported_events.filter( + last_run_status=BatchImportation.STATUS.FAILED + ).count() + nb_canceled = imported_events.filter( + last_run_status=BatchImportation.STATUS.CANCELED + ).count() + nb_running = imported_events.filter( + last_run_status=BatchImportation.STATUS.RUNNING + ).count() + nb_all = imported_events.count() + + # get some info about imported (or not) events + srcs = RecurrentImport.objects.all().values_list("source") + in_future = Event.objects.filter(Q(start_day__gte=today)) + nb_in_rimport = in_future.filter(Q(import_sources__overlap=srcs)).count() + nb_in_orphan_import = in_future.filter( + ( + Q(import_sources__isnull=False) + & (Q(modified_date__isnull=True) | Q(modified_date__lte=F("imported_date"))) + ) + & ~Q(import_sources__overlap=srcs) + ).count() + + # get all non moderated events + nb_not_moderated = Event.get_nb_not_moderated(today, nb_mod_days, nb_classes) + + 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_in_rimport": nb_in_rimport, + "nb_in_orphan_import": nb_in_orphan_import, + }, + ) + + +class UserProfileUpdateView( + SuccessMessageMixin, + LoginRequiredMixin, + UpdateView, +): + model = UserProfile + success_message = _("Your user profile has been successfully modified.") + success_url = reverse_lazy("administration") + form_class = UserProfileForm + + def get_object(self): + return self.request.user.userprofile diff --git a/src/agenda_culturel/views/oldviews.py b/src/agenda_culturel/views/oldviews.py index 321c5f9..c66a3d0 100644 --- a/src/agenda_culturel/views/oldviews.py +++ b/src/agenda_culturel/views/oldviews.py @@ -1,4 +1,3 @@ -import logging from datetime import date, timedelta from django.contrib import messages @@ -9,10 +8,7 @@ from django.contrib.auth.mixins import ( UserPassesTestMixin, ) from django.contrib.messages.views import SuccessMessageMixin -from django.core.cache import cache -from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator -from django.db.models import Aggregate, FloatField -from django.db.models import F, OuterRef, Q, Subquery +from django.core.paginator import EmptyPage, PageNotAnInteger from django.http import ( HttpResponseForbidden, HttpResponseRedirect, @@ -30,14 +26,12 @@ from django.views.generic.edit import ( UpdateView, ) -from .utils import get_event_qs -from ..calendar import CalendarList +from .utils import PaginatorFilter from ..celery import ( import_events_from_url, import_events_from_urls, ) from ..filters import ( - EventFilter, EventFilterAdmin, ) from ..forms import ( @@ -47,157 +41,14 @@ from ..forms import ( SimpleContactForm, URLSubmissionFormSet, URLSubmissionFormWithContact, - UserProfileForm, ) from ..import_tasks.extractor import Extractor from ..models import ( - BatchImportation, - Category, DuplicatedEvents, Event, Message, - RecurrentImport, - StaticContent, - UserProfile, ) -logger = logging.getLogger(__name__) - - -class Median(Aggregate): - function = "PERCENTILE_CONT" - name = "median" - output_field = FloatField() - template = "%(function)s(0.5) WITHIN GROUP (ORDER BY %(expressions)s)" - - -class PaginatorFilter(Paginator): - def __init__(self, filter, nb, request): - self.request = request - self.filter = filter - - super().__init__(filter.qs, nb) - - self.url_first_page = PaginatorFilter.update_param( - self.request.get_full_path(), "page", 1 - ) - self.url_last_page = PaginatorFilter.update_param( - self.request.get_full_path(), "page", self.num_pages - ) - - def update_param(params, key, value): - p = params.split("?") - root = p[0] - if len(p) > 1: - other = p[1] - others = other.split("&") - others = [o for o in others if not o.startswith(key)] - others += [key + "=" + str(value)] - return root + "?" + "&".join(others) - else: - return root + "?" + key + "=" + str(value) - - def page(self, *args, **kwargs): - page = super().page(*args, **kwargs) - - try: - page.url_previous_page = PaginatorFilter.update_param( - self.request.get_full_path(), - "page", - page.previous_page_number(), - ) - except EmptyPage: - page.url_previous_page = self.request.get_full_path() - - try: - page.url_next_page = PaginatorFilter.update_param( - self.request.get_full_path(), "page", page.next_page_number() - ) - except EmptyPage: - page.url_next_page = self.request.get_full_path() - - return page - - -# -# -# Useful for translation -to_be_translated = [ - _("Recurrent import name"), - _("Add another"), - _("Browse..."), - _("No file selected."), -] - - -def upcoming_events(request, year=None, month=None, day=None, neighsize=1, cat=None): - now = date.today() - if year is None: - year = now.year - if month is None: - month = now.month - if day is None: - day = now.day - - day = date(year, month, day) - day = day + timedelta(days=neighsize) - - request = EventFilter.set_default_values(request) - qs = get_event_qs(request).select_related("exact_location") - if cat is not None: - category = Category.objects.filter(slug=cat).first() - qs = qs.filter(category=category) - else: - category = None - - filter = EventFilter(request.GET, qs, request=request) - - if filter.has_category_parameters(): - return HttpResponseRedirect(filter.get_new_url()) - - cal = CalendarList( - day + timedelta(days=-neighsize), - day + timedelta(days=neighsize), - filter, - True, - ) - - context = { - "calendar": cal, - "now": now, - "day": day, - "init_date": now if cal.today_in_calendar() else day, - "filter": filter, - "date_pred": day + timedelta(days=-neighsize - 1), - "date_next": day + timedelta(days=neighsize + 1), - "category": category, - } - - return render(request, "agenda_culturel/page-upcoming.html", context) - - -class StaticContentCreateView(LoginRequiredMixin, CreateView): - model = StaticContent - fields = ["text"] - permission_required = "agenda_culturel.add_staticcontent" - - def form_valid(self, form): - form.instance.name = self.request.GET["name"] - form.instance.url_path = self.request.GET["url_path"] - return super().form_valid(form) - - -class StaticContentUpdateView( - SuccessMessageMixin, - PermissionRequiredMixin, - LoginRequiredMixin, - UpdateView, -): - model = StaticContent - permission_required = "agenda_culturel.change_staticcontent" - fields = ["text"] - success_message = _("The static content has been successfully updated.") - def update_from_source(request, pk): event = get_object_or_404(Event, pk=pk) @@ -691,91 +542,6 @@ def import_from_url(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() - - # get information about recent modifications - days = [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] - ) - - # get last batch imports - rel_event = Event.objects.filter( - import_sources__contains=[OuterRef("url_source")] - ).values("pk")[:1] - batch_imports = ( - BatchImportation.objects.all() - .select_related("recurrentImport") - .annotate(event_id=Subquery(rel_event)) - .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]) - ) - - nb_failed = imported_events.filter( - last_run_status=BatchImportation.STATUS.FAILED - ).count() - nb_canceled = imported_events.filter( - last_run_status=BatchImportation.STATUS.CANCELED - ).count() - nb_running = imported_events.filter( - last_run_status=BatchImportation.STATUS.RUNNING - ).count() - nb_all = imported_events.count() - - # get some info about imported (or not) events - srcs = RecurrentImport.objects.all().values_list("source") - in_future = Event.objects.filter(Q(start_day__gte=today)) - nb_in_rimport = in_future.filter(Q(import_sources__overlap=srcs)).count() - nb_in_orphan_import = in_future.filter( - ( - Q(import_sources__isnull=False) - & (Q(modified_date__isnull=True) | Q(modified_date__lte=F("imported_date"))) - ) - & ~Q(import_sources__overlap=srcs) - ).count() - - # get all non moderated events - nb_not_moderated = Event.get_nb_not_moderated(today, nb_mod_days, nb_classes) - - 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_in_rimport": nb_in_rimport, - "nb_in_orphan_import": nb_in_orphan_import, - }, - ) - - @login_required(login_url="/accounts/login/") @permission_required("agenda_culturel.view_event") def recent(request): @@ -797,29 +563,3 @@ def recent(request): "agenda_culturel/recent.html", {"filter": filter, "paginator_filter": response}, ) - - -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", - ) - - -class UserProfileUpdateView( - SuccessMessageMixin, - LoginRequiredMixin, - UpdateView, -): - model = UserProfile - success_message = _("Your user profile has been successfully modified.") - success_url = reverse_lazy("administration") - form_class = UserProfileForm - - def get_object(self): - return self.request.user.userprofile diff --git a/src/agenda_culturel/views/utils.py b/src/agenda_culturel/views/utils.py index a9f8b39..90a2210 100644 --- a/src/agenda_culturel/views/utils.py +++ b/src/agenda_culturel/views/utils.py @@ -1,6 +1,9 @@ -from ..models import Event +from django.core.paginator import EmptyPage, Paginator from django.utils.translation import gettext_lazy as _ +from django.db.models import Aggregate, FloatField +from ..models import Event + # # # Useful for translation @@ -17,3 +20,58 @@ def get_event_qs(request): return Event.objects.filter() else: return Event.objects.filter(status=Event.STATUS.PUBLISHED) + + +class Median(Aggregate): + function = "PERCENTILE_CONT" + name = "median" + output_field = FloatField() + template = "%(function)s(0.5) WITHIN GROUP (ORDER BY %(expressions)s)" + + +class PaginatorFilter(Paginator): + def __init__(self, filter, nb, request): + self.request = request + self.filter = filter + + super().__init__(filter.qs, nb) + + self.url_first_page = PaginatorFilter.update_param( + self.request.get_full_path(), "page", 1 + ) + self.url_last_page = PaginatorFilter.update_param( + self.request.get_full_path(), "page", self.num_pages + ) + + def update_param(params, key, value): + p = params.split("?") + root = p[0] + if len(p) > 1: + other = p[1] + others = other.split("&") + others = [o for o in others if not o.startswith(key)] + others += [key + "=" + str(value)] + return root + "?" + "&".join(others) + else: + return root + "?" + key + "=" + str(value) + + def page(self, *args, **kwargs): + page = super().page(*args, **kwargs) + + try: + page.url_previous_page = PaginatorFilter.update_param( + self.request.get_full_path(), + "page", + page.previous_page_number(), + ) + except EmptyPage: + page.url_previous_page = self.request.get_full_path() + + try: + page.url_next_page = PaginatorFilter.update_param( + self.request.get_full_path(), "page", page.next_page_number() + ) + except EmptyPage: + page.url_next_page = self.request.get_full_path() + + return page