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) self.id = self.id + '-' + str(id)
class CalendarList: 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.firstdate = firstdate
self.lastdate = lastdate self.lastdate = lastdate
self.now = date.today() self.now = date.today()
self.filter = filter self.filter = filter
self.ignore_dup = ignore_dup
if exact: if exact:
self.c_firstdate = self.firstdate self.c_firstdate = self.firstdate
@ -219,6 +220,9 @@ class CalendarList:
qs = Event.objects.all() qs = Event.objects.all()
else: else:
qs = self.filter.qs 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()) 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()) lastdatetime = timezone.make_aware(datetime.combine(self.c_lastdate, time.max), timezone.get_default_timezone())
self.events = qs.filter( self.events = qs.filter(
@ -230,7 +234,7 @@ class CalendarList:
| Q(recurrence_dtend__lt=startdatetime) | 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()) firstdate = datetime.fromordinal(self.c_firstdate.toordinal())
if firstdate.tzinfo is None or firstdate.tzinfo.utcoffset(firstdate) is None: if firstdate.tzinfo is None or firstdate.tzinfo.utcoffset(firstdate) is None:

View File

@ -162,17 +162,17 @@ class FixDuplicates(Form):
choices += [ choices += [
( (
"SelectA", "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 += [ choices += [
( (
"SelectB", "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 += [ choices += [
("Merge", "Ces événements sont identiques, on fusionne à la main") ("Merge", "Ces événements sont identiques, créé une nouvelle version par fusion")
] ]
else: else:
choices = [("NotDuplicates", "Ces événements sont tous différents")] choices = [("NotDuplicates", "Ces événements sont tous différents")]
@ -191,11 +191,11 @@ class FixDuplicates(Form):
"Select" + i, "Select" + i,
"Ces événements sont identiques, on garde " "Ces événements sont identiques, on garde "
+ i + i
+ " et on met les autres à la corbeille", + " et on masque les autres",
) )
] ]
choices += [ 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 self.fields["action"].choices = choices
@ -248,7 +248,7 @@ class MergeDuplicates(Form):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
choices = [ 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(): for f in self.duplicates.get_items_comparison():

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: agenda_culturel\n" "Project-Id-Version: agenda_culturel\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2023-10-29 14:16+0000\n"
"Last-Translator: Jean-Marie Favreau <jeanmarie.favreau@free.fr>\n" "Last-Translator: Jean-Marie Favreau <jeanmarie.favreau@free.fr>\n"
"Language-Team: 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" msgstr "Soir"
#: agenda_culturel/forms.py:44 agenda_culturel/models.py:171 #: agenda_culturel/forms.py:44 agenda_culturel/models.py:171
#: agenda_culturel/models.py:355 agenda_culturel/models.py:1378 #: agenda_culturel/models.py:355 agenda_culturel/models.py:1401
#: agenda_culturel/models.py:1480 #: agenda_culturel/models.py:1506
msgid "Category" msgid "Category"
msgstr "Catégorie" 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." msgid "Optional. If you dont specify a category, well find it for you."
msgstr "" msgstr ""
"Facultatif. Si tu ne donnes pas la catégorie, on s'occupe de la trouver." "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." msgid "The end date must be after the start date."
msgstr "La date de fin doit être après la date de début." 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." 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." 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." msgid "JSON in the format expected for the import."
msgstr "JSON dans le format attendu pour l'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 {}" msgid "Apply category {} to the event {}"
msgstr "Appliquer la catégorie {} à l'événement {}" msgstr "Appliquer la catégorie {} à l'événement {}"
#: agenda_culturel/forms.py:422 agenda_culturel/models.py:279 #: agenda_culturel/forms.py:421 agenda_culturel/models.py:279
#: agenda_culturel/models.py:1532 #: agenda_culturel/models.py:1558
msgid "Place" msgid "Place"
msgstr "Lieu" msgstr "Lieu"
#: agenda_culturel/forms.py:424 #: agenda_culturel/forms.py:423
msgid "Create a missing place" msgid "Create a missing place"
msgstr "Créer un lieu manquant" msgstr "Créer un lieu manquant"
#: agenda_culturel/forms.py:434 #: agenda_culturel/forms.py:433
msgid "Add \"{}\" to the aliases of the place" msgid "Add \"{}\" to the aliases of the place"
msgstr "Ajouter « {} » aux alias du lieu" 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" msgid "On saving, use aliases to detect all matching events with missing place"
msgstr "" msgstr ""
"Lors de l'enregistrement, utiliser des alias pour détecter tous les " "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:49 agenda_culturel/models.py:94
#: agenda_culturel/models.py:241 agenda_culturel/models.py:258 #: 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" msgid "Name"
msgstr "Nom" msgstr "Nom"
@ -212,7 +212,18 @@ msgstr "Position pour ordonner les catégories"
msgid "Categories" msgid "Categories"
msgstr "Catégories" 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" msgid "Duplicated events"
msgstr "Événements dupliqués" msgstr "Événements dupliqués"
@ -272,7 +283,7 @@ msgstr ""
msgid "Places" msgid "Places"
msgstr "Lieux" msgstr "Lieux"
#: agenda_culturel/models.py:333 agenda_culturel/models.py:1365 #: agenda_culturel/models.py:333 agenda_culturel/models.py:1388
msgid "Published" msgid "Published"
msgstr "Publié" msgstr "Publié"
@ -292,7 +303,7 @@ msgstr "Titre"
msgid "Short title" msgid "Short title"
msgstr "Titre court" 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" msgid "Status"
msgstr "Status" msgstr "Status"
@ -326,7 +337,7 @@ msgstr "Heure de fin"
msgid "Recurrence" msgid "Recurrence"
msgstr "Récurrence" 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" msgid "Location"
msgstr "Localisation" msgstr "Localisation"
@ -378,181 +389,193 @@ msgstr "Description de l'illustration"
msgid "Alternative text used by screen readers for the image" msgid "Alternative text used by screen readers for the image"
msgstr "Texte alternatif utiliser par les lecteurs d'écrans pour l'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 #: 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" msgid "Importation source"
msgstr "Source d'importation" msgstr "Source d'importation"
#: agenda_culturel/models.py:440 #: agenda_culturel/models.py:448
msgid "Importation source used to detect removed entries." msgid "Importation source used to detect removed entries."
msgstr "Source d'importation utilisée pour détecter les éléments supprimés/" 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" msgid "UUIDs"
msgstr "UUIDs" msgstr "UUIDs"
#: agenda_culturel/models.py:447 #: agenda_culturel/models.py:455
msgid "UUIDs from import to detect duplicated entries." msgid "UUIDs from import to detect duplicated entries."
msgstr "UUIDs utilisés pendant l'import pour détecter les entrées dupliquées" 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" msgid "URLs"
msgstr "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." 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é." 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" msgid "Tags"
msgstr "Étiquettes" msgstr "Étiquettes"
#: agenda_culturel/models.py:462 #: agenda_culturel/models.py:470
msgid "A list of tags that describe the event." msgid "A list of tags that describe the event."
msgstr "Une liste d'étiquettes décrivant l'événement" msgstr "Une liste d'étiquettes décrivant l'événement"
#: agenda_culturel/models.py:469 #: agenda_culturel/models.py:477
msgid "Possibly duplicated" msgid "Possibly duplicated"
msgstr "Possibles doublons" msgstr "Possibles doublons"
#: agenda_culturel/models.py:518 #: agenda_culturel/models.py:539
msgid "Event" msgid "Event"
msgstr "Événement" msgstr "Événement"
#: agenda_culturel/models.py:519 #: agenda_culturel/models.py:540
msgid "Events" msgid "Events"
msgstr "Événements" msgstr "Événements"
#: agenda_culturel/models.py:1242 #: agenda_culturel/models.py:1264
msgid "Contact message" msgid "Contact message"
msgstr "Message de contact" msgstr "Message de contact"
#: agenda_culturel/models.py:1243 #: agenda_culturel/models.py:1265
msgid "Contact messages" msgid "Contact messages"
msgstr "Messages de contact" msgstr "Messages de contact"
#: agenda_culturel/models.py:1246 #: agenda_culturel/models.py:1268
msgid "Subject" msgid "Subject"
msgstr "Sujet" msgstr "Sujet"
#: agenda_culturel/models.py:1247 #: agenda_culturel/models.py:1269
msgid "The subject of your message" msgid "The subject of your message"
msgstr "Sujet de votre message" msgstr "Sujet de votre message"
#: agenda_culturel/models.py:1252 #: agenda_culturel/models.py:1274
msgid "Your name" msgid "Your name"
msgstr "Votre nom" msgstr "Votre nom"
#: agenda_culturel/models.py:1258 #: agenda_culturel/models.py:1280
msgid "Email address" msgid "Email address"
msgstr "Adresse email" msgstr "Adresse email"
#: agenda_culturel/models.py:1259 #: agenda_culturel/models.py:1281
msgid "Your email address" msgid "Your email address"
msgstr "Votre adresse email" msgstr "Votre adresse email"
#: agenda_culturel/models.py:1264 #: agenda_culturel/models.py:1286
msgid "Message" msgid "Message"
msgstr "Message" msgstr "Message"
#: agenda_culturel/models.py:1264 #: agenda_culturel/models.py:1286
msgid "Your message" msgid "Your message"
msgstr "Votre 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" msgid "Spam"
msgstr "Spam" msgstr "Spam"
#: agenda_culturel/models.py:1270 #: agenda_culturel/models.py:1292
msgid "This message is a spam." msgid "This message is a spam."
msgstr "Ce message est un 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" msgid "Closed"
msgstr "Fermé" 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" 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" 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" msgid "Comments"
msgstr "Commentaires" msgstr "Commentaires"
#: agenda_culturel/models.py:1283 #: agenda_culturel/models.py:1305
msgid "Comments on the message from the moderation team" msgid "Comments on the message from the moderation team"
msgstr "Commentaires sur ce message par l'équipe de modération" 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" msgid "Recurrent import"
msgstr "Import récurrent" msgstr "Import récurrent"
#: agenda_culturel/models.py:1296 #: agenda_culturel/models.py:1318
msgid "Recurrent imports" msgid "Recurrent imports"
msgstr "Imports récurrents" msgstr "Imports récurrents"
#: agenda_culturel/models.py:1300 #: agenda_culturel/models.py:1322
msgid "ical" msgid "ical"
msgstr "ical" msgstr "ical"
#: agenda_culturel/models.py:1301 #: agenda_culturel/models.py:1323
msgid "ical no busy" msgid "ical no busy"
msgstr "ical sans busy" msgstr "ical sans busy"
#: agenda_culturel/models.py:1302 #: agenda_culturel/models.py:1324
msgid "ical no VC" msgid "ical no VC"
msgstr "ical sans VC" msgstr "ical sans VC"
#: agenda_culturel/models.py:1303 #: agenda_culturel/models.py:1325
msgid "lacoope.org" msgid "lacoope.org"
msgstr "lacoope.org" msgstr "lacoope.org"
#: agenda_culturel/models.py:1304 #: agenda_culturel/models.py:1326
msgid "la comédie" msgid "la comédie"
msgstr "la comédie" msgstr "la comédie"
#: agenda_culturel/models.py:1305 #: agenda_culturel/models.py:1327
msgid "le fotomat" msgid "le fotomat"
msgstr "le fotomat" msgstr "le fotomat"
#: agenda_culturel/models.py:1306 #: agenda_culturel/models.py:1328
#, fuzzy #, fuzzy
#| msgid "la puce à loreille" #| msgid "la puce à loreille"
msgid "la puce à l'oreille" msgid "la puce à l'oreille"
msgstr "la puce à loreille" msgstr "la puce à loreille"
#: agenda_culturel/models.py:1307 #: agenda_culturel/models.py:1329
msgid "Plugin wordpress MEC" msgid "Plugin wordpress MEC"
msgstr "Plugin wordpress MEC" msgstr "Plugin wordpress MEC"
#: agenda_culturel/models.py:1308 #: agenda_culturel/models.py:1330
msgid "Événements d'une page FB" msgid "Événements d'une page FB"
msgstr "É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" msgid "la cour des 3 coquins"
msgstr "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" msgid "simple"
msgstr "simple" msgstr "simple"
#: agenda_culturel/models.py:1313 #: agenda_culturel/models.py:1336
msgid "Headless Chromium" msgid "Headless Chromium"
msgstr "chromium sans interface" msgstr "chromium sans interface"
#: agenda_culturel/models.py:1314 #: agenda_culturel/models.py:1337
msgid "Headless Chromium (pause)" msgid "Headless Chromium (pause)"
msgstr "chromium sans interface (pause)" msgstr "chromium sans interface (pause)"
#: agenda_culturel/models.py:1319 #: agenda_culturel/models.py:1342
msgid "daily" msgid "daily"
msgstr "chaque jour" msgstr "chaque jour"
#: agenda_culturel/models.py:1321 #: agenda_culturel/models.py:1344
msgid "weekly" msgid "weekly"
msgstr "chaque semaine" msgstr "chaque semaine"
#: agenda_culturel/models.py:1326 #: agenda_culturel/models.py:1349
msgid "" msgid ""
"Recurrent import name. Be careful to choose a name that is easy to " "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." "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 " "Nom de l'import récurrent. Attention à choisir un nom compréhensible, car il "
"sera public, et affiché sur la page à propos du site." "sera public, et affiché sur la page à propos du site."
#: agenda_culturel/models.py:1333 #: agenda_culturel/models.py:1356
msgid "Processor" msgid "Processor"
msgstr "Processeur" msgstr "Processeur"
#: agenda_culturel/models.py:1336 #: agenda_culturel/models.py:1359
msgid "Downloader" msgid "Downloader"
msgstr "Téléchargeur" msgstr "Téléchargeur"
#: agenda_culturel/models.py:1343 #: agenda_culturel/models.py:1366
msgid "Import recurrence" msgid "Import recurrence"
msgstr "Récurrence d'import" msgstr "Récurrence d'import"
#: agenda_culturel/models.py:1350 #: agenda_culturel/models.py:1373
msgid "Source" msgid "Source"
msgstr "Source" msgstr "Source"
#: agenda_culturel/models.py:1351 #: agenda_culturel/models.py:1374
msgid "URL of the source document" msgid "URL of the source document"
msgstr "URL du document source" msgstr "URL du document source"
#: agenda_culturel/models.py:1355 #: agenda_culturel/models.py:1378
msgid "Browsable url" msgid "Browsable url"
msgstr "URL navigable" 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." msgid "URL of the corresponding document that will be shown to visitors."
msgstr "URL correspondant au document et qui sera montrée aux visiteurs" 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)" msgid "Status of each imported event (published or draft)"
msgstr "Status de chaque événement importé (publié ou brouillon)" 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" msgid "Address for each imported event"
msgstr "Adresse de chaque événement importé" msgstr "Adresse de chaque événement importé"
#: agenda_culturel/models.py:1379 #: agenda_culturel/models.py:1402
msgid "Category of each imported event" msgid "Category of each imported event"
msgstr "Catégorie de chaque événement importé" msgstr "Catégorie de chaque événement importé"
#: agenda_culturel/models.py:1387 #: agenda_culturel/models.py:1410
msgid "Tags for each imported event" msgid "Tags for each imported event"
msgstr "Étiquettes de chaque événement importé" 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." msgid "A list of tags that describe each imported event."
msgstr "Une liste d'étiquettes décrivant chaque événement importé" msgstr "Une liste d'étiquettes décrivant chaque événement importé"
#: agenda_culturel/models.py:1414 #: agenda_culturel/models.py:1440
msgid "Running" msgid "Running"
msgstr "En cours" msgstr "En cours"
#: agenda_culturel/models.py:1415 #: agenda_culturel/models.py:1441
msgid "Canceled" msgid "Canceled"
msgstr "Annulé" msgstr "Annulé"
#: agenda_culturel/models.py:1416 #: agenda_culturel/models.py:1442
msgid "Success" msgid "Success"
msgstr "Succès" msgstr "Succès"
#: agenda_culturel/models.py:1417 #: agenda_culturel/models.py:1443
msgid "Failed" msgid "Failed"
msgstr "Erreur" msgstr "Erreur"
#: agenda_culturel/models.py:1420 #: agenda_culturel/models.py:1446
msgid "Batch importation" msgid "Batch importation"
msgstr "Importation par lot" msgstr "Importation par lot"
#: agenda_culturel/models.py:1421 #: agenda_culturel/models.py:1447
msgid "Batch importations" msgid "Batch importations"
msgstr "Importations par lot" msgstr "Importations par lot"
#: agenda_culturel/models.py:1429 #: agenda_culturel/models.py:1455
msgid "Reference to the recurrent import processing" msgid "Reference to the recurrent import processing"
msgstr "Référence du processus d'import récurrent" 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)" msgid "URL (if not recurrent import)"
msgstr "URL (si pas d'import récurrent)" 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." msgid "Source URL if no RecurrentImport is associated."
msgstr "URL source si aucun import récurrent n'est associé" msgstr "URL source si aucun import récurrent n'est associé"
#: agenda_culturel/models.py:1452 #: agenda_culturel/models.py:1478
msgid "Error message" msgid "Error message"
msgstr "Votre message" msgstr "Votre message"
#: agenda_culturel/models.py:1456 #: agenda_culturel/models.py:1482
msgid "Number of collected events" msgid "Number of collected events"
msgstr "Nombre d'événements collectés" msgstr "Nombre d'événements collectés"
#: agenda_culturel/models.py:1459 #: agenda_culturel/models.py:1485
msgid "Number of imported events" msgid "Number of imported events"
msgstr "Nombre d'événements importés" msgstr "Nombre d'événements importés"
#: agenda_culturel/models.py:1462 #: agenda_culturel/models.py:1488
msgid "Number of updated events" msgid "Number of updated events"
msgstr "Nombre d'événements mis à jour" msgstr "Nombre d'événements mis à jour"
#: agenda_culturel/models.py:1465 #: agenda_culturel/models.py:1491
msgid "Number of removed events" msgid "Number of removed events"
msgstr "Nombre d'événements supprimés" msgstr "Nombre d'événements supprimés"
#: agenda_culturel/models.py:1473 #: agenda_culturel/models.py:1499
msgid "Weight" msgid "Weight"
msgstr "Poids" msgstr "Poids"
#: agenda_culturel/models.py:1474 #: agenda_culturel/models.py:1500
msgid "The lower is the weight, the earlier the filter is applied" 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" 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" msgid "Category applied to the event"
msgstr "Catégorie appliquée à l'événement" msgstr "Catégorie appliquée à l'événement"
#: agenda_culturel/models.py:1486 #: agenda_culturel/models.py:1512
msgid "Contained in the title" msgid "Contained in the title"
msgstr "Contenu dans le titre" msgstr "Contenu dans le titre"
#: agenda_culturel/models.py:1487 #: agenda_culturel/models.py:1513
msgid "Text contained in the event title" msgid "Text contained in the event title"
msgstr "Texte contenu dans le titre de l'événement" msgstr "Texte contenu dans le titre de l'événement"
#: agenda_culturel/models.py:1493 #: agenda_culturel/models.py:1519
msgid "Exact title extract" msgid "Exact title extract"
msgstr "Extrait exact du titre" msgstr "Extrait exact du titre"
#: agenda_culturel/models.py:1495 #: agenda_culturel/models.py:1521
msgid "" msgid ""
"If checked, the extract will be searched for in the title using the exact " "If checked, the extract will be searched for in the title using the exact "
"form (capitals, accents)." "form (capitals, accents)."
@ -696,19 +719,19 @@ msgstr ""
"Si coché, l'extrait sera recherché dans le titre en utilisant la forme " "Si coché, l'extrait sera recherché dans le titre en utilisant la forme "
"exacte (majuscules, accents)" "exacte (majuscules, accents)"
#: agenda_culturel/models.py:1501 #: agenda_culturel/models.py:1527
msgid "Contained in the description" msgid "Contained in the description"
msgstr "Contenu dans la description" msgstr "Contenu dans la description"
#: agenda_culturel/models.py:1502 #: agenda_culturel/models.py:1528
msgid "Text contained in the description" msgid "Text contained in the description"
msgstr "Texte contenu dans la description" msgstr "Texte contenu dans la description"
#: agenda_culturel/models.py:1508 #: agenda_culturel/models.py:1534
msgid "Exact description extract" msgid "Exact description extract"
msgstr "Extrait exact de description" msgstr "Extrait exact de description"
#: agenda_culturel/models.py:1510 #: agenda_culturel/models.py:1536
msgid "" msgid ""
"If checked, the extract will be searched for in the description using the " "If checked, the extract will be searched for in the description using the "
"exact form (capitals, accents)." "exact form (capitals, accents)."
@ -716,19 +739,19 @@ msgstr ""
"Si coché, l'extrait sera recherché dans la description en utilisant la forme " "Si coché, l'extrait sera recherché dans la description en utilisant la forme "
"exacte (majuscules, accents)" "exacte (majuscules, accents)"
#: agenda_culturel/models.py:1516 #: agenda_culturel/models.py:1542
msgid "Contained in the location" msgid "Contained in the location"
msgstr "Contenu dans la localisation" msgstr "Contenu dans la localisation"
#: agenda_culturel/models.py:1517 #: agenda_culturel/models.py:1543
msgid "Text contained in the event location" msgid "Text contained in the event location"
msgstr "Texte contenu dans la localisation de l'événement" msgstr "Texte contenu dans la localisation de l'événement"
#: agenda_culturel/models.py:1523 #: agenda_culturel/models.py:1549
msgid "Exact location extract" msgid "Exact location extract"
msgstr "Extrait exact de localisation" msgstr "Extrait exact de localisation"
#: agenda_culturel/models.py:1525 #: agenda_culturel/models.py:1551
msgid "" msgid ""
"If checked, the extract will be searched for in the location using the exact " "If checked, the extract will be searched for in the location using the exact "
"form (capitals, accents)." "form (capitals, accents)."
@ -736,56 +759,56 @@ msgstr ""
"Si coché, l'extrait sera recherché dans la localisation en utilisant la " "Si coché, l'extrait sera recherché dans la localisation en utilisant la "
"forme exacte (majuscules, accents)" "forme exacte (majuscules, accents)"
#: agenda_culturel/models.py:1533 #: agenda_culturel/models.py:1559
msgid "Location from place" msgid "Location from place"
msgstr "Localisation depuis le lieu" msgstr "Localisation depuis le lieu"
#: agenda_culturel/models.py:1540 #: agenda_culturel/models.py:1566
msgid "Categorisation rule" msgid "Categorisation rule"
msgstr "Règle de catégorisation" msgstr "Règle de catégorisation"
#: agenda_culturel/models.py:1541 #: agenda_culturel/models.py:1567
msgid "Categorisation rules" msgid "Categorisation rules"
msgstr "Règles de catégorisation" 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" msgid "Question"
msgstr "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" msgid "Text that will be shown to moderators"
msgstr "Text tel que présenté aux modérateurices" msgstr "Text tel que présenté aux modérateurices"
#: agenda_culturel/models.py:1615 #: agenda_culturel/models.py:1641
msgid "Moderation question" msgid "Moderation question"
msgstr "Question de modération" msgstr "Question de modération"
#: agenda_culturel/models.py:1616 #: agenda_culturel/models.py:1642
msgid "Moderation questions" msgid "Moderation questions"
msgstr "Questions de modération" msgstr "Questions de modération"
#: agenda_culturel/models.py:1641 #: agenda_culturel/models.py:1667
msgid "Associated question from moderation" msgid "Associated question from moderation"
msgstr "Question associée pour la modération" msgstr "Question associée pour la modération"
#: agenda_culturel/models.py:1646 #: agenda_culturel/models.py:1672
msgid "Answer" msgid "Answer"
msgstr "Réponse" msgstr "Réponse"
#: agenda_culturel/models.py:1653 #: agenda_culturel/models.py:1679
msgid "Adds tags" msgid "Adds tags"
msgstr "Ajoute les étiquettes" 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." msgid "A list of tags that will be added if you choose this answer."
msgstr "" msgstr ""
"Une liste d'étiquettes qui seront ajoutées si vous choisissez cette réponse." "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" msgid "Removes tags"
msgstr "Retire les étiquettes" 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." msgid "A list of tags that will be removed if you choose this answer."
msgstr "" msgstr ""
"Une liste d'étiquettes qui seront retirées si vous choisissez cette réponse." "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." msgid "Integrating {} into our import process."
msgstr "Intégration de {} dans notre processus d'import." 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." msgid "Your message has been sent successfully."
msgstr "Votre message a été envoyé avec succès." 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." msgid "The contact message has been successfully deleted."
msgstr "Le message de contact a été supprimé avec succès." 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." msgid "The contact message properties has been successfully modified."
msgstr "Les propriétés du message de contact ont été modifié avec succès." 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" msgid "Open"
msgstr "Ouvert" msgstr "Ouvert"
#: agenda_culturel/views.py:934 #: agenda_culturel/views.py:944
msgid "Non spam" msgid "Non spam"
msgstr "Non spam" msgstr "Non spam"
#: agenda_culturel/views.py:997 #: agenda_culturel/views.py:1007
#, fuzzy #, fuzzy
#| msgid "The event has been successfully deleted." #| msgid "The event has been successfully deleted."
msgid "Spam has been successfully deleted." msgid "Spam has been successfully deleted."
msgstr "L'événement a été supprimé avec succès" msgstr "L'événement a été supprimé avec succès"
#: agenda_culturel/views.py:1014 #: agenda_culturel/views.py:1024
msgid "Search" msgid "Search"
msgstr "Rechercher" msgstr "Rechercher"
#: agenda_culturel/views.py:1200 #: agenda_culturel/views.py:1210
msgid "The import has been run successfully." msgid "The import has been run successfully."
msgstr "L'import a été lancé avec succès" 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." msgid "The import has been canceled."
msgstr "L'import a été annulé" msgstr "L'import a été annulé"
#: agenda_culturel/views.py:1293 #: agenda_culturel/views.py:1303
msgid "The recurrent import has been successfully modified." msgid "The recurrent import has been successfully modified."
msgstr "L'import récurrent a été modifié avec succès." 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." msgid "The recurrent import has been successfully deleted."
msgstr "L'import récurrent a été supprimé avec succès" 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." msgid "The import has been launched."
msgstr "L'import a été lancé" msgstr "L'import a été lancé"
#: agenda_culturel/views.py:1364 #: agenda_culturel/views.py:1374
msgid "Imports has been launched." msgid "Imports has been launched."
msgstr "Les imports ont été lancés" msgstr "Les imports ont été lancés"
#: agenda_culturel/views.py:1449 #: agenda_culturel/views.py:1467
msgid "The merge has been successfully completed." msgid "Creation of a merged event has been successfully completed."
msgstr "La fusion a été réalisée avec succès." 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." msgid "Events have been marked as unduplicated."
msgstr "Les événements ont été marqués comme non dupliqués." msgstr "Les événements ont été marqués comme non dupliqués."
#: agenda_culturel/views.py:1505 #: agenda_culturel/views.py:1526
msgid "" msgid "The selected event has been retained, while the other has been masked."
"The selected event has been retained, while the other has been moved to the "
"recycle bin."
msgstr "" msgstr ""
"L'événement sélectionné a été conservé, l'autre a été déplacé dans la " "L'événement sélectionné a été retenu, l'autre a été masqué."
"corbeille."
#: agenda_culturel/views.py:1512 #: agenda_culturel/views.py:1533
msgid "" msgid ""
"The selected event has been retained, while the others have been moved to " "The selected event has been retained, while the others havec been masked."
"the recycle bin."
msgstr "" msgstr ""
"L'événement sélectionné a été conservé, les autres ont été déplacés dans la " "L'événement sélectionné a été retenu, les autres ont été masqués."
"corbeille."
#: agenda_culturel/views.py:1523 #: agenda_culturel/views.py:1546
msgid "The event has been withdrawn from the group and made independent." 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." 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." msgid "Cleaning up duplicates: {} item(s) removed."
msgstr "Nettoyage des dupliqués: {} élément(s) supprimés." 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." msgid "The event was successfully duplicated."
msgstr "L'événement a été marqué dupliqué avec succès." msgstr "L'événement a été marqué dupliqué avec succès."
#: agenda_culturel/views.py:1608 #: agenda_culturel/views.py:1633
msgid "" msgid ""
"The event has been successfully flagged as a duplicate. The moderation team " "The event has been successfully flagged as a duplicate. The moderation team "
"will deal with your suggestion shortly." "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 " "L'événement a été signalé comme dupliqué avec succès. Votre suggestion sera "
"prochainement prise en charge par l'équipe de modération." "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." msgid "The categorisation rule has been successfully modified."
msgstr "La règle de catégorisation a été modifiée avec succès." 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." msgid "The categorisation rule has been successfully deleted."
msgstr "La règle de catégorisation a été supprimée avec succès" 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." msgid "The rules were successfully applied and 1 event was categorised."
msgstr "" msgstr ""
"Les règles ont été appliquées avec succès et 1 événement a été catégorisé" "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." msgid "The rules were successfully applied and {} events were categorised."
msgstr "" msgstr ""
"Les règles ont été appliquées avec succès et {} événements ont été " "Les règles ont été appliquées avec succès et {} événements ont été "
"catégorisés" "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." msgid "The rules were successfully applied and no events were categorised."
msgstr "" msgstr ""
"Les règles ont été appliquées avec succès et aucun événement n'a été " "Les règles ont été appliquées avec succès et aucun événement n'a été "
"catégorisé" "catégorisé"
#: agenda_culturel/views.py:1793 #: agenda_culturel/views.py:1818
msgid "The moderation question has been created with success." msgid "The moderation question has been created with success."
msgstr "La question de modération a été créée avec succès." 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:1944 agenda_culturel/views.py:2006
#: agenda_culturel/views.py:2019 #: agenda_culturel/views.py:2044
msgid "{} events have been updated." msgid "{} events have been updated."
msgstr "{} événements ont été mis à jour." msgstr "{} événements ont été mis à jour."
#: agenda_culturel/views.py:1922 agenda_culturel/views.py:1983 #: agenda_culturel/views.py:1947 agenda_culturel/views.py:2008
#: agenda_culturel/views.py:2022 #: agenda_culturel/views.py:2047
msgid "1 event has been updated." msgid "1 event has been updated."
msgstr "1 événement a été mis à jour" msgstr "1 événement a été mis à jour"
#: agenda_culturel/views.py:1924 agenda_culturel/views.py:1985 #: agenda_culturel/views.py:1949 agenda_culturel/views.py:2010
#: agenda_culturel/views.py:2024 #: agenda_culturel/views.py:2049
msgid "No events have been modified." msgid "No events have been modified."
msgstr "Aucun événement n'a été modifié." 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." msgid "The place has been successfully updated."
msgstr "Le lieu a été modifié avec succès." 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." msgid "The place has been successfully created."
msgstr "Le lieu a été créé avec succès." 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." msgid "The selected place has been assigned to the event."
msgstr "Le lieu sélectionné a été assigné à l'événement." 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." msgid "A new alias has been added to the selected place."
msgstr "Un nouvel alias a été créé pour le lieu sélectionné." 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): 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: class Meta:
verbose_name = _("Duplicated events") verbose_name = _("Duplicated events")
verbose_name_plural = _("Duplicated events") verbose_name_plural = _("Duplicated events")
def nb_duplicated(self): 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): def get_duplicated(self):
return Event.objects.filter(possibly_duplicated=self).order_by( return self.event_set.order_by(
"created_date" "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): def merge_into(self, other):
# for all objects associated to this group # 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 # change their group membership
e.possibly_duplicated = other e.possibly_duplicated = other
# save them # save them
e.save() e.save()
other.save(force_fixed=False)
# then delete the empty group # then delete the empty group
self.delete() 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): def merge_groups(groups):
if len(groups) == 0: if len(groups) == 0:
return None return None
@ -216,8 +248,8 @@ class DuplicatedEvents(models.Model):
nb, d = singletons.delete() nb, d = singletons.delete()
return nb return nb
def remove_similar_entries(): def fix_similar_entries():
to_be_removed = [] to_be_fixed = []
dup_events = Event.objects.order_by('possibly_duplicated').prefetch_related('possibly_duplicated', 'category') dup_events = Event.objects.order_by('possibly_duplicated').prefetch_related('possibly_duplicated', 'category')
duplicates = defaultdict(list) duplicates = defaultdict(list)
@ -228,15 +260,28 @@ class DuplicatedEvents(models.Model):
comp = Event.get_comparison(duplicates[d]) comp = Event.get_comparison(duplicates[d])
similar = len([c for c in comp if not c["similar"]]) == 0 similar = len([c for c in comp if not c["similar"]]) == 0
if similar: 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: if nb > 0:
logger.warning("Removing: " + str(nb) + " similar duplicated") logger.warning("Removing: " + str(nb) + " similar duplicated")
for s in to_be_removed: for s in to_be_fixed:
s.delete() s.fix()
return nb 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): class ReferenceLocation(models.Model):
name = models.CharField(verbose_name=_("Name"), help_text=_("Name of the location"), unique=True, null=False) 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) 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, 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( import_sources = ArrayField(
models.CharField(max_length=512), models.CharField(max_length=512),
verbose_name=_("Importation source"), verbose_name=_("Importation source"),
@ -487,10 +540,13 @@ class Event(models.Model):
last = self.get_consolidated_end_day() last = self.get_consolidated_end_day()
return [first + timedelta(n) for n in range(int((last - first).days) + 1)] 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 first = self.start_day
last = self.get_consolidated_end_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()] return [(len(d.events), d.date) for dstr, d in calendar.get_calendar_days().items()]
def is_single_day(self, intuitive=True): 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): for attr in Event.data_fields(all=True, local_img=False, exact_location=False):
values = [getattr(e, attr) for e in events] values = [getattr(e, attr) for e in events]
values = ["" if v is None else v for v in values] 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 # only consider fixed part of Facebook urls
if attr == "image": if attr == "image":
values = [v.split("?")[0] if "fbcdn.net" in v else v for v in values] values = [v.split("?")[0] if "fbcdn.net" in v else v for v in values]
@ -941,7 +998,7 @@ class Event(models.Model):
else: else:
# otherwise merge existing groups # otherwise merge existing groups
group = DuplicatedEvents.merge_groups(groups) group = DuplicatedEvents.merge_groups(groups)
group.save() group.save(force_fixed=False)
# set the possibly duplicated group for the current object # set the possibly duplicated group for the current object
self.possibly_duplicated = group self.possibly_duplicated = group
@ -1170,7 +1227,7 @@ class Event(models.Model):
return dtstart, dtend 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 = self.current_date if hasattr(self, "current_date") else self.start_day
day_events = CalendarDay(self.start_day).get_events() day_events = CalendarDay(self.start_day).get_events()
return [ return [
@ -1179,6 +1236,7 @@ class Event(models.Model):
if e != self if e != self
and self.is_concurrent_event(e, day) and self.is_concurrent_event(e, day)
and e.status == Event.STATUS.PUBLISHED 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): def is_concurrent_event(self, e, day):

View File

@ -886,8 +886,8 @@ aside nav a.badge {
float: right; float: right;
} }
aside nav.paragraph li a { aside nav.paragraph li a, aside .no-breakline li a {
display: inline-block; display: inline;
} }
.body-fixed { .body-fixed {
@ -1027,6 +1027,9 @@ article>article {
@extend .badge-circle; @extend .badge-circle;
font-size: 140%; font-size: 140%;
} }
.badge-secondary {
background-color: var(--secondary);
}
li { li {
list-style-type: none; list-style-type: none;
} }
@ -1039,7 +1042,7 @@ article>article {
background: rgba(255, 0, 76, 0.1); background: rgba(255, 0, 76, 0.1);
border-radius: 0.5em; border-radius: 0.5em;
} }
}
.badge-small { .badge-small {
@extend .badge-circle; @extend .badge-circle;
font-size: 70%; font-size: 70%;
@ -1048,6 +1051,11 @@ article>article {
line-height: 2em; line-height: 2em;
margin-right: 1em; margin-right: 1em;
} }
span.badge-small {
display: inline-block;
float: none;
margin-right: 0;
} }
.strike { .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 utils_extra %}
{% load event_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 %} {% load cat_extra %}
{% block entete_header %} {% block entete_header %}
@ -19,29 +19,19 @@
<div class="grid two-columns"> <div class="grid two-columns">
<article> <article>
<header> <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> </header>
<div> <div>
{% for obj in paginator_filter %} {% for obj in paginator_filter %}
{% with obj.get_duplicated as events %} {% with obj.get_duplicated as events %}
<article> {% include "agenda_culturel/duplicate-inc.html" with duplicate=obj events=events %}
<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>
{% endwith %} {% endwith %}
{% endfor %} {% endfor %}
</div> </div>

View File

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

View File

@ -3,7 +3,6 @@
{% load utils_extra %} {% load utils_extra %}
{% load event_extra %} {% load event_extra %}
{% block title %}{% block og_title %}Événements possiblement dupliqués{% endblock %}{% endblock %}
{% load cat_extra %} {% load cat_extra %}
{% block entete_header %} {% block entete_header %}
@ -13,11 +12,17 @@
{% block content %} {% block content %}
<article> <article>
<header> <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é. <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> 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 <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 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> il vous faudra peut-être réaliser certaines opérations à la main.</p>
@ -29,52 +34,11 @@
<input type="submit" value="Appliquer"> <input type="submit" value="Appliquer">
</div> </div>
</form> </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> </header>
<div class="grid"> {% include "agenda_culturel/duplicate-diff-inc.html" with object=object %}
{% 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 %}
</article> </article>
<article> {% include "agenda_culturel/duplicate-info-inc.html" with object=object %}
<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>
{% endblock %} {% endblock %}

View File

@ -15,7 +15,7 @@
<header> <header>
<h1>Fusionner les événements dupliqués</h1> <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 <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> </header>
<form method="post"> <form method="post">

View File

@ -2,6 +2,7 @@
{% load cat_extra %} {% load cat_extra %}
{% load utils_extra %} {% load utils_extra %}
{% load event_extra %}
{% block title %}{% block og_title %}{{ event.title }}{% endblock %}{% endblock %} {% block title %}{% block og_title %}{{ event.title }}{% endblock %}{% endblock %}
@ -84,22 +85,34 @@
{% endwith %} {% endwith %}
</article> </article>
{% if event.possibly_duplicated %} {% if event.possibly_duplicated %}
{% with poss_dup=event.get_possibly_duplicated|only_allowed:user.is_authenticated %}
{% if poss_dup.count > 0 %}
<article> <article>
<header> <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> <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 <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 une autre version
{% else %} {% else %}
d'autres versions d'autres versions
{% endif %} {% endif %}
ci-dessous.</p> ci-dessous.</p>
{% endif %}
</header> </header>
<nav> <nav>
<ul> <ul class="no-breakline">
{% for e in event.get_possibly_duplicated %} {% for e in poss_dup %}
<li> <li>
<a href="{{ e.get_absolute_url }}">{{ e }}</a> {{ event|picto_status }} <a href="{{ e.get_absolute_url }}">{{ e }}</a>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
@ -109,6 +122,8 @@
<a role="button" href="{% url 'fix_duplicate' event.possibly_duplicated.pk %}">Corriger {% picto_from_name "tool" %}</a> <a role="button" href="{% url 'fix_duplicate' event.possibly_duplicated.pk %}">Corriger {% picto_from_name "tool" %}</a>
</footer> </footer>
{% endif %} {% endif %}
{% endif %}
{% endwith %}
</article> </article>
{% endif %} {% endif %}
<article> <article>

View File

@ -18,6 +18,20 @@
{% picto_from_name "map-pin" %} {% picto_from_name "map-pin" %}
{% include "agenda_culturel/event-location-inc.html" with event=event %} {% include "agenda_culturel/event-location-inc.html" with event=event %}
</p> </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> </header>
{% if event.has_image_url %} {% if event.has_image_url %}

View File

@ -2,7 +2,7 @@ from django import template
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.template.defaultfilters import pluralize 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 @register.simple_tag
def show_badge_duplicated(placement="top"): 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: if nb_duplicated != 0:
return mark_safe( return mark_safe(
'<a href="' '<a href="'
+ reverse_lazy("duplicates") + reverse_lazy("duplicates")
+ '" class="badge" data-placement="' + '?fixed=false" class="badge" data-placement="'
+ placement + placement
+ '" data-tooltip="' + '" data-tooltip="'
+ str(nb_duplicated) + str(nb_duplicated)

View File

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