L'import des événements ne se fait plus côté Celery, mais directement en django, ce qui permet :
- aux internautes d'éditer le résultat de l'import avant de le soumettre. Fix #45 - de vérifier que l'url n'existe pas déjà. Fix #31 -
This commit is contained in:
parent
794bed6b74
commit
41d6448077
@ -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)
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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:
|
||||
|
@ -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']
|
||||
|
||||
|
@ -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."
|
||||
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é."
|
@ -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',
|
||||
),
|
||||
]
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ urlpatterns = [
|
||||
path("event/<int:pk>-<extra>", EventDetailView.as_view(), name="view_event"),
|
||||
path("event/<int:pk>/edit", EventUpdateView.as_view(), name="edit_event"),
|
||||
path("event/<int:pk>/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')),
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user