parent
bd6edfd0e9
commit
8d23f83057
@ -2,7 +2,7 @@ from django.forms import ModelForm, ValidationError, TextInput, Form, URLField,
|
||||
from datetime import date
|
||||
from django_better_admin_arrayfield.forms.widgets import DynamicArrayWidget
|
||||
|
||||
from .models import Event, BatchImportation, RecurrentImport
|
||||
from .models import Event, BatchImportation, RecurrentImport, CategorisationRule
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from string import ascii_uppercase as auc
|
||||
from .templatetags.utils_extra import int_to_abc
|
||||
@ -33,6 +33,11 @@ class RecurrentImportForm(ModelForm):
|
||||
'defaultTags': DynamicArrayWidgetTags(),
|
||||
}
|
||||
|
||||
class CategorisationRuleImportForm(ModelForm):
|
||||
class Meta:
|
||||
model = CategorisationRule
|
||||
fields = '__all__'
|
||||
|
||||
class EventForm(ModelForm):
|
||||
|
||||
class Meta:
|
||||
|
@ -143,7 +143,6 @@ class ICALNoVCExtractor(ICALExtractor):
|
||||
return text
|
||||
else:
|
||||
result = self.parser.format(text)
|
||||
logger.warning(result)
|
||||
return result
|
||||
|
||||
def add_event(self, title, category, start_day, location, description, tags, uuid, recurrences=None, url_human=None, start_time=None, end_day=None, end_time=None, last_modified=None, published=False, image=None, image_alt=None):
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: agenda_culturel\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-02-16 16:06+0000\n"
|
||||
"POT-Creation-Date: 2024-02-17 11:31+0000\n"
|
||||
"PO-Revision-Date: 2023-10-29 14:16+0000\n"
|
||||
"Last-Translator: Jean-Marie Favreau <jeanmarie.favreau@free.fr>\n"
|
||||
"Language-Team: Jean-Marie Favreau <jeanmarie.favreau@free.fr>\n"
|
||||
@ -17,374 +17,430 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: agenda_culturel/forms.py:62
|
||||
#: agenda_culturel/forms.py:70
|
||||
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:77
|
||||
#: agenda_culturel/forms.py:85
|
||||
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:84
|
||||
#: agenda_culturel/forms.py:92
|
||||
msgid "JSON in the format expected for the import."
|
||||
msgstr "JSON dans le format attendu pour l'import"
|
||||
|
||||
#: agenda_culturel/models.py:32 agenda_culturel/models.py:61
|
||||
#: agenda_culturel/models.py:702
|
||||
#: agenda_culturel/models.py:33 agenda_culturel/models.py:62
|
||||
#: agenda_culturel/models.py:703
|
||||
msgid "Name"
|
||||
msgstr "Nom"
|
||||
|
||||
#: agenda_culturel/models.py:32 agenda_culturel/models.py:61
|
||||
#: agenda_culturel/models.py:33 agenda_culturel/models.py:62
|
||||
msgid "Category name"
|
||||
msgstr "Nom de la catégorie"
|
||||
|
||||
#: agenda_culturel/models.py:33
|
||||
#: agenda_culturel/models.py:34
|
||||
msgid "Content"
|
||||
msgstr "Contenu"
|
||||
|
||||
#: agenda_culturel/models.py:33
|
||||
#: agenda_culturel/models.py:34
|
||||
msgid "Text as shown to the visitors"
|
||||
msgstr "Text tel que présenté aux visiteureuses"
|
||||
|
||||
#: agenda_culturel/models.py:34
|
||||
#: agenda_culturel/models.py:35
|
||||
msgid "URL path"
|
||||
msgstr ""
|
||||
|
||||
#: agenda_culturel/models.py:34
|
||||
#: agenda_culturel/models.py:35
|
||||
msgid "URL path where the content is included."
|
||||
msgstr ""
|
||||
|
||||
#: agenda_culturel/models.py:62
|
||||
#: agenda_culturel/models.py:63
|
||||
msgid "Alternative Name"
|
||||
msgstr "Nom alternatif"
|
||||
|
||||
#: agenda_culturel/models.py:62
|
||||
#: agenda_culturel/models.py:63
|
||||
msgid "Alternative name used with a time period"
|
||||
msgstr "Nom alternatif utilisé avec une période de temps"
|
||||
|
||||
#: agenda_culturel/models.py:63
|
||||
#: agenda_culturel/models.py:64
|
||||
msgid "Short name"
|
||||
msgstr "Nom court"
|
||||
|
||||
#: agenda_culturel/models.py:63
|
||||
#: agenda_culturel/models.py:64
|
||||
msgid "Short name of the category"
|
||||
msgstr "Nom court de la catégorie"
|
||||
|
||||
#: agenda_culturel/models.py:64
|
||||
#: agenda_culturel/models.py:65
|
||||
msgid "Color"
|
||||
msgstr "Couleur"
|
||||
|
||||
#: agenda_culturel/models.py:64
|
||||
#: agenda_culturel/models.py:65
|
||||
msgid "Color used as background for the category"
|
||||
msgstr "Couleur utilisée comme fond de la catégorie"
|
||||
|
||||
#: agenda_culturel/models.py:101 agenda_culturel/models.py:168
|
||||
#: agenda_culturel/models.py:741
|
||||
#: agenda_culturel/models.py:102 agenda_culturel/models.py:169
|
||||
#: agenda_culturel/models.py:743 agenda_culturel/models.py:782
|
||||
msgid "Category"
|
||||
msgstr "Catégorie"
|
||||
|
||||
#: agenda_culturel/models.py:102
|
||||
#: agenda_culturel/models.py:103
|
||||
msgid "Categories"
|
||||
msgstr "Catégories"
|
||||
|
||||
#: agenda_culturel/models.py:153 agenda_culturel/models.py:739
|
||||
#: agenda_culturel/models.py:154 agenda_culturel/models.py:741
|
||||
msgid "Published"
|
||||
msgstr "Publié"
|
||||
|
||||
#: agenda_culturel/models.py:154
|
||||
#: agenda_culturel/models.py:155
|
||||
msgid "Draft"
|
||||
msgstr "Brouillon"
|
||||
|
||||
#: agenda_culturel/models.py:155
|
||||
#: agenda_culturel/models.py:156
|
||||
msgid "Trash"
|
||||
msgstr "Corbeille"
|
||||
|
||||
#: agenda_culturel/models.py:164
|
||||
#: agenda_culturel/models.py:165
|
||||
msgid "Title"
|
||||
msgstr "Titre"
|
||||
|
||||
#: agenda_culturel/models.py:164
|
||||
#: agenda_culturel/models.py:165
|
||||
msgid "Short title"
|
||||
msgstr "Titre court"
|
||||
|
||||
#: agenda_culturel/models.py:166 agenda_culturel/models.py:764
|
||||
#: agenda_culturel/models.py:167 agenda_culturel/models.py:766
|
||||
msgid "Status"
|
||||
msgstr "Status"
|
||||
|
||||
#: agenda_culturel/models.py:168
|
||||
#: agenda_culturel/models.py:169
|
||||
msgid "Category of the event"
|
||||
msgstr "Catégorie de l'événement"
|
||||
|
||||
#: agenda_culturel/models.py:170
|
||||
#: agenda_culturel/models.py:171
|
||||
msgid "Day of the event"
|
||||
msgstr "Date de l'événement"
|
||||
|
||||
#: agenda_culturel/models.py:171
|
||||
#: agenda_culturel/models.py:172
|
||||
msgid "Starting time"
|
||||
msgstr "Heure de début"
|
||||
|
||||
#: agenda_culturel/models.py:173
|
||||
#: agenda_culturel/models.py:174
|
||||
msgid "End day of the event"
|
||||
msgstr "Fin de l'événement"
|
||||
|
||||
#: agenda_culturel/models.py:173
|
||||
#: agenda_culturel/models.py:174
|
||||
msgid "End day of the event, only required if different from the start day."
|
||||
msgstr ""
|
||||
"Date de fin de l'événement, uniquement nécessaire s'il est différent du "
|
||||
"premier jour de l'événement"
|
||||
|
||||
#: agenda_culturel/models.py:174
|
||||
#: agenda_culturel/models.py:175
|
||||
msgid "Final time"
|
||||
msgstr "Heure de fin"
|
||||
|
||||
#: agenda_culturel/models.py:176
|
||||
#: agenda_culturel/models.py:177
|
||||
msgid "Recurrence"
|
||||
msgstr "Récurrence"
|
||||
|
||||
#: agenda_culturel/models.py:178 agenda_culturel/models.py:740
|
||||
#: agenda_culturel/models.py:179 agenda_culturel/models.py:742
|
||||
msgid "Location"
|
||||
msgstr "Localisation"
|
||||
|
||||
#: agenda_culturel/models.py:178
|
||||
#: agenda_culturel/models.py:179
|
||||
msgid "Address of the event"
|
||||
msgstr "Adresse de l'événement"
|
||||
|
||||
#: agenda_culturel/models.py:180
|
||||
#: agenda_culturel/models.py:181
|
||||
msgid "Description"
|
||||
msgstr "Description"
|
||||
|
||||
#: agenda_culturel/models.py:180
|
||||
#: agenda_culturel/models.py:181
|
||||
msgid "General description of the event"
|
||||
msgstr "Description générale de l'événement"
|
||||
|
||||
#: agenda_culturel/models.py:182
|
||||
#: agenda_culturel/models.py:183
|
||||
msgid "Illustration (local image)"
|
||||
msgstr "Illustration (image locale)"
|
||||
|
||||
#: agenda_culturel/models.py:182
|
||||
#: agenda_culturel/models.py:183
|
||||
msgid "Illustration image stored in the agenda server"
|
||||
msgstr "Image d'illustration stockée sur le serveur de l'agenda"
|
||||
|
||||
#: agenda_culturel/models.py:184
|
||||
#: agenda_culturel/models.py:185
|
||||
msgid "Illustration"
|
||||
msgstr "Illustration"
|
||||
|
||||
#: agenda_culturel/models.py:184
|
||||
#: agenda_culturel/models.py:185
|
||||
msgid "URL of the illustration image"
|
||||
msgstr "URL de l'image illustrative"
|
||||
|
||||
#: agenda_culturel/models.py:185
|
||||
#: agenda_culturel/models.py:186
|
||||
msgid "Illustration description"
|
||||
msgstr "Description de l'illustration"
|
||||
|
||||
#: agenda_culturel/models.py:185
|
||||
#: agenda_culturel/models.py:186
|
||||
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:187
|
||||
#: agenda_culturel/models.py:188
|
||||
msgid "Importation source"
|
||||
msgstr "Source d'importation"
|
||||
|
||||
#: agenda_culturel/models.py:187
|
||||
#: agenda_culturel/models.py:188
|
||||
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:188
|
||||
#: agenda_culturel/models.py:189
|
||||
msgid "UUIDs"
|
||||
msgstr "UUIDs"
|
||||
|
||||
#: agenda_culturel/models.py:188
|
||||
#: agenda_culturel/models.py:189
|
||||
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:189
|
||||
#: agenda_culturel/models.py:190
|
||||
msgid "URLs"
|
||||
msgstr "URLs"
|
||||
|
||||
#: agenda_culturel/models.py:189
|
||||
#: agenda_culturel/models.py:190
|
||||
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:191
|
||||
#: agenda_culturel/models.py:192
|
||||
msgid "Tags"
|
||||
msgstr "Étiquettes"
|
||||
|
||||
#: agenda_culturel/models.py:191
|
||||
#: agenda_culturel/models.py:192
|
||||
msgid "A list of tags that describe the event."
|
||||
msgstr "Une liste d'étiquettes décrivant l'événement"
|
||||
|
||||
#: agenda_culturel/models.py:193
|
||||
#: agenda_culturel/models.py:194
|
||||
msgid "Possibly duplicated"
|
||||
msgstr "Possibles doublons"
|
||||
|
||||
#: agenda_culturel/models.py:234
|
||||
#: agenda_culturel/models.py:235
|
||||
msgid "Event"
|
||||
msgstr "Événement"
|
||||
|
||||
#: agenda_culturel/models.py:235
|
||||
#: agenda_culturel/models.py:236
|
||||
msgid "Events"
|
||||
msgstr "Événements"
|
||||
|
||||
#: agenda_culturel/models.py:701
|
||||
#: agenda_culturel/models.py:702
|
||||
msgid "Subject"
|
||||
msgstr "Sujet"
|
||||
|
||||
#: agenda_culturel/models.py:701
|
||||
#: agenda_culturel/models.py:702
|
||||
msgid "The subject of your message"
|
||||
msgstr "Sujet de votre message"
|
||||
|
||||
#: agenda_culturel/models.py:702
|
||||
#: agenda_culturel/models.py:703
|
||||
msgid "Your name"
|
||||
msgstr "Votre nom"
|
||||
|
||||
#: agenda_culturel/models.py:703
|
||||
#: agenda_culturel/models.py:704
|
||||
msgid "Email address"
|
||||
msgstr "Adresse email"
|
||||
|
||||
#: agenda_culturel/models.py:703
|
||||
#: agenda_culturel/models.py:704
|
||||
msgid "Your email address"
|
||||
msgstr "Votre adresse email"
|
||||
|
||||
#: agenda_culturel/models.py:704
|
||||
#: agenda_culturel/models.py:705
|
||||
msgid "Message"
|
||||
msgstr "Message"
|
||||
|
||||
#: agenda_culturel/models.py:704
|
||||
#: agenda_culturel/models.py:705
|
||||
msgid "Your message"
|
||||
msgstr "Votre message"
|
||||
|
||||
#: agenda_culturel/models.py:708 agenda_culturel/views.py:376
|
||||
#: agenda_culturel/models.py:709 agenda_culturel/views.py:376
|
||||
msgid "Closed"
|
||||
msgstr "Fermé"
|
||||
|
||||
#: agenda_culturel/models.py:708
|
||||
#: agenda_culturel/models.py:709
|
||||
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:709
|
||||
#: agenda_culturel/models.py:710
|
||||
msgid "Comments"
|
||||
msgstr "Commentaires"
|
||||
|
||||
#: agenda_culturel/models.py:709
|
||||
#: agenda_culturel/models.py:710
|
||||
msgid "Comments on the message from the moderation team"
|
||||
msgstr "Commentaires sur ce message par l'équipe de modération"
|
||||
|
||||
#: agenda_culturel/models.py:718
|
||||
#: agenda_culturel/models.py:719
|
||||
msgid "ical"
|
||||
msgstr "ical"
|
||||
|
||||
#: agenda_culturel/models.py:719
|
||||
#: agenda_culturel/models.py:720
|
||||
msgid "ical no busy"
|
||||
msgstr "ical sans busy"
|
||||
|
||||
#: agenda_culturel/models.py:722
|
||||
#: agenda_culturel/models.py:721
|
||||
msgid "ical no VC"
|
||||
msgstr "ical sans VC"
|
||||
|
||||
#: agenda_culturel/models.py:724
|
||||
msgid "simple"
|
||||
msgstr "simple"
|
||||
|
||||
#: agenda_culturel/models.py:723
|
||||
#: agenda_culturel/models.py:725
|
||||
msgid "Headless Chromium"
|
||||
msgstr "chromium sans interface"
|
||||
|
||||
#: agenda_culturel/models.py:727
|
||||
#: agenda_culturel/models.py:729
|
||||
msgid "daily"
|
||||
msgstr "chaque jour"
|
||||
|
||||
#: agenda_culturel/models.py:728
|
||||
#: agenda_culturel/models.py:730
|
||||
msgid "weekly"
|
||||
msgstr "chaque semaine"
|
||||
|
||||
#: agenda_culturel/models.py:730
|
||||
#: agenda_culturel/models.py:732
|
||||
msgid "Processor"
|
||||
msgstr "Processeur"
|
||||
|
||||
#: agenda_culturel/models.py:731
|
||||
#: agenda_culturel/models.py:733
|
||||
msgid "Downloader"
|
||||
msgstr "Téléchargeur"
|
||||
|
||||
#: agenda_culturel/models.py:733
|
||||
#: agenda_culturel/models.py:735
|
||||
msgid "Import recurrence"
|
||||
msgstr "Récurrence d'import"
|
||||
|
||||
#: agenda_culturel/models.py:736
|
||||
#: agenda_culturel/models.py:738
|
||||
msgid "Source"
|
||||
msgstr "Source"
|
||||
|
||||
#: agenda_culturel/models.py:736
|
||||
#: agenda_culturel/models.py:738
|
||||
msgid "URL of the source document"
|
||||
msgstr "URL du document source"
|
||||
|
||||
#: agenda_culturel/models.py:737
|
||||
#: agenda_culturel/models.py:739
|
||||
msgid "Browsable url"
|
||||
msgstr "URL navigable"
|
||||
|
||||
#: agenda_culturel/models.py:737
|
||||
#: agenda_culturel/models.py:739
|
||||
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:739
|
||||
#: agenda_culturel/models.py:741
|
||||
msgid "Status of each imported event (published or draft)"
|
||||
msgstr "Status de chaque événement importé (publié ou brouillon)"
|
||||
|
||||
#: agenda_culturel/models.py:740
|
||||
#: agenda_culturel/models.py:742
|
||||
msgid "Address for each imported event"
|
||||
msgstr "Adresse de chaque événement importé"
|
||||
|
||||
#: agenda_culturel/models.py:741
|
||||
#: agenda_culturel/models.py:743
|
||||
msgid "Category of each imported event"
|
||||
msgstr "Catégorie de chaque événement importé"
|
||||
|
||||
#: agenda_culturel/models.py:742
|
||||
#: agenda_culturel/models.py:744
|
||||
msgid "Tags for each imported event"
|
||||
msgstr "Étiquettes de chaque événement importé"
|
||||
|
||||
#: agenda_culturel/models.py:742
|
||||
#: agenda_culturel/models.py:744
|
||||
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:754
|
||||
#: agenda_culturel/models.py:756
|
||||
msgid "Running"
|
||||
msgstr "En cours"
|
||||
|
||||
#: agenda_culturel/models.py:755
|
||||
#: agenda_culturel/models.py:757
|
||||
msgid "Canceled"
|
||||
msgstr "Annulé"
|
||||
|
||||
#: agenda_culturel/models.py:756
|
||||
#: agenda_culturel/models.py:758
|
||||
msgid "Success"
|
||||
msgstr "Succès"
|
||||
|
||||
#: agenda_culturel/models.py:757
|
||||
#: agenda_culturel/models.py:759
|
||||
msgid "Failed"
|
||||
msgstr "Erreur"
|
||||
|
||||
#: agenda_culturel/models.py:762
|
||||
#: agenda_culturel/models.py:764
|
||||
msgid "Recurrent import"
|
||||
msgstr "Import récurrent"
|
||||
|
||||
#: agenda_culturel/models.py:762
|
||||
#: agenda_culturel/models.py:764
|
||||
msgid "Reference to the recurrent import processing"
|
||||
msgstr "Référence du processus d'import récurrent"
|
||||
|
||||
#: agenda_culturel/models.py:766
|
||||
#: agenda_culturel/models.py:768
|
||||
msgid "Error message"
|
||||
msgstr "Votre message"
|
||||
|
||||
#: agenda_culturel/models.py:768
|
||||
#: agenda_culturel/models.py:770
|
||||
msgid "Number of collected events"
|
||||
msgstr "Nombre d'événements collectés"
|
||||
|
||||
#: agenda_culturel/models.py:769
|
||||
#: agenda_culturel/models.py:771
|
||||
msgid "Number of imported events"
|
||||
msgstr "Nombre d'événements importés"
|
||||
|
||||
#: agenda_culturel/models.py:770
|
||||
#: agenda_culturel/models.py:772
|
||||
msgid "Number of updated events"
|
||||
msgstr "Nombre d'événements mis à jour"
|
||||
|
||||
#: agenda_culturel/models.py:771
|
||||
#: agenda_culturel/models.py:773
|
||||
msgid "Number of removed events"
|
||||
msgstr "Nombre d'événements supprimés"
|
||||
|
||||
#: agenda_culturel/models.py:780
|
||||
msgid "Weight"
|
||||
msgstr ""
|
||||
|
||||
#: agenda_culturel/models.py:780
|
||||
msgid "The lower is the weight, the earlier the filter is applied"
|
||||
msgstr ""
|
||||
|
||||
#: agenda_culturel/models.py:782
|
||||
msgid "Category applied to the event"
|
||||
msgstr "Catégorie appliquée à l'événement"
|
||||
|
||||
#: agenda_culturel/models.py:784
|
||||
msgid "Contained in the description"
|
||||
msgstr "Contenu dans la description"
|
||||
|
||||
#: agenda_culturel/models.py:784
|
||||
msgid "Text contained in the description"
|
||||
msgstr "Texte contenu dans la description"
|
||||
|
||||
#: agenda_culturel/models.py:785
|
||||
msgid "Exact description extract"
|
||||
msgstr "Extrait exact de description"
|
||||
|
||||
#: agenda_culturel/models.py:785
|
||||
msgid ""
|
||||
"If checked, the extract will be searched for in the description using the "
|
||||
"exact form (capitals, accents)."
|
||||
msgstr ""
|
||||
"Si coché, l'extrait sera recherché dans la description en utilisant la forme "
|
||||
"exacte (majuscules, accents)"
|
||||
|
||||
#: agenda_culturel/models.py:787
|
||||
msgid "Contained in the title"
|
||||
msgstr "Contenu dans le titre"
|
||||
|
||||
#: agenda_culturel/models.py:787
|
||||
msgid "Text contained in the event title"
|
||||
msgstr "Texte contenu dans le titre de l'événement"
|
||||
|
||||
#: agenda_culturel/models.py:788
|
||||
msgid "Exact title extract"
|
||||
msgstr "Extrait exact du titre"
|
||||
|
||||
#: agenda_culturel/models.py:788
|
||||
msgid ""
|
||||
"If checked, the extract will be searched for in the title using the exact "
|
||||
"form (capitals, accents)."
|
||||
msgstr ""
|
||||
"Si coché, l'extrait sera recherché dans le titre en utilisant la forme "
|
||||
"exacte (majuscules, accents)"
|
||||
|
||||
#: agenda_culturel/settings/base.py:135
|
||||
msgid "English"
|
||||
msgstr "anglais"
|
||||
@ -530,6 +586,31 @@ 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:814
|
||||
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:820
|
||||
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:831
|
||||
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:833
|
||||
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:835
|
||||
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é"
|
||||
|
||||
msgid "Add another"
|
||||
msgstr "Ajouter un autre"
|
||||
|
||||
|
26
src/agenda_culturel/migrations/0033_categorisationrule.py
Normal file
26
src/agenda_culturel/migrations/0033_categorisationrule.py
Normal file
@ -0,0 +1,26 @@
|
||||
# Generated by Django 4.2.7 on 2024-02-17 10:40
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agenda_culturel', '0032_alter_recurrentimport_defaultcategory_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CategorisationRule',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('weight', models.IntegerField(default=0, help_text='The lower is the weight, the earlier the filter is applied', verbose_name='Weight')),
|
||||
('description_contains', models.CharField(blank=True, help_text='Text contained in the description', max_length=512, null=True, verbose_name='Contained in the description')),
|
||||
('desc_exact', models.BooleanField(default=False, help_text='If checked, the extract will be searched for in the description using the exact form (capitals, accents).', verbose_name='Exact description extract')),
|
||||
('title_contains', models.CharField(blank=True, help_text='Text contained in the event title', max_length=512, null=True, verbose_name='Contained in the title')),
|
||||
('title_exact', models.BooleanField(default=False, help_text='If checked, the extract will be searched for in the title using the exact form (capitals, accents).', verbose_name='Exact title extract')),
|
||||
('category', models.ForeignKey(help_text='Category applied to the event', on_delete=django.db.models.deletion.CASCADE, to='agenda_culturel.category', verbose_name='Category')),
|
||||
],
|
||||
),
|
||||
]
|
@ -15,6 +15,7 @@ from django.db.models import Q
|
||||
import recurrence.fields
|
||||
import recurrence
|
||||
import copy
|
||||
import unicodedata
|
||||
|
||||
from django.template.defaultfilters import date as _date
|
||||
from datetime import time, timedelta, date
|
||||
@ -26,6 +27,9 @@ from .calendar import CalendarList, CalendarDay
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def remove_accents(input_str):
|
||||
nfkd_form = unicodedata.normalize('NFKD', input_str)
|
||||
return u"".join([c for c in nfkd_form if not unicodedata.combining(c)])
|
||||
|
||||
class StaticContent(models.Model):
|
||||
|
||||
@ -363,6 +367,10 @@ class Event(models.Model):
|
||||
if self.image and not self.local_image:
|
||||
self.download_image()
|
||||
|
||||
# try to detect category
|
||||
if self.is_in_importation_process():
|
||||
CategorisationRule.apply_rules(self)
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
@ -772,3 +780,46 @@ class BatchImportation(models.Model):
|
||||
nb_removed = models.PositiveIntegerField(verbose_name=_('Number of removed events'), default=0)
|
||||
|
||||
celery_id = models.CharField(max_length=128, default="")
|
||||
|
||||
|
||||
class CategorisationRule(models.Model):
|
||||
|
||||
weight = models.IntegerField(verbose_name=_('Weight'), help_text=_("The lower is the weight, the earlier the filter is applied"), default=0)
|
||||
|
||||
category = models.ForeignKey(Category, verbose_name=_('Category'), help_text=_('Category applied to the event'), on_delete=models.CASCADE)
|
||||
|
||||
description_contains = models.CharField(verbose_name=_('Contained in the description'), help_text=_('Text contained in the description'), max_length=512, blank=True, null=True)
|
||||
desc_exact = models.BooleanField(verbose_name=_('Exact description extract'), help_text=_("If checked, the extract will be searched for in the description using the exact form (capitals, accents)."), default=False)
|
||||
|
||||
title_contains = models.CharField(verbose_name=_('Contained in the title'), help_text=_('Text contained in the event title'), max_length=512, blank=True, null=True)
|
||||
title_exact = models.BooleanField(verbose_name=_('Exact title extract'), help_text=_("If checked, the extract will be searched for in the title using the exact form (capitals, accents)."), default=False)
|
||||
|
||||
# on applique toutes les règles, de la première à la dernière
|
||||
def apply_rules(event):
|
||||
rules = CategorisationRule.objects.all().order_by("weight", "pk")
|
||||
|
||||
for rule in rules:
|
||||
if rule.match(event):
|
||||
event.category = rule.category
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
def match(self, event):
|
||||
if self.description_contains:
|
||||
if self.desc_exact:
|
||||
if self.description_contains in event.description:
|
||||
return True
|
||||
else:
|
||||
if remove_accents(self.description_contains).lower() in remove_accents(event.description).lower():
|
||||
return True
|
||||
|
||||
if self.title_contains:
|
||||
if self.title_exact:
|
||||
if self.title_contains in event.title:
|
||||
return True
|
||||
else:
|
||||
if remove_accents(self.title_contains).lower() in remove_accents(event.title).lower():
|
||||
return True
|
||||
|
||||
return False
|
||||
|
@ -0,0 +1,84 @@
|
||||
{% extends "agenda_culturel/page.html" %}
|
||||
|
||||
{% block title %}Règles de catégorisation{% endblock %}
|
||||
|
||||
{% load utils_extra %}
|
||||
{% load cat_extra %}
|
||||
{% block entete_header %}
|
||||
{% css_categories %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="grid two-columns">
|
||||
<article>
|
||||
<header>
|
||||
<div class="slide-buttons">
|
||||
<a href="{% url 'apply_catrules'%}" role="button" data-tooltip="Appliquer à tous les événements sans catégorie">Appliquer {% picto_from_name "arrow-down-circle" %}</a>
|
||||
<a href="{% url 'add_catrule'%}" role="button">Ajouter {% picto_from_name "plus-circle" %}</a>
|
||||
</div>
|
||||
<h1>Règles de catégorisation</h1>
|
||||
<p>Chaque règle est considérée dans l'ordre croissant des poids. La première règle satisfaite est appliquée par un changement de catégorie, et on les suivantes ne sont pas appliquées.</p>
|
||||
<p>Une règle est satisfaite si au moins une des conditions est satisfaite.</p>
|
||||
<p>Les règles sont appliquées à l'import sur tous les événements, et à la demande sur les événements sans catégorie.</p>
|
||||
</header>
|
||||
|
||||
<table role="grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Identifiant</th>
|
||||
<th>Catégorie</th>
|
||||
<th>Conditions</th>
|
||||
<th>Poids</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for obj in paginator_filter %}
|
||||
<tr>
|
||||
<td>{{ obj.pk }}</a></td>
|
||||
<td>{{ obj.category }}</td>
|
||||
<td>
|
||||
<ul>
|
||||
{% if obj.title_contains %}
|
||||
<li>Le titre contient {% if obj.title_exact %}exactement {% endif %} « {{ obj.title_contains }} »</li>
|
||||
{% endif %}
|
||||
{% if obj.description_contains %}
|
||||
<li>La description contient {% if obj.desc_exact %}exactement {% endif %} « {{ obj.description_contains }} »</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</td>
|
||||
<td>{{ obj.weight }}</td>
|
||||
<td>
|
||||
<div class="buttons">
|
||||
<a href="{% url 'edit_catrule' obj.pk %}" role="button">Modifier</a>
|
||||
<a href="{% url 'delete_catrule' obj.pk %}" role="button">Supprimer</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<footer>
|
||||
<span>
|
||||
{% if paginator_filter.has_previous %}
|
||||
<a href="?page=1" role="button">« premier</a>
|
||||
<a href="?page={{ paginator_filter.previous_page_number }}" role="button">précédent</a>
|
||||
{% endif %}
|
||||
|
||||
<span>
|
||||
Page {{ paginator_filter.number }} sur {{ paginator_filter.paginator.num_pages }}
|
||||
</span>
|
||||
|
||||
{% if paginator_filter.has_next %}
|
||||
<a href="?page={{ paginator_filter.next_page_number }}" role="button">suivant</a>
|
||||
<a href="?page={{ paginator_filter.paginator.num_pages }}" role="button">dernier »</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
</footer>
|
||||
</article>
|
||||
|
||||
{% include "agenda_culturel/side-nav.html" with current="catrules" %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,30 @@
|
||||
{% extends "agenda_culturel/page.html" %}
|
||||
|
||||
{% block title %}Supprimer la règle de catégorisation {{ object.pk }}{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>Suppression de la règle de catégorisation {{ object.pk }}</h1>
|
||||
<form method="post">{% csrf_token %}
|
||||
<p>Êtes-vous sûr·e de vouloir supprimer la règle de catégorisation « {{ object.pk }} »
|
||||
par la catégorie {{ object.category }},
|
||||
de poids {{ object.weight }} et
|
||||
correspondant aux filtres ci-dessous ?</p>
|
||||
<ul>
|
||||
{% if object.title_contains %}
|
||||
<li>Le titre contient {% if object.title_exact %}exactement {% endif %} « {{ object.title_contains }} »</li>
|
||||
{% endif %}
|
||||
{% if object.description_contains %}
|
||||
<li>La description contient {% if object.desc_exact %}exactement {% endif %} « {{ object.description_contains }} »</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
{{ form }}
|
||||
<div class="grid">
|
||||
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{% url 'categorisation_rules' %}{% endif %}" role="button" class="secondary">Annuler</a>
|
||||
<input type="submit" value="Confirmer">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,27 @@
|
||||
{% extends "agenda_culturel/page.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Règle de catéorisation{% endblock %}
|
||||
|
||||
{% block entete_header %}
|
||||
<script src="/static/admin/js/vendor/jquery/jquery.js"></script>
|
||||
<script src="/static/admin/js/jquery.init.js"></script>
|
||||
<link href="{% static 'css/django_better_admin_arrayfield.min.css' %}" type="text/css" media="all" rel="stylesheet">
|
||||
<script src="{% static 'js/django_better_admin_arrayfield.min.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>Règle de catéorisation</h1>
|
||||
|
||||
<article>
|
||||
<form method="post">{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<div class="grid">
|
||||
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{% url 'categorisation_rules' %}{% endif %}" role="button" class="secondary">Annuler</a>
|
||||
<input type="submit" value="Envoyer">
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
||||
|
||||
{% endblock %}
|
@ -14,11 +14,12 @@
|
||||
<li><a {% if current == "tags" %}class="selected" {% endif %}href="{% url 'view_all_tags' %}">Consulter les étiquettes</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<h2>Importations</h2>
|
||||
<h3>Traitements automatiques</h3>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a {% if current == "imports" %}class="selected" {% endif %}href="{% url 'imports' %}">Historiques des importations</a></li>
|
||||
<li><a {% if current == "rimports" %}class="selected" {% endif %}href="{% url 'recurrent_imports' %}">Importations récurrentes</a></li>
|
||||
<li><a {% if current == "catrules" %}class="selected" {% endif %}href="{% url 'categorisation_rules' %}">Règles de catégorisation</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<h3>Messages</h3>
|
||||
|
@ -46,6 +46,11 @@ urlpatterns = [
|
||||
path("rimports/<int:pk>/edit", RecurrentImportUpdateView.as_view(), name="edit_rimport"),
|
||||
path("rimports/<int:pk>/delete", RecurrentImportDeleteView.as_view(), name="delete_rimport"),
|
||||
path("rimports/<int:pk>/run", run_rimport, name="run_rimport"),
|
||||
path("catrules/", categorisation_rules, name="categorisation_rules"),
|
||||
path("catrules/add", CategorisationRuleCreateView.as_view(), name="add_catrule"),
|
||||
path("catrules/<int:pk>/edit", CategorisationRuleUpdateView.as_view(), name="edit_catrule"),
|
||||
path("catrules/<int:pk>/delete", CategorisationRuleDeleteView.as_view(), name="delete_catrule"),
|
||||
path("catrules/apply", apply_categorisation_rules, name="apply_catrules"),
|
||||
path("duplicates/", duplicates, name="duplicates"),
|
||||
path("duplicates/<int:pk>", DuplicatedEventsDetailView.as_view(), name="view_duplicate"),
|
||||
path("duplicates/<int:pk>/fix", fix_duplicate, name="fix_duplicate"),
|
||||
|
@ -11,9 +11,9 @@ from django.http import HttpResponseRedirect
|
||||
from django.urls import reverse
|
||||
import urllib
|
||||
|
||||
from .forms import EventSubmissionForm, EventForm, BatchImportationForm, FixDuplicates, SelectEventInList, MergeDuplicates, RecurrentImportForm
|
||||
from .forms import EventSubmissionForm, EventForm, BatchImportationForm, FixDuplicates, SelectEventInList, MergeDuplicates, RecurrentImportForm, CategorisationRuleImportForm
|
||||
|
||||
from .models import Event, Category, StaticContent, ContactMessage, BatchImportation, DuplicatedEvents, RecurrentImport
|
||||
from .models import Event, Category, StaticContent, ContactMessage, BatchImportation, DuplicatedEvents, RecurrentImport, CategorisationRule, remove_accents
|
||||
from django.utils import timezone
|
||||
from enum import StrEnum
|
||||
from datetime import date, timedelta
|
||||
@ -181,9 +181,6 @@ def view_tag(request, t):
|
||||
|
||||
|
||||
def tag_list(request):
|
||||
def remove_accents(input_str):
|
||||
nfkd_form = unicodedata.normalize('NFKD', input_str)
|
||||
return u"".join([c for c in nfkd_form if not unicodedata.combining(c)])
|
||||
|
||||
tags = Event.get_all_tags()
|
||||
context = {"tags": sorted(tags, key=lambda x: remove_accents(x).lower())}
|
||||
@ -782,3 +779,62 @@ def set_duplicate(request, year, month, day, pk):
|
||||
return render(request, 'agenda_culturel/set_duplicate.html', context={'form': form, 'event': event})
|
||||
|
||||
|
||||
|
||||
#########################
|
||||
## categorisation rules
|
||||
#########################
|
||||
|
||||
@login_required(login_url="/accounts/login/")
|
||||
def categorisation_rules(request):
|
||||
paginator = Paginator(CategorisationRule.objects.all().order_by("weight", "pk"), 10)
|
||||
page = request.GET.get('page')
|
||||
|
||||
try:
|
||||
response = paginator.page(page)
|
||||
except PageNotAnInteger:
|
||||
response = paginator.page(1)
|
||||
except EmptyPage:
|
||||
response = paginator.page(paginator.num_pages)
|
||||
|
||||
return render(request, 'agenda_culturel/categorisation_rules.html', {'paginator_filter': response} )
|
||||
|
||||
class CategorisationRuleCreateView(LoginRequiredMixin, CreateView):
|
||||
model = CategorisationRule
|
||||
success_url = reverse_lazy('categorisation_rules')
|
||||
form_class = CategorisationRuleImportForm
|
||||
|
||||
|
||||
class CategorisationRuleUpdateView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
|
||||
model = CategorisationRule
|
||||
form_class = CategorisationRuleImportForm
|
||||
success_url = reverse_lazy('categorisation_rules')
|
||||
success_message = _('The categorisation rule has been successfully modified.')
|
||||
|
||||
|
||||
class CategorisationRuleDeleteView(SuccessMessageMixin, LoginRequiredMixin, DeleteView):
|
||||
model = CategorisationRule
|
||||
success_url = reverse_lazy('categorisation_rules')
|
||||
success_message = _('The categorisation rule has been successfully deleted.')
|
||||
|
||||
@login_required(login_url="/accounts/login/")
|
||||
def apply_categorisation_rules(request):
|
||||
|
||||
nb = 0
|
||||
for e in Event.objects.filter(category=Category.get_default_category_id()):
|
||||
success = CategorisationRule.apply_rules(e)
|
||||
if success:
|
||||
nb += 1
|
||||
e.save()
|
||||
|
||||
|
||||
if nb != 0:
|
||||
if nb == 1:
|
||||
messages.success(request, _("The rules were successfully applied and 1 event was categorised."))
|
||||
else:
|
||||
messages.success(request, _("The rules were successfully applied and {} events were categorised.").format(nb))
|
||||
else:
|
||||
messages.info(request, _("The rules were successfully applied and no events were categorised."))
|
||||
|
||||
|
||||
return HttpResponseRedirect(reverse_lazy("categorisation_rules"))
|
||||
|
Loading…
Reference in New Issue
Block a user