Première version fonctionnelle de l'import d'événements
This commit is contained in:
parent
07729353ae
commit
bec3fef0bf
@ -12,6 +12,7 @@ from selenium.webdriver.chrome.options import Options
|
|||||||
import icalendar
|
import icalendar
|
||||||
from datetime import datetime, date
|
from datetime import datetime, date
|
||||||
import json
|
import json
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -76,7 +77,7 @@ class Extractor(ABC):
|
|||||||
def clear_events(self):
|
def clear_events(self):
|
||||||
self.events = []
|
self.events = []
|
||||||
|
|
||||||
def add_event(self, title, category, start_day, location, description, tags, url=None, url_human=None, start_time=None, end_day=None, end_time=None, last_modified=None, published=False):
|
def add_event(self, title, category, start_day, location, description, tags, uuid, url_human=None, start_time=None, end_day=None, end_time=None, last_modified=None, published=False):
|
||||||
if title is None:
|
if title is None:
|
||||||
print("ERROR: cannot import an event without name")
|
print("ERROR: cannot import an event without name")
|
||||||
return
|
return
|
||||||
@ -88,13 +89,12 @@ class Extractor(ABC):
|
|||||||
"title": title,
|
"title": title,
|
||||||
"category": category,
|
"category": category,
|
||||||
"start_day": start_day,
|
"start_day": start_day,
|
||||||
|
"uuid": uuid,
|
||||||
"location": location,
|
"location": location,
|
||||||
"descritpion": description,
|
"description": description,
|
||||||
"tags": tags,
|
"tags": tags,
|
||||||
"published": published
|
"published": published
|
||||||
}
|
}
|
||||||
if url is not None:
|
|
||||||
event["url"] = url
|
|
||||||
if url_human is not None:
|
if url_human is not None:
|
||||||
event["url_human"] = url_human
|
event["url_human"] = url_human
|
||||||
if start_time is not None:
|
if start_time is not None:
|
||||||
@ -167,6 +167,12 @@ class ICALExtractor(Extractor):
|
|||||||
location = self.default_value_if_exists(default_values, "location")
|
location = self.default_value_if_exists(default_values, "location")
|
||||||
|
|
||||||
description = self.get_item_from_vevent(event, "DESCRIPTION")
|
description = self.get_item_from_vevent(event, "DESCRIPTION")
|
||||||
|
if description is not None:
|
||||||
|
soup = BeautifulSoup(description)
|
||||||
|
delimiter = '\n'
|
||||||
|
for line_break in soup.findAll('br'):
|
||||||
|
line_break.replaceWith(delimiter)
|
||||||
|
description = soup.get_text()
|
||||||
|
|
||||||
last_modified = self.get_item_from_vevent(event, "LAST_MODIFIED")
|
last_modified = self.get_item_from_vevent(event, "LAST_MODIFIED")
|
||||||
|
|
||||||
@ -183,7 +189,7 @@ class ICALExtractor(Extractor):
|
|||||||
if rrule is not None:
|
if rrule is not None:
|
||||||
print("Recurrent event not yet supported", rrule)
|
print("Recurrent event not yet supported", rrule)
|
||||||
|
|
||||||
self.add_event(title, category, start_day, location, description, tags, url=event_url, url_human=url_human, start_time=start_time, end_day=end_day, end_time=end_time, last_modified=last_modified, published=published)
|
self.add_event(title, category, start_day, location, description, tags, uuid=event_url, url_human=url_human, start_time=start_time, end_day=end_day, end_time=end_time, last_modified=last_modified, published=published)
|
||||||
|
|
||||||
return self.get_structure()
|
return self.get_structure()
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django import forms
|
from django import forms
|
||||||
from .models import Event, Category, StaticContent
|
from .models import Event, Category, StaticContent, DuplicatedEvents, BatchImportation
|
||||||
from django_better_admin_arrayfield.admin.mixins import DynamicArrayMixin
|
from django_better_admin_arrayfield.admin.mixins import DynamicArrayMixin
|
||||||
from django_better_admin_arrayfield.forms.widgets import DynamicArrayWidget
|
from django_better_admin_arrayfield.forms.widgets import DynamicArrayWidget
|
||||||
from django_better_admin_arrayfield.models.fields import DynamicArrayField
|
from django_better_admin_arrayfield.models.fields import DynamicArrayField
|
||||||
@ -8,6 +8,8 @@ from django_better_admin_arrayfield.models.fields import DynamicArrayField
|
|||||||
|
|
||||||
admin.site.register(Category)
|
admin.site.register(Category)
|
||||||
admin.site.register(StaticContent)
|
admin.site.register(StaticContent)
|
||||||
|
admin.site.register(DuplicatedEvents)
|
||||||
|
admin.site.register(BatchImportation)
|
||||||
|
|
||||||
|
|
||||||
class URLWidget(DynamicArrayWidget):
|
class URLWidget(DynamicArrayWidget):
|
||||||
|
@ -25,12 +25,17 @@ app.config_from_object("django.conf:settings", namespace="CELERY")
|
|||||||
# Load task modules from all registered Django apps.
|
# Load task modules from all registered Django apps.
|
||||||
app.autodiscover_tasks()
|
app.autodiscover_tasks()
|
||||||
|
|
||||||
def close_import_task(taskid, success, error_message):
|
def close_import_task(taskid, success, error_message, importer):
|
||||||
from agenda_culturel.models import BatchImportation
|
from agenda_culturel.models import BatchImportation
|
||||||
|
|
||||||
task = BatchImportation.objects.get(celery_id=taskid)
|
task = BatchImportation.objects.get(celery_id=taskid)
|
||||||
task.status = BatchImportation.STATUS.SUCCESS if success else BatchImportation.STATUS.FAILED
|
task.status = BatchImportation.STATUS.SUCCESS if success else BatchImportation.STATUS.FAILED
|
||||||
fields = ["status"]
|
task.nb_initial = importer.get_nb_events()
|
||||||
|
task.nb_imported = importer.get_nb_imported_events()
|
||||||
|
task.nb_updated = importer.get_nb_updated_events()
|
||||||
|
task.nb_removed = importer.get_nb_removed_events()
|
||||||
|
|
||||||
|
fields = ["status", "nb_initial", "nb_updated", "nb_imported", "nb_removed"]
|
||||||
if not success:
|
if not success:
|
||||||
task.error_message = error_message
|
task.error_message = error_message
|
||||||
fields.append("error_message")
|
fields.append("error_message")
|
||||||
@ -46,10 +51,14 @@ def import_events_from_json(self, json):
|
|||||||
|
|
||||||
importer = EventsImporter(self.request.id)
|
importer = EventsImporter(self.request.id)
|
||||||
|
|
||||||
|
try:
|
||||||
success, error_message = importer.import_events(json)
|
success, error_message = importer.import_events(json)
|
||||||
|
|
||||||
# finally, close task
|
# finally, close task
|
||||||
close_import_task(self.request.id, success, error_message)
|
close_import_task(self.request.id, success, error_message, importer)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(e)
|
||||||
|
close_import_task(self.request.id, False, e, importer)
|
||||||
|
|
||||||
|
|
||||||
@app.task(bind=True)
|
@app.task(bind=True)
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
from django.forms import ModelForm, ValidationError, TextInput, Form, URLField, MultipleHiddenInput
|
from django.forms import ModelForm, ValidationError, TextInput, Form, URLField, MultipleHiddenInput, Textarea, CharField
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
|
|
||||||
from .models import Event
|
from .models import Event, BatchImportation
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
class EventSubmissionForm(Form):
|
class EventSubmissionForm(Form):
|
||||||
@ -13,7 +13,7 @@ class EventForm(ModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Event
|
model = Event
|
||||||
fields = '__all__'
|
exclude = ["possibly_duplicated"]
|
||||||
widgets = {
|
widgets = {
|
||||||
'start_day': TextInput(attrs={'type': 'date', 'onchange': 'update_datetimes(event);', "onfocus": "this.oldvalue = this.value;"}),
|
'start_day': TextInput(attrs={'type': 'date', 'onchange': 'update_datetimes(event);', "onfocus": "this.oldvalue = this.value;"}),
|
||||||
'start_time': TextInput(attrs={'type': 'time', 'onchange': 'update_datetimes(event);', "onfocus": "this.oldvalue = this.value;"}),
|
'start_time': TextInput(attrs={'type': 'time', 'onchange': 'update_datetimes(event);', "onfocus": "this.oldvalue = this.value;"}),
|
||||||
@ -54,3 +54,25 @@ class EventForm(ModelForm):
|
|||||||
|
|
||||||
return end_time
|
return end_time
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class BatchImportationForm(ModelForm):
|
||||||
|
|
||||||
|
json = CharField(label="JSON (facultatif)", widget=Textarea(attrs={"rows":"10"}), help_text=_("JSON in the format expected for the import. If the JSON is provided here, we will ignore the URLs given above, and use the information provided by the json without importing any additional events from the URL."), required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = BatchImportation
|
||||||
|
fields = ['source', 'browsable_url']
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super().clean()
|
||||||
|
json = cleaned_data.get("json")
|
||||||
|
source = cleaned_data.get("source")
|
||||||
|
browsable_url = cleaned_data.get("browsable_url")
|
||||||
|
|
||||||
|
if (not json or json == "") and (not source or source == "") and (not browsable_url or browsable_url == ""):
|
||||||
|
raise ValidationError(_("You need to fill in either the json or the source possibly supplemented by the navigable URL."))
|
||||||
|
|
||||||
|
# Always return a value to use as the new cleaned data, even if
|
||||||
|
# this method didn't change it.
|
||||||
|
return cleaned_data
|
||||||
|
@ -1,14 +1,37 @@
|
|||||||
from agenda_culturel.models import Event
|
from agenda_culturel.models import Event
|
||||||
import json
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
class EventsImporter:
|
class EventsImporter:
|
||||||
|
|
||||||
def __init__(self, celery_id):
|
def __init__(self, celery_id):
|
||||||
self.celery_id = celery_id
|
self.celery_id = celery_id
|
||||||
|
self.error_message = ""
|
||||||
|
self.init_result_properties()
|
||||||
|
|
||||||
|
def init_result_properties(self):
|
||||||
|
self.event_objects = []
|
||||||
|
self.db_event_objects = []
|
||||||
|
self.nb_updated = 0
|
||||||
|
self.nb_removed = 0
|
||||||
|
self.date = None
|
||||||
|
self.url = None
|
||||||
|
|
||||||
|
def get_nb_events(self):
|
||||||
|
return len(self.event_objects)
|
||||||
|
|
||||||
|
def get_nb_imported_events(self):
|
||||||
|
return len(self.db_event_objects)
|
||||||
|
|
||||||
|
def get_nb_updated_events(self):
|
||||||
|
return self.nb_updated
|
||||||
|
|
||||||
|
def get_nb_removed_events(self):
|
||||||
|
return self.nb_removed
|
||||||
|
|
||||||
def import_events(self, json_structure):
|
def import_events(self, json_structure):
|
||||||
|
self.init_result_properties()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
structure = json.loads(json_structure)
|
structure = json.loads(json_structure)
|
||||||
@ -17,32 +40,53 @@ class EventsImporter:
|
|||||||
|
|
||||||
if not "header" in structure:
|
if not "header" in structure:
|
||||||
return (False, "JSON is not correctly structured: missing header")
|
return (False, "JSON is not correctly structured: missing header")
|
||||||
|
if not "events" in structure:
|
||||||
|
return (False, "JSON is not correctly structured: missing events")
|
||||||
|
|
||||||
if "url" in structure["header"]:
|
if "url" in structure["header"]:
|
||||||
self.url = structure["header"]["url"]
|
self.url = structure["header"]["url"]
|
||||||
else:
|
else:
|
||||||
return (False, "JSON is not correctly structured: missing url in header")
|
return (False, "JSON is not correctly structured: missing url in header")
|
||||||
|
|
||||||
if "url" in structure["header"]:
|
if "date" in structure["header"]:
|
||||||
self.date = structure["header"]["date"]
|
self.date = structure["header"]["date"]
|
||||||
|
|
||||||
# load events
|
# get events
|
||||||
for event in structure["events"]:
|
for event in structure["events"]:
|
||||||
self.import_event(event)
|
if "created_date" not in event:
|
||||||
|
if self.date is not None:
|
||||||
|
event["created_date"] = self.date
|
||||||
|
else:
|
||||||
|
event["created_date"] = datetime.now()
|
||||||
|
|
||||||
# update object with infos from header, and with the list of imported objects
|
if not self.import_event(event):
|
||||||
# TODO
|
return (False, self.error_message)
|
||||||
|
|
||||||
# events that are missing from the import but in database are turned into drafts
|
# import them
|
||||||
# TODO
|
self.save_imported()
|
||||||
|
|
||||||
return (True, "")
|
return (True, "")
|
||||||
|
|
||||||
|
def save_imported(self):
|
||||||
|
self.db_event_objects, self.nb_updated, self.nb_removed = Event.import_events(self.event_objects, remove_missing=True)
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_event_structure(self, event):
|
||||||
|
if "title" not in event:
|
||||||
|
self.error_message = "JSON is not correctly structured: one event without title"
|
||||||
|
return False
|
||||||
|
if "start_day" not in event:
|
||||||
|
self.error_message = "JSON is not correctly structured: one event without start_day"
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def import_event(self, event):
|
def import_event(self, event):
|
||||||
# TODO
|
if self.is_valid_event_structure(event):
|
||||||
|
event_obj = Event.from_structure(event)
|
||||||
pass
|
self.event_objects.append(event_obj)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: agenda_culturel\n"
|
"Project-Id-Version: agenda_culturel\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-12-23 08:38+0000\n"
|
"POT-Creation-Date: 2023-12-29 15:35+0000\n"
|
||||||
"PO-Revision-Date: 2023-10-29 14:16+0000\n"
|
"PO-Revision-Date: 2023-10-29 14:16+0000\n"
|
||||||
"Last-Translator: Jean-Marie Favreau <jeanmarie.favreau@free.fr>\n"
|
"Last-Translator: Jean-Marie Favreau <jeanmarie.favreau@free.fr>\n"
|
||||||
"Language-Team: Jean-Marie Favreau <jeanmarie.favreau@free.fr>\n"
|
"Language-Team: Jean-Marie Favreau <jeanmarie.favreau@free.fr>\n"
|
||||||
@ -17,261 +17,311 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
#: agenda_culturel/forms.py:37
|
#: agenda_culturel/forms.py:38
|
||||||
msgid "The end date must be after the start date."
|
msgid "The end date must be after the start date."
|
||||||
msgstr "La date de fin doit être après la date de début."
|
msgstr "La date de fin doit être après la date de début."
|
||||||
|
|
||||||
#: agenda_culturel/forms.py:52
|
#: agenda_culturel/forms.py:53
|
||||||
msgid "The end time cannot be earlier than the start time."
|
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."
|
msgstr "L'heure de fin ne peut pas être avant l'heure de début."
|
||||||
|
|
||||||
#: agenda_culturel/models.py:23 agenda_culturel/models.py:52
|
#: agenda_culturel/forms.py:61
|
||||||
#: agenda_culturel/models.py:211
|
msgid ""
|
||||||
|
"JSON in the format expected for the import. If the JSON is provided here, we "
|
||||||
|
"will ignore the URLs given above, and use the information provided by the "
|
||||||
|
"json without importing any additional events from the URL."
|
||||||
|
msgstr ""
|
||||||
|
"JSON au format attendu pour l'import. Si le JSON est fourni ici, on ignorera "
|
||||||
|
"les URL données au dessus, et on utilisera les informations fournies par le "
|
||||||
|
"json sans réaliser d'importation supplémentaire d'événements depuis l'URL."
|
||||||
|
|
||||||
|
#: agenda_culturel/forms.py:74
|
||||||
|
msgid ""
|
||||||
|
"You need to fill in either the json or the source possibly supplemented by "
|
||||||
|
"the navigable URL."
|
||||||
|
msgstr ""
|
||||||
|
"Vous devez renseigner soit le json soit la source éventuellement complétée "
|
||||||
|
"de l'URL navigable."
|
||||||
|
|
||||||
|
#: agenda_culturel/models.py:26 agenda_culturel/models.py:55
|
||||||
|
#: agenda_culturel/models.py:465
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Nom"
|
msgstr "Nom"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:23 agenda_culturel/models.py:52
|
#: agenda_culturel/models.py:26 agenda_culturel/models.py:55
|
||||||
msgid "Category name"
|
msgid "Category name"
|
||||||
msgstr "Nom de la catégorie"
|
msgstr "Nom de la catégorie"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:24
|
#: agenda_culturel/models.py:27
|
||||||
msgid "Content"
|
msgid "Content"
|
||||||
msgstr "Contenu"
|
msgstr "Contenu"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:24
|
#: agenda_culturel/models.py:27
|
||||||
msgid "Text as shown to the visitors"
|
msgid "Text as shown to the visitors"
|
||||||
msgstr "Text tel que présenté aux visiteureuses"
|
msgstr "Text tel que présenté aux visiteureuses"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:25
|
#: agenda_culturel/models.py:28
|
||||||
msgid "URL path"
|
msgid "URL path"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: agenda_culturel/models.py:25
|
#: agenda_culturel/models.py:28
|
||||||
msgid "URL path where the content is included."
|
msgid "URL path where the content is included."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: agenda_culturel/models.py:53
|
#: agenda_culturel/models.py:56
|
||||||
msgid "Alternative Name"
|
msgid "Alternative Name"
|
||||||
msgstr "Nom alternatif"
|
msgstr "Nom alternatif"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:53
|
#: agenda_culturel/models.py:56
|
||||||
msgid "Alternative name used with a time period"
|
msgid "Alternative name used with a time period"
|
||||||
msgstr "Nom alternatif utilisé avec une période de temps"
|
msgstr "Nom alternatif utilisé avec une période de temps"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:54
|
#: agenda_culturel/models.py:57
|
||||||
msgid "Short name"
|
msgid "Short name"
|
||||||
msgstr "Nom court"
|
msgstr "Nom court"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:54
|
#: agenda_culturel/models.py:57
|
||||||
msgid "Short name of the category"
|
msgid "Short name of the category"
|
||||||
msgstr "Nom court de la catégorie"
|
msgstr "Nom court de la catégorie"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:55
|
#: agenda_culturel/models.py:58
|
||||||
msgid "Color"
|
msgid "Color"
|
||||||
msgstr "Couleur"
|
msgstr "Couleur"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:55
|
#: agenda_culturel/models.py:58
|
||||||
msgid "Color used as background for the category"
|
msgid "Color used as background for the category"
|
||||||
msgstr "Couleur utilisée comme fond de la catégorie"
|
msgstr "Couleur utilisée comme fond de la catégorie"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:92 agenda_culturel/models.py:109
|
#: agenda_culturel/models.py:95 agenda_culturel/models.py:142
|
||||||
msgid "Category"
|
msgid "Category"
|
||||||
msgstr "Catégorie"
|
msgstr "Catégorie"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:93
|
#: agenda_culturel/models.py:96
|
||||||
msgid "Categories"
|
msgid "Categories"
|
||||||
msgstr "Catégories"
|
msgstr "Catégories"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:98
|
#: agenda_culturel/models.py:130
|
||||||
msgid "Published"
|
msgid "Published"
|
||||||
msgstr "Publié"
|
msgstr "Publié"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:99
|
#: agenda_culturel/models.py:131
|
||||||
msgid "Draft"
|
msgid "Draft"
|
||||||
msgstr "Brouillon"
|
msgstr "Brouillon"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:100
|
#: agenda_culturel/models.py:132
|
||||||
msgid "Trash"
|
msgid "Trash"
|
||||||
msgstr "Corbeille"
|
msgstr "Corbeille"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:105
|
#: agenda_culturel/models.py:138
|
||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Titre"
|
msgstr "Titre"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:105
|
#: agenda_culturel/models.py:138
|
||||||
msgid "Short title"
|
msgid "Short title"
|
||||||
msgstr "Titre court"
|
msgstr "Titre court"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:107 agenda_culturel/models.py:238
|
#: agenda_culturel/models.py:140 agenda_culturel/models.py:492
|
||||||
msgid "Status"
|
msgid "Status"
|
||||||
msgstr "Status"
|
msgstr "Status"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:109
|
#: agenda_culturel/models.py:142
|
||||||
msgid "Category of the event"
|
msgid "Category of the event"
|
||||||
msgstr "Catégorie de l'événement"
|
msgstr "Catégorie de l'événement"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:111
|
#: agenda_culturel/models.py:144
|
||||||
msgid "Day of the event"
|
msgid "Day of the event"
|
||||||
msgstr "Date de l'événement"
|
msgstr "Date de l'événement"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:112
|
#: agenda_culturel/models.py:145
|
||||||
msgid "Starting time"
|
msgid "Starting time"
|
||||||
msgstr "Heure de début"
|
msgstr "Heure de début"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:114
|
#: agenda_culturel/models.py:147
|
||||||
msgid "End day of the event"
|
msgid "End day of the event"
|
||||||
msgstr "Fin de l'événement"
|
msgstr "Fin de l'événement"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:114
|
#: agenda_culturel/models.py:147
|
||||||
msgid "End day of the event, only required if different from the start day."
|
msgid "End day of the event, only required if different from the start day."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Date de fin de l'événement, uniquement nécessaire s'il est différent du "
|
"Date de fin de l'événement, uniquement nécessaire s'il est différent du "
|
||||||
"premier jour de l'événement"
|
"premier jour de l'événement"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:115
|
#: agenda_culturel/models.py:148
|
||||||
msgid "Final time"
|
msgid "Final time"
|
||||||
msgstr "Heure de fin"
|
msgstr "Heure de fin"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:117
|
#: agenda_culturel/models.py:150
|
||||||
msgid "Location"
|
msgid "Location"
|
||||||
msgstr "Localisation"
|
msgstr "Localisation"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:117
|
#: agenda_culturel/models.py:150
|
||||||
msgid "Address of the event"
|
msgid "Address of the event"
|
||||||
msgstr "Adresse de l'événement"
|
msgstr "Adresse de l'événement"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:119
|
#: agenda_culturel/models.py:152
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr "Description"
|
msgstr "Description"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:119
|
#: agenda_culturel/models.py:152
|
||||||
msgid "General description of the event"
|
msgid "General description of the event"
|
||||||
msgstr "Description générale de l'événement"
|
msgstr "Description générale de l'événement"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:121
|
#: agenda_culturel/models.py:154
|
||||||
msgid "Illustration (local image)"
|
msgid "Illustration (local image)"
|
||||||
msgstr "Illustration (image locale)"
|
msgstr "Illustration (image locale)"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:121
|
#: agenda_culturel/models.py:154
|
||||||
msgid "Illustration image stored in the agenda server"
|
msgid "Illustration image stored in the agenda server"
|
||||||
msgstr "Image d'illustration stockée sur le serveur de l'agenda"
|
msgstr "Image d'illustration stockée sur le serveur de l'agenda"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:123
|
#: agenda_culturel/models.py:156
|
||||||
msgid "Illustration"
|
msgid "Illustration"
|
||||||
msgstr "Illustration"
|
msgstr "Illustration"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:123
|
#: agenda_culturel/models.py:156
|
||||||
msgid "URL of the illustration image"
|
msgid "URL of the illustration image"
|
||||||
msgstr "URL de l'image illustrative"
|
msgstr "URL de l'image illustrative"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:124
|
#: agenda_culturel/models.py:157
|
||||||
msgid "Illustration description"
|
msgid "Illustration description"
|
||||||
msgstr "Description de l'illustration"
|
msgstr "Description de l'illustration"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:124
|
#: agenda_culturel/models.py:157
|
||||||
msgid "Alternative text used by screen readers for the image"
|
msgid "Alternative text used by screen readers for the image"
|
||||||
msgstr "Texte alternatif utiliser par les lecteurs d'écrans pour l'image"
|
msgstr "Texte alternatif utiliser par les lecteurs d'écrans pour l'image"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:126
|
#: agenda_culturel/models.py:159
|
||||||
|
msgid "UUIDs"
|
||||||
|
msgstr "UUIDs"
|
||||||
|
|
||||||
|
#: agenda_culturel/models.py:159
|
||||||
|
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:160
|
||||||
msgid "URLs"
|
msgid "URLs"
|
||||||
msgstr "URLs"
|
msgstr "URLs"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:126
|
#: agenda_culturel/models.py:160
|
||||||
msgid "List of all the urls where this event can be found."
|
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é."
|
msgstr "Liste de toutes les urls où l'événement peut être trouvé."
|
||||||
|
|
||||||
#: agenda_culturel/models.py:128
|
#: agenda_culturel/models.py:162
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr "Étiquettes"
|
msgstr "Étiquettes"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:128
|
#: agenda_culturel/models.py:162
|
||||||
msgid "A list of tags that describe the event."
|
msgid "A list of tags that describe the event."
|
||||||
msgstr "Une liste d'étiquettes décrivant l'événement"
|
msgstr "Une liste d'étiquettes décrivant l'événement"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:158
|
#: agenda_culturel/models.py:164
|
||||||
|
msgid "Possibly duplicated"
|
||||||
|
msgstr "Possibles doublons"
|
||||||
|
|
||||||
|
#: agenda_culturel/models.py:194
|
||||||
msgid "Event"
|
msgid "Event"
|
||||||
msgstr "Événement"
|
msgstr "Événement"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:159
|
#: agenda_culturel/models.py:195
|
||||||
msgid "Events"
|
msgid "Events"
|
||||||
msgstr "Événements"
|
msgstr "Événements"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:210
|
#: agenda_culturel/models.py:464
|
||||||
msgid "Subject"
|
msgid "Subject"
|
||||||
msgstr "Sujet"
|
msgstr "Sujet"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:210
|
#: agenda_culturel/models.py:464
|
||||||
msgid "The subject of your message"
|
msgid "The subject of your message"
|
||||||
msgstr "Sujet de votre message"
|
msgstr "Sujet de votre message"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:211
|
#: agenda_culturel/models.py:465
|
||||||
msgid "Your name"
|
msgid "Your name"
|
||||||
msgstr "Votre nom"
|
msgstr "Votre nom"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:212
|
#: agenda_culturel/models.py:466
|
||||||
msgid "Email address"
|
msgid "Email address"
|
||||||
msgstr "Adresse email"
|
msgstr "Adresse email"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:212
|
#: agenda_culturel/models.py:466
|
||||||
msgid "Your email address"
|
msgid "Your email address"
|
||||||
msgstr "Votre adresse email"
|
msgstr "Votre adresse email"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:213
|
#: agenda_culturel/models.py:467
|
||||||
msgid "Message"
|
msgid "Message"
|
||||||
msgstr "Message"
|
msgstr "Message"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:213
|
#: agenda_culturel/models.py:467
|
||||||
msgid "Your message"
|
msgid "Your message"
|
||||||
msgstr "Votre message"
|
msgstr "Votre message"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:217 agenda_culturel/views.py:341
|
#: agenda_culturel/models.py:471 agenda_culturel/views.py:343
|
||||||
msgid "Closed"
|
msgid "Closed"
|
||||||
msgstr "Fermé"
|
msgstr "Fermé"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:217
|
#: agenda_culturel/models.py:471
|
||||||
msgid "this message has been processed and no longer needs to be handled"
|
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"
|
msgstr "Ce message a été traité et ne nécessite plus d'être pris en charge"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:218
|
#: agenda_culturel/models.py:472
|
||||||
msgid "Comments"
|
msgid "Comments"
|
||||||
msgstr "Commentaires"
|
msgstr "Commentaires"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:218
|
#: agenda_culturel/models.py:472
|
||||||
msgid "Comments on the message from the moderation team"
|
msgid "Comments on the message from the moderation team"
|
||||||
msgstr "Commentaires sur ce message par l'équipe de modération"
|
msgstr "Commentaires sur ce message par l'équipe de modération"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:227
|
#: agenda_culturel/models.py:481
|
||||||
msgid "Running"
|
msgid "Running"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: agenda_culturel/models.py:228
|
#: agenda_culturel/models.py:482
|
||||||
msgid "Canceled"
|
msgid "Canceled"
|
||||||
msgstr "Annulé"
|
msgstr "Annulé"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:229
|
#: agenda_culturel/models.py:483
|
||||||
msgid "Success"
|
msgid "Success"
|
||||||
msgstr "Succès"
|
msgstr "Succès"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:230
|
#: agenda_culturel/models.py:484
|
||||||
msgid "Failed"
|
msgid "Failed"
|
||||||
msgstr "Erreur"
|
msgstr "Erreur"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:235
|
#: agenda_culturel/models.py:489
|
||||||
msgid "Source"
|
msgid "Source"
|
||||||
msgstr "Source"
|
msgstr "Source"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:235
|
#: agenda_culturel/models.py:489
|
||||||
msgid "URL of the source document"
|
msgid "URL of the source document"
|
||||||
msgstr "URL du document source"
|
msgstr "URL du document source"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:236
|
#: agenda_culturel/models.py:490
|
||||||
msgid "Browsable url"
|
msgid "Browsable url"
|
||||||
msgstr "URL navigable"
|
msgstr "URL navigable"
|
||||||
|
|
||||||
#: agenda_culturel/models.py:236
|
#: agenda_culturel/models.py:490
|
||||||
msgid "URL of the corresponding document that will be shown to visitors."
|
msgid "URL of the corresponding document that will be shown to visitors."
|
||||||
msgstr "URL correspondant au document et qui sera montrée aux visiteurs"
|
msgstr "URL correspondant au document et qui sera montrée aux visiteurs"
|
||||||
|
|
||||||
|
#: agenda_culturel/models.py:494
|
||||||
|
msgid "Error message"
|
||||||
|
msgstr "Votre message"
|
||||||
|
|
||||||
|
#: agenda_culturel/models.py:496
|
||||||
|
msgid "Number of collected events"
|
||||||
|
msgstr "Nombre d'événements collectés"
|
||||||
|
|
||||||
|
#: agenda_culturel/models.py:497
|
||||||
|
msgid "Number of imported events"
|
||||||
|
msgstr "Nombre d'événements importés"
|
||||||
|
|
||||||
|
#: agenda_culturel/models.py:498
|
||||||
|
msgid "Number of updated events"
|
||||||
|
msgstr "Nombre d'événements mis à jour"
|
||||||
|
|
||||||
|
#: agenda_culturel/models.py:499
|
||||||
|
msgid "Number of removed events"
|
||||||
|
msgstr "Nombre d'événements supprimés"
|
||||||
|
|
||||||
#: agenda_culturel/settings/base.py:134
|
#: agenda_culturel/settings/base.py:134
|
||||||
msgid "English"
|
msgid "English"
|
||||||
msgstr "anglais"
|
msgstr "anglais"
|
||||||
@ -280,27 +330,27 @@ msgstr "anglais"
|
|||||||
msgid "French"
|
msgid "French"
|
||||||
msgstr "français"
|
msgstr "français"
|
||||||
|
|
||||||
#: agenda_culturel/views.py:188
|
#: agenda_culturel/views.py:190
|
||||||
msgid "The static content has been successfully updated."
|
msgid "The static content has been successfully updated."
|
||||||
msgstr "Le contenu statique a été modifié avec succès."
|
msgstr "Le contenu statique a été modifié avec succès."
|
||||||
|
|
||||||
#: agenda_culturel/views.py:194
|
#: agenda_culturel/views.py:196
|
||||||
msgid "The event has been successfully modified."
|
msgid "The event has been successfully modified."
|
||||||
msgstr "L'événement a été modifié avec succès."
|
msgstr "L'événement a été modifié avec succès."
|
||||||
|
|
||||||
#: agenda_culturel/views.py:205
|
#: agenda_culturel/views.py:207
|
||||||
msgid "The event has been successfully deleted."
|
msgid "The event has been successfully deleted."
|
||||||
msgstr "L'événement a été supprimé avec succès"
|
msgstr "L'événement a été supprimé avec succès"
|
||||||
|
|
||||||
#: agenda_culturel/views.py:222
|
#: agenda_culturel/views.py:224
|
||||||
msgid "The status has been successfully modified."
|
msgid "The status has been successfully modified."
|
||||||
msgstr "Le status a été modifié avec succès."
|
msgstr "Le status a été modifié avec succès."
|
||||||
|
|
||||||
#: agenda_culturel/views.py:244
|
#: agenda_culturel/views.py:246
|
||||||
msgid "The event is saved."
|
msgid "The event is saved."
|
||||||
msgstr "L'événement est enregistré."
|
msgstr "L'événement est enregistré."
|
||||||
|
|
||||||
#: agenda_culturel/views.py:247
|
#: agenda_culturel/views.py:249
|
||||||
msgid ""
|
msgid ""
|
||||||
"The event has been submitted and will be published as soon as it has been "
|
"The event has been submitted and will be published as soon as it has been "
|
||||||
"validated by the moderation team."
|
"validated by the moderation team."
|
||||||
@ -308,7 +358,7 @@ msgstr ""
|
|||||||
"L'événement a été soumis et sera publié dès qu'il aura été validé par "
|
"L'événement a été soumis et sera publié dès qu'il aura été validé par "
|
||||||
"l'équipe de modération."
|
"l'équipe de modération."
|
||||||
|
|
||||||
#: agenda_culturel/views.py:277
|
#: agenda_culturel/views.py:279
|
||||||
msgid ""
|
msgid ""
|
||||||
"The event has been successfully extracted, and you can now submit it after "
|
"The event has been successfully extracted, and you can now submit it after "
|
||||||
"modifying it if necessary."
|
"modifying it if necessary."
|
||||||
@ -316,7 +366,7 @@ msgstr ""
|
|||||||
"L'événement a été extrait avec succès, vous pouvez maintenant le soumettre "
|
"L'événement a été extrait avec succès, vous pouvez maintenant le soumettre "
|
||||||
"après l'avoir modifié au besoin."
|
"après l'avoir modifié au besoin."
|
||||||
|
|
||||||
#: agenda_culturel/views.py:281
|
#: agenda_culturel/views.py:283
|
||||||
msgid ""
|
msgid ""
|
||||||
"Unable to extract an event from the proposed URL. Please use the form below "
|
"Unable to extract an event from the proposed URL. Please use the form below "
|
||||||
"to submit the event."
|
"to submit the event."
|
||||||
@ -324,12 +374,12 @@ msgstr ""
|
|||||||
"Impossible d'extraire un événement depuis l'URL proposée. Veuillez utiliser "
|
"Impossible d'extraire un événement depuis l'URL proposée. Veuillez utiliser "
|
||||||
"le formulaire ci-dessous pour soumettre l'événement."
|
"le formulaire ci-dessous pour soumettre l'événement."
|
||||||
|
|
||||||
#: agenda_culturel/views.py:290
|
#: agenda_culturel/views.py:292
|
||||||
msgid "This URL has already been submitted, and you can find the event below."
|
msgid "This URL has already been submitted, and you can find the event below."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Cette URL a déjà été soumise, et vous trouverez l'événement ci-dessous."
|
"Cette URL a déjà été soumise, et vous trouverez l'événement ci-dessous."
|
||||||
|
|
||||||
#: agenda_culturel/views.py:294
|
#: agenda_culturel/views.py:296
|
||||||
msgid ""
|
msgid ""
|
||||||
"This URL has already been submitted, but has not been selected for "
|
"This URL has already been submitted, but has not been selected for "
|
||||||
"publication by the moderation team."
|
"publication by the moderation team."
|
||||||
@ -337,23 +387,23 @@ msgstr ""
|
|||||||
"Cette URL a déjà été soumise, mais n'a pas été retenue par l'équipe de "
|
"Cette URL a déjà été soumise, mais n'a pas été retenue par l'équipe de "
|
||||||
"modération pour la publication."
|
"modération pour la publication."
|
||||||
|
|
||||||
#: agenda_culturel/views.py:296
|
#: agenda_culturel/views.py:298
|
||||||
msgid "This URL has already been submitted and is awaiting moderation."
|
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"
|
msgstr "Cette URL a déjà été soumise, et est en attente de modération"
|
||||||
|
|
||||||
#: agenda_culturel/views.py:318
|
#: agenda_culturel/views.py:320
|
||||||
msgid "Your message has been sent successfully."
|
msgid "Your message has been sent successfully."
|
||||||
msgstr "L'événement a été supprimé avec succès"
|
msgstr "L'événement a été supprimé avec succès"
|
||||||
|
|
||||||
#: agenda_culturel/views.py:326
|
#: agenda_culturel/views.py:328
|
||||||
msgid "The contact message properties has been successfully modified."
|
msgid "The contact message properties has been successfully modified."
|
||||||
msgstr "Les propriétés du message de contact ont été modifié avec succès."
|
msgstr "Les propriétés du message de contact ont été modifié avec succès."
|
||||||
|
|
||||||
#: agenda_culturel/views.py:341
|
#: agenda_culturel/views.py:343
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
msgstr "Ouvert"
|
msgstr "Ouvert"
|
||||||
|
|
||||||
#: agenda_culturel/views.py:381
|
#: agenda_culturel/views.py:383
|
||||||
msgid "Search"
|
msgid "Search"
|
||||||
msgstr "Rechercher"
|
msgstr "Rechercher"
|
||||||
|
|
||||||
@ -361,7 +411,7 @@ msgstr "Rechercher"
|
|||||||
msgid "The import has been run successfully."
|
msgid "The import has been run successfully."
|
||||||
msgstr "L'import a été lancé avec succès"
|
msgstr "L'import a été lancé avec succès"
|
||||||
|
|
||||||
#: agenda_culturel/views.py:507
|
#: agenda_culturel/views.py:521
|
||||||
msgid "The import has been canceled."
|
msgid "The import has been canceled."
|
||||||
msgstr "L'import a été annulé"
|
msgstr "L'import a été annulé"
|
||||||
|
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.2.7 on 2023-12-23 12:51
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('agenda_culturel', '0015_event_uuids'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='batchimportation',
|
||||||
|
name='nb_removed',
|
||||||
|
field=models.PositiveIntegerField(default=0, verbose_name='Number of removed events'),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.2.7 on 2023-12-23 13:01
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('agenda_culturel', '0016_batchimportation_nb_removed'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='batchimportation',
|
||||||
|
name='nb_updated',
|
||||||
|
field=models.PositiveIntegerField(default=0, verbose_name='Number of updated events'),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 4.2.7 on 2023-12-23 13:31
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('agenda_culturel', '0017_batchimportation_nb_updated'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='event',
|
||||||
|
name='imported_date',
|
||||||
|
field=models.DateTimeField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='event',
|
||||||
|
name='created_date',
|
||||||
|
field=models.DateTimeField(editable=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='event',
|
||||||
|
name='modified_date',
|
||||||
|
field=models.DateTimeField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 4.2.7 on 2023-12-29 11:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('agenda_culturel', '0018_event_imported_date_alter_event_created_date_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DuplicatedEvents',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='event',
|
||||||
|
name='possibly_duplicated',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='agenda_culturel.duplicatedevents'),
|
||||||
|
),
|
||||||
|
]
|
11
src/agenda_culturel/migrations/0020_trigram_extension.py
Normal file
11
src/agenda_culturel/migrations/0020_trigram_extension.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
from django.db import migrations
|
||||||
|
from django.contrib.postgres.operations import TrigramExtension
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('agenda_culturel', '0019_duplicatedevents_event_possibly_duplicated'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [TrigramExtension()]
|
@ -9,6 +9,9 @@ from urllib.parse import urlparse
|
|||||||
import urllib.request
|
import urllib.request
|
||||||
import os
|
import os
|
||||||
from django.core.files import File
|
from django.core.files import File
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.contrib.postgres.search import TrigramSimilarity
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
|
|
||||||
from django.template.defaultfilters import date as _date
|
from django.template.defaultfilters import date as _date
|
||||||
@ -92,6 +95,35 @@ class Category(models.Model):
|
|||||||
verbose_name = _('Category')
|
verbose_name = _('Category')
|
||||||
verbose_name_plural = _('Categories')
|
verbose_name_plural = _('Categories')
|
||||||
|
|
||||||
|
|
||||||
|
class DuplicatedEvents(models.Model):
|
||||||
|
|
||||||
|
def nb_duplicated(self):
|
||||||
|
return Event.objects.filter(possibly_duplicated=self).count()
|
||||||
|
|
||||||
|
def merge_into(self, other):
|
||||||
|
# for all objects associated to this group
|
||||||
|
for e in Event.objects.filter(possibly_duplicated=self):
|
||||||
|
# change their group membership
|
||||||
|
e.possibly_duplicated = other
|
||||||
|
# save them
|
||||||
|
e.save()
|
||||||
|
# then delete the empty group
|
||||||
|
self.delete()
|
||||||
|
|
||||||
|
def merge_groups(groups):
|
||||||
|
if len(groups) == 0:
|
||||||
|
return None
|
||||||
|
elif len(groups) == 1:
|
||||||
|
return groups[0]
|
||||||
|
else:
|
||||||
|
result = groups[0]
|
||||||
|
for g in groups[1:]:
|
||||||
|
g.merge_into(result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Event(models.Model):
|
class Event(models.Model):
|
||||||
|
|
||||||
class STATUS(models.TextChoices):
|
class STATUS(models.TextChoices):
|
||||||
@ -99,8 +131,9 @@ class Event(models.Model):
|
|||||||
DRAFT = "draft", _("Draft")
|
DRAFT = "draft", _("Draft")
|
||||||
TRASH = "trash", _("Trash")
|
TRASH = "trash", _("Trash")
|
||||||
|
|
||||||
created_date = models.DateTimeField(auto_now_add=True)
|
created_date = models.DateTimeField(editable=False)
|
||||||
modified_date = models.DateTimeField(auto_now=True)
|
imported_date = models.DateTimeField(blank=True, null=True)
|
||||||
|
modified_date = models.DateTimeField(blank=True, null=True)
|
||||||
|
|
||||||
title = models.CharField(verbose_name=_('Title'), help_text=_('Short title'), max_length=512)
|
title = models.CharField(verbose_name=_('Title'), help_text=_('Short title'), max_length=512)
|
||||||
|
|
||||||
@ -128,6 +161,8 @@ class Event(models.Model):
|
|||||||
|
|
||||||
tags = ArrayField(models.CharField(max_length=64), verbose_name=_('Tags'), help_text=_("A list of tags that describe the event."), blank=True, null=True)
|
tags = ArrayField(models.CharField(max_length=64), verbose_name=_('Tags'), help_text=_("A list of tags that describe the event."), blank=True, null=True)
|
||||||
|
|
||||||
|
possibly_duplicated = models.ForeignKey(DuplicatedEvents, verbose_name=_('Possibly duplicated'), on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
|
||||||
def get_consolidated_end_day(self, intuitive=True):
|
def get_consolidated_end_day(self, intuitive=True):
|
||||||
if intuitive:
|
if intuitive:
|
||||||
end_day = self.get_consolidated_end_day(False)
|
end_day = self.get_consolidated_end_day(False)
|
||||||
@ -180,14 +215,13 @@ class Event(models.Model):
|
|||||||
return self.status == Event.STATUS.TRASH
|
return self.status == Event.STATUS.TRASH
|
||||||
|
|
||||||
def modified(self):
|
def modified(self):
|
||||||
return abs((self.modified_date - self.created_date).total_seconds()) > 1
|
return self.modified_date is None or abs((self.modified_date - self.created_date).total_seconds()) > 1
|
||||||
|
|
||||||
def nb_draft_events():
|
def nb_draft_events():
|
||||||
return Event.objects.filter(status=Event.STATUS.DRAFT).count()
|
return Event.objects.filter(status=Event.STATUS.DRAFT).count()
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
# if the image is defined but not locally downloaded
|
def download_image(self):
|
||||||
if self.image and not self.local_image:
|
|
||||||
# first download file
|
# first download file
|
||||||
|
|
||||||
a = urlparse(self.image)
|
a = urlparse(self.image)
|
||||||
@ -201,9 +235,228 @@ class Event(models.Model):
|
|||||||
# if the download is ok, then create the corresponding file object
|
# if the download is ok, then create the corresponding file object
|
||||||
self.local_image = File(name=basename, file=open(tmpfile, "rb"))
|
self.local_image = File(name=basename, file=open(tmpfile, "rb"))
|
||||||
|
|
||||||
|
|
||||||
|
def update_dates(self):
|
||||||
|
now = timezone.now()
|
||||||
|
if not self.id:
|
||||||
|
self.created_date = now
|
||||||
|
if hasattr(self, "require_imported_date"):
|
||||||
|
self.imported_date = now
|
||||||
|
self.modified_date = now
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_save(self):
|
||||||
|
self.update_dates()
|
||||||
|
|
||||||
|
# if the image is defined but not locally downloaded
|
||||||
|
if self.image and not self.local_image:
|
||||||
|
self.download_image()
|
||||||
|
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
|
||||||
|
self.prepare_save()
|
||||||
|
|
||||||
|
# delete duplicated group if it's only with one element
|
||||||
|
if self.possibly_duplicated is not None and self.possibly_duplicated.nb_duplicated() == 1:
|
||||||
|
self.possibly_duplicated.delete()
|
||||||
|
self.possibly_duplicated = None
|
||||||
|
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def from_structure(event_structure):
|
||||||
|
if "category" in event_structure and event_structure["category"] is not None:
|
||||||
|
event_structure["category"] = Category.objects.get(name=event_structure["category"])
|
||||||
|
|
||||||
|
if "uuid" in event_structure and event_structure["uuid"] is not None:
|
||||||
|
event_structure["uuids"] = [event_structure["uuid"]]
|
||||||
|
del event_structure["uuid"]
|
||||||
|
|
||||||
|
if "published" in event_structure and event_structure["published"] is not None:
|
||||||
|
event_structure["status"] = Event.STATUS.PUBLISHED
|
||||||
|
del event_structure["published"]
|
||||||
|
else:
|
||||||
|
event_structure["status"] = Event.STATUS.DRAFT
|
||||||
|
|
||||||
|
if "url_human" in event_structure and event_structure["url_human"] is not None:
|
||||||
|
event_structure["reference_urls"] = [event_structure["url_human"]]
|
||||||
|
del event_structure["url_human"]
|
||||||
|
|
||||||
|
if "last_modified" in event_structure and event_structure["last_modified"] is not None:
|
||||||
|
event_structure["created_date"] = event_structure["last_modified"]
|
||||||
|
del event_structure["last_modified"]
|
||||||
|
else:
|
||||||
|
event_structure["created_date"] = timezone.now()
|
||||||
|
|
||||||
|
if "start_time" in event_structure:
|
||||||
|
event_structure["start_time"] = time.fromisoformat(event_structure["start_time"])
|
||||||
|
|
||||||
|
if "end_time" in event_structure:
|
||||||
|
event_structure["end_time"] = time.fromisoformat(event_structure["end_time"])
|
||||||
|
|
||||||
|
if "description" in event_structure and event_structure["description"] is None:
|
||||||
|
event_structure["description"] = ""
|
||||||
|
|
||||||
|
return Event(**event_structure)
|
||||||
|
|
||||||
|
|
||||||
|
def find_similar_events(self):
|
||||||
|
start_time_test = Q(start_time=self.start_time)
|
||||||
|
|
||||||
|
if self.start_time is not None:
|
||||||
|
# convert str start_time to time
|
||||||
|
if isinstance(self.start_time, str):
|
||||||
|
self.start_time = time.fromisoformat(self.start_time)
|
||||||
|
interval = (time(self.start_time.hour - 1, self.start_time.minute) if self.start_time.hour >= 1 else time(0, 0),
|
||||||
|
time(self.start_time.hour + 1, self.start_time.minute) if self.start_time.hour < 23 else time(23, 59))
|
||||||
|
start_time_test = start_time_test | Q(start_time__range=interval)
|
||||||
|
|
||||||
|
return Event.objects.annotate(similarity_title=TrigramSimilarity("title", self.title)). \
|
||||||
|
annotate(similarity_location=TrigramSimilarity("location", self.location)). \
|
||||||
|
filter(Q(start_day=self.start_day) & start_time_test & Q(similarity_title__gt=0.5) & Q(similarity_title__gt=0.3))
|
||||||
|
|
||||||
|
|
||||||
|
def find_same_events_by_uuid(self):
|
||||||
|
return None if self.uuids is None or len(self.uuids) == 0 else Event.objects.filter(uuids__contains=self.uuids)
|
||||||
|
|
||||||
|
|
||||||
|
def get_possibly_duplicated(self):
|
||||||
|
if self.possibly_duplicated is None:
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
return Event.objects.filter(possibly_duplicated=self.possibly_duplicated).exclude(pk=self.pk)
|
||||||
|
|
||||||
|
|
||||||
|
def set_possibly_duplicated(self, events):
|
||||||
|
# get existing groups
|
||||||
|
groups = list(set([e.possibly_duplicated for e in events] + [self.possibly_duplicated]))
|
||||||
|
groups = [g for g in groups if g is not None]
|
||||||
|
|
||||||
|
# do we have to create a new group?
|
||||||
|
if len(groups) == 0:
|
||||||
|
group = DuplicatedEvents.objects.create()
|
||||||
|
logger.warning("set possibily duplicated 0 {}".format(group))
|
||||||
|
else:
|
||||||
|
# otherwise merge existing groups
|
||||||
|
group = DuplicatedEvents.merge_groups(groups)
|
||||||
|
logger.warning("set possibily duplicated not 0 {}".format(group))
|
||||||
|
group.save()
|
||||||
|
|
||||||
|
# set the possibly duplicated group for the current object
|
||||||
|
self.possibly_duplicated = group
|
||||||
|
|
||||||
|
# and for the other events
|
||||||
|
for e in events:
|
||||||
|
e.possibly_duplicated = group
|
||||||
|
# finally save the other events
|
||||||
|
Event.objects.bulk_update(events, fields=["possibly_duplicated"])
|
||||||
|
|
||||||
|
|
||||||
|
def data_fields():
|
||||||
|
return ["title", "location", "start_day", "start_time", "end_day", "end_time", "description", "image", "image_alt", "image_alt", "reference_urls"]
|
||||||
|
|
||||||
|
def same_event_by_data(self, other):
|
||||||
|
for attr in Event.data_fields():
|
||||||
|
if str(getattr(self, attr)) != str(getattr(other, attr)):
|
||||||
|
logger.warning("on trouve une différence dans {}: {} vs {}".format(attr, getattr(self, attr), getattr(other, attr)))
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def find_same_event_by_data_in_list(self, events):
|
||||||
|
return [e for e in events if self.same_event_by_data(e)]
|
||||||
|
|
||||||
|
|
||||||
|
def find_last_imported_not_modified(events):
|
||||||
|
events = [e for e in events if e.imported_date is not None and (e.modified_date is None or e.modified_date <= e.imported_date)]
|
||||||
|
if len(events) == 0:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
events.sort(key=lambda e: e.imported_date, reverse=True)
|
||||||
|
return events[0]
|
||||||
|
|
||||||
|
|
||||||
|
def update(self, other):
|
||||||
|
# TODO: what about category, tags?
|
||||||
|
# set attributes
|
||||||
|
for attr in Event.data_fields():
|
||||||
|
setattr(self, attr, getattr(other, attr))
|
||||||
|
# add a possible missing uuid
|
||||||
|
if self.uuids is None:
|
||||||
|
self.uuids = []
|
||||||
|
for uuid in other.uuids:
|
||||||
|
if not uuid in self.uuids:
|
||||||
|
self.uuids.append(uuid)
|
||||||
|
|
||||||
|
|
||||||
|
# Limitation: the given events should not be considered similar one to another...
|
||||||
|
def import_events(events, remove_missing=False):
|
||||||
|
to_import = []
|
||||||
|
to_update = []
|
||||||
|
|
||||||
|
# for each event, check if it's a new one, or a one to be updated
|
||||||
|
for event in events:
|
||||||
|
# imported events should be updated
|
||||||
|
event.require_imported_date = True
|
||||||
|
event.prepare_save()
|
||||||
|
|
||||||
|
# check if the event has already be imported (using uuid)
|
||||||
|
same_events = event.find_same_events_by_uuid()
|
||||||
|
|
||||||
|
if len(same_events) != 0:
|
||||||
|
# check if one event has been imported and not modified in this list
|
||||||
|
same_imported = Event.find_last_imported_not_modified(same_events)
|
||||||
|
|
||||||
|
if same_imported:
|
||||||
|
# if this event exists, it will be updated with new data
|
||||||
|
same_imported.update(event)
|
||||||
|
same_imported.require_imported_date = True
|
||||||
|
same_imported.prepare_save()
|
||||||
|
to_update.append(same_imported)
|
||||||
|
else:
|
||||||
|
# otherwise, the new event possibly a duplication of the others.
|
||||||
|
event.set_possibly_duplicated(same_events)
|
||||||
|
# it will be imported
|
||||||
|
to_import.append(event)
|
||||||
|
else:
|
||||||
|
# if uuid is unique (or not available), check for similar events
|
||||||
|
similar_events = event.find_similar_events()
|
||||||
|
|
||||||
|
# if it exists similar events, add this relation to the event
|
||||||
|
if len(similar_events) != 0:
|
||||||
|
|
||||||
|
# check if an event from the list is exactly the same as the new one (using data)
|
||||||
|
same_events = event.find_same_event_by_data_in_list(similar_events)
|
||||||
|
if same_events is not None and len(same_events) > 0:
|
||||||
|
# merge with the first one
|
||||||
|
same_events[0].update(event)
|
||||||
|
same_events[0].require_imported_date = True
|
||||||
|
same_events[0].prepare_save()
|
||||||
|
to_update.append(same_events[0])
|
||||||
|
else:
|
||||||
|
# the event is possibly a duplication of the others
|
||||||
|
event.set_possibly_duplicated(similar_events)
|
||||||
|
to_import.append(event)
|
||||||
|
else:
|
||||||
|
# import this new event
|
||||||
|
to_import.append(event)
|
||||||
|
|
||||||
|
# then import all the new events
|
||||||
|
imported = Event.objects.bulk_create(to_import)
|
||||||
|
nb_updated = Event.objects.bulk_update(to_update, fields = Event.data_fields() + ["imported_date", "modified_date", "uuids"])
|
||||||
|
|
||||||
|
nb_removed = 0
|
||||||
|
if remove_missing:
|
||||||
|
# events that are missing from the import but in database are turned into drafts
|
||||||
|
# TODO
|
||||||
|
# TODO: ajouter self.source, ou faire référence à l'objet BatchImportation
|
||||||
|
pass
|
||||||
|
|
||||||
|
return imported, nb_updated, nb_removed
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ContactMessage(models.Model):
|
class ContactMessage(models.Model):
|
||||||
@ -242,5 +495,7 @@ class BatchImportation(models.Model):
|
|||||||
|
|
||||||
nb_initial = models.PositiveIntegerField(verbose_name=_('Number of collected events'), default=0)
|
nb_initial = models.PositiveIntegerField(verbose_name=_('Number of collected events'), default=0)
|
||||||
nb_imported = models.PositiveIntegerField(verbose_name=_('Number of imported events'), default=0)
|
nb_imported = models.PositiveIntegerField(verbose_name=_('Number of imported events'), default=0)
|
||||||
|
nb_updated = models.PositiveIntegerField(verbose_name=_('Number of updated events'), default=0)
|
||||||
|
nb_removed = models.PositiveIntegerField(verbose_name=_('Number of removed events'), default=0)
|
||||||
|
|
||||||
celery_id = models.CharField(max_length=128, default="")
|
celery_id = models.CharField(max_length=128, default="")
|
||||||
|
@ -196,3 +196,6 @@ if os_getenv("EMAIL_BACKEND"):
|
|||||||
EMAIL_USE_TLS = os_getenv("EMAIL_USE_TLS", False)
|
EMAIL_USE_TLS = os_getenv("EMAIL_USE_TLS", False)
|
||||||
EMAIL_USE_SSL = os_getenv("EMAIL_USE_SSL", False)
|
EMAIL_USE_SSL = os_getenv("EMAIL_USE_SSL", False)
|
||||||
DEFAULT_FROM_EMAIL = os_getenv("DEFAULT_FROM_EMAIL")
|
DEFAULT_FROM_EMAIL = os_getenv("DEFAULT_FROM_EMAIL")
|
||||||
|
|
||||||
|
# increase upload size for debug experiments
|
||||||
|
DATA_UPLOAD_MAX_MEMORY_SIZE = 10 * 2621440
|
@ -11,12 +11,6 @@
|
|||||||
<article>
|
<article>
|
||||||
<form method="post">{% csrf_token %}
|
<form method="post">{% csrf_token %}
|
||||||
{{ form.as_p }}
|
{{ form.as_p }}
|
||||||
<p>
|
|
||||||
<label for="id_json">JSON (facultatif) :</label>
|
|
||||||
<textarea id="id_json" name="json" rows="10"></textarea>
|
|
||||||
<span class="helptext">JSON au format attendu pour l'import. Si le JSON est fourni ici, on ignorera les URL données au dessus, et on utilisera les informations fournies par le json sans réaliser d'importation supplémentaire d'événements depuis l'URL.</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<input type="submit" value="Envoyer">
|
<input type="submit" value="Envoyer">
|
||||||
</form>
|
</form>
|
||||||
</article>
|
</article>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
!<{% extends "agenda_culturel/page.html" %}
|
{% extends "agenda_culturel/page.html" %}
|
||||||
|
|
||||||
{% block title %}Importations par lot{% endblock %}
|
{% block title %}Importations par lot{% endblock %}
|
||||||
|
|
||||||
@ -19,10 +19,17 @@
|
|||||||
<table role="grid">
|
<table role="grid">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Identifiant</th>
|
<th rowspan="2">Identifiant</th>
|
||||||
<th>Date</th>
|
<th rowspan="2">Date</th>
|
||||||
<th>Status</th>
|
<th rowspan="2">Status</th>
|
||||||
<th>Action</th>
|
<th rowspan="2">Action</th>
|
||||||
|
<th colspan="4">événements</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>initial</th>
|
||||||
|
<th>importés</th>
|
||||||
|
<th>mis à jour</th>
|
||||||
|
<th>supprimés</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -32,6 +39,10 @@
|
|||||||
<td>{{ obj.created_date }}</td>
|
<td>{{ obj.created_date }}</td>
|
||||||
<td><span{% if obj.status == "failed" %} data-tooltip="{{ obj.error_message }}"{% endif %}>{{ obj.status }}</span></td>
|
<td><span{% if obj.status == "failed" %} data-tooltip="{{ obj.error_message }}"{% endif %}>{{ obj.status }}</span></td>
|
||||||
<td>{% if obj.status == "running" %}<a href="{% url 'cancel_import' obj.id %}">Annuler</a>{% endif %}</td>
|
<td>{% if obj.status == "running" %}<a href="{% url 'cancel_import' obj.id %}">Annuler</a>{% endif %}</td>
|
||||||
|
<td>{% if obj.status == "success" %}{{ obj.nb_initial }}{% endif %}</td>
|
||||||
|
<td>{% if obj.status == "success" %}{{ obj.nb_imported }}{% endif %}</td>
|
||||||
|
<td>{% if obj.status == "success" %}{{ obj.nb_updated }}{% endif %}</td>
|
||||||
|
<td>{% if obj.status == "success" %}{{ obj.nb_removed }}{% endif %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
{% include "agenda_culturel/single-event/event-single-inc.html" with event=event filter=filter %}
|
{% include "agenda_culturel/single-event/event-single-inc.html" with event=event filter=filter %}
|
||||||
<aside>
|
<aside>
|
||||||
|
<!-- TODO: en même temps -->
|
||||||
<article>
|
<article>
|
||||||
<head>
|
<head>
|
||||||
<h2>À la même date</h2>
|
<h2>À la même date</h2>
|
||||||
@ -29,8 +30,23 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
</article>
|
</article>
|
||||||
|
{% if event.possibly_duplicated %}
|
||||||
|
<article>
|
||||||
|
<head>
|
||||||
|
<h2>Possiblement dupliqués</h2>
|
||||||
|
</head>
|
||||||
|
<ul>
|
||||||
|
{% for e in event.get_possibly_duplicated %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ e.get_absolute_url }}">{{ e }}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</article>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ from django.http import HttpResponseRedirect
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
import urllib
|
import urllib
|
||||||
|
|
||||||
from .forms import EventSubmissionForm, EventForm
|
from .forms import EventSubmissionForm, EventForm, BatchImportationForm
|
||||||
|
|
||||||
from .models import Event, Category, StaticContent, ContactMessage, BatchImportation
|
from .models import Event, Category, StaticContent, ContactMessage, BatchImportation
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
@ -484,21 +484,22 @@ def imports(request):
|
|||||||
return render(request, 'agenda_culturel/imports.html', {'paginator_filter': response} )
|
return render(request, 'agenda_culturel/imports.html', {'paginator_filter': response} )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BatchImportationCreateView(SuccessMessageMixin, LoginRequiredMixin, CreateView):
|
class BatchImportationCreateView(SuccessMessageMixin, LoginRequiredMixin, CreateView):
|
||||||
model = BatchImportation
|
model = BatchImportation
|
||||||
fields = ['source', 'browsable_url']
|
|
||||||
|
|
||||||
success_url = reverse_lazy('imports')
|
success_url = reverse_lazy('imports')
|
||||||
success_message = _('The import has been run successfully.')
|
success_message = _('The import has been run successfully.')
|
||||||
|
|
||||||
|
form_class = BatchImportationForm
|
||||||
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
|
|
||||||
# run import
|
# run import
|
||||||
if "json" in form.data and form.data["json"] is not None and form.data["json"].strip() != "":
|
if "json" in form.data and form.data["json"] is not None and form.data["json"].strip() != "":
|
||||||
result = import_events_from_json.delay(form.data["json"])
|
result = import_events_from_json.delay(form.data["json"])
|
||||||
else:
|
else:
|
||||||
result = import_events_from_url.delay(self.object.source, self.object.browsable_url)
|
result = import_events_from_url.delay(form.data["source"], form.data["browsable_url"])
|
||||||
|
|
||||||
# update the object with celery_id
|
# update the object with celery_id
|
||||||
form.instance.celery_id = result.id
|
form.instance.celery_id = result.id
|
||||||
|
Loading…
Reference in New Issue
Block a user