From a09f6751e343fbd7ec0a3574a80eab9685120302 Mon Sep 17 00:00:00 2001
From: Jean-Marie Favreau
Date: Tue, 29 Oct 2024 11:53:20 +0100
Subject: [PATCH] Nouvelle gestion des duplicate
Fix #168
---
src/agenda_culturel/calendar.py | 8 +-
src/agenda_culturel/forms.py | 12 +-
.../locale/fr/LC_MESSAGES/django.po | 344 +++++++++---------
...091_duplicatedevents_fixed_event_masked.py | 23 ++
src/agenda_culturel/models.py | 84 ++++-
src/agenda_culturel/static/style.scss | 28 +-
.../agenda_culturel/duplicate-diff-inc.html | 31 ++
.../agenda_culturel/duplicate-inc.html | 25 ++
.../agenda_culturel/duplicate-info-inc.html | 11 +
.../templates/agenda_culturel/duplicate.html | 41 +++
.../templates/agenda_culturel/duplicates.html | 28 +-
.../agenda_culturel/event-info-inc.html | 1 +
.../agenda_culturel/fix_duplicate.html | 58 +--
.../agenda_culturel/merge_duplicate.html | 2 +-
.../templates/agenda_culturel/page-event.html | 23 +-
.../single-event/event-single-inc.html | 14 +
.../templatetags/duplicated_extra.py | 7 +-
.../templatetags/event_extra.py | 10 +-
src/agenda_culturel/views.py | 53 +--
19 files changed, 509 insertions(+), 294 deletions(-)
create mode 100644 src/agenda_culturel/migrations/0091_duplicatedevents_fixed_event_masked.py
create mode 100644 src/agenda_culturel/templates/agenda_culturel/duplicate-diff-inc.html
create mode 100644 src/agenda_culturel/templates/agenda_culturel/duplicate-inc.html
create mode 100644 src/agenda_culturel/templates/agenda_culturel/duplicate-info-inc.html
create mode 100644 src/agenda_culturel/templates/agenda_culturel/duplicate.html
diff --git a/src/agenda_culturel/calendar.py b/src/agenda_culturel/calendar.py
index 36feebc..7999109 100644
--- a/src/agenda_culturel/calendar.py
+++ b/src/agenda_culturel/calendar.py
@@ -171,11 +171,12 @@ class IntervalInDay(DayInCalendar):
self.id = self.id + '-' + str(id)
class CalendarList:
- def __init__(self, firstdate, lastdate, filter=None, exact=False):
+ 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
if exact:
self.c_firstdate = self.firstdate
@@ -219,6 +220,9 @@ class CalendarList:
qs = Event.objects.all()
else:
qs = self.filter.qs
+
+ if self.ignore_dup:
+ qs = qs.exclude(possibly_duplicated=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())
self.events = qs.filter(
@@ -230,7 +234,7 @@ class CalendarList:
| Q(recurrence_dtend__lt=startdatetime)
)
)
- ).order_by("start_time").prefetch_related("exact_location").prefetch_related("category")
+ ).filter(masked=False).order_by("start_time").prefetch_related("exact_location").prefetch_related("category")
firstdate = datetime.fromordinal(self.c_firstdate.toordinal())
if firstdate.tzinfo is None or firstdate.tzinfo.utcoffset(firstdate) is None:
diff --git a/src/agenda_culturel/forms.py b/src/agenda_culturel/forms.py
index 52cbb2e..1159027 100644
--- a/src/agenda_culturel/forms.py
+++ b/src/agenda_culturel/forms.py
@@ -162,17 +162,17 @@ class FixDuplicates(Form):
choices += [
(
"SelectA",
- "Ces événements sont identiques, on garde A et on met B à la corbeille",
+ "Ces événements sont identiques, on garde A et on masque B",
)
]
choices += [
(
"SelectB",
- "Ces événements sont identiques, on garde B et on met A à la corbeille",
+ "Ces événements sont identiques, on garde B et on masque A",
)
]
choices += [
- ("Merge", "Ces événements sont identiques, on fusionne à la main")
+ ("Merge", "Ces événements sont identiques, créé une nouvelle version par fusion")
]
else:
choices = [("NotDuplicates", "Ces événements sont tous différents")]
@@ -191,11 +191,11 @@ class FixDuplicates(Form):
"Select" + i,
"Ces événements sont identiques, on garde "
+ i
- + " et on met les autres à la corbeille",
+ + " et on masque les autres",
)
]
choices += [
- ("Merge", "Ces événements sont identiques, on fusionne à la main")
+ ("Merge", "Ces événements sont identiques, on en créé un nouveau par fusion")
]
self.fields["action"].choices = choices
@@ -248,7 +248,7 @@ class MergeDuplicates(Form):
super().__init__(*args, **kwargs)
choices = [
- ("event" + i, "Valeur de l'évenement " + i) for i in auc[0:nb_events]
+ ("event" + i, "Valeur de l'événement " + i) for i in auc[0:nb_events]
]
for f in self.duplicates.get_items_comparison():
diff --git a/src/agenda_culturel/locale/fr/LC_MESSAGES/django.po b/src/agenda_culturel/locale/fr/LC_MESSAGES/django.po
index 29a5297..00e23d5 100644
--- a/src/agenda_culturel/locale/fr/LC_MESSAGES/django.po
+++ b/src/agenda_culturel/locale/fr/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: agenda_culturel\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-10-18 00:07+0200\n"
+"POT-Creation-Date: 2024-10-28 18:58+0100\n"
"PO-Revision-Date: 2023-10-29 14:16+0000\n"
"Last-Translator: Jean-Marie Favreau \n"
"Language-Team: Jean-Marie Favreau \n"
@@ -91,46 +91,46 @@ msgid "Evening"
msgstr "Soir"
#: agenda_culturel/forms.py:44 agenda_culturel/models.py:171
-#: agenda_culturel/models.py:355 agenda_culturel/models.py:1378
-#: agenda_culturel/models.py:1480
+#: agenda_culturel/models.py:355 agenda_culturel/models.py:1401
+#: agenda_culturel/models.py:1506
msgid "Category"
msgstr "Catégorie"
-#: agenda_culturel/forms.py:48
+#: agenda_culturel/forms.py:47
msgid "Optional. If you dont specify a category, well find it for you."
msgstr ""
"Facultatif. Si tu ne donnes pas la catégorie, on s'occupe de la trouver."
-#: agenda_culturel/forms.py:123
+#: agenda_culturel/forms.py:122
msgid "The end date must be after the start date."
msgstr "La date de fin doit être après la date de début."
-#: agenda_culturel/forms.py:139
+#: agenda_culturel/forms.py:138
msgid "The end time cannot be earlier than the start time."
msgstr "L'heure de fin ne peut pas être avant l'heure de début."
-#: agenda_culturel/forms.py:149
+#: agenda_culturel/forms.py:148
msgid "JSON in the format expected for the import."
msgstr "JSON dans le format attendu pour l'import"
-#: agenda_culturel/forms.py:407
+#: agenda_culturel/forms.py:406
msgid "Apply category {} to the event {}"
msgstr "Appliquer la catégorie {} à l'événement {}"
-#: agenda_culturel/forms.py:422 agenda_culturel/models.py:279
-#: agenda_culturel/models.py:1532
+#: agenda_culturel/forms.py:421 agenda_culturel/models.py:279
+#: agenda_culturel/models.py:1558
msgid "Place"
msgstr "Lieu"
-#: agenda_culturel/forms.py:424
+#: agenda_culturel/forms.py:423
msgid "Create a missing place"
msgstr "Créer un lieu manquant"
-#: agenda_culturel/forms.py:434
+#: agenda_culturel/forms.py:433
msgid "Add \"{}\" to the aliases of the place"
msgstr "Ajouter « {} » aux alias du lieu"
-#: agenda_culturel/forms.py:461
+#: agenda_culturel/forms.py:460
msgid "On saving, use aliases to detect all matching events with missing place"
msgstr ""
"Lors de l'enregistrement, utiliser des alias pour détecter tous les "
@@ -138,7 +138,7 @@ msgstr ""
#: agenda_culturel/models.py:49 agenda_culturel/models.py:94
#: agenda_culturel/models.py:241 agenda_culturel/models.py:258
-#: agenda_culturel/models.py:1251 agenda_culturel/models.py:1324
+#: agenda_culturel/models.py:1273 agenda_culturel/models.py:1347
msgid "Name"
msgstr "Nom"
@@ -212,7 +212,18 @@ msgstr "Position pour ordonner les catégories"
msgid "Categories"
msgstr "Catégories"
-#: agenda_culturel/models.py:177 agenda_culturel/models.py:178
+#: agenda_culturel/models.py:178
+msgid "Fixed"
+msgstr "Résolu"
+
+#: agenda_culturel/models.py:179
+msgid ""
+"This duplicated events is fixed, ie exactly one of the listed events is not "
+"masked."
+msgstr "Cet événement dupliqué est résolu, c'est-à-dire qu'exactement un seul "
+"des événements listés n'est pas masqué."
+
+#: agenda_culturel/models.py:186 agenda_culturel/models.py:187
msgid "Duplicated events"
msgstr "Événements dupliqués"
@@ -272,7 +283,7 @@ msgstr ""
msgid "Places"
msgstr "Lieux"
-#: agenda_culturel/models.py:333 agenda_culturel/models.py:1365
+#: agenda_culturel/models.py:333 agenda_culturel/models.py:1388
msgid "Published"
msgstr "Publié"
@@ -292,7 +303,7 @@ msgstr "Titre"
msgid "Short title"
msgstr "Titre court"
-#: agenda_culturel/models.py:350 agenda_culturel/models.py:1448
+#: agenda_culturel/models.py:350 agenda_culturel/models.py:1474
msgid "Status"
msgstr "Status"
@@ -326,7 +337,7 @@ msgstr "Heure de fin"
msgid "Recurrence"
msgstr "Récurrence"
-#: agenda_culturel/models.py:390 agenda_culturel/models.py:1370
+#: agenda_culturel/models.py:390 agenda_culturel/models.py:1393
msgid "Location"
msgstr "Localisation"
@@ -378,181 +389,193 @@ msgstr "Description de l'illustration"
msgid "Alternative text used by screen readers for the image"
msgstr "Texte alternatif utiliser par les lecteurs d'écrans pour l'image"
+#: agenda_culturel/models.py:438
+msgid "Masked"
+msgstr "Masqué"
+
#: agenda_culturel/models.py:439
+msgid "This event is masked by a duplicated version."
+msgstr "L'événement est masqué par une version dupliquée."
+
+#: agenda_culturel/models.py:447
msgid "Importation source"
msgstr "Source d'importation"
-#: agenda_culturel/models.py:440
+#: agenda_culturel/models.py:448
msgid "Importation source used to detect removed entries."
msgstr "Source d'importation utilisée pour détecter les éléments supprimés/"
-#: agenda_culturel/models.py:446
+#: agenda_culturel/models.py:454
msgid "UUIDs"
msgstr "UUIDs"
-#: agenda_culturel/models.py:447
+#: agenda_culturel/models.py:455
msgid "UUIDs from import to detect duplicated entries."
msgstr "UUIDs utilisés pendant l'import pour détecter les entrées dupliquées"
-#: agenda_culturel/models.py:453
+#: agenda_culturel/models.py:461
msgid "URLs"
msgstr "URLs"
-#: agenda_culturel/models.py:454
+#: agenda_culturel/models.py:462
msgid "List of all the urls where this event can be found."
msgstr "Liste de toutes les urls où l'événement peut être trouvé."
-#: agenda_culturel/models.py:461
+#: agenda_culturel/models.py:469
msgid "Tags"
msgstr "Étiquettes"
-#: agenda_culturel/models.py:462
+#: agenda_culturel/models.py:470
msgid "A list of tags that describe the event."
msgstr "Une liste d'étiquettes décrivant l'événement"
-#: agenda_culturel/models.py:469
+#: agenda_culturel/models.py:477
msgid "Possibly duplicated"
msgstr "Possibles doublons"
-#: agenda_culturel/models.py:518
+#: agenda_culturel/models.py:539
msgid "Event"
msgstr "Événement"
-#: agenda_culturel/models.py:519
+#: agenda_culturel/models.py:540
msgid "Events"
msgstr "Événements"
-#: agenda_culturel/models.py:1242
+#: agenda_culturel/models.py:1264
msgid "Contact message"
msgstr "Message de contact"
-#: agenda_culturel/models.py:1243
+#: agenda_culturel/models.py:1265
msgid "Contact messages"
msgstr "Messages de contact"
-#: agenda_culturel/models.py:1246
+#: agenda_culturel/models.py:1268
msgid "Subject"
msgstr "Sujet"
-#: agenda_culturel/models.py:1247
+#: agenda_culturel/models.py:1269
msgid "The subject of your message"
msgstr "Sujet de votre message"
-#: agenda_culturel/models.py:1252
+#: agenda_culturel/models.py:1274
msgid "Your name"
msgstr "Votre nom"
-#: agenda_culturel/models.py:1258
+#: agenda_culturel/models.py:1280
msgid "Email address"
msgstr "Adresse email"
-#: agenda_culturel/models.py:1259
+#: agenda_culturel/models.py:1281
msgid "Your email address"
msgstr "Votre adresse email"
-#: agenda_culturel/models.py:1264
+#: agenda_culturel/models.py:1286
msgid "Message"
msgstr "Message"
-#: agenda_culturel/models.py:1264
+#: agenda_culturel/models.py:1286
msgid "Your message"
msgstr "Votre message"
-#: agenda_culturel/models.py:1269 agenda_culturel/views.py:934
+#: agenda_culturel/models.py:1291 agenda_culturel/views.py:944
msgid "Spam"
msgstr "Spam"
-#: agenda_culturel/models.py:1270
+#: agenda_culturel/models.py:1292
msgid "This message is a spam."
msgstr "Ce message est un spam."
-#: agenda_culturel/models.py:1275 agenda_culturel/views.py:929
+#: agenda_culturel/models.py:1297 agenda_culturel/views.py:939
msgid "Closed"
msgstr "Fermé"
-#: agenda_culturel/models.py:1277
+#: agenda_culturel/models.py:1299
msgid "this message has been processed and no longer needs to be handled"
msgstr "Ce message a été traité et ne nécessite plus d'être pris en charge"
-#: agenda_culturel/models.py:1282
+#: agenda_culturel/models.py:1304
msgid "Comments"
msgstr "Commentaires"
-#: agenda_culturel/models.py:1283
+#: agenda_culturel/models.py:1305
msgid "Comments on the message from the moderation team"
msgstr "Commentaires sur ce message par l'équipe de modération"
-#: agenda_culturel/models.py:1295 agenda_culturel/models.py:1428
+#: agenda_culturel/models.py:1317 agenda_culturel/models.py:1454
msgid "Recurrent import"
msgstr "Import récurrent"
-#: agenda_culturel/models.py:1296
+#: agenda_culturel/models.py:1318
msgid "Recurrent imports"
msgstr "Imports récurrents"
-#: agenda_culturel/models.py:1300
+#: agenda_culturel/models.py:1322
msgid "ical"
msgstr "ical"
-#: agenda_culturel/models.py:1301
+#: agenda_culturel/models.py:1323
msgid "ical no busy"
msgstr "ical sans busy"
-#: agenda_culturel/models.py:1302
+#: agenda_culturel/models.py:1324
msgid "ical no VC"
msgstr "ical sans VC"
-#: agenda_culturel/models.py:1303
+#: agenda_culturel/models.py:1325
msgid "lacoope.org"
msgstr "lacoope.org"
-#: agenda_culturel/models.py:1304
+#: agenda_culturel/models.py:1326
msgid "la comédie"
msgstr "la comédie"
-#: agenda_culturel/models.py:1305
+#: agenda_culturel/models.py:1327
msgid "le fotomat"
msgstr "le fotomat"
-#: agenda_culturel/models.py:1306
+#: agenda_culturel/models.py:1328
#, fuzzy
#| msgid "la puce à loreille"
msgid "la puce à l'oreille"
msgstr "la puce à loreille"
-#: agenda_culturel/models.py:1307
+#: agenda_culturel/models.py:1329
msgid "Plugin wordpress MEC"
msgstr "Plugin wordpress MEC"
-#: agenda_culturel/models.py:1308
+#: agenda_culturel/models.py:1330
msgid "Événements d'une page FB"
msgstr "Événements d'une page FB"
-#: agenda_culturel/models.py:1309
+#: agenda_culturel/models.py:1331
msgid "la cour des 3 coquins"
msgstr "la cour des 3 coquins"
-#: agenda_culturel/models.py:1312
+#: agenda_culturel/models.py:1332
+msgid "Arachnée concert"
+msgstr "Arachnée concert"
+
+#: agenda_culturel/models.py:1335
msgid "simple"
msgstr "simple"
-#: agenda_culturel/models.py:1313
+#: agenda_culturel/models.py:1336
msgid "Headless Chromium"
msgstr "chromium sans interface"
-#: agenda_culturel/models.py:1314
+#: agenda_culturel/models.py:1337
msgid "Headless Chromium (pause)"
msgstr "chromium sans interface (pause)"
-#: agenda_culturel/models.py:1319
+#: agenda_culturel/models.py:1342
msgid "daily"
msgstr "chaque jour"
-#: agenda_culturel/models.py:1321
+#: agenda_culturel/models.py:1344
msgid "weekly"
msgstr "chaque semaine"
-#: agenda_culturel/models.py:1326
+#: agenda_culturel/models.py:1349
msgid ""
"Recurrent import name. Be careful to choose a name that is easy to "
"understand, as it will be public and displayed on the sites About page."
@@ -560,135 +583,135 @@ msgstr ""
"Nom de l'import récurrent. Attention à choisir un nom compréhensible, car il "
"sera public, et affiché sur la page à propos du site."
-#: agenda_culturel/models.py:1333
+#: agenda_culturel/models.py:1356
msgid "Processor"
msgstr "Processeur"
-#: agenda_culturel/models.py:1336
+#: agenda_culturel/models.py:1359
msgid "Downloader"
msgstr "Téléchargeur"
-#: agenda_culturel/models.py:1343
+#: agenda_culturel/models.py:1366
msgid "Import recurrence"
msgstr "Récurrence d'import"
-#: agenda_culturel/models.py:1350
+#: agenda_culturel/models.py:1373
msgid "Source"
msgstr "Source"
-#: agenda_culturel/models.py:1351
+#: agenda_culturel/models.py:1374
msgid "URL of the source document"
msgstr "URL du document source"
-#: agenda_culturel/models.py:1355
+#: agenda_culturel/models.py:1378
msgid "Browsable url"
msgstr "URL navigable"
-#: agenda_culturel/models.py:1357
+#: agenda_culturel/models.py:1380
msgid "URL of the corresponding document that will be shown to visitors."
msgstr "URL correspondant au document et qui sera montrée aux visiteurs"
-#: agenda_culturel/models.py:1366
+#: agenda_culturel/models.py:1389
msgid "Status of each imported event (published or draft)"
msgstr "Status de chaque événement importé (publié ou brouillon)"
-#: agenda_culturel/models.py:1371
+#: agenda_culturel/models.py:1394
msgid "Address for each imported event"
msgstr "Adresse de chaque événement importé"
-#: agenda_culturel/models.py:1379
+#: agenda_culturel/models.py:1402
msgid "Category of each imported event"
msgstr "Catégorie de chaque événement importé"
-#: agenda_culturel/models.py:1387
+#: agenda_culturel/models.py:1410
msgid "Tags for each imported event"
msgstr "Étiquettes de chaque événement importé"
-#: agenda_culturel/models.py:1388
+#: agenda_culturel/models.py:1411
msgid "A list of tags that describe each imported event."
msgstr "Une liste d'étiquettes décrivant chaque événement importé"
-#: agenda_culturel/models.py:1414
+#: agenda_culturel/models.py:1440
msgid "Running"
msgstr "En cours"
-#: agenda_culturel/models.py:1415
+#: agenda_culturel/models.py:1441
msgid "Canceled"
msgstr "Annulé"
-#: agenda_culturel/models.py:1416
+#: agenda_culturel/models.py:1442
msgid "Success"
msgstr "Succès"
-#: agenda_culturel/models.py:1417
+#: agenda_culturel/models.py:1443
msgid "Failed"
msgstr "Erreur"
-#: agenda_culturel/models.py:1420
+#: agenda_culturel/models.py:1446
msgid "Batch importation"
msgstr "Importation par lot"
-#: agenda_culturel/models.py:1421
+#: agenda_culturel/models.py:1447
msgid "Batch importations"
msgstr "Importations par lot"
-#: agenda_culturel/models.py:1429
+#: agenda_culturel/models.py:1455
msgid "Reference to the recurrent import processing"
msgstr "Référence du processus d'import récurrent"
-#: agenda_culturel/models.py:1437
+#: agenda_culturel/models.py:1463
msgid "URL (if not recurrent import)"
msgstr "URL (si pas d'import récurrent)"
-#: agenda_culturel/models.py:1439
+#: agenda_culturel/models.py:1465
msgid "Source URL if no RecurrentImport is associated."
msgstr "URL source si aucun import récurrent n'est associé"
-#: agenda_culturel/models.py:1452
+#: agenda_culturel/models.py:1478
msgid "Error message"
msgstr "Votre message"
-#: agenda_culturel/models.py:1456
+#: agenda_culturel/models.py:1482
msgid "Number of collected events"
msgstr "Nombre d'événements collectés"
-#: agenda_culturel/models.py:1459
+#: agenda_culturel/models.py:1485
msgid "Number of imported events"
msgstr "Nombre d'événements importés"
-#: agenda_culturel/models.py:1462
+#: agenda_culturel/models.py:1488
msgid "Number of updated events"
msgstr "Nombre d'événements mis à jour"
-#: agenda_culturel/models.py:1465
+#: agenda_culturel/models.py:1491
msgid "Number of removed events"
msgstr "Nombre d'événements supprimés"
-#: agenda_culturel/models.py:1473
+#: agenda_culturel/models.py:1499
msgid "Weight"
msgstr "Poids"
-#: agenda_culturel/models.py:1474
+#: agenda_culturel/models.py:1500
msgid "The lower is the weight, the earlier the filter is applied"
msgstr "Plus le poids est léger, plus le filtre sera appliqué tôt"
-#: agenda_culturel/models.py:1481
+#: agenda_culturel/models.py:1507
msgid "Category applied to the event"
msgstr "Catégorie appliquée à l'événement"
-#: agenda_culturel/models.py:1486
+#: agenda_culturel/models.py:1512
msgid "Contained in the title"
msgstr "Contenu dans le titre"
-#: agenda_culturel/models.py:1487
+#: agenda_culturel/models.py:1513
msgid "Text contained in the event title"
msgstr "Texte contenu dans le titre de l'événement"
-#: agenda_culturel/models.py:1493
+#: agenda_culturel/models.py:1519
msgid "Exact title extract"
msgstr "Extrait exact du titre"
-#: agenda_culturel/models.py:1495
+#: agenda_culturel/models.py:1521
msgid ""
"If checked, the extract will be searched for in the title using the exact "
"form (capitals, accents)."
@@ -696,19 +719,19 @@ msgstr ""
"Si coché, l'extrait sera recherché dans le titre en utilisant la forme "
"exacte (majuscules, accents)"
-#: agenda_culturel/models.py:1501
+#: agenda_culturel/models.py:1527
msgid "Contained in the description"
msgstr "Contenu dans la description"
-#: agenda_culturel/models.py:1502
+#: agenda_culturel/models.py:1528
msgid "Text contained in the description"
msgstr "Texte contenu dans la description"
-#: agenda_culturel/models.py:1508
+#: agenda_culturel/models.py:1534
msgid "Exact description extract"
msgstr "Extrait exact de description"
-#: agenda_culturel/models.py:1510
+#: agenda_culturel/models.py:1536
msgid ""
"If checked, the extract will be searched for in the description using the "
"exact form (capitals, accents)."
@@ -716,19 +739,19 @@ msgstr ""
"Si coché, l'extrait sera recherché dans la description en utilisant la forme "
"exacte (majuscules, accents)"
-#: agenda_culturel/models.py:1516
+#: agenda_culturel/models.py:1542
msgid "Contained in the location"
msgstr "Contenu dans la localisation"
-#: agenda_culturel/models.py:1517
+#: agenda_culturel/models.py:1543
msgid "Text contained in the event location"
msgstr "Texte contenu dans la localisation de l'événement"
-#: agenda_culturel/models.py:1523
+#: agenda_culturel/models.py:1549
msgid "Exact location extract"
msgstr "Extrait exact de localisation"
-#: agenda_culturel/models.py:1525
+#: agenda_culturel/models.py:1551
msgid ""
"If checked, the extract will be searched for in the location using the exact "
"form (capitals, accents)."
@@ -736,56 +759,56 @@ msgstr ""
"Si coché, l'extrait sera recherché dans la localisation en utilisant la "
"forme exacte (majuscules, accents)"
-#: agenda_culturel/models.py:1533
+#: agenda_culturel/models.py:1559
msgid "Location from place"
msgstr "Localisation depuis le lieu"
-#: agenda_culturel/models.py:1540
+#: agenda_culturel/models.py:1566
msgid "Categorisation rule"
msgstr "Règle de catégorisation"
-#: agenda_culturel/models.py:1541
+#: agenda_culturel/models.py:1567
msgid "Categorisation rules"
msgstr "Règles de catégorisation"
-#: agenda_culturel/models.py:1608 agenda_culturel/models.py:1640
+#: agenda_culturel/models.py:1634 agenda_culturel/models.py:1666
msgid "Question"
msgstr "Question"
-#: agenda_culturel/models.py:1609 agenda_culturel/models.py:1647
+#: agenda_culturel/models.py:1635 agenda_culturel/models.py:1673
msgid "Text that will be shown to moderators"
msgstr "Text tel que présenté aux modérateurices"
-#: agenda_culturel/models.py:1615
+#: agenda_culturel/models.py:1641
msgid "Moderation question"
msgstr "Question de modération"
-#: agenda_culturel/models.py:1616
+#: agenda_culturel/models.py:1642
msgid "Moderation questions"
msgstr "Questions de modération"
-#: agenda_culturel/models.py:1641
+#: agenda_culturel/models.py:1667
msgid "Associated question from moderation"
msgstr "Question associée pour la modération"
-#: agenda_culturel/models.py:1646
+#: agenda_culturel/models.py:1672
msgid "Answer"
msgstr "Réponse"
-#: agenda_culturel/models.py:1653
+#: agenda_culturel/models.py:1679
msgid "Adds tags"
msgstr "Ajoute les étiquettes"
-#: agenda_culturel/models.py:1654
+#: agenda_culturel/models.py:1680
msgid "A list of tags that will be added if you choose this answer."
msgstr ""
"Une liste d'étiquettes qui seront ajoutées si vous choisissez cette réponse."
-#: agenda_culturel/models.py:1660
+#: agenda_culturel/models.py:1686
msgid "Removes tags"
msgstr "Retire les étiquettes"
-#: agenda_culturel/models.py:1661
+#: agenda_culturel/models.py:1687
msgid "A list of tags that will be removed if you choose this answer."
msgstr ""
"Une liste d'étiquettes qui seront retirées si vous choisissez cette réponse."
@@ -890,97 +913,92 @@ msgstr "Intégration de {} url(s) dans notre processus d'import."
msgid "Integrating {} into our import process."
msgstr "Intégration de {} dans notre processus d'import."
-#: agenda_culturel/views.py:889
+#: agenda_culturel/views.py:899
msgid "Your message has been sent successfully."
msgstr "Votre message a été envoyé avec succès."
-#: agenda_culturel/views.py:899
+#: agenda_culturel/views.py:909
msgid "The contact message has been successfully deleted."
msgstr "Le message de contact a été supprimé avec succès."
-#: agenda_culturel/views.py:913
+#: agenda_culturel/views.py:923
msgid "The contact message properties has been successfully modified."
msgstr "Les propriétés du message de contact ont été modifié avec succès."
-#: agenda_culturel/views.py:929
+#: agenda_culturel/views.py:939
msgid "Open"
msgstr "Ouvert"
-#: agenda_culturel/views.py:934
+#: agenda_culturel/views.py:944
msgid "Non spam"
msgstr "Non spam"
-#: agenda_culturel/views.py:997
+#: agenda_culturel/views.py:1007
#, fuzzy
#| msgid "The event has been successfully deleted."
msgid "Spam has been successfully deleted."
msgstr "L'événement a été supprimé avec succès"
-#: agenda_culturel/views.py:1014
+#: agenda_culturel/views.py:1024
msgid "Search"
msgstr "Rechercher"
-#: agenda_culturel/views.py:1200
+#: agenda_culturel/views.py:1210
msgid "The import has been run successfully."
msgstr "L'import a été lancé avec succès"
-#: agenda_culturel/views.py:1219
+#: agenda_culturel/views.py:1229
msgid "The import has been canceled."
msgstr "L'import a été annulé"
-#: agenda_culturel/views.py:1293
+#: agenda_culturel/views.py:1303
msgid "The recurrent import has been successfully modified."
msgstr "L'import récurrent a été modifié avec succès."
-#: agenda_culturel/views.py:1302
+#: agenda_culturel/views.py:1312
msgid "The recurrent import has been successfully deleted."
msgstr "L'import récurrent a été supprimé avec succès"
-#: agenda_culturel/views.py:1342
+#: agenda_culturel/views.py:1352
msgid "The import has been launched."
msgstr "L'import a été lancé"
-#: agenda_culturel/views.py:1364
+#: agenda_culturel/views.py:1374
msgid "Imports has been launched."
msgstr "Les imports ont été lancés"
-#: agenda_culturel/views.py:1449
-msgid "The merge has been successfully completed."
-msgstr "La fusion a été réalisée avec succès."
+#: agenda_culturel/views.py:1467
+msgid "Creation of a merged event has been successfully completed."
+msgstr "Création d'un événement fusionné réalisée avec succès."
-#: agenda_culturel/views.py:1483
+#: agenda_culturel/views.py:1504
msgid "Events have been marked as unduplicated."
msgstr "Les événements ont été marqués comme non dupliqués."
-#: agenda_culturel/views.py:1505
-msgid ""
-"The selected event has been retained, while the other has been moved to the "
-"recycle bin."
+#: agenda_culturel/views.py:1526
+msgid "The selected event has been retained, while the other has been masked."
msgstr ""
-"L'événement sélectionné a été conservé, l'autre a été déplacé dans la "
-"corbeille."
+"L'événement sélectionné a été retenu, l'autre a été masqué."
-#: agenda_culturel/views.py:1512
+#: agenda_culturel/views.py:1533
msgid ""
-"The selected event has been retained, while the others have been moved to "
-"the recycle bin."
+"The selected event has been retained, while the others havec been masked."
msgstr ""
-"L'événement sélectionné a été conservé, les autres ont été déplacés dans la "
-"corbeille."
+"L'événement sélectionné a été retenu, les autres ont été masqués."
-#: agenda_culturel/views.py:1523
+#: agenda_culturel/views.py:1546
msgid "The event has been withdrawn from the group and made independent."
msgstr "L'événement a été retiré du groupe et rendu indépendant."
-#: agenda_culturel/views.py:1556
+#: agenda_culturel/views.py:1578
msgid "Cleaning up duplicates: {} item(s) removed."
msgstr "Nettoyage des dupliqués: {} élément(s) supprimés."
-#: agenda_culturel/views.py:1600
+#: agenda_culturel/views.py:1625
msgid "The event was successfully duplicated."
msgstr "L'événement a été marqué dupliqué avec succès."
-#: agenda_culturel/views.py:1608
+#: agenda_culturel/views.py:1633
msgid ""
"The event has been successfully flagged as a duplicate. The moderation team "
"will deal with your suggestion shortly."
@@ -988,63 +1006,63 @@ msgstr ""
"L'événement a été signalé comme dupliqué avec succès. Votre suggestion sera "
"prochainement prise en charge par l'équipe de modération."
-#: agenda_culturel/views.py:1661
+#: agenda_culturel/views.py:1686
msgid "The categorisation rule has been successfully modified."
msgstr "La règle de catégorisation a été modifiée avec succès."
-#: agenda_culturel/views.py:1670
+#: agenda_culturel/views.py:1695
msgid "The categorisation rule has been successfully deleted."
msgstr "La règle de catégorisation a été supprimée avec succès"
-#: agenda_culturel/views.py:1692 agenda_culturel/views.py:1739
+#: agenda_culturel/views.py:1717 agenda_culturel/views.py:1764
msgid "The rules were successfully applied and 1 event was categorised."
msgstr ""
"Les règles ont été appliquées avec succès et 1 événement a été catégorisé"
-#: agenda_culturel/views.py:1699 agenda_culturel/views.py:1746
+#: agenda_culturel/views.py:1724 agenda_culturel/views.py:1771
msgid "The rules were successfully applied and {} events were categorised."
msgstr ""
"Les règles ont été appliquées avec succès et {} événements ont été "
"catégorisés"
-#: agenda_culturel/views.py:1706 agenda_culturel/views.py:1753
+#: agenda_culturel/views.py:1731 agenda_culturel/views.py:1778
msgid "The rules were successfully applied and no events were categorised."
msgstr ""
"Les règles ont été appliquées avec succès et aucun événement n'a été "
"catégorisé"
-#: agenda_culturel/views.py:1793
+#: agenda_culturel/views.py:1818
msgid "The moderation question has been created with success."
msgstr "La question de modération a été créée avec succès."
-#: agenda_culturel/views.py:1919 agenda_culturel/views.py:1981
-#: agenda_culturel/views.py:2019
+#: agenda_culturel/views.py:1944 agenda_culturel/views.py:2006
+#: agenda_culturel/views.py:2044
msgid "{} events have been updated."
msgstr "{} événements ont été mis à jour."
-#: agenda_culturel/views.py:1922 agenda_culturel/views.py:1983
-#: agenda_culturel/views.py:2022
+#: agenda_culturel/views.py:1947 agenda_culturel/views.py:2008
+#: agenda_culturel/views.py:2047
msgid "1 event has been updated."
msgstr "1 événement a été mis à jour"
-#: agenda_culturel/views.py:1924 agenda_culturel/views.py:1985
-#: agenda_culturel/views.py:2024
+#: agenda_culturel/views.py:1949 agenda_culturel/views.py:2010
+#: agenda_culturel/views.py:2049
msgid "No events have been modified."
msgstr "Aucun événement n'a été modifié."
-#: agenda_culturel/views.py:1933
+#: agenda_culturel/views.py:1958
msgid "The place has been successfully updated."
msgstr "Le lieu a été modifié avec succès."
-#: agenda_culturel/views.py:1942
+#: agenda_culturel/views.py:1967
msgid "The place has been successfully created."
msgstr "Le lieu a été créé avec succès."
-#: agenda_culturel/views.py:2007
+#: agenda_culturel/views.py:2032
msgid "The selected place has been assigned to the event."
msgstr "Le lieu sélectionné a été assigné à l'événement."
-#: agenda_culturel/views.py:2011
+#: agenda_culturel/views.py:2036
msgid "A new alias has been added to the selected place."
msgstr "Un nouvel alias a été créé pour le lieu sélectionné."
diff --git a/src/agenda_culturel/migrations/0091_duplicatedevents_fixed_event_masked.py b/src/agenda_culturel/migrations/0091_duplicatedevents_fixed_event_masked.py
new file mode 100644
index 0000000..4c7ad8a
--- /dev/null
+++ b/src/agenda_culturel/migrations/0091_duplicatedevents_fixed_event_masked.py
@@ -0,0 +1,23 @@
+# Generated by Django 4.2.9 on 2024-10-20 11:38
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('agenda_culturel', '0090_alter_recurrentimport_processor'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='duplicatedevents',
+ name='fixed',
+ field=models.BooleanField(blank=True, default=False, help_text='This duplicated events is fixed, ie exactly one of the listed events is not masked.', null=True, verbose_name='Fixed'),
+ ),
+ migrations.AddField(
+ model_name='event',
+ name='masked',
+ field=models.BooleanField(blank=True, default=False, help_text='This event is masked by a duplicated version.', null=True, verbose_name='Masked'),
+ ),
+ ]
diff --git a/src/agenda_culturel/models.py b/src/agenda_culturel/models.py
index 7f7dd06..5583b51 100644
--- a/src/agenda_culturel/models.py
+++ b/src/agenda_culturel/models.py
@@ -173,28 +173,60 @@ class Category(models.Model):
class DuplicatedEvents(models.Model):
+
+ fixed = models.BooleanField(
+ verbose_name=_("Fixed"),
+ help_text=_("This duplicated events is fixed, ie exactly one of the listed events is not masked."),
+ default=False,
+ blank=True,
+ null=True,
+ )
+
class Meta:
verbose_name = _("Duplicated events")
verbose_name_plural = _("Duplicated events")
def nb_duplicated(self):
- return Event.objects.filter(possibly_duplicated=self).count()
+ return self.event_set.count()
+
+ def nb_duplicated_not_fixed(self):
+ return self.event_set.filter(possibly_duplicated=self, fixed=False).count()
def get_duplicated(self):
- return Event.objects.filter(possibly_duplicated=self).order_by(
+ return self.event_set.order_by(
"created_date"
)
+ def get_absolute_url(self):
+ return reverse("view_duplicate", kwargs={"pk": self.pk})
+
+ def get_one_event(self):
+ return self.event_set.filter(masked=False).first()
+
def merge_into(self, other):
# for all objects associated to this group
- for e in Event.objects.filter(possibly_duplicated=self):
+ for e in self.event_set.all():
# change their group membership
e.possibly_duplicated = other
# save them
e.save()
+ other.save(force_fixed=False)
# then delete the empty group
self.delete()
+ def fix(self, event=None):
+ events = self.event_set.all()
+ if event is None:
+ events = events.sort_by("-created_date")
+ for e in events:
+ if event is None:
+ event = e
+ e.masked = e != event
+ Event.objects.bulk_update(events, fields=["masked"])
+ self.save()
+ return len(events)
+
+
def merge_groups(groups):
if len(groups) == 0:
return None
@@ -216,8 +248,8 @@ class DuplicatedEvents(models.Model):
nb, d = singletons.delete()
return nb
- def remove_similar_entries():
- to_be_removed = []
+ def fix_similar_entries():
+ to_be_fixed = []
dup_events = Event.objects.order_by('possibly_duplicated').prefetch_related('possibly_duplicated', 'category')
duplicates = defaultdict(list)
@@ -228,15 +260,28 @@ class DuplicatedEvents(models.Model):
comp = Event.get_comparison(duplicates[d])
similar = len([c for c in comp if not c["similar"]]) == 0
if similar:
- to_be_removed.append(d)
+ to_be_fixed.append(d)
- nb = len(to_be_removed)
+ nb = len(to_be_fixed)
if nb > 0:
logger.warning("Removing: " + str(nb) + " similar duplicated")
- for s in to_be_removed:
- s.delete()
+ for s in to_be_fixed:
+ s.fix()
+
return nb
+
+ def save(self, *args, **kwargs):
+ if "force_fixed" in kwargs:
+ self.fixed = kwargs["force_fixed"]
+ del kwargs["force_fixed"]
+ elif not self.pk:
+ self.fixed = False
+ else:
+ self.fixed = self.event_set.filter(masked=False).count() == 1
+
+ super().save(*args, **kwargs)
+
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)
@@ -434,6 +479,14 @@ class Event(models.Model):
max_length=1024,
)
+ masked = models.BooleanField(
+ verbose_name=_("Masked"),
+ help_text=_("This event is masked by a duplicated version."),
+ default=False,
+ blank=True,
+ null=True,
+ )
+
import_sources = ArrayField(
models.CharField(max_length=512),
verbose_name=_("Importation source"),
@@ -487,10 +540,13 @@ 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):
+ def get_nb_events_same_dates(self, remove_same_dup=True):
first = self.start_day
last = self.get_consolidated_end_day()
- calendar = CalendarList(first, last, exact=True)
+ ignore_dup = None
+ if remove_same_dup:
+ ignore_dup = self.possibly_duplicated
+ 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):
@@ -911,6 +967,7 @@ class Event(models.Model):
for attr in Event.data_fields(all=True, local_img=False, exact_location=False):
values = [getattr(e, attr) for e in events]
values = ["" if v is None else v for v in values]
+ values = [[] if attr == "tags" and v is "" else v for v in values]
# only consider fixed part of Facebook urls
if attr == "image":
values = [v.split("?")[0] if "fbcdn.net" in v else v for v in values]
@@ -941,7 +998,7 @@ class Event(models.Model):
else:
# otherwise merge existing groups
group = DuplicatedEvents.merge_groups(groups)
- group.save()
+ group.save(force_fixed=False)
# set the possibly duplicated group for the current object
self.possibly_duplicated = group
@@ -1170,7 +1227,7 @@ class Event(models.Model):
return dtstart, dtend
- def get_concurrent_events(self):
+ 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(self.start_day).get_events()
return [
@@ -1179,6 +1236,7 @@ class Event(models.Model):
if e != self
and self.is_concurrent_event(e, day)
and e.status == Event.STATUS.PUBLISHED
+ and (e.possibly_duplicated is None or e.possibly_duplicated != self.possibly_duplicated)
]
def is_concurrent_event(self, e, day):
diff --git a/src/agenda_culturel/static/style.scss b/src/agenda_culturel/static/style.scss
index 3312de5..2bdd1f0 100644
--- a/src/agenda_culturel/static/style.scss
+++ b/src/agenda_culturel/static/style.scss
@@ -886,8 +886,8 @@ aside nav a.badge {
float: right;
}
-aside nav.paragraph li a {
- display: inline-block;
+aside nav.paragraph li a, aside .no-breakline li a {
+ display: inline;
}
.body-fixed {
@@ -1027,6 +1027,9 @@ article>article {
@extend .badge-circle;
font-size: 140%;
}
+ .badge-secondary {
+ background-color: var(--secondary);
+ }
li {
list-style-type: none;
}
@@ -1039,15 +1042,20 @@ article>article {
background: rgba(255, 0, 76, 0.1);
border-radius: 0.5em;
}
+}
+.badge-small {
+ @extend .badge-circle;
+ font-size: 70%;
+ width: 2em;
+ height: 2em;
+ line-height: 2em;
+ margin-right: 1em;
+}
- .badge-small {
- @extend .badge-circle;
- font-size: 70%;
- width: 2em;
- height: 2em;
- line-height: 2em;
- margin-right: 1em;
- }
+span.badge-small {
+ display: inline-block;
+ float: none;
+ margin-right: 0;
}
.strike {
diff --git a/src/agenda_culturel/templates/agenda_culturel/duplicate-diff-inc.html b/src/agenda_culturel/templates/agenda_culturel/duplicate-diff-inc.html
new file mode 100644
index 0000000..2c09214
--- /dev/null
+++ b/src/agenda_culturel/templates/agenda_culturel/duplicate-diff-inc.html
@@ -0,0 +1,31 @@
+{% load utils_extra %}
+{% load event_extra %}
+
+
{% block title %}{% block og_title %}Événements {% if not object.fixed %}possiblement{% endif %} dupliqués{% endblock %}{% endblock %}
+ {% if object.fixed %}
+
Les événements ci-dessous sont des duplicats du même événement, probablement issus de différentes
+ sources. La version qui sera affichée aux internautes en priorité est la version
+ {% for e in object.get_duplicated %}{% if not e.masked %}{{ forloop.counter0|int_to_abc }}{% endif %}{% endfor %}.
+
+ {% else %}
+
Les événements ci-dessous ont été détectés ou signalés comme possiblement dupliqué.
+ Les éléments qui diffèrent ont été dupliqués et mis en évidence.
{{ paginator.count }} événement{{ paginator.count|pluralize }} dupliqués {% if filter.form.fixed.value %}sont résolus{% else %}doivent être résolus{% endif %}.
{% for obj in paginator_filter %}
{% with obj.get_duplicated as events %}
-
- Possible duplication : {{ events|length }} événements le {{ events.0.start_day }}
-
-
- {% for e in events %}
-
{{ e.start_day }}{% if e.start_time %} à {{ e.start_time }}{% endif %} : {{ e|picto_status }} {{ e.title }} créé le {{ e.created_date }}
- {% endfor %}
-
- {% if perms.agenda_culturel.change_duplicatedevents %}
-
- {% endif %}
-
+ {% include "agenda_culturel/duplicate-inc.html" with duplicate=obj events=events %}
{% endwith %}
{% endfor %}
diff --git a/src/agenda_culturel/templates/agenda_culturel/event-info-inc.html b/src/agenda_culturel/templates/agenda_culturel/event-info-inc.html
index fd968dc..21c8cb9 100644
--- a/src/agenda_culturel/templates/agenda_culturel/event-info-inc.html
+++ b/src/agenda_culturel/templates/agenda_culturel/event-info-inc.html
@@ -5,6 +5,7 @@
{% if object.modified_date %}
Dernière modification : {{ object.modified_date }}
{% endif %}
{% if object.moderated_date %}
Dernière modération : {{ object.moderated_date }}
{% endif %}
{% if object.imported_date %}
Dernière importation : {{ object.imported_date }}
{% endif %}
+
Masqué : {{ object.masked }}
{% if object.uuids %}
{% if object.uuids|length > 0 %}
{% block title %}{% block og_title %}{% if object.fixed %}Modifier{% else %}Corriger{% endif %} des événements {% if not object.fixed %}possiblement{% endif %} dupliqués{% endblock %}{% endblock %}
+ {% if object.fixed %}
+
Les événements ci-dessous sont des duplicats du même événement, probablement issus de différentes
+ sources. La version qui sera affichée aux internautes en priorité est la version
+ {% for e in object.get_duplicated %}{% if not e.masked %}{{ forloop.counter0|int_to_abc }}{% endif %}{% endfor %}.
+
+ {% else %}
Les événements ci-dessous ont été détectés ou signalés comme possiblement dupliqué.
Les éléments qui diffèrent ont été dupliqués et mis en évidence.
+ {% endif %}
- {% if form %}
Choisissez dans la liste ci-dessous l'action que vous voulez réaliser. À noter que
s'il y a plus de deux événements, toutes les possibilités ne sont pas disponibles, et
il vous faudra peut-être réaliser certaines opérations à la main.
- {% endif %}
- {% endfor %}
+ {% include "agenda_culturel/duplicate-diff-inc.html" with object=object %}
-
-
Informations techniques
- {% for e in object.get_duplicated %}
-
-
{{ e }}
- {% include "agenda_culturel/event-info-inc.html" with object=e %}
-
- {% endfor %}
-
+{% include "agenda_culturel/duplicate-info-inc.html" with object=object %}
+
{% endblock %}
diff --git a/src/agenda_culturel/templates/agenda_culturel/merge_duplicate.html b/src/agenda_culturel/templates/agenda_culturel/merge_duplicate.html
index 7d6e997..86289da 100644
--- a/src/agenda_culturel/templates/agenda_culturel/merge_duplicate.html
+++ b/src/agenda_culturel/templates/agenda_culturel/merge_duplicate.html
@@ -15,7 +15,7 @@
Fusionner les événements dupliqués
Pour chacun des champs non identiques, choisissez la version qui vous convient pour créer un événement
- résultat de la fusion. Les événements source seront déplacés dans la corbeille.
+ résultat de la fusion. Les événements source seront masqués.