ajout de pages aux vues generales

This commit is contained in:
SebF 2025-04-15 10:38:39 +02:00
parent a8b10f3899
commit 7f7f46a9bb
5 changed files with 291 additions and 300 deletions

View File

@ -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:<cat>/ce-mois-ci", month_view, name="ce_mois_ci_category"),
path("mois/<int:year>/<int:month>/", month_view, name="month_view"),
path("ce-mois-ci", month_view, name="ce_mois_ci"),
path("cat:<cat>/a-venir/", upcoming_events, name="a_venir_category"),
path(
"cat:<cat>/a-venir/<int:year>/<int:month>/<int:day>/",
upcoming_events,
name="a_venir_jour_category",
),
path("a-venir/", upcoming_events, name="a_venir"),
path(
"a-venir/<int:year>/<int:month>/<int:day>/",
upcoming_events,
name="a_venir_jour",
),
path(
"cat:<cat>/semaine/<int:year>/<int:week>/",
week_view,
@ -236,11 +248,24 @@ urlpatterns = [
path("duplicates/<int:pk>/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/<int:pk>/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:<cat>/", home, name="home_category"),
path("cat:<cat>/a-venir/", upcoming_events, name="a_venir_category"),
path(
"cat:<cat>/a-venir/<int:year>/<int:month>/<int:day>/",
upcoming_events,
name="a_venir_jour_category",
),
path("a-venir/", upcoming_events, name="a_venir"),
path(
"a-venir/<int:year>/<int:month>/<int:day>/",
upcoming_events,
name="a_venir_jour",
),
path("recent/", recent, name="recent"),
path("administration/", administration, name="administration"),
path(
"event/<int:year>/<int:month>/<int:day>/<int:pk>-<extra>",
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/<int:pk>/edit",
StaticContentUpdateView.as_view(),
name="edit_static_content",
),
path("rimports/<int:pk>/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:

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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