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

{% event_field_verbose_name e.key %}

+ {% if e.similar %} +
Identique : {% field_to_html e.values e.key %}
+ {% else %} +
+ {% for i in e.values %} +
{{ forloop.counter0|int_to_abc }}
{% field_to_html i e.key %}
+ {% endfor %} +
+ {% endif %} +{% endfor %} diff --git a/src/agenda_culturel/templates/agenda_culturel/duplicate-inc.html b/src/agenda_culturel/templates/agenda_culturel/duplicate-inc.html new file mode 100644 index 0000000..c584bc2 --- /dev/null +++ b/src/agenda_culturel/templates/agenda_culturel/duplicate-inc.html @@ -0,0 +1,25 @@ +{% load cat_extra %} +{% load utils_extra %} +{% load event_extra %} + + +
+
+ {% if duplicate.fixed %}Duplication{% else %}Possible duplication{% endif %} +  : {{ events|length }} événements le {{ events.0.start_day }} +
+
    + {% for e in events %} +
  • {{ e.start_day }}{% if e.start_time %} à {{ e.start_time }}{% endif %} : {{ e|picto_status }} {{ e.title }} créé le {{ e.created_date }}
  • + {% endfor %} +
+ {% if perms.agenda_culturel.change_duplicatedevents %} + + {% endif %} +
diff --git a/src/agenda_culturel/templates/agenda_culturel/duplicate-info-inc.html b/src/agenda_culturel/templates/agenda_culturel/duplicate-info-inc.html new file mode 100644 index 0000000..fbaf216 --- /dev/null +++ b/src/agenda_culturel/templates/agenda_culturel/duplicate-info-inc.html @@ -0,0 +1,11 @@ +
+

Informations techniques

+ {% for e in object.get_duplicated %} +
+
+

{{ e }}

+
+ {% include "agenda_culturel/event-info-inc.html" with object=e %} +
+ {% endfor %} +
diff --git a/src/agenda_culturel/templates/agenda_culturel/duplicate.html b/src/agenda_culturel/templates/agenda_culturel/duplicate.html new file mode 100644 index 0000000..cfceb96 --- /dev/null +++ b/src/agenda_culturel/templates/agenda_culturel/duplicate.html @@ -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 %} +
+
+ < Tous les dupliqués + +

{% block title %}{% block og_title %}Événements {% if not object.fixed %}possiblement{% endif %} dupliqués{% endblock %}{% endblock %}

+ {% if object.fixed %} +

Les événements ci-dessous sont des duplicats du même événement, probablement issus de différentes + sources. La version qui sera affichée aux internautes en priorité est la version + {% for e in object.get_duplicated %}{% if not e.masked %}{{ forloop.counter0|int_to_abc }}{% endif %}{% endfor %}. +

+ {% else %} +

Les événements ci-dessous ont été détectés ou signalés comme possiblement dupliqué. + Les éléments qui diffèrent ont été dupliqués et mis en évidence.

+ {% endif %} + + +
+ {% include "agenda_culturel/duplicate-diff-inc.html" with object=object %} +
+ +{% include "agenda_culturel/duplicate-info-inc.html" with object=object %} + +{% endblock %} + diff --git a/src/agenda_culturel/templates/agenda_culturel/duplicates.html b/src/agenda_culturel/templates/agenda_culturel/duplicates.html index ba99e85..0207046 100644 --- a/src/agenda_culturel/templates/agenda_culturel/duplicates.html +++ b/src/agenda_culturel/templates/agenda_culturel/duplicates.html @@ -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 @@
-

Événements possiblement dupliqués

+

Événements dupliqués

+
+ {{ filter.form.as_div }}
+
+
+ +

{{ paginator.count }} événement{{ paginator.count|pluralize }} dupliqués {% if filter.form.fixed.value %}sont résolus{% else %}doivent être résolus{% endif %}.

{% for obj in paginator_filter %} {% with obj.get_duplicated as events %} -
-
Possible duplication : {{ events|length }} événements le {{ events.0.start_day }} -
-
    - {% for e in events %} -
  • {{ e.start_day }}{% if e.start_time %} à {{ e.start_time }}{% endif %} : {{ e|picto_status }} {{ e.title }} créé le {{ e.created_date }}
  • - {% endfor %} -
- {% if perms.agenda_culturel.change_duplicatedevents %} - - {% endif %} -
+ {% include "agenda_culturel/duplicate-inc.html" with duplicate=obj events=events %} {% endwith %} {% endfor %}
diff --git a/src/agenda_culturel/templates/agenda_culturel/event-info-inc.html b/src/agenda_culturel/templates/agenda_culturel/event-info-inc.html index fd968dc..21c8cb9 100644 --- a/src/agenda_culturel/templates/agenda_culturel/event-info-inc.html +++ b/src/agenda_culturel/templates/agenda_culturel/event-info-inc.html @@ -5,6 +5,7 @@ {% if object.modified_date %}
  • Dernière modification : {{ object.modified_date }}
  • {% endif %} {% if object.moderated_date %}
  • Dernière modération : {{ object.moderated_date }}
  • {% endif %} {% if object.imported_date %}
  • Dernière importation : {{ object.imported_date }}
  • {% endif %} +
  • Masqué : {{ object.masked }}
  • {% if object.uuids %} {% if object.uuids|length > 0 %}
  • UUIDs (identifiants uniques d'événements dans les sources) : diff --git a/src/agenda_culturel/templates/agenda_culturel/fix_duplicate.html b/src/agenda_culturel/templates/agenda_culturel/fix_duplicate.html index 7adb993..5e6cc00 100644 --- a/src/agenda_culturel/templates/agenda_culturel/fix_duplicate.html +++ b/src/agenda_culturel/templates/agenda_culturel/fix_duplicate.html @@ -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 %}
    -

    Corriger des événements possiblement dupliqués

    +

    {% block title %}{% block og_title %}{% if object.fixed %}Modifier{% else %}Corriger{% endif %} des événements {% if not object.fixed %}possiblement{% endif %} dupliqués{% endblock %}{% endblock %}

    + {% if object.fixed %} +

    Les événements ci-dessous sont des duplicats du même événement, probablement issus de différentes + sources. La version qui sera affichée aux internautes en priorité est la version + {% for e in object.get_duplicated %}{% if not e.masked %}{{ forloop.counter0|int_to_abc }}{% endif %}{% endfor %}. +

    + {% else %}

    Les événements ci-dessous ont été détectés ou signalés comme possiblement dupliqué. Les éléments qui diffèrent ont été dupliqués et mis en évidence.

    + {% endif %} - {% if form %}

    Choisissez dans la liste ci-dessous l'action que vous voulez réaliser. À noter que s'il y a plus de deux événements, toutes les possibilités ne sont pas disponibles, et il vous faudra peut-être réaliser certaines opérations à la main.

    @@ -29,52 +34,11 @@
  • - {% else %} - - {% endif %} -
    - {% for e in object.get_duplicated %} -
    -
    - {{ forloop.counter0|int_to_abc }} -
    -
      -
    • {{ e.title }}
    • -
    • Création : {{ e.created_date }}
    • -
    • Dernière modification : {{ e.modified_date }}
    • - {% if e.imported_date %}
    • Dernière importation : {{ e.imported_date }}
    • {% endif %} -
    -
    - {% endfor %} -
    - {% for e in object.get_items_comparison %} -

    {% event_field_verbose_name e.key %}

    - {% if e.similar %} -
    Identique : {% field_to_html e.values e.key %}
    - {% else %} -
    - {% for i in e.values %} -
    {{ forloop.counter0|int_to_abc }}
    {% field_to_html i e.key %}
    - {% endfor %} -
    - {% endif %} - {% endfor %} + {% include "agenda_culturel/duplicate-diff-inc.html" with object=object %} -
    -

    Informations techniques

    - {% for e in object.get_duplicated %} -
    -

    {{ e }}

    - {% include "agenda_culturel/event-info-inc.html" with object=e %} -
    - {% endfor %} -
    +{% include "agenda_culturel/duplicate-info-inc.html" with object=object %} + {% endblock %} diff --git a/src/agenda_culturel/templates/agenda_culturel/merge_duplicate.html b/src/agenda_culturel/templates/agenda_culturel/merge_duplicate.html index 7d6e997..86289da 100644 --- a/src/agenda_culturel/templates/agenda_culturel/merge_duplicate.html +++ b/src/agenda_culturel/templates/agenda_culturel/merge_duplicate.html @@ -15,7 +15,7 @@

    Fusionner les événements dupliqués

    Pour chacun des champs non identiques, choisissez la version qui vous convient pour créer un événement - résultat de la fusion. Les événements source seront déplacés dans la corbeille.

    + résultat de la fusion. Les événements source seront masqués.

    diff --git a/src/agenda_culturel/templates/agenda_culturel/page-event.html b/src/agenda_culturel/templates/agenda_culturel/page-event.html index cad0d2b..07eb24b 100644 --- a/src/agenda_culturel/templates/agenda_culturel/page-event.html +++ b/src/agenda_culturel/templates/agenda_culturel/page-event.html @@ -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 %} {% if event.possibly_duplicated %} + {% with poss_dup=event.get_possibly_duplicated|only_allowed:user.is_authenticated %} + {% if poss_dup.count > 0 %}
    + {% if event.possibly_duplicated.fixed %} +

    Sources multiples

    +

    L'événement affiché a également été trouvé à partir + {% if poss_dup.count == 1 %} + d'une autre source + {% else %} + d'autres sources + {% endif %} :

    + {% else %}

    Possibles doublons

    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.

    + {% endif %}
    {% endif %}
    diff --git a/src/agenda_culturel/templates/agenda_culturel/single-event/event-single-inc.html b/src/agenda_culturel/templates/agenda_culturel/single-event/event-single-inc.html index 594442a..e1c7321 100644 --- a/src/agenda_culturel/templates/agenda_culturel/single-event/event-single-inc.html +++ b/src/agenda_culturel/templates/agenda_culturel/single-event/event-single-inc.html @@ -18,6 +18,20 @@ {% picto_from_name "map-pin" %} {% include "agenda_culturel/event-location-inc.html" with event=event %}

    + {% if event.possibly_duplicated %} +

    + {% if event.possibly_duplicated.fixed %} + cet événement a été {% if user.is_authenticated %}importé plusieurs fois{% else %}importé plusieurs fois{% endif %}, + {% if event.masked %} + vous pouvez consulter l'import principal + {% else %} + et vous consultez l'import principal + {% endif %} + {% else %} + cet événement a probablement été {% if user.is_authenticated %}importé plusieurs fois{% else %}importé plusieurs fois{% endif %} + {% endif %} +

    + {% endif %} {% if event.has_image_url %} diff --git a/src/agenda_culturel/templatetags/duplicated_extra.py b/src/agenda_culturel/templatetags/duplicated_extra.py index f583426..78ee429 100644 --- a/src/agenda_culturel/templatetags/duplicated_extra.py +++ b/src/agenda_culturel/templatetags/duplicated_extra.py @@ -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( ' )+", "
    ", res)) \ No newline at end of file + return mark_safe(re.sub("(
    )+", "
    ", res)) + + +@register.filter +def only_allowed(elist, is_authenticated): + if not is_authenticated: + return elist.filter(status=Event.STATUS.PUBLISHED) + else: + return elist \ No newline at end of file diff --git a/src/agenda_culturel/views.py b/src/agenda_culturel/views.py index 11ce8ee..6cd86a2 100644 --- a/src/agenda_culturel/views.py +++ b/src/agenda_culturel/views.py @@ -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}, )