From 41d6448077950f560ec34c7954f058aafc976672 Mon Sep 17 00:00:00 2001 From: Jean-Marie Favreau Date: Sun, 26 Nov 2023 16:00:02 +0100 Subject: [PATCH] =?UTF-8?q?L'import=20des=20=C3=A9v=C3=A9nements=20ne=20se?= =?UTF-8?q?=20fait=20plus=20c=C3=B4t=C3=A9=20Celery,=20mais=20directement?= =?UTF-8?q?=20en=20django,=20ce=20qui=20permet=20:=20-=20aux=20internautes?= =?UTF-8?q?=20d'=C3=A9diter=20le=20r=C3=A9sultat=20de=20l'import=20avant?= =?UTF-8?q?=20de=20le=20soumettre.=20Fix=20#45=20-=20de=20v=C3=A9rifier=20?= =?UTF-8?q?que=20l'url=20n'existe=20pas=20d=C3=A9j=C3=A0.=20Fix=20#31=20-?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/agenda_culturel/admin.py | 3 +- src/agenda_culturel/celery.py | 19 ---- src/agenda_culturel/extractors.py | 33 ++++--- src/agenda_culturel/forms.py | 15 ++- .../locale/fr/LC_MESSAGES/django.po | 20 +++- .../0005_delete_eventsubmissionform.py | 16 ++++ src/agenda_culturel/models.py | 12 --- src/agenda_culturel/static/style.scss | 11 ++- src/agenda_culturel/urls.py | 2 +- src/agenda_culturel/views.py | 92 +++++++++++++------ 10 files changed, 134 insertions(+), 89 deletions(-) create mode 100644 src/agenda_culturel/migrations/0005_delete_eventsubmissionform.py diff --git a/src/agenda_culturel/admin.py b/src/agenda_culturel/admin.py index c3a537b..810ac8f 100644 --- a/src/agenda_culturel/admin.py +++ b/src/agenda_culturel/admin.py @@ -1,12 +1,11 @@ from django.contrib import admin from django import forms -from .models import Event, EventSubmissionForm, Category, StaticContent +from .models import Event, Category, StaticContent from django_better_admin_arrayfield.admin.mixins import DynamicArrayMixin from django_better_admin_arrayfield.forms.widgets import DynamicArrayWidget from django_better_admin_arrayfield.models.fields import DynamicArrayField -admin.site.register(EventSubmissionForm) admin.site.register(Category) admin.site.register(StaticContent) diff --git a/src/agenda_culturel/celery.py b/src/agenda_culturel/celery.py index 8a4d90a..fd98ffe 100644 --- a/src/agenda_culturel/celery.py +++ b/src/agenda_culturel/celery.py @@ -26,25 +26,6 @@ app.config_from_object("django.conf:settings", namespace="CELERY") app.autodiscover_tasks() -@app.task(bind=True) -def create_event_from_submission(self, url): - from agenda_culturel.models import Event - - logger.info(f"{url=}") - - if len(Event.objects.filter(reference_urls__contains=[url])) != 0: - logger.info("Already known url: %s", url) - else: - try: - logger.info("About to create event from submission") - events = ExtractorAllURLs.extract(url) - - if events != None: - for e in events: - e.save() - - except Exception as e: - logger.error(e) app.conf.timezone = "Europe/Paris" diff --git a/src/agenda_culturel/extractors.py b/src/agenda_culturel/extractors.py index b4438a5..1b58cab 100644 --- a/src/agenda_culturel/extractors.py +++ b/src/agenda_culturel/extractors.py @@ -15,13 +15,10 @@ import os from bs4 import BeautifulSoup import json -from datetime import datetime +from datetime import datetime, date - - -from celery.utils.log import get_task_logger - -logger = get_task_logger(__name__) +import logging +logger = logging.getLogger(__name__) class Extractor: @@ -128,9 +125,13 @@ class ExtractorFacebook(Extractor): return self.elements[key] if key in self.elements else None - def get_element_datetime(self, key): + def get_element_date(self, key): v = self.get_element(key) - return datetime.fromtimestamp(v) if v is not None else None + return datetime.fromtimestamp(v).date() if v is not None and v != 0 else None + + def get_element_time(self, key): + v = self.get_element(key) + return datetime.fromtimestamp(v).strftime('%H:%M') if v is not None and v != 0 else None def add_fragment(self, i, event): self.fragments[i] = event @@ -171,7 +172,7 @@ class ExtractorFacebook(Extractor): if "end_timestamp" not in self.elements and len(self.possible_end_timestamp) != 0: for s in self.possible_end_timestamp: - if s["start_timestamp"] == self.elements["start_timestamp"]: + if "start_timestamp" in s and "start_timestamp" in self.elements and s["start_timestamp"] == self.elements["start_timestamp"]: self.elements["end_timestamp"] = s["end_timestamp"] break @@ -207,13 +208,12 @@ class ExtractorFacebook(Extractor): local_image = None if image is None else Extractor.download_media(image) - return Event(title=self.get_element("name"), status=Event.STATUS.DRAFT, - start_day=self.get_element_datetime("start_timestamp"), - start_time=self.get_element_datetime("start_timestamp"), - end_day=self.get_element_datetime("end_timestamp"), - end_time=self.get_element_datetime("end_timestamp"), + start_day=self.get_element_date("start_timestamp"), + start_time=self.get_element_time("start_timestamp"), + end_day=self.get_element_date("end_timestamp"), + end_time=self.get_element_time("end_timestamp"), location=self.get_element("event_place_name"), description=self.get_element("description"), local_image=local_image, @@ -226,7 +226,7 @@ class ExtractorFacebook(Extractor): if ExtractorFacebook.is_known_url(url): u = urlparse(url) - return u.scheme + "://" + u.netloc + u.path + return "https://www.facebook.com" + u.path else: return url @@ -247,7 +247,7 @@ class ExtractorFacebook(Extractor): if fevent is not None: logger.info("Facebook event: " + str(fevent)) result = fevent.build_event(url) - return [result] + return result return None @@ -272,7 +272,6 @@ class ExtractorAllURLs: for e in ExtractorAllURLs.extractors: result = e.process_page(txt, url) - if result is not None: return result else: diff --git a/src/agenda_culturel/forms.py b/src/agenda_culturel/forms.py index 7259ff7..8a2f0da 100644 --- a/src/agenda_culturel/forms.py +++ b/src/agenda_culturel/forms.py @@ -1,15 +1,12 @@ -from django.forms import ModelForm, ValidationError, TextInput -from django.views.generic import FormView +from django.forms import ModelForm, ValidationError, TextInput, Form, URLField from datetime import date -from .models import EventSubmissionForm, Event +from .models import Event from django.utils.translation import gettext_lazy as _ -class EventSubmissionModelForm(ModelForm): - class Meta: - model = EventSubmissionForm - fields = ["url"] +class EventSubmissionForm(Form): + url = URLField(max_length=512) class EventForm(ModelForm): @@ -25,9 +22,9 @@ class EventForm(ModelForm): } - def __init__(self, instance, *args, **kwargs): + def __init__(self, *args, **kwargs): is_authenticated = kwargs.pop('is_authenticated', False) - super().__init__(instance=instance, *args, **kwargs) + super().__init__(*args, **kwargs) if not is_authenticated: del self.fields['status'] diff --git a/src/agenda_culturel/locale/fr/LC_MESSAGES/django.po b/src/agenda_culturel/locale/fr/LC_MESSAGES/django.po index 39ed308..6441a0d 100644 --- a/src/agenda_culturel/locale/fr/LC_MESSAGES/django.po +++ b/src/agenda_culturel/locale/fr/LC_MESSAGES/django.po @@ -253,4 +253,22 @@ msgid "The event has been submitted and will be published as soon as it has been msgstr "L'événement a été soumis et sera publié dès qu'il aura été validé par l'équipe de modération." msgid "The URL has been taken into account, and the associated event will be available in a few moments for validation." -msgstr "L'URL a été prise en compte, et l'événement associé sera disponible dans quelques instants pour validation." \ No newline at end of file +msgstr "L'URL a été prise en compte, et l'événement associé sera disponible dans quelques instants pour validation." + +msgid "The event has been successfully extracted, and you can now submit it after modifying it if necessary." +msgstr "L'événement a été extrait avec succès, vous pouvez maintenant le soumettre après l'avoir modifié au besoin." + +msgid "Unable to extract an event from the proposed URL. Please use the form below to submit the event." +msgstr "Impossible d'extraire un événement depuis l'URL proposée. Veuillez utiliser le formulaire ci-dessous pour soumettre l'événement." + +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." + +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" + +msgid "This URL has already been submitted, but has not been selected for publication by the moderation team." +msgstr "Cette URL a déjà été soumise, mais n'a pas été retenue par l'équipe de modération pour la publication." + +msgid "The event is saved." +msgstr "L'événement est enregistré." \ No newline at end of file diff --git a/src/agenda_culturel/migrations/0005_delete_eventsubmissionform.py b/src/agenda_culturel/migrations/0005_delete_eventsubmissionform.py new file mode 100644 index 0000000..fbd3077 --- /dev/null +++ b/src/agenda_culturel/migrations/0005_delete_eventsubmissionform.py @@ -0,0 +1,16 @@ +# Generated by Django 4.2.7 on 2023-11-26 12:16 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('agenda_culturel', '0004_alter_event_category'), + ] + + operations = [ + migrations.DeleteModel( + name='EventSubmissionForm', + ), + ] diff --git a/src/agenda_culturel/models.py b/src/agenda_culturel/models.py index f28a932..444a4fc 100644 --- a/src/agenda_culturel/models.py +++ b/src/agenda_culturel/models.py @@ -172,15 +172,3 @@ class Event(models.Model): def modified(self): return abs((self.modified_date - self.created_date).total_seconds()) > 1 - - -class EventSubmissionForm(models.Model): - url = models.URLField(max_length=512, verbose_name=_('URL'), help_text=_("URL where this event can be found.")) - - class Meta: - db_table = "eventsubmissionform" - verbose_name = _("Event submission form") - verbose_name_plural = _("Event submissions forms") - - def __str__(self): - return self.url diff --git a/src/agenda_culturel/static/style.scss b/src/agenda_culturel/static/style.scss index 14157fb..02b2da4 100644 --- a/src/agenda_culturel/static/style.scss +++ b/src/agenda_culturel/static/style.scss @@ -348,7 +348,8 @@ $green-50: #e8f5e9 !default; $green-800: #1b5e20 !default; $red-50: #ffebee !default; $red-900: #b71c1c !default; - +$yellow-50: #ecf4a4 !default; +$yellow-900: #616918 !default; // simple picocss alerts // inherit responsive typography, responsive spacing, icons and size @@ -385,14 +386,20 @@ $red-900: #b71c1c !default; --icon: var(--icon-valid); --color: #{$green-800}; } +.message.info { + --background-color: #{$yellow-50}; + --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='#{rgba(darken($yellow-900, 15%), .999)}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E"); + --color: #{$yellow-900}; +} + .footer { opacity: 0.7; font-size: 80%; } .errorlist { - @extend .message.danger; + @extend .message, .danger; margin-left: 0; padding-left: 3.7em; } diff --git a/src/agenda_culturel/urls.py b/src/agenda_culturel/urls.py index 311fe2d..4af3082 100644 --- a/src/agenda_culturel/urls.py +++ b/src/agenda_culturel/urls.py @@ -21,7 +21,7 @@ urlpatterns = [ path("event/-", EventDetailView.as_view(), name="view_event"), path("event//edit", EventUpdateView.as_view(), name="edit_event"), path("event//delete", EventDeleteView.as_view(), name="delete_event"), - path("importer", EventSubmissionFormView.as_view(), name="event_import_form"), + path("importer", import_from_url, name="event_import_form"), path("ajouter", EventCreateView.as_view(), name="add_event"), path("admin/", admin.site.urls), path('accounts/', include('django.contrib.auth.urls')), diff --git a/src/agenda_culturel/views.py b/src/agenda_culturel/views.py index 2b57a93..ca1f367 100644 --- a/src/agenda_culturel/views.py +++ b/src/agenda_culturel/views.py @@ -7,9 +7,11 @@ from django import forms from django.contrib.postgres.search import SearchQuery, SearchHeadline from django.core.exceptions import PermissionDenied +from django.http import HttpResponseRedirect +from django.urls import reverse +import urllib -from .forms import EventSubmissionModelForm, EventForm -from .celery import create_event_from_submission +from .forms import EventSubmissionForm, EventForm from .models import Event, Category, StaticContent from django.utils import timezone @@ -28,6 +30,7 @@ from django.contrib import messages from django.contrib.messages.views import SuccessMessageMixin from .calendar import CalendarMonth, CalendarWeek +from .extractors import ExtractorAllURLs import unicodedata @@ -213,6 +216,11 @@ class EventUpdateView(SuccessMessageMixin, LoginRequiredMixin, UpdateView): form_class = EventForm success_message = _('The event has been successfully modified.') + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs['is_authenticated'] = self.request.user.is_authenticated + return kwargs + class EventDeleteView(SuccessMessageMixin, LoginRequiredMixin, DeleteView): model = Event @@ -224,40 +232,72 @@ class EventDetailView(UserPassesTestMixin, DetailView): model = Event template_name = "agenda_culturel/page-event.html" - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context["now"] = timezone.now() - return context - def test_func(self): return self.request.user.is_authenticated or self.get_object().status != Event.STATUS.PUBLISHED -class EventSubmissionFormView(FormView): - form_class = EventSubmissionModelForm - template_name = "agenda_culturel/import.html" +def import_from_url(request): - def form_valid(self, form): - form.save() - self.create_event(form.cleaned_data) + import logging + logger = logging.getLogger(__name__) - return super().form_valid(form) - - def create_event(self, valid_data): - url = valid_data["url"] - if self.request.user.is_authenticated: - messages.success(self.request, _("The URL has been taken into account, and the associated event will be available in a few moments for validation.")) + if "title" in request.POST and request.method == 'POST': + logger.error("on passe l") + form = EventForm(request.POST) + logger.error("on passe i") + if form.is_valid(): + logger.error("valide") + new_event = form.save() + if request.user.is_authenticated: + messages.success(request, _("The event is saved.")) + return HttpResponseRedirect(new_event.get_absolute_url()) + else: + messages.success(request, _("The event has been submitted and will be published as soon as it has been validated by the moderation team.")) + return HttpResponseRedirect(reverse("home")) else: - messages.success(self.request, _("The URL has been submitted and the associated event will be integrated in the agenda after validation.")) - create_event_from_submission.delay(url) + return render(request, 'agenda_culturel/event_form.html', context={'form': form }) + else: + form = EventSubmissionForm() - def get_success_url(self, **kwargs): - if self.request.user.is_authenticated: - return reverse_lazy("view_all_events") - else: - return reverse_lazy("home") + if request.method == 'POST': + form = EventSubmissionForm(request.POST) + if form.is_valid(): + cd = form.cleaned_data + url = cd.get('url') + + url = ExtractorAllURLs.clean_url(url) + + existing = Event.objects.filter(reference_urls__contains=[url]) + + if len(existing) == 0: + event = ExtractorAllURLs.extract(url) + if event != None: + form = EventForm(instance=event) + messages.success(request, _("The event has been successfully extracted, and you can now submit it after modifying it if necessary.")) + return render(request, 'agenda_culturel/event_form.html', context={'form': form }) + else: + form = EventForm(initial={'url': [url]}) + messages.error(request, _("Unable to extract an event from the proposed URL. Please use the form below to submit the event.")) + return render(request, 'agenda_culturel/importer.html', context={'form': form }) + else: + published = [e for e in existing if e.status == Event.STATUS.PUBLISHED] + drafts = [e for e in existing if e.status == Event.STATUS.DRAFT] + trash = [e for e in existing if e.status == Event.STATUS.TRASH] + + if request.user.is_authenticated or len(published) > 1: + event = published[0] if len(published) > 1 else existing[0] + messages.info(request, _("This URL has already been submitted, and you can find the event below.")) + return HttpResponseRedirect(event.get_absolute_url()) + else: + if len(drafts) > 0: + messages.info(request, _("This URL has already been submitted, but has not been selected for publication by the moderation team.")) + elif len(trash) > 0: + messages.info(request, _("This URL has already been submitted and is awaiting moderation.")) + + + return render(request, 'agenda_culturel/import.html', context={'form': form }) class EventFilterAdmin(django_filters.FilterSet): status = django_filters.MultipleChoiceFilter(choices=Event.STATUS.choices, widget=forms.CheckboxSelectMultiple)