From 9ec22919f1b27056f9f8844912256cb62f94f375 Mon Sep 17 00:00:00 2001 From: SebF Date: Fri, 4 Apr 2025 11:26:21 +0200 Subject: [PATCH] =?UTF-8?q?S=C3=A9paration=20des=20vues=20event=20dupliqu?= =?UTF-8?q?=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agenda_culturel/urls.py | 50 +-- src/agenda_culturel/views/__init__.py | 1 + .../views/event_duplicate_views.py | 344 ++++++++++++++++++ src/agenda_culturel/views/oldviews.py | 336 +---------------- 4 files changed, 372 insertions(+), 359 deletions(-) create mode 100644 src/agenda_culturel/views/event_duplicate_views.py diff --git a/src/agenda_culturel/urls.py b/src/agenda_culturel/urls.py index 4c89592..28fd6b8 100644 --- a/src/agenda_culturel/urls.py +++ b/src/agenda_culturel/urls.py @@ -26,6 +26,13 @@ from .views import ( # Errors internal_server_error, page_not_found, + # Event duplicates + DuplicatedEventsDetailView, + duplicates, + fix_duplicate, + merge_duplicate, + set_duplicate, + update_duplicate_event, # General pages about, import_requirements, @@ -88,18 +95,14 @@ from .views import ( recent, administration, activite, - fix_duplicate, clear_cache, export_event_ical, MessageDeleteView, EventDetailView, EventUpdateView, - duplicates, - DuplicatedEventsDetailView, StaticContentCreateView, StaticContentUpdateView, MessageCreateView, - merge_duplicate, EventCreateView, event_search, event_search_full, @@ -107,14 +110,12 @@ from .views import ( update_from_source, change_status_event, EventDeleteView, - set_duplicate, import_event_proxy, import_from_url, import_from_urls, view_messages, MessageUpdateView, statistics, - update_duplicate_event, UserProfileUpdateView, ) @@ -167,6 +168,25 @@ urlpatterns = [ # Errors path("500/", internal_server_error, name="internal_server_error"), path("404/", page_not_found, name="page_not_found"), + path("duplicates//merge", merge_duplicate, name="merge_duplicate"), + path( + "event/////set_duplicate", + set_duplicate, + name="set_duplicate", + ), + path( + "duplicates//update/", + update_duplicate_event, + name="update_event", + ), + # Event duplicates + path( + "duplicates/", + DuplicatedEventsDetailView.as_view(), + name="view_duplicate", + ), + path("duplicates/", duplicates, name="duplicates"), + path("duplicates//fix", fix_duplicate, name="fix_duplicate"), # General pages path("a-propos", about, name="about"), path("besoin-pour-import", import_requirements, name="import_requirements"), @@ -424,11 +444,6 @@ urlpatterns = [ name="change_status_event", ), path("event//delete", EventDeleteView.as_view(), name="delete_event"), - path( - "event/////set_duplicate", - set_duplicate, - name="set_duplicate", - ), path("ajouter", import_event_proxy, name="add_event"), path("ajouter/url", import_from_url, name="add_event_url"), path("ajouter/urls", import_from_urls, name="add_event_urls"), @@ -463,19 +478,6 @@ urlpatterns = [ name="delete_message", ), path("rimports//stats", statistics, name="stats_rimport"), - path("duplicates/", duplicates, name="duplicates"), - path( - "duplicates/", - DuplicatedEventsDetailView.as_view(), - name="view_duplicate", - ), - path("duplicates//fix", fix_duplicate, name="fix_duplicate"), - path("duplicates//merge", merge_duplicate, name="merge_duplicate"), - path( - "duplicates//update/", - update_duplicate_event, - name="update_event", - ), path( "organisme//ical", export_ical, diff --git a/src/agenda_culturel/views/__init__.py b/src/agenda_culturel/views/__init__.py index 507c829..48dd34b 100644 --- a/src/agenda_culturel/views/__init__.py +++ b/src/agenda_culturel/views/__init__.py @@ -1,5 +1,6 @@ from .oldviews import * from .categorisation_rules_view import * +from .event_duplicate_views import * from .errors import * from .general_pages_views import * from .import_batch_views import * diff --git a/src/agenda_culturel/views/event_duplicate_views.py b/src/agenda_culturel/views/event_duplicate_views.py new file mode 100644 index 0000000..4bf5254 --- /dev/null +++ b/src/agenda_culturel/views/event_duplicate_views.py @@ -0,0 +1,344 @@ +from datetime import date + +from django.contrib import messages +from django.contrib.auth.decorators import login_required, permission_required +from django.contrib.auth.mixins import LoginRequiredMixin +from django.core.paginator import PageNotAnInteger, EmptyPage +from django.http import HttpResponseRedirect +from django.shortcuts import get_object_or_404, render +from django.urls import reverse_lazy +from django.utils.translation import gettext_lazy as _ +from django.views.generic import DetailView, UpdateView + +from src.agenda_culturel.calendar import CalendarDay +from src.agenda_culturel.filters import DuplicatedEventsFilter +from src.agenda_culturel.forms import MergeDuplicates, FixDuplicates, SelectEventInList +from src.agenda_culturel.models import DuplicatedEvents, Event +from src.agenda_culturel.views import PaginatorFilter + + +class DuplicatedEventsDetailView(LoginRequiredMixin, DetailView): + model = DuplicatedEvents + template_name = "agenda_culturel/duplicate.html" + + +@login_required(login_url="/accounts/login/") +@permission_required( + ["agenda_culturel.change_event", "agenda_culturel.change_duplicatedevents"] +) +def update_duplicate_event(request, pk, epk): + edup = get_object_or_404(DuplicatedEvents, pk=pk) + event = get_object_or_404(Event, pk=epk) + + form = MergeDuplicates(duplicates=edup, event=event) + + if request.method == "POST": + form = MergeDuplicates(request.POST, duplicates=edup) + if form.is_valid(): + for f in edup.get_items_comparison(): + if not f["similar"]: + selected = form.get_selected_events(f["key"]) + if selected is not None: + if isinstance(selected, list): + values = [ + x + for x in [getattr(s, f["key"]) for s in selected] + if x is not None + ] + if len(values) != 0: + if isinstance(values[0], str): + setattr(event, f["key"], "\n".join(values)) + else: + setattr(event, f["key"], sum(values, [])) + else: + if f["key"] == "organisers": + event.organisers.set(selected.organisers.all()) + else: + setattr( + event, + f["key"], + getattr(selected, f["key"]), + ) + if f["key"] == "image": + setattr( + event, + "local_image", + getattr(selected, "local_image"), + ) + + event.other_versions.fix(event) + event.save() + + messages.info(request, _("Update successfully completed.")) + return HttpResponseRedirect(event.get_absolute_url()) + + return render( + request, + "agenda_culturel/update_duplicate.html", + context={ + "form": form, + "object": edup, + "event_id": edup.get_event_index(event), + }, + ) + + +@login_required(login_url="/accounts/login/") +@permission_required( + ["agenda_culturel.change_event", "agenda_culturel.change_duplicatedevents"] +) +def merge_duplicate(request, pk): + edup = get_object_or_404(DuplicatedEvents, pk=pk) + form = MergeDuplicates(duplicates=edup) + + if request.method == "POST": + form = MergeDuplicates(request.POST, duplicates=edup) + if form.is_valid(): + events = edup.get_duplicated() + + # build fields for the new event + new_event_data = {} + for f in edup.get_items_comparison(): + if f["similar"]: + new_event_data[f["key"]] = getattr(events[0], f["key"]) + else: + selected = form.get_selected_events(f["key"]) + if selected is None: + new_event_data[f["key"]] = None + elif isinstance(selected, list): + values = [ + x + for x in [getattr(s, f["key"]) for s in selected] + if x is not None + ] + if len(values) == 0: + new_event_data[f["key"]] = None + else: + if isinstance(values[0], str): + new_event_data[f["key"]] = "\n".join(values) + else: + new_event_data[f["key"]] = sum(values, []) + else: + new_event_data[f["key"]] = getattr(selected, f["key"]) + if f["key"] == "image" and "local_image" not in new_event_data: + new_event_data["local_image"] = getattr( + selected, "local_image" + ) + + organisers = new_event_data.pop("organisers", None) + # create a new event that merge the selected events + new_event = Event(**new_event_data) + new_event.status = Event.STATUS.PUBLISHED + new_event.other_versions = edup + new_event.save() + if organisers is not None: + new_event.organisers.set(organisers.all()) + edup.fix(new_event) + + messages.info( + request, + _("Creation of a merged event has been successfully completed."), + ) + return HttpResponseRedirect(new_event.get_absolute_url()) + + return render( + request, + "agenda_culturel/merge_duplicate.html", + context={"form": form, "object": edup}, + ) + + +@login_required(login_url="/accounts/login/") +@permission_required( + ["agenda_culturel.change_event", "agenda_culturel.change_duplicatedevents"] +) +def fix_duplicate(request, pk): + edup = get_object_or_404(DuplicatedEvents.objects.select_related(), pk=pk) + + if request.method == "POST": + form = FixDuplicates(request.POST, edup=edup) + + if form.is_valid(): + if form.is_action_no_duplicates(): + # all events are different + events = edup.get_duplicated() + + # get redirection date + if len(events) == 0: + date = None + else: + s_events = [e for e in events if not e.has_recurrences()] + if len(s_events) != 0: + s_event = s_events[0] + else: + s_event = events[0] + date = s_event.start_day + + messages.success(request, _("Events have been marked as unduplicated.")) + # delete the duplicated event (other_versions will be set to None on all events) + edup.delete() + if date is None: + return HttpResponseRedirect(reverse_lazy("home")) + else: + return HttpResponseRedirect( + reverse_lazy("day_view", args=[date.year, date.month, date.day]) + ) + + elif form.is_action_select(): + # one element has been selected to be the representative + selected = form.get_selected_event(edup) + if selected is None: + messages.error( + request, + _( + "The selected item is no longer included in the list of duplicates. Someone else has probably modified the list in the meantime." + ), + ) + else: + edup.fix(selected) + messages.success( + request, + _("The selected event has been set as representative"), + ) + return HttpResponseRedirect(edup.get_absolute_url()) + elif form.is_action_remove(): + # one element is removed from the set + event = form.get_selected_event(edup) + if event is None: + messages.error( + request, + _( + "The selected item is no longer included in the list of duplicates. Someone else has probably modified the list in the meantime." + ), + ) + return HttpResponseRedirect(edup.get_absolute_url()) + else: + event.other_versions = None + if edup.representative == event: + edup.representative = None + event.set_no_modification_date_changed() + event.save() + edup.save() + edup.events = [e for e in edup.events if e.pk != event.pk] + messages.success( + request, + _( + "The event has been withdrawn from the group and made independent." + ), + ) + if edup.nb_duplicated() == 1: + return HttpResponseRedirect(edup.get_absolute_url()) + else: + form = FixDuplicates(edup=edup) + elif form.is_action_update(): + # otherwise, an event will be updated using other elements + event = form.get_selected_event(edup) + if event is None: + messages.error( + request, + _( + "The selected item is no longer included in the list of duplicates. Someone else has probably modified the list in the meantime." + ), + ) + return HttpResponseRedirect(edup.get_absolute_url()) + else: + return HttpResponseRedirect( + reverse_lazy("update_event", args=[edup.pk, event.pk]) + ) + else: + # otherwise, a new event will be created using a merging process + return HttpResponseRedirect( + reverse_lazy("merge_duplicate", args=[edup.pk]) + ) + else: + form = FixDuplicates(edup=edup) + + return render( + request, + "agenda_culturel/fix_duplicate.html", + context={"form": form, "object": edup}, + ) + + +class DuplicatedEventsUpdateView( + LoginRequiredMixin, UpdateView +): # Todo à supprimer, pas d’utilisation ? + model = DuplicatedEvents + fields = () + template_name = "agenda_culturel/fix_duplicate.html" + + +@login_required(login_url="/accounts/login/") +@permission_required("agenda_culturel.view_duplicatedevents") +def duplicates(request): + nb_removed = DuplicatedEvents.remove_singletons() + if nb_removed > 0: + messages.success( + request, + _("Cleaning up duplicates: {} item(s) fixed.").format(nb_removed), + ) + + filter = DuplicatedEventsFilter( + request.GET, queryset=DuplicatedEvents.objects.all().order_by("-pk") + ) + paginator = PaginatorFilter(filter, 10, request) + page = request.GET.get("page") + + try: + response = paginator.page(page) + except PageNotAnInteger: + response = paginator.page(1) + except EmptyPage: + response = paginator.page(paginator.num_pages) + + return render( + request, + "agenda_culturel/duplicates.html", + { + "filter": filter, + "paginator_filter": response, + "paginator": paginator, + }, + ) + + +def set_duplicate(request, year, month, day, pk): + event = get_object_or_404(Event, pk=pk) + cday = CalendarDay(date(year, month, day)) + others = [ + e + for e in cday.get_events() + if e != event + and (event.other_versions is None or event.other_versions != e.other_versions) + and e.status != Event.STATUS.TRASH + ] + + form = SelectEventInList(events=others) + + if request.method == "POST": + form = SelectEventInList(request.POST, events=others) + if form.is_valid(): + selected = [o for o in others if o.pk == int(form.cleaned_data["event"])] + event.set_other_versions(selected) + # save them without updating modified date + event.set_no_modification_date_changed() + event.save() + if request.user.is_authenticated: + messages.success(request, _("The event was successfully duplicated.")) + return HttpResponseRedirect( + reverse_lazy("view_duplicate", args=[event.other_versions.pk]) + ) + else: + messages.info( + request, + _( + "The event has been successfully flagged as a duplicate. The moderation team will deal with your suggestion shortly." + ), + ) + return HttpResponseRedirect(event.get_absolute_url()) + + return render( + request, + "agenda_culturel/set_duplicate.html", + context={"form": form, "event": event}, + ) diff --git a/src/agenda_culturel/views/oldviews.py b/src/agenda_culturel/views/oldviews.py index 2890b9c..3799dfa 100644 --- a/src/agenda_culturel/views/oldviews.py +++ b/src/agenda_culturel/views/oldviews.py @@ -42,13 +42,12 @@ from django.views.generic.edit import ( from honeypot.decorators import check_honeypot from .utils import get_event_qs -from ..calendar import CalendarDay, CalendarList, CalendarMonth, CalendarWeek +from ..calendar import CalendarList, CalendarMonth, CalendarWeek from ..celery import ( import_events_from_url, import_events_from_urls, ) from ..filters import ( - DuplicatedEventsFilter, EventFilter, EventFilterAdmin, MessagesFilterAdmin, @@ -58,11 +57,8 @@ from ..filters import ( from ..forms import ( EventForm, EventFormWithContact, - FixDuplicates, - MergeDuplicates, MessageEventForm, MessageForm, - SelectEventInList, SimpleContactForm, URLSubmissionFormSet, URLSubmissionFormWithContact, @@ -1293,336 +1289,6 @@ def event_search_full(request): return event_search(request, True) -######################### -## duplicated events -######################### - - -class DuplicatedEventsDetailView(LoginRequiredMixin, DetailView): - model = DuplicatedEvents - template_name = "agenda_culturel/duplicate.html" - - -@login_required(login_url="/accounts/login/") -@permission_required( - ["agenda_culturel.change_event", "agenda_culturel.change_duplicatedevents"] -) -def update_duplicate_event(request, pk, epk): - edup = get_object_or_404(DuplicatedEvents, pk=pk) - event = get_object_or_404(Event, pk=epk) - - form = MergeDuplicates(duplicates=edup, event=event) - - if request.method == "POST": - form = MergeDuplicates(request.POST, duplicates=edup) - if form.is_valid(): - for f in edup.get_items_comparison(): - if not f["similar"]: - selected = form.get_selected_events(f["key"]) - if selected is not None: - if isinstance(selected, list): - values = [ - x - for x in [getattr(s, f["key"]) for s in selected] - if x is not None - ] - if len(values) != 0: - if isinstance(values[0], str): - setattr(event, f["key"], "\n".join(values)) - else: - setattr(event, f["key"], sum(values, [])) - else: - if f["key"] == "organisers": - event.organisers.set(selected.organisers.all()) - else: - setattr( - event, - f["key"], - getattr(selected, f["key"]), - ) - if f["key"] == "image": - setattr( - event, - "local_image", - getattr(selected, "local_image"), - ) - - event.other_versions.fix(event) - event.save() - - messages.info(request, _("Update successfully completed.")) - return HttpResponseRedirect(event.get_absolute_url()) - - return render( - request, - "agenda_culturel/update_duplicate.html", - context={ - "form": form, - "object": edup, - "event_id": edup.get_event_index(event), - }, - ) - - -@login_required(login_url="/accounts/login/") -@permission_required( - ["agenda_culturel.change_event", "agenda_culturel.change_duplicatedevents"] -) -def merge_duplicate(request, pk): - edup = get_object_or_404(DuplicatedEvents, pk=pk) - form = MergeDuplicates(duplicates=edup) - - if request.method == "POST": - form = MergeDuplicates(request.POST, duplicates=edup) - if form.is_valid(): - events = edup.get_duplicated() - - # build fields for the new event - new_event_data = {} - for f in edup.get_items_comparison(): - if f["similar"]: - new_event_data[f["key"]] = getattr(events[0], f["key"]) - else: - selected = form.get_selected_events(f["key"]) - if selected is None: - new_event_data[f["key"]] = None - elif isinstance(selected, list): - values = [ - x - for x in [getattr(s, f["key"]) for s in selected] - if x is not None - ] - if len(values) == 0: - new_event_data[f["key"]] = None - else: - if isinstance(values[0], str): - new_event_data[f["key"]] = "\n".join(values) - else: - new_event_data[f["key"]] = sum(values, []) - else: - new_event_data[f["key"]] = getattr(selected, f["key"]) - if f["key"] == "image" and "local_image" not in new_event_data: - new_event_data["local_image"] = getattr( - selected, "local_image" - ) - - organisers = new_event_data.pop("organisers", None) - # create a new event that merge the selected events - new_event = Event(**new_event_data) - new_event.status = Event.STATUS.PUBLISHED - new_event.other_versions = edup - new_event.save() - if organisers is not None: - new_event.organisers.set(organisers.all()) - edup.fix(new_event) - - messages.info( - request, - _("Creation of a merged event has been successfully completed."), - ) - return HttpResponseRedirect(new_event.get_absolute_url()) - - return render( - request, - "agenda_culturel/merge_duplicate.html", - context={"form": form, "object": edup}, - ) - - -@login_required(login_url="/accounts/login/") -@permission_required( - ["agenda_culturel.change_event", "agenda_culturel.change_duplicatedevents"] -) -def fix_duplicate(request, pk): - edup = get_object_or_404(DuplicatedEvents.objects.select_related(), pk=pk) - - if request.method == "POST": - form = FixDuplicates(request.POST, edup=edup) - - if form.is_valid(): - if form.is_action_no_duplicates(): - # all events are different - events = edup.get_duplicated() - - # get redirection date - if len(events) == 0: - date = None - else: - s_events = [e for e in events if not e.has_recurrences()] - if len(s_events) != 0: - s_event = s_events[0] - else: - s_event = events[0] - date = s_event.start_day - - messages.success(request, _("Events have been marked as unduplicated.")) - # delete the duplicated event (other_versions will be set to None on all events) - edup.delete() - if date is None: - return HttpResponseRedirect(reverse_lazy("home")) - else: - return HttpResponseRedirect( - reverse_lazy("day_view", args=[date.year, date.month, date.day]) - ) - - elif form.is_action_select(): - # one element has been selected to be the representative - selected = form.get_selected_event(edup) - if selected is None: - messages.error( - request, - _( - "The selected item is no longer included in the list of duplicates. Someone else has probably modified the list in the meantime." - ), - ) - else: - edup.fix(selected) - messages.success( - request, - _("The selected event has been set as representative"), - ) - return HttpResponseRedirect(edup.get_absolute_url()) - elif form.is_action_remove(): - # one element is removed from the set - event = form.get_selected_event(edup) - if event is None: - messages.error( - request, - _( - "The selected item is no longer included in the list of duplicates. Someone else has probably modified the list in the meantime." - ), - ) - return HttpResponseRedirect(edup.get_absolute_url()) - else: - event.other_versions = None - if edup.representative == event: - edup.representative = None - event.set_no_modification_date_changed() - event.save() - edup.save() - edup.events = [e for e in edup.events if e.pk != event.pk] - messages.success( - request, - _( - "The event has been withdrawn from the group and made independent." - ), - ) - if edup.nb_duplicated() == 1: - return HttpResponseRedirect(edup.get_absolute_url()) - else: - form = FixDuplicates(edup=edup) - elif form.is_action_update(): - # otherwise, an event will be updated using other elements - event = form.get_selected_event(edup) - if event is None: - messages.error( - request, - _( - "The selected item is no longer included in the list of duplicates. Someone else has probably modified the list in the meantime." - ), - ) - return HttpResponseRedirect(edup.get_absolute_url()) - else: - return HttpResponseRedirect( - reverse_lazy("update_event", args=[edup.pk, event.pk]) - ) - else: - # otherwise, a new event will be created using a merging process - return HttpResponseRedirect( - reverse_lazy("merge_duplicate", args=[edup.pk]) - ) - else: - form = FixDuplicates(edup=edup) - - return render( - request, - "agenda_culturel/fix_duplicate.html", - context={"form": form, "object": edup}, - ) - - -class DuplicatedEventsUpdateView(LoginRequiredMixin, UpdateView): - model = DuplicatedEvents - fields = () - template_name = "agenda_culturel/fix_duplicate.html" - - -@login_required(login_url="/accounts/login/") -@permission_required("agenda_culturel.view_duplicatedevents") -def duplicates(request): - nb_removed = DuplicatedEvents.remove_singletons() - if nb_removed > 0: - messages.success( - request, - _("Cleaning up duplicates: {} item(s) fixed.").format(nb_removed), - ) - - filter = DuplicatedEventsFilter( - request.GET, queryset=DuplicatedEvents.objects.all().order_by("-pk") - ) - paginator = PaginatorFilter(filter, 10, request) - page = request.GET.get("page") - - try: - response = paginator.page(page) - except PageNotAnInteger: - response = paginator.page(1) - except EmptyPage: - response = paginator.page(paginator.num_pages) - - return render( - request, - "agenda_culturel/duplicates.html", - { - "filter": filter, - "paginator_filter": response, - "paginator": paginator, - }, - ) - - -def set_duplicate(request, year, month, day, pk): - event = get_object_or_404(Event, pk=pk) - cday = CalendarDay(date(year, month, day)) - others = [ - e - for e in cday.get_events() - if e != event - and (event.other_versions is None or event.other_versions != e.other_versions) - and e.status != Event.STATUS.TRASH - ] - - form = SelectEventInList(events=others) - - if request.method == "POST": - form = SelectEventInList(request.POST, events=others) - if form.is_valid(): - selected = [o for o in others if o.pk == int(form.cleaned_data["event"])] - event.set_other_versions(selected) - # save them without updating modified date - event.set_no_modification_date_changed() - event.save() - if request.user.is_authenticated: - messages.success(request, _("The event was successfully duplicated.")) - return HttpResponseRedirect( - reverse_lazy("view_duplicate", args=[event.other_versions.pk]) - ) - else: - messages.info( - request, - _( - "The event has been successfully flagged as a duplicate. The moderation team will deal with your suggestion shortly." - ), - ) - return HttpResponseRedirect(event.get_absolute_url()) - - return render( - request, - "agenda_culturel/set_duplicate.html", - context={"form": form, "event": event}, - ) - - def statistics(request, pk=None): if pk is not None: rimport = RecurrentImport.objects.filter(pk=pk)