diff --git a/src/agenda_culturel/locale/fr/LC_MESSAGES/django.po b/src/agenda_culturel/locale/fr/LC_MESSAGES/django.po index 2c6ea30..3ff3e29 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: 2023-12-16 20:13+0000\n" +"POT-Creation-Date: 2023-12-23 08:38+0000\n" "PO-Revision-Date: 2023-10-29 14:16+0000\n" "Last-Translator: Jean-Marie Favreau \n" "Language-Team: Jean-Marie Favreau \n" @@ -17,262 +17,290 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: agenda_culturel/forms.py:36 -msgid "The start date cannot be earler than today." -msgstr "La date de début ne peut pas être avant aujourd'hui." - -#: agenda_culturel/forms.py:46 +#: agenda_culturel/forms.py:37 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:48 -msgid "The end date cannot be earler than today." -msgstr "La date de fin ne peut pas être avant aujourd'hui." - -#: agenda_culturel/forms.py:63 +#: agenda_culturel/forms.py:52 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/models.py:19 agenda_culturel/models.py:48 -#: agenda_culturel/models.py:183 +#: agenda_culturel/models.py:23 agenda_culturel/models.py:52 +#: agenda_culturel/models.py:211 msgid "Name" msgstr "Nom" -#: agenda_culturel/models.py:19 agenda_culturel/models.py:48 +#: agenda_culturel/models.py:23 agenda_culturel/models.py:52 msgid "Category name" msgstr "Nom de la catégorie" -#: agenda_culturel/models.py:20 +#: agenda_culturel/models.py:24 msgid "Content" msgstr "Contenu" -#: agenda_culturel/models.py:20 +#: agenda_culturel/models.py:24 msgid "Text as shown to the visitors" msgstr "Text tel que présenté aux visiteureuses" -#: agenda_culturel/models.py:21 +#: agenda_culturel/models.py:25 msgid "URL path" msgstr "" -#: agenda_culturel/models.py:21 +#: agenda_culturel/models.py:25 msgid "URL path where the content is included." msgstr "" -#: agenda_culturel/models.py:49 +#: agenda_culturel/models.py:53 msgid "Alternative Name" msgstr "Nom alternatif" -#: agenda_culturel/models.py:49 +#: agenda_culturel/models.py:53 msgid "Alternative name used with a time period" msgstr "Nom alternatif utilisé avec une période de temps" -#: agenda_culturel/models.py:50 +#: agenda_culturel/models.py:54 msgid "Short name" msgstr "Nom court" -#: agenda_culturel/models.py:50 +#: agenda_culturel/models.py:54 msgid "Short name of the category" msgstr "Nom court de la catégorie" -#: agenda_culturel/models.py:51 +#: agenda_culturel/models.py:55 msgid "Color" msgstr "Couleur" -#: agenda_culturel/models.py:51 +#: agenda_culturel/models.py:55 msgid "Color used as background for the category" msgstr "Couleur utilisée comme fond de la catégorie" -#: agenda_culturel/models.py:88 agenda_culturel/models.py:105 +#: agenda_culturel/models.py:92 agenda_culturel/models.py:109 msgid "Category" msgstr "Catégorie" -#: agenda_culturel/models.py:89 +#: agenda_culturel/models.py:93 msgid "Categories" msgstr "Catégories" -#: agenda_culturel/models.py:94 +#: agenda_culturel/models.py:98 msgid "Published" msgstr "Publié" -#: agenda_culturel/models.py:95 +#: agenda_culturel/models.py:99 msgid "Draft" msgstr "Brouillon" -#: agenda_culturel/models.py:96 +#: agenda_culturel/models.py:100 msgid "Trash" msgstr "Corbeille" -#: agenda_culturel/models.py:101 +#: agenda_culturel/models.py:105 msgid "Title" msgstr "Titre" -#: agenda_culturel/models.py:101 +#: agenda_culturel/models.py:105 msgid "Short title" msgstr "Titre court" -#: agenda_culturel/models.py:103 +#: agenda_culturel/models.py:107 agenda_culturel/models.py:238 msgid "Status" msgstr "Status" -#: agenda_culturel/models.py:105 +#: agenda_culturel/models.py:109 msgid "Category of the event" msgstr "Catégorie de l'événement" -#: agenda_culturel/models.py:107 +#: agenda_culturel/models.py:111 msgid "Day of the event" msgstr "Date de l'événement" -#: agenda_culturel/models.py:108 +#: agenda_culturel/models.py:112 msgid "Starting time" msgstr "Heure de début" -#: agenda_culturel/models.py:110 +#: agenda_culturel/models.py:114 msgid "End day of the event" msgstr "Fin de l'événement" -#: agenda_culturel/models.py:110 +#: agenda_culturel/models.py:114 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:111 +#: agenda_culturel/models.py:115 msgid "Final time" msgstr "Heure de fin" -#: agenda_culturel/models.py:113 +#: agenda_culturel/models.py:117 msgid "Location" msgstr "Localisation" -#: agenda_culturel/models.py:113 +#: agenda_culturel/models.py:117 msgid "Address of the event" msgstr "Adresse de l'événement" -#: agenda_culturel/models.py:115 +#: agenda_culturel/models.py:119 msgid "Description" msgstr "Description" -#: agenda_culturel/models.py:115 +#: agenda_culturel/models.py:119 msgid "General description of the event" msgstr "Description générale de l'événement" -#: agenda_culturel/models.py:117 +#: agenda_culturel/models.py:121 msgid "Illustration (local image)" msgstr "Illustration (image locale)" -#: agenda_culturel/models.py:117 +#: agenda_culturel/models.py:121 msgid "Illustration image stored in the agenda server" msgstr "Image d'illustration stockée sur le serveur de l'agenda" -#: agenda_culturel/models.py:119 +#: agenda_culturel/models.py:123 msgid "Illustration" msgstr "Illustration" -#: agenda_culturel/models.py:119 +#: agenda_culturel/models.py:123 msgid "URL of the illustration image" msgstr "URL de l'image illustrative" -#: agenda_culturel/models.py:120 +#: agenda_culturel/models.py:124 msgid "Illustration description" msgstr "Description de l'illustration" -#: agenda_culturel/models.py:120 +#: agenda_culturel/models.py:124 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:122 +#: agenda_culturel/models.py:126 msgid "URLs" msgstr "URLs" -#: agenda_culturel/models.py:122 +#: agenda_culturel/models.py:126 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:124 +#: agenda_culturel/models.py:128 msgid "Tags" msgstr "Étiquettes" -#: agenda_culturel/models.py:124 +#: agenda_culturel/models.py:128 msgid "A list of tags that describe the event." msgstr "Une liste d'étiquettes décrivant l'événement" -#: agenda_culturel/models.py:149 +#: agenda_culturel/models.py:158 msgid "Event" msgstr "Événement" -#: agenda_culturel/models.py:150 +#: agenda_culturel/models.py:159 msgid "Events" msgstr "Événements" -#: agenda_culturel/models.py:182 +#: agenda_culturel/models.py:210 msgid "Subject" msgstr "Sujet" -#: agenda_culturel/models.py:182 +#: agenda_culturel/models.py:210 msgid "The subject of your message" msgstr "Sujet de votre message" -#: agenda_culturel/models.py:183 +#: agenda_culturel/models.py:211 msgid "Your name" msgstr "Votre nom" -#: agenda_culturel/models.py:184 +#: agenda_culturel/models.py:212 msgid "Email address" msgstr "Adresse email" -#: agenda_culturel/models.py:184 +#: agenda_culturel/models.py:212 msgid "Your email address" msgstr "Votre adresse email" -#: agenda_culturel/models.py:185 +#: agenda_culturel/models.py:213 msgid "Message" msgstr "Message" -#: agenda_culturel/models.py:185 +#: agenda_culturel/models.py:213 msgid "Your message" msgstr "Votre message" -#: agenda_culturel/models.py:189 agenda_culturel/views.py:334 +#: agenda_culturel/models.py:217 agenda_culturel/views.py:341 msgid "Closed" msgstr "Fermé" -#: agenda_culturel/models.py:189 +#: agenda_culturel/models.py:217 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:190 +#: agenda_culturel/models.py:218 msgid "Comments" msgstr "Commentaires" -#: agenda_culturel/models.py:190 +#: agenda_culturel/models.py:218 msgid "Comments on the message from the moderation team" msgstr "Commentaires sur ce message par l'équipe de modération" -#: agenda_culturel/settings/base.py:128 +#: agenda_culturel/models.py:227 +msgid "Running" +msgstr "" + +#: agenda_culturel/models.py:228 +msgid "Canceled" +msgstr "Annulé" + +#: agenda_culturel/models.py:229 +msgid "Success" +msgstr "Succès" + +#: agenda_culturel/models.py:230 +msgid "Failed" +msgstr "Erreur" + +#: agenda_culturel/models.py:235 +msgid "Source" +msgstr "Source" + +#: agenda_culturel/models.py:235 +msgid "URL of the source document" +msgstr "URL du document source" + +#: agenda_culturel/models.py:236 +msgid "Browsable url" +msgstr "URL navigable" + +#: agenda_culturel/models.py:236 +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/settings/base.py:134 msgid "English" msgstr "anglais" -#: agenda_culturel/settings/base.py:129 +#: agenda_culturel/settings/base.py:135 msgid "French" msgstr "français" -#: agenda_culturel/views.py:182 +#: agenda_culturel/views.py:188 msgid "The static content has been successfully updated." msgstr "Le contenu statique a été modifié avec succès." -#: agenda_culturel/views.py:188 +#: agenda_culturel/views.py:194 msgid "The event has been successfully modified." msgstr "L'événement a été modifié avec succès." -#: agenda_culturel/views.py:199 +#: agenda_culturel/views.py:205 msgid "The event has been successfully deleted." msgstr "L'événement a été supprimé avec succès" -#: agenda_culturel/views.py:237 +#: agenda_culturel/views.py:222 +msgid "The status has been successfully modified." +msgstr "Le status a été modifié avec succès." + +#: agenda_culturel/views.py:244 msgid "The event is saved." msgstr "L'événement est enregistré." -#: agenda_culturel/views.py:240 +#: agenda_culturel/views.py:247 msgid "" "The event has been submitted and will be published as soon as it has been " "validated by the moderation team." @@ -280,7 +308,7 @@ msgstr "" "L'événement a été soumis et sera publié dès qu'il aura été validé par " "l'équipe de modération." -#: agenda_culturel/views.py:270 +#: agenda_culturel/views.py:277 msgid "" "The event has been successfully extracted, and you can now submit it after " "modifying it if necessary." @@ -288,7 +316,7 @@ msgstr "" "L'événement a été extrait avec succès, vous pouvez maintenant le soumettre " "après l'avoir modifié au besoin." -#: agenda_culturel/views.py:274 +#: agenda_culturel/views.py:281 msgid "" "Unable to extract an event from the proposed URL. Please use the form below " "to submit the event." @@ -296,12 +324,12 @@ msgstr "" "Impossible d'extraire un événement depuis l'URL proposée. Veuillez utiliser " "le formulaire ci-dessous pour soumettre l'événement." -#: agenda_culturel/views.py:283 +#: agenda_culturel/views.py:290 msgid "This URL has already been submitted, and you can find the event below." msgstr "" "Cette URL a déjà été soumise, et vous trouverez l'événement ci-dessous." -#: agenda_culturel/views.py:287 +#: agenda_culturel/views.py:294 msgid "" "This URL has already been submitted, but has not been selected for " "publication by the moderation team." @@ -309,26 +337,34 @@ msgstr "" "Cette URL a déjà été soumise, mais n'a pas été retenue par l'équipe de " "modération pour la publication." -#: agenda_culturel/views.py:289 +#: agenda_culturel/views.py:296 msgid "This URL has already been submitted and is awaiting moderation." msgstr "Cette URL a déjà été soumise, et est en attente de modération" -#: agenda_culturel/views.py:311 +#: agenda_culturel/views.py:318 msgid "Your message has been sent successfully." msgstr "L'événement a été supprimé avec succès" -#: agenda_culturel/views.py:319 +#: agenda_culturel/views.py:326 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:334 +#: agenda_culturel/views.py:341 msgid "Open" msgstr "Ouvert" -#: agenda_culturel/views.py:374 +#: agenda_culturel/views.py:381 msgid "Search" msgstr "Rechercher" +#: agenda_culturel/views.py:491 +msgid "The import has been run successfully." +msgstr "L'import a été lancé avec succès" + +#: agenda_culturel/views.py:507 +msgid "The import has been canceled." +msgstr "L'import a été annulé" + msgid "Add another" msgstr "Ajouter un autre" @@ -336,4 +372,4 @@ msgid "Browse..." msgstr "Naviguer..." msgid "No file selected." -msgstr "Pas de fichier sélectionné." \ No newline at end of file +msgstr "Pas de fichier sélectionné." diff --git a/src/agenda_culturel/migrations/0011_batchimportation.py b/src/agenda_culturel/migrations/0011_batchimportation.py new file mode 100644 index 0000000..22e16a4 --- /dev/null +++ b/src/agenda_culturel/migrations/0011_batchimportation.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.7 on 2023-12-23 08:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('agenda_culturel', '0010_alter_contactmessage_comments'), + ] + + operations = [ + migrations.CreateModel( + name='BatchImportation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_date', models.DateTimeField(auto_now_add=True)), + ('source', models.URLField(blank=True, help_text='URL of the source document', max_length=1024, null=True, verbose_name='Source')), + ('browsable_url', models.URLField(blank=True, help_text='URL of the corresponding document that will be shown to visitors.', max_length=1024, null=True, verbose_name='Browsable url')), + ('running', models.BooleanField(default=True, help_text='This batch importation is still running', verbose_name='Running')), + ('success', models.BooleanField(default=False, help_text='This batch importation successfully finished', verbose_name='Success')), + ], + ), + ] diff --git a/src/agenda_culturel/migrations/0012_remove_batchimportation_running_and_more.py b/src/agenda_culturel/migrations/0012_remove_batchimportation_running_and_more.py new file mode 100644 index 0000000..58cb89f --- /dev/null +++ b/src/agenda_culturel/migrations/0012_remove_batchimportation_running_and_more.py @@ -0,0 +1,26 @@ +# Generated by Django 4.2.7 on 2023-12-23 08:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('agenda_culturel', '0011_batchimportation'), + ] + + operations = [ + migrations.RemoveField( + model_name='batchimportation', + name='running', + ), + migrations.RemoveField( + model_name='batchimportation', + name='success', + ), + migrations.AddField( + model_name='batchimportation', + name='status', + field=models.CharField(choices=[('running', 'Running'), ('canceled', 'Canceled'), ('success', 'Success'), ('failed', 'Failed')], default='running', max_length=20, verbose_name='Status'), + ), + ] diff --git a/src/agenda_culturel/models.py b/src/agenda_culturel/models.py index 419739f..ee0c5cf 100644 --- a/src/agenda_culturel/models.py +++ b/src/agenda_culturel/models.py @@ -219,3 +219,20 @@ class ContactMessage(models.Model): def nb_open_contactmessages(): return ContactMessage.objects.filter(closed=False).count() + + +class BatchImportation(models.Model): + + class STATUS(models.TextChoices): + RUNNING = "running", _("Running") + CANCELED = "canceled", _("Canceled") + SUCCESS = "success", _("Success") + FAILED = "failed", _("Failed") + + + created_date = models.DateTimeField(auto_now_add=True) + + source = models.URLField(verbose_name=_('Source'), help_text=_("URL of the source document"), max_length=1024, blank=True, null=True) + browsable_url = models.URLField(verbose_name=_('Browsable url'), help_text=_("URL of the corresponding document that will be shown to visitors."), max_length=1024, blank=True, null=True) + + status = models.CharField(_("Status"), max_length=20, choices=STATUS.choices, default=STATUS.RUNNING) \ No newline at end of file diff --git a/src/agenda_culturel/templates/agenda_culturel/batchimportation_form.html b/src/agenda_culturel/templates/agenda_culturel/batchimportation_form.html new file mode 100644 index 0000000..5fb4fe1 --- /dev/null +++ b/src/agenda_culturel/templates/agenda_culturel/batchimportation_form.html @@ -0,0 +1,24 @@ +{% extends "agenda_culturel/page.html" %} +{% load static %} + +{% block title %}Importation par lot{% endblock %} + + +{% block content %} + +

Importation par lot

+ +
+
{% csrf_token %} + {{ form.as_p }} +

+ + + JSON au format attendu pour l'import. Si le JSON est fourni ici, on ne lancera pas une récupération depuis l'URL donnée en paramètre. +

+ + +
+
+ +{% endblock %} \ No newline at end of file diff --git a/src/agenda_culturel/templates/agenda_culturel/cancel_import_confirm.html b/src/agenda_culturel/templates/agenda_culturel/cancel_import_confirm.html new file mode 100644 index 0000000..99fea5d --- /dev/null +++ b/src/agenda_culturel/templates/agenda_culturel/cancel_import_confirm.html @@ -0,0 +1,25 @@ +{% extends "agenda_culturel/page.html" %} + +{% block title %}Supprimer {{ object.title }}{% endblock %} + + +{% block content %} + +
+
+

Annulation de l'import {{ object.id }} ({{ object.created_date }})

+
+
{% csrf_token %} +

Êtes-vous sûr·e de vouloir annuler l'importation « {{ object.id }} » ?

+ {{ form }} + + +
+
+ +{% endblock %} \ No newline at end of file diff --git a/src/agenda_culturel/templates/agenda_culturel/imports.html b/src/agenda_culturel/templates/agenda_culturel/imports.html new file mode 100644 index 0000000..6e39324 --- /dev/null +++ b/src/agenda_culturel/templates/agenda_culturel/imports.html @@ -0,0 +1,61 @@ +{% extends "agenda_culturel/page.html" %} + +{% block title %}Importations par lot{% endblock %} + +{% load utils_extra %} +{% load cat_extra %} +{% block entete_header %} + {% css_categories %} +{% endblock %} + +{% block content %} +
+
+
+ Nouvel import +

Importations par lot

+
+ + + + + + + + + + + + {% for obj in paginator_filter %} + + + + + + + {% endfor %} + +
IdentifiantDateStatusAction
{{ obj.id }}{{ obj.created_date }}{{ obj.status }}{% if obj.status == "running" %}Annuler{% endif %}
+
+ + {% if paginator_filter.has_previous %} + « premier + précédent + {% endif %} + + + Page {{ paginator_filter.number }} sur {{ paginator_filter.paginator.num_pages }} + + + {% if paginator_filter.has_next %} + suivant + dernier » + {% endif %} + +
+
+ +{% include "agenda_culturel/side-nav.html" with current="imports" %} +
+ +{% endblock %} \ No newline at end of file diff --git a/src/agenda_culturel/templates/agenda_culturel/side-nav.html b/src/agenda_culturel/templates/agenda_culturel/side-nav.html index 51068f9..b18ede6 100644 --- a/src/agenda_culturel/templates/agenda_culturel/side-nav.html +++ b/src/agenda_culturel/templates/agenda_culturel/side-nav.html @@ -10,6 +10,7 @@
  • Derniers événements soumis{% show_badges_events %}
  • Toutes les étiquettes
  • Messages de contact{% show_badge_contactmessages %}
  • +
  • Importations par lot{% show_badge_contactmessages %}
  • diff --git a/src/agenda_culturel/urls.py b/src/agenda_culturel/urls.py index 8d79276..afc25ef 100644 --- a/src/agenda_culturel/urls.py +++ b/src/agenda_culturel/urls.py @@ -34,6 +34,9 @@ urlpatterns = [ path('contact', ContactMessageCreateView.as_view(), name='contact'), path('contactmessages', contactmessages, name='contactmessages'), path('contactmessage/', ContactMessageUpdateView.as_view(), name='contactmessage'), + path("imports/", imports, name="imports"), + path("imports/add", BatchImportationCreateView.as_view(), name="add_import"), + path("imports//cancel", cancel_import, name="cancel_import"), ] if settings.DEBUG: diff --git a/src/agenda_culturel/views.py b/src/agenda_culturel/views.py index e99577e..62ee72a 100644 --- a/src/agenda_culturel/views.py +++ b/src/agenda_culturel/views.py @@ -13,7 +13,7 @@ import urllib from .forms import EventSubmissionForm, EventForm -from .models import Event, Category, StaticContent, ContactMessage +from .models import Event, Category, StaticContent, ContactMessage, BatchImportation from django.utils import timezone from enum import StrEnum from datetime import date, timedelta @@ -219,6 +219,7 @@ def change_status_event(request, pk, status): if request.method == 'POST': event.status = Event.STATUS(status) event.save(update_fields=["status"]) + messages.success(request, _("The status has been successfully modified.")) if request.user.is_authenticated: return HttpResponseRedirect(event.get_absolute_url()) @@ -460,3 +461,54 @@ def event_search(request, full=False): def event_search_full(request): return event_search(request, True) + + +######################### +## batch importations +######################### + +@login_required(login_url="/accounts/login/") +def imports(request): + paginator = Paginator(BatchImportation.objects.all().order_by("-created_date"), 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/imports.html', {'paginator_filter': response} ) + + + +class BatchImportationCreateView(SuccessMessageMixin, LoginRequiredMixin, CreateView): + model = BatchImportation + fields = ['source', 'browsable_url'] + + success_url = reverse_lazy('imports') + success_message = _('The import has been run successfully.') + + def form_valid(self, form): + # TODO run a celery script + + return super().form_valid(form) + + +@login_required(login_url="/accounts/login/") +def cancel_import(request, pk): + import_process = get_object_or_404(BatchImportation, pk=pk) + + if request.method == 'POST': + # TODO cancel the celery import + + import_process.status = BatchImportation.STATUS.CANCELED + import_process.save(update_fields=["status"]) + + messages.success(request, _("The import has been canceled.")) + return HttpResponseRedirect(reverse_lazy("imports")) + else: + cancel_url = reverse_lazy("imports") + return render(request, 'agenda_culturel/cancel_import_confirm.html', {"object": import_process, "cancel_url": cancel_url}) +