Nouvelle gestion des duplicate

Fix #168
This commit is contained in:
Jean-Marie Favreau 2024-10-29 11:53:20 +01:00
parent cb214c0926
commit a09f6751e3
19 changed files with 509 additions and 294 deletions

View File

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

View File

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

View File

@ -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 <jeanmarie.favreau@free.fr>\n"
"Language-Team: Jean-Marie Favreau <jeanmarie.favreau@free.fr>\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é."

View File

@ -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'),
),
]

View File

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

View File

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

View File

@ -0,0 +1,31 @@
{% load utils_extra %}
{% load event_extra %}
<div class="grid">
{% for e in object.get_duplicated %}
<div class="grid entete-badge">
<div class="badge-large{% if e.masked %} badge-secondary{% endif %}">
{{ forloop.counter0|int_to_abc }}
</div>
<ul>
<li>{{ e|picto_status }} <a href="{{ e.get_absolute_url }}">{{ e.title }}</a></li>
<li>Masqué&nbsp;: {{ e.masked|yesno:"Oui,Non" }}</li>
<li>Création&nbsp;: {{ e.created_date }}</li>
<li>Dernière modification&nbsp;: {{ e.modified_date }}</li>
{% if e.imported_date %}<li>Dernière importation&nbsp;: {{ e.imported_date }}</li>{% endif %}
</ul>
</div>
{% endfor %}
</div>
{% for e in object.get_items_comparison %}
<h3>{% event_field_verbose_name e.key %}</h3>
{% if e.similar %}
<div class="comparison-item">Identique&nbsp;: {% field_to_html e.values e.key %}</div>
{% else %}
<div class="grid comparison-item">
{% for i in e.values %}
<div class="duplicated"><div class="badge-small">{{ forloop.counter0|int_to_abc }} </div> {% field_to_html i e.key %}</div>
{% endfor %}
</div>
{% endif %}
{% endfor %}

View File

@ -0,0 +1,25 @@
{% load cat_extra %}
{% load utils_extra %}
{% load event_extra %}
<article>
<header><a href="{% url 'view_duplicate' duplicate.pk %}">
{% if duplicate.fixed %}Duplication{% else %}Possible duplication{% endif %}
&nbsp;:</a> {{ events|length }} événements le {{ events.0.start_day }}
</header>
<ul>
{% for e in events %}
<li>{{ e.start_day }}{% if e.start_time %} à {{ e.start_time }}{% endif %}&nbsp;: {{ e|picto_status }} <a href="{{ e.get_absolute_url }}">{{ e.title }}</a> créé le {{ e.created_date }}</li>
{% endfor %}
</ul>
{% if perms.agenda_culturel.change_duplicatedevents %}
<footer class="infos-and-buttons">
<div class="infos"></div>
<div class="buttons">
<a role="button" href="{% url 'view_duplicate' duplicate.pk %}">Consulter {% picto_from_name "eye" %}</a>
<a role="button" href="{% url 'fix_duplicate' duplicate.pk %}">{% if duplicate.fixed %}Modifier{% else %}Corriger{% endif %} {% picto_from_name "tool" %}</a>
</div>
</footer>
{% endif %}
</article>

View File

@ -0,0 +1,11 @@
<article>
<header><h2>Informations techniques</h2></header>
{% for e in object.get_duplicated %}
<article>
<header>
<h3>{{ e }}</h3>
</header>
{% include "agenda_culturel/event-info-inc.html" with object=e %}
</article>
{% endfor %}
</article>

View File

@ -0,0 +1,41 @@
{% extends "agenda_culturel/page-admin.html" %}
{% load utils_extra %}
{% load event_extra %}
{% load cat_extra %}
{% block entete_header %}
{% css_categories %}
{% endblock %}
{% block content %}
<article>
<header>
<a href="{% url 'duplicates' %}" role="button">&lt; Tous les dupliqués</a>
<h1>{% block title %}{% block og_title %}Événements {% if not object.fixed %}possiblement{% endif %} dupliqués{% endblock %}{% endblock %}</h1>
{% if object.fixed %}
<p>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 %}<span class="badge-small">{{ forloop.counter0|int_to_abc }}</span>{% endif %}{% endfor %}.
</p>
{% else %}
<p>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. </p>
{% endif %}
<div class="infos-and-buttons">
<div class="infos"></div>
<div class="buttons">
<a role="button" href="{% url 'fix_duplicate' object.pk %}">Corriger {% picto_from_name "tool" %}</a>
</div>
</div>
</header>
{% include "agenda_culturel/duplicate-diff-inc.html" with object=object %}
</article>
{% include "agenda_culturel/duplicate-info-inc.html" with object=object %}
{% endblock %}

View File

@ -3,7 +3,7 @@
{% load utils_extra %}
{% load event_extra %}
{% block title %}{% block og_title %}Événements possiblement dupliqués{% endblock %}{% endblock %}
{% block title %}{% block og_title %}Événements dupliqués{% endblock %}{% endblock %}
{% load cat_extra %}
{% block entete_header %}
@ -19,29 +19,19 @@
<div class="grid two-columns">
<article>
<header>
<h1>Événements possiblement dupliqués</h1>
<h1>Événements dupliqués</h1>
<form method="get" class="form django-form recent moderation-events">
{{ filter.form.as_div }}<br />
<button type="submit">Filtrer</button><br />
</form>
<p>{{ paginator.count }} événement{{ paginator.count|pluralize }} dupliqués {% if filter.form.fixed.value %}sont résolus{% else %}doivent être résolus{% endif %}.</p>
</header>
<div>
{% for obj in paginator_filter %}
{% with obj.get_duplicated as events %}
<article>
<header><a href="{% url 'view_duplicate' obj.pk %}">Possible duplication&nbsp;:</a> {{ events|length }} événements le {{ events.0.start_day }}
</header>
<ul>
{% for e in events %}
<li>{{ e.start_day }}{% if e.start_time %} à {{ e.start_time }}{% endif %}&nbsp;: {{ e|picto_status }} <a href="{{ e.get_absolute_url }}">{{ e.title }}</a> créé le {{ e.created_date }}</li>
{% endfor %}
</ul>
{% if perms.agenda_culturel.change_duplicatedevents %}
<footer class="infos-and-buttons">
<div class="infos"></div>
<div class="buttons">
<a role="button" href="{% url 'fix_duplicate' obj.pk %}">Corriger {% picto_from_name "tool" %}</a>
</div>
</footer>
{% endif %}
</article>
{% include "agenda_culturel/duplicate-inc.html" with duplicate=obj events=events %}
{% endwith %}
{% endfor %}
</div>

View File

@ -5,6 +5,7 @@
{% if object.modified_date %}<li>Dernière modification&nbsp;: {{ object.modified_date }}</li>{% endif %}
{% if object.moderated_date %}<li>Dernière modération&nbsp;: {{ object.moderated_date }}</li>{% endif %}
{% if object.imported_date %}<li>Dernière importation&nbsp;: {{ object.imported_date }}</li>{% endif %}
<li>Masqué&nbsp;: {{ object.masked }}</li>
{% if object.uuids %}
{% if object.uuids|length > 0 %}
<li>UUIDs (identifiants uniques d'événements dans les sources)&nbsp;:

View File

@ -3,7 +3,6 @@
{% load utils_extra %}
{% load event_extra %}
{% block title %}{% block og_title %}Événements possiblement dupliqués{% endblock %}{% endblock %}
{% load cat_extra %}
{% block entete_header %}
@ -13,11 +12,17 @@
{% block content %}
<article>
<header>
<h1>Corriger des événements possiblement dupliqués</h1>
<h1>{% 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 %}</h1>
{% if object.fixed %}
<p>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 %}<span class="badge-small">{{ forloop.counter0|int_to_abc }}</span>{% endif %}{% endfor %}.
</p>
{% else %}
<p>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. </p>
{% endif %}
{% if form %}
<p>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.</p>
@ -29,52 +34,11 @@
<input type="submit" value="Appliquer">
</div>
</form>
{% else %}
<div class="infos-and-buttons">
<div class="infos"></div>
<div class="buttons">
<a role="button" href="{% url 'fix_duplicate' object.pk %}">Corriger {% picto_from_name "tool" %}</a>
</div>
</div>
{% endif %}
</header>
<div class="grid">
{% for e in object.get_duplicated %}
<div class="grid entete-badge">
<div class="badge-large">
{{ forloop.counter0|int_to_abc }}
</div>
<ul>
<li><a href="{{ e.get_absolute_url }}">{{ e.title }}</a></li>
<li>Création&nbsp;: {{ e.created_date }}</li>
<li>Dernière modification&nbsp;: {{ e.modified_date }}</li>
{% if e.imported_date %}<li>Dernière importation&nbsp;: {{ e.imported_date }}</li>{% endif %}
</ul>
</div>
{% endfor %}
</div>
{% for e in object.get_items_comparison %}
<h3>{% event_field_verbose_name e.key %}</h3>
{% if e.similar %}
<div class="comparison-item">Identique&nbsp;: {% field_to_html e.values e.key %}</div>
{% else %}
<div class="grid comparison-item">
{% for i in e.values %}
<div class="duplicated"><div class="badge-small">{{ forloop.counter0|int_to_abc }} </div> {% field_to_html i e.key %}</div>
{% endfor %}
</div>
{% endif %}
{% endfor %}
{% include "agenda_culturel/duplicate-diff-inc.html" with object=object %}
</article>
<article>
<header><h2>Informations techniques</h2></header>
{% for e in object.get_duplicated %}
<article>
<h3>{{ e }}</h3>
{% include "agenda_culturel/event-info-inc.html" with object=e %}
</article>
{% endfor %}
</article>
{% include "agenda_culturel/duplicate-info-inc.html" with object=object %}
{% endblock %}

View File

@ -15,7 +15,7 @@
<header>
<h1>Fusionner les événements dupliqués</h1>
<p>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.</p>
résultat de la fusion. Les événements source seront masqués.</p>
</header>
<form method="post">

View File

@ -2,6 +2,7 @@
{% load cat_extra %}
{% load utils_extra %}
{% load event_extra %}
{% block title %}{% block og_title %}{{ event.title }}{% endblock %}{% endblock %}
@ -84,22 +85,34 @@
{% endwith %}
</article>
{% if event.possibly_duplicated %}
{% with poss_dup=event.get_possibly_duplicated|only_allowed:user.is_authenticated %}
{% if poss_dup.count > 0 %}
<article>
<header>
{% if event.possibly_duplicated.fixed %}
<h2>Sources multiples</h2>
<p class="remarque">L'événement affiché a également été trouvé à partir
{% if poss_dup.count == 1 %}
d'une autre source
{% else %}
d'autres sources
{% endif %}&nbsp;:</p>
{% else %}
<h2>Possibles doublons</h2>
<p class="remarque">Notre algorithme a détecté que l'événement affiché pourrait être dupliqué sur l'agenda, et consultable dans
{% if event.get_possibly_duplicated.count == 1 %}
{% if poss_dup.count == 1 %}
une autre version
{% else %}
d'autres versions
{% endif %}
ci-dessous.</p>
{% endif %}
</header>
<nav>
<ul>
{% for e in event.get_possibly_duplicated %}
<ul class="no-breakline">
{% for e in poss_dup %}
<li>
<a href="{{ e.get_absolute_url }}">{{ e }}</a>
{{ event|picto_status }} <a href="{{ e.get_absolute_url }}">{{ e }}</a>
</li>
{% endfor %}
</ul>
@ -109,6 +122,8 @@
<a role="button" href="{% url 'fix_duplicate' event.possibly_duplicated.pk %}">Corriger {% picto_from_name "tool" %}</a>
</footer>
{% endif %}
{% endif %}
{% endwith %}
</article>
{% endif %}
<article>

View File

@ -18,6 +18,20 @@
{% picto_from_name "map-pin" %}
{% include "agenda_culturel/event-location-inc.html" with event=event %}
</p>
{% if event.possibly_duplicated %}
<p class="remarque">
{% if event.possibly_duplicated.fixed %}
cet événement a été {% if user.is_authenticated %}<a href="{{ event.possibly_duplicated.get_absolute_url }}">importé plusieurs fois</a>{% else %}importé plusieurs fois{% endif %},
{% if event.masked %}
vous pouvez <a href="{{ event.possibly_duplicated.get_one_event.get_absolute_url }}">consulter l'import principal</a>
{% else %}
et vous consultez l'import principal
{% endif %}
{% else %}
cet événement a probablement été {% if user.is_authenticated %}<a href="{{ event.possibly_duplicated.get_absolute_url }}">importé plusieurs fois</a>{% else %}importé plusieurs fois{% endif %}
{% endif %}
</p>
{% endif %}
</header>
{% if event.has_image_url %}

View File

@ -2,7 +2,7 @@ from django import template
from django.utils.safestring import mark_safe
from django.urls import reverse_lazy
from django.template.defaultfilters import pluralize
from django.db.models import Count
from django.db.models import Count, Q
@ -16,13 +16,14 @@ register = template.Library()
@register.simple_tag
def show_badge_duplicated(placement="top"):
nb_duplicated = DuplicatedEvents.objects.annotate(nb_duplicated=Count('event')).filter(nb_duplicated__gte=1).count()
nb_duplicated = DuplicatedEvents.objects.annotate(nb_duplicated=Count('event',
filter=Q(fixed=False), distinct=True)).filter(nb_duplicated__gte=1).count()
if nb_duplicated != 0:
return mark_safe(
'<a href="'
+ reverse_lazy("duplicates")
+ '" class="badge" data-placement="'
+ '?fixed=false" class="badge" data-placement="'
+ placement
+ '" data-tooltip="'
+ str(nb_duplicated)

View File

@ -144,4 +144,12 @@ def robust_urlize(txt):
def linebreaks2(txt):
res = linebreaks(txt)
return mark_safe(re.sub("(<br> )+", "<br>", res))
return mark_safe(re.sub("(<br> )+", "<br>", res))
@register.filter
def only_allowed(elist, is_authenticated):
if not is_authenticated:
return elist.filter(status=Event.STATUS.PUBLISHED)
else:
return elist

View File

@ -1386,10 +1386,15 @@ def run_all_rimports(request, status=None):
## duplicated events
#########################
class DuplicatedEventsFilter(django_filters.FilterSet):
class Meta:
model = DuplicatedEvents
fields = ["fixed"]
class DuplicatedEventsDetailView(LoginRequiredMixin, DetailView):
model = DuplicatedEvents
template_name = "agenda_culturel/fix_duplicate.html"
template_name = "agenda_culturel/duplicate.html"
@login_required(login_url="/accounts/login/")
@ -1445,18 +1450,14 @@ def merge_duplicate(request, pk):
# create a new event that merge the selected events
new_event = Event(**new_event_data)
new_event.masked = False
new_event.status = Event.STATUS.PUBLISHED
new_event.possibly_duplicated = edup
new_event.set_skip_duplicate_check()
new_event.save()
# move the old ones in trash
for e in events:
e.status = Event.STATUS.TRASH
Event.objects.bulk_update(events, fields=["status"])
# remove duplicate
edup.delete()
edup.fix(new_event)
messages.info(request, _("The merge has been successfully completed."))
messages.info(request, _("Creation of a merged event has been successfully completed."))
return HttpResponseRedirect(new_event.get_absolute_url())
return render(
@ -1480,6 +1481,9 @@ def fix_duplicate(request, pk):
if form.is_valid():
if form.is_action_no_duplicates():
events = edup.get_duplicated()
for e in events:
e.masked = False
e.save()
if len(events) == 0:
date = None
else:
@ -1501,32 +1505,28 @@ def fix_duplicate(request, pk):
elif form.is_action_select():
selected = form.get_selected_event(edup)
not_selected = [e for e in edup.get_duplicated() if e != selected]
nb = len(not_selected)
for e in not_selected:
e.status = Event.STATUS.TRASH
Event.objects.bulk_update(not_selected, fields=["status"])
url = selected.get_absolute_url()
edup.delete()
nb = edup.fix(selected) - 1
if nb == 1:
messages.success(
request,
_(
"The selected event has been retained, while the other has been moved to the recycle bin."
"The selected event has been retained, while the other has been masked."
),
)
else:
messages.success(
request,
_(
"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."
),
)
return HttpResponseRedirect(url)
return HttpResponseRedirect(edup.get_absolute_url())
elif form.is_action_remove():
event = form.get_selected_event(edup)
event.possibly_duplicated = None
event.masked = False
event.save()
edup.save()
messages.success(
request,
_(
@ -1534,7 +1534,7 @@ def fix_duplicate(request, pk):
),
)
if edup.nb_duplicated() == 1:
return HttpResponseRedirect(event.get_absolute_url())
return HttpResponseRedirect(edup.get_absolute_url())
else:
form = FixDuplicates(nb_events=edup.nb_duplicated())
else:
@ -1559,16 +1559,19 @@ class DuplicatedEventsUpdateView(LoginRequiredMixin, UpdateView):
@permission_required("agenda_culturel.view_duplicatedevents")
def duplicates(request):
nb_removed = DuplicatedEvents.remove_singletons()
nb_similar = DuplicatedEvents.remove_similar_entries()
nb_similar = DuplicatedEvents.fix_similar_entries()
if nb_removed > 0 or nb_similar > 0:
messages.success(
request,
_("Cleaning up duplicates: {} item(s) removed.").format(
_("Cleaning up duplicates: {} item(s) fixed.").format(
nb_removed + nb_similar
),
)
paginator = Paginator(DuplicatedEvents.objects.all(), 10)
filter = DuplicatedEventsFilter(
request.GET, queryset=DuplicatedEvents.objects.all().order_by("-pk")
)
paginator = PaginatorFilter(filter, 10, request)
page = request.GET.get("page")
try:
@ -1581,7 +1584,7 @@ def duplicates(request):
return render(
request,
"agenda_culturel/duplicates.html",
{"filter": filter, "paginator_filter": response},
{"filter": filter, "paginator_filter": response, "paginator": paginator},
)