Séparation des vues event dupliqués

This commit is contained in:
SebF 2025-04-04 11:26:21 +02:00
parent b529a33b24
commit 9ec22919f1
4 changed files with 372 additions and 359 deletions

View File

@ -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/<int:pk>/merge", merge_duplicate, name="merge_duplicate"),
path(
"event/<int:year>/<int:month>/<int:day>/<int:pk>/set_duplicate",
set_duplicate,
name="set_duplicate",
),
path(
"duplicates/<int:pk>/update/<int:epk>",
update_duplicate_event,
name="update_event",
),
# Event duplicates
path(
"duplicates/<int:pk>",
DuplicatedEventsDetailView.as_view(),
name="view_duplicate",
),
path("duplicates/", duplicates, name="duplicates"),
path("duplicates/<int:pk>/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/<int:pk>/delete", EventDeleteView.as_view(), name="delete_event"),
path(
"event/<int:year>/<int:month>/<int:day>/<int:pk>/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/<int:pk>/stats", statistics, name="stats_rimport"),
path("duplicates/", duplicates, name="duplicates"),
path(
"duplicates/<int:pk>",
DuplicatedEventsDetailView.as_view(),
name="view_duplicate",
),
path("duplicates/<int:pk>/fix", fix_duplicate, name="fix_duplicate"),
path("duplicates/<int:pk>/merge", merge_duplicate, name="merge_duplicate"),
path(
"duplicates/<int:pk>/update/<int:epk>",
update_duplicate_event,
name="update_event",
),
path(
"organisme/<int:organisation_pk>/ical",
export_ical,

View File

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

View File

@ -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 dutilisation ?
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},
)

View File

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