parent
96401b6519
commit
37817cc8f5
@ -10,7 +10,8 @@ from .models import (
|
||||
RecurrentImport,
|
||||
Place,
|
||||
ContactMessage,
|
||||
ReferenceLocation
|
||||
ReferenceLocation,
|
||||
Organisation
|
||||
)
|
||||
from django_better_admin_arrayfield.admin.mixins import DynamicArrayMixin
|
||||
from django_better_admin_arrayfield.forms.widgets import DynamicArrayWidget
|
||||
@ -26,6 +27,7 @@ admin.site.register(RecurrentImport)
|
||||
admin.site.register(Place)
|
||||
admin.site.register(ContactMessage)
|
||||
admin.site.register(ReferenceLocation)
|
||||
admin.site.register(Organisation)
|
||||
|
||||
|
||||
class URLWidget(DynamicArrayWidget):
|
||||
|
@ -162,13 +162,14 @@ def run_recurrent_import_internal(rimport, downloader, req_id):
|
||||
location = rimport.defaultLocation
|
||||
tags = rimport.defaultTags
|
||||
published = rimport.defaultPublished
|
||||
organisers = [] if rimport.defaultOrganiser is None else [rimport.defaultOrganiser.pk]
|
||||
|
||||
try:
|
||||
# get events from website
|
||||
events = u2e.process(
|
||||
url,
|
||||
browsable_url,
|
||||
default_values={"category": category, "location": location, "tags": tags},
|
||||
default_values={"category": category, "location": location, "tags": tags, "organisers": organisers},
|
||||
published=published,
|
||||
)
|
||||
|
||||
|
@ -186,6 +186,7 @@ class EventForm(ModelForm):
|
||||
super().__init__(*args, **kwargs)
|
||||
if not is_authenticated:
|
||||
del self.fields["status"]
|
||||
del self.fields["organisers"]
|
||||
self.fields['category'].queryset = self.fields['category'].queryset.order_by('name')
|
||||
self.fields['category'].empty_label = None
|
||||
self.fields['category'].initial = Category.get_default_category()
|
||||
@ -262,6 +263,7 @@ class EventModerateForm(ModelForm):
|
||||
fields = [
|
||||
"status",
|
||||
"category",
|
||||
"organisers",
|
||||
"exact_location",
|
||||
"tags"
|
||||
]
|
||||
|
@ -187,6 +187,7 @@ class Extractor(ABC):
|
||||
"start_day": start_day,
|
||||
"uuids": uuids,
|
||||
"location": location if location else self.default_value_if_exists(default_values, "location"),
|
||||
"organisers": self.default_value_if_exists(default_values, "organisers"),
|
||||
"description": description,
|
||||
"tags": tags + tags_default,
|
||||
"published": published,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,35 @@
|
||||
# Generated by Django 4.2.9 on 2024-11-22 10:12
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django_ckeditor_5.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agenda_culturel', '0113_remove_tag_category'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Organisation',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(help_text='Organisation name', max_length=512, unique=True, verbose_name='Name')),
|
||||
('website', models.URLField(blank=True, help_text='Website of the organisation', max_length=1024, null=True, verbose_name='Website')),
|
||||
('description', django_ckeditor_5.fields.CKEditor5Field(blank=True, help_text='Description of the organisation.', null=True, verbose_name='Description')),
|
||||
('principal_place', models.ForeignKey(blank=True, help_text='Place mainly associated with this organizer. Mainly used if there is a similarity in the name, to avoid redundant displays.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='agenda_culturel.place', verbose_name='Principal place')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='organisers',
|
||||
field=models.ManyToManyField(blank=True, help_text='list of event organisers. Organizers will only be displayed if one of them does not normally use the venue.', related_name='organised_events', to='agenda_culturel.organisation', verbose_name='Location (free form)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='recurrentimport',
|
||||
name='defaultOrganiser',
|
||||
field=models.ForeignKey(blank=True, default=None, help_text='Organiser of each imported event', null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='agenda_culturel.organisation', verbose_name='Organiser'),
|
||||
),
|
||||
]
|
@ -0,0 +1,22 @@
|
||||
# Generated by Django 4.2.9 on 2024-11-22 10:30
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('agenda_culturel', '0114_organisation_event_organisers_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='organisation',
|
||||
options={'verbose_name': 'Organisation', 'verbose_name_plural': 'Organisations'},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='organisers',
|
||||
field=models.ManyToManyField(blank=True, help_text='list of event organisers. Organizers will only be displayed if one of them does not normally use the venue.', related_name='organised_events', to='agenda_culturel.organisation', verbose_name='Organisers'),
|
||||
),
|
||||
]
|
@ -479,6 +479,46 @@ class Place(models.Model):
|
||||
tags = []
|
||||
return tags
|
||||
|
||||
class Organisation(models.Model):
|
||||
name = models.CharField(
|
||||
verbose_name=_("Name"), help_text=_("Organisation name"), max_length=512, null=False, unique=True
|
||||
)
|
||||
|
||||
website = models.URLField(
|
||||
verbose_name=_("Website"),
|
||||
help_text=_("Website of the organisation"),
|
||||
max_length=1024,
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
|
||||
description = CKEditor5Field(
|
||||
verbose_name=_("Description"),
|
||||
help_text=_("Description of the organisation."),
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
|
||||
principal_place = models.ForeignKey(
|
||||
Place,
|
||||
verbose_name=_("Principal place"),
|
||||
help_text=_("Place mainly associated with this organizer. Mainly used if there is a similarity in the name, to avoid redundant displays."),
|
||||
null=True,
|
||||
on_delete=models.SET_NULL,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Organisation")
|
||||
verbose_name_plural = _("Organisations")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("view_organisation", kwargs={'pk': self.pk})
|
||||
|
||||
|
||||
|
||||
class Event(models.Model):
|
||||
class STATUS(models.TextChoices):
|
||||
@ -556,6 +596,15 @@ class Event(models.Model):
|
||||
blank=True
|
||||
)
|
||||
|
||||
organisers = models.ManyToManyField(Organisation,
|
||||
related_name='organised_events',
|
||||
verbose_name=_("Organisers"),
|
||||
help_text=_(
|
||||
"list of event organisers. Organizers will only be displayed if one of them does not normally use the venue."
|
||||
),
|
||||
blank=True
|
||||
)
|
||||
|
||||
description = models.TextField(
|
||||
verbose_name=_("Description"),
|
||||
help_text=_("General description of the event"),
|
||||
@ -754,6 +803,19 @@ class Event(models.Model):
|
||||
|
||||
return self.other_versions.get_local_version()
|
||||
|
||||
def get_shown_organisers(self):
|
||||
if self.organisers.count() == 0:
|
||||
return None
|
||||
if self.exact_location is None:
|
||||
has_significant = True
|
||||
else:
|
||||
has_significant = self.organisers.filter(~Q(principal_place=self.exact_location)).count() > 0
|
||||
|
||||
if has_significant:
|
||||
return self.organisers.all()
|
||||
else:
|
||||
return None
|
||||
|
||||
def nb_draft_events():
|
||||
return Event.objects.filter(status=Event.STATUS.DRAFT).count()
|
||||
|
||||
@ -779,6 +841,12 @@ class Event(models.Model):
|
||||
# if the download is ok, then create the corresponding file object
|
||||
self.local_image = File(name=basename, file=open(tmpfile, "rb"))
|
||||
|
||||
def add_pending_organisers(self, organisers):
|
||||
self.pending_organisers = organisers
|
||||
|
||||
def has_pending_organisers(self):
|
||||
return hasattr(self, "pending_organisers")
|
||||
|
||||
def set_skip_duplicate_check(self):
|
||||
self.skip_duplicate_check = True
|
||||
|
||||
@ -920,29 +988,38 @@ class Event(models.Model):
|
||||
self.recurrence_dtend = self.recurrence_dtstart
|
||||
|
||||
def prepare_save(self):
|
||||
logger.warning('AAA')
|
||||
self.update_modification_dates()
|
||||
logger.warning('BBB')
|
||||
|
||||
self.update_recurrence_dtstartend()
|
||||
logger.warning('CCC')
|
||||
|
||||
# if the image is defined but not locally downloaded
|
||||
if self.image and not self.local_image:
|
||||
self.download_image()
|
||||
logger.warning('DDD')
|
||||
|
||||
# remove "/" from tags
|
||||
if self.tags:
|
||||
self.tags = [t.replace('/', '-') for t in self.tags]
|
||||
logger.warning('EEE')
|
||||
|
||||
# in case of importation process
|
||||
if self.is_in_importation_process():
|
||||
logger.warning('EE1')
|
||||
# try to detect location
|
||||
if not self.exact_location:
|
||||
for p in Place.objects.all():
|
||||
if p.match(self):
|
||||
self.exact_location = p
|
||||
break
|
||||
logger.warning('EE2')
|
||||
# try to detect category
|
||||
if not self.category or self.category.name == Category.default_name:
|
||||
CategorisationRule.apply_rules(self)
|
||||
logger.warning('EE3')
|
||||
logger.warning('FFF')
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.prepare_save()
|
||||
@ -991,6 +1068,10 @@ class Event(models.Model):
|
||||
e.save()
|
||||
|
||||
def from_structure(event_structure, import_source=None):
|
||||
logger.warning("from structure")
|
||||
# organisers is a manytomany relation thus cannot be initialised before creation of the event
|
||||
organisers = event_structure.pop('organisers', None)
|
||||
|
||||
if "category" in event_structure and event_structure["category"] is not None:
|
||||
try:
|
||||
event_structure["category"] = Category.objects.get(
|
||||
@ -1066,7 +1147,11 @@ class Event(models.Model):
|
||||
if import_source is not None:
|
||||
event_structure["import_sources"] = [import_source]
|
||||
|
||||
return Event(**event_structure)
|
||||
result = Event(**event_structure)
|
||||
result.add_pending_organisers(organisers)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def find_similar_events(self):
|
||||
start_time_test = Q(start_time=self.start_time)
|
||||
@ -1164,10 +1249,24 @@ class Event(models.Model):
|
||||
def masked(self):
|
||||
return self.other_versions and self.other_versions.representative != self
|
||||
|
||||
def get_organisers(self):
|
||||
if self.pk:
|
||||
return self.organisers.all()
|
||||
else:
|
||||
if self.has_pending_organisers():
|
||||
return self.pending_organisers
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
def get_comparison(events, all=True):
|
||||
result = []
|
||||
for attr in Event.data_fields(all=all, local_img=False, exact_location=False):
|
||||
values = [getattr(e, attr) for e in events]
|
||||
if attr == 'organisers':
|
||||
values = [[str(o) for o in e.get_organisers()] for e in events]
|
||||
logger.warning("values: " + str(values))
|
||||
else:
|
||||
values = [getattr(e, attr) for e in events]
|
||||
values = ["" if v is None else v for v in values]
|
||||
values = [[] if attr == "tags" and v == "" else v for v in values]
|
||||
# only consider fixed part of Facebook urls
|
||||
@ -1218,7 +1317,7 @@ class Event(models.Model):
|
||||
elist = list(events) + ([self] if self.pk is not None else [])
|
||||
Event.objects.bulk_update(elist, fields=["other_versions"])
|
||||
|
||||
def data_fields(local_img=True, exact_location=True, all=True):
|
||||
def data_fields(local_img=True, exact_location=True, all=True, no_m2m=False):
|
||||
result = []
|
||||
|
||||
if all:
|
||||
@ -1237,6 +1336,8 @@ class Event(models.Model):
|
||||
"description",
|
||||
"image",
|
||||
]
|
||||
if not no_m2m:
|
||||
result += ["organisers"]
|
||||
if all and local_img:
|
||||
result += ["local_image"]
|
||||
if all and exact_location:
|
||||
@ -1263,11 +1364,18 @@ class Event(models.Model):
|
||||
return events[0]
|
||||
|
||||
def update(self, other, all):
|
||||
|
||||
if other.has_pending_organisers():
|
||||
logger.warning("set dans le update")
|
||||
self.organisers.set(other.pending_organisers)
|
||||
|
||||
# set attributes
|
||||
for attr in Event.data_fields(all=all):
|
||||
for attr in Event.data_fields(all=all, no_m2m=True):
|
||||
logger.warning('on set l attribut ' + attr + ' sur ' + str(self.pk) + ' avec valeur ' + str(getattr(other, attr)))
|
||||
setattr(self, attr, getattr(other, attr))
|
||||
|
||||
logger.warning('suite')
|
||||
|
||||
# adjust modified date if required
|
||||
if other.modified_date and self.modified_date < other.modified_date:
|
||||
self.modified_date = other.modified_date
|
||||
@ -1281,6 +1389,8 @@ class Event(models.Model):
|
||||
|
||||
# Limitation: the given events should not be considered similar one to another...
|
||||
def import_events(events, remove_missing_from_source=None):
|
||||
logger.warning("import_events")
|
||||
|
||||
to_import = []
|
||||
to_update = []
|
||||
|
||||
@ -1290,6 +1400,7 @@ class Event(models.Model):
|
||||
|
||||
# for each event, check if it's a new one, or a one to be updated
|
||||
for event in events:
|
||||
logger.warning("event " + str(event))
|
||||
sdate = date.fromisoformat(event.start_day)
|
||||
if event.end_day:
|
||||
edate = date.fromisoformat(event.end_day)
|
||||
@ -1304,14 +1415,20 @@ class Event(models.Model):
|
||||
if event.uuids and len(event.uuids) > 0:
|
||||
uuids |= set(event.uuids)
|
||||
|
||||
logger.warning("avant " + str(event))
|
||||
|
||||
# imported events should be updated
|
||||
event.set_in_importation_process()
|
||||
logger.warning("step " + str(event))
|
||||
event.prepare_save()
|
||||
|
||||
logger.warning("neeext " + str(event))
|
||||
|
||||
# check if the event has already be imported (using uuid)
|
||||
same_events = event.find_same_events_by_uuid()
|
||||
|
||||
if len(same_events) != 0:
|
||||
logger.warning("same non nuls")
|
||||
# check if one event has been imported and not modified in this list
|
||||
same_imported = Event.find_last_pure_import(same_events)
|
||||
pure = True
|
||||
@ -1331,14 +1448,15 @@ class Event(models.Model):
|
||||
if same_imported.other_versions.representative != same_imported:
|
||||
same_imported.other_versions.representative = None
|
||||
same_imported.other_versions.save()
|
||||
|
||||
logger.warning('on va y updater')
|
||||
same_imported.update(event, pure) # we only update all tags if it"s a pure import
|
||||
same_imported.set_in_importation_process()
|
||||
same_imported.prepare_save()
|
||||
to_update.append(same_imported)
|
||||
else:
|
||||
# otherwise, the new event possibly a duplication of the remaining others.
|
||||
|
||||
logger.warning("hop trash")
|
||||
|
||||
# check if it should be published
|
||||
trash = len([e for e in same_events if e.status != Event.STATUS.TRASH]) == 0
|
||||
if trash:
|
||||
@ -1359,14 +1477,24 @@ class Event(models.Model):
|
||||
# import this new event
|
||||
to_import.append(event)
|
||||
|
||||
logger.warning("apres boucle")
|
||||
# then import all the new events
|
||||
imported = Event.objects.bulk_create(to_import)
|
||||
# update organisers (m2m relation)
|
||||
for i, ti in zip(imported, to_import):
|
||||
if ti.has_pending_organisers():
|
||||
logger.warning("set apres bulk create " + str(i.pk))
|
||||
|
||||
i.organisers.set(ti.pending_organisers)
|
||||
|
||||
nb_updated = Event.objects.bulk_update(
|
||||
to_update,
|
||||
fields=Event.data_fields()
|
||||
fields=Event.data_fields(no_m2m=True)
|
||||
+ ["imported_date", "modified_date", "uuids", "status"],
|
||||
)
|
||||
|
||||
logger.warning("avant remove")
|
||||
|
||||
nb_draft = 0
|
||||
if remove_missing_from_source is not None and max_date is not None:
|
||||
# events that are missing from the import but in database are turned into drafts
|
||||
@ -1400,6 +1528,8 @@ class Event(models.Model):
|
||||
|
||||
nb_draft = Event.objects.bulk_update(to_draft, fields=["status"])
|
||||
|
||||
logger.warning("fin ça fait fin")
|
||||
|
||||
return imported, nb_updated, nb_draft
|
||||
|
||||
def set_current_date(self, date):
|
||||
@ -1678,6 +1808,17 @@ class RecurrentImport(models.Model):
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
defaultOrganiser = models.ForeignKey(
|
||||
Organisation,
|
||||
verbose_name=_("Organiser"),
|
||||
help_text=_("Organiser of each imported event"),
|
||||
default=None,
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_DEFAULT,
|
||||
)
|
||||
|
||||
defaultCategory = models.ForeignKey(
|
||||
Category,
|
||||
verbose_name=_("Category"),
|
||||
|
@ -271,6 +271,12 @@ svg {
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 600px) {
|
||||
.details-entete {
|
||||
padding-left: 28%;
|
||||
}
|
||||
}
|
||||
|
||||
.ephemeris-hour {
|
||||
@extend .ephemeris;
|
||||
padding: 1.5em 0.1em;
|
||||
@ -1385,7 +1391,7 @@ img.preview {
|
||||
scroll-margin-top: 7em;
|
||||
}
|
||||
|
||||
.a-venir, .place, .tag, .tag-descriptions {
|
||||
.a-venir, .place, .tag, .tag-descriptions, .organisation {
|
||||
article#filters {
|
||||
margin: 2em 0;
|
||||
}
|
||||
|
@ -122,5 +122,17 @@ Duplication de {% else %}
|
||||
}
|
||||
);
|
||||
|
||||
const organisers = document.querySelector('#id_organisers');
|
||||
const choices_organisers = new Choices(organisers,
|
||||
{
|
||||
placeholderValue: 'Sélectionner les organisateurs ',
|
||||
allowHTML: true,
|
||||
delimiter: ',',
|
||||
removeItemButton: true,
|
||||
shouldSort: true,
|
||||
callbackOnCreateTemplates: () => (show_firstgroup)
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
@ -92,6 +92,18 @@
|
||||
callbackOnCreateTemplates: () => (show_firstgroup)
|
||||
}
|
||||
);
|
||||
const organisers = document.querySelector('#id_organisers');
|
||||
const choices_organisers = new Choices(organisers,
|
||||
{
|
||||
placeholderValue: 'Sélectionner les organisateurs ',
|
||||
allowHTML: true,
|
||||
delimiter: ',',
|
||||
removeItemButton: true,
|
||||
shouldSort: true,
|
||||
callbackOnCreateTemplates: () => (show_firstgroup)
|
||||
});
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
@ -4,9 +4,11 @@
|
||||
<a href="?page={{ page_obj.previous_page_number }}" role="button">précédent</a>
|
||||
{% endif %}
|
||||
|
||||
{% if page_obj.paginator.num_pages != 1 %}
|
||||
<span>
|
||||
Page {{ page_obj.number }} sur {{ page_obj.paginator.num_pages }}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<a href="?page={{ page_obj.next_page_number }}" role="button">suivant</a>
|
||||
|
@ -0,0 +1,25 @@
|
||||
{% extends "agenda_culturel/page.html" %}
|
||||
|
||||
{% block title %}{% block og_title %}Supprimer l'organisateur {{ object.name }}{% endblock %}{% endblock %}
|
||||
|
||||
{% block fluid %}{% endblock %}
|
||||
|
||||
{% block configurer-bouton %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<article>
|
||||
<header>
|
||||
<h1>Supprimer l'organisateur {{ object.name }}</h1>
|
||||
</header>
|
||||
<form method="post">{% csrf_token %}
|
||||
<p>Êtes-vous sûr·e de vouloir supprimer l'organisateur « {{ object.name }} ({{ object.pk }}) » ?</p>
|
||||
{{ form }}
|
||||
<div class="grid buttons">
|
||||
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{{ object.get_absolute_url }}{% endif %}" role="button" class="secondary">Annuler</a>
|
||||
<input type="submit" value="Confirmer">
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,84 @@
|
||||
{% extends "agenda_culturel/page.html" %}
|
||||
|
||||
{% block title %}{% block og_title %}{{ object.name }}{% endblock %}{% endblock %}
|
||||
|
||||
{% load tag_extra %}
|
||||
{% load utils_extra %}
|
||||
{% load cat_extra %}
|
||||
{% load static %}
|
||||
{% load cache %}
|
||||
{% load i18n %}
|
||||
{% load l10n %}
|
||||
|
||||
{% block entete_header %}
|
||||
{% css_categories %}
|
||||
<script src="/static/admin/js/vendor/jquery/jquery.js"></script>
|
||||
<script src="/static/admin/js/jquery.init.js"></script>
|
||||
<script src="{% static 'location_field/leaflet/leaflet.js' %}"></script>
|
||||
<link href="{% static 'location_field/leaflet/leaflet.css' %}" type="text/css" media="all" rel="stylesheet">
|
||||
{% endblock %}
|
||||
|
||||
{% block fluid %}{% endblock %}
|
||||
|
||||
{% block body-class %}organisation{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<article>
|
||||
<header>
|
||||
<a href="{% url 'view_organisations' %}" role="button">{% picto_from_name "chevron-left" %} Toutes les organisations</a>
|
||||
{% if perms.agenda_culturel.change_organisation %}
|
||||
<div class="slide-buttons">
|
||||
<a href="{% url 'edit_organisation' object.pk %}" role="button">Modifier {% picto_from_name "edit-3" %}</a>
|
||||
<a href="{% url 'delete_organisation' object.pk %}" role="button">Supprimer {% picto_from_name "trash-2" %}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<h1>{{ object.name }}</h1>
|
||||
{% if object.website or object.principal_place %}
|
||||
<ul>
|
||||
{% if object.website %}
|
||||
<li><strong>Site internet :</strong> <a href="{{ object.website }}">{{ object.website }}</a></li>
|
||||
{% endif %}
|
||||
{% if object.principal_place %}
|
||||
<li><strong>Lieu principal :</strong> <a href="{{ object.principal_place.get_absolute_url }}">{{ object.principal_place }}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{{ object.description|safe }}
|
||||
</header>
|
||||
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
{% with cache_timeout=user.is_authenticated|yesno:"30,600" %}
|
||||
{% cache cache_timeout organisation_list user.is_authenticated object page_obj.number past %}
|
||||
<div class="slide-buttons">
|
||||
{% if past %}
|
||||
<a href="{{ object.get_absolute_url }}" role="button">Voir les événements à venir</a>
|
||||
{% else %}
|
||||
<a href="{% url 'view_organisation_past' object.pk %}" role="button">Voir les événements passés</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if past %}
|
||||
<h2>Événements passés</h2>
|
||||
{% else %}
|
||||
<h2>Événements à venir</h2>
|
||||
{% endif %}
|
||||
{% if object_list %}
|
||||
|
||||
{% include "agenda_culturel/navigation.html" with page_obj=page_obj %}
|
||||
|
||||
{% for event in object_list %}
|
||||
{% include "agenda_culturel/single-event/event-elegant-inc.html" with event=event day=0 no_location=1 %}
|
||||
{% endfor %}
|
||||
|
||||
{% include "agenda_culturel/navigation.html" with page_obj=page_obj %}
|
||||
|
||||
{% else %}
|
||||
<p><em>Aucun événement</em></p>
|
||||
{% endif %}
|
||||
{% endcache %}
|
||||
{% endwith %}
|
||||
<footer>
|
||||
</footer>
|
||||
</article>
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,33 @@
|
||||
{% extends "agenda_culturel/page-admin.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block entete_header %}
|
||||
<script src="{% url 'jsi18n' %}"></script>
|
||||
<script src="/static/admin/js/vendor/jquery/jquery.js"></script>
|
||||
<script src="/static/admin/js/jquery.init.js"></script>
|
||||
<script>window.CKEDITOR_BASEPATH = '/static/ckeditor/ckeditor/';</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}{% block og_title %}{% if object %}Description de {{ object.name }}{% else %}Description d'un organisme{% endif %}{% endblock %}{% endblock %}
|
||||
|
||||
{% block fluid %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<article>
|
||||
<header>
|
||||
<h1>{% if object %}Description de {{ object.name }}{% else %}Description d'un organisme{% endif %}</h1>
|
||||
</header>
|
||||
|
||||
<form method="post">{% csrf_token %}
|
||||
{{ form.media }}
|
||||
{{ form.as_p }}
|
||||
|
||||
<div class="grid buttons">
|
||||
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{% if object %}{{ object.get_absolute_url }}{% else %}{% url 'view_organisations' %}{% endif %}{% endif %}" role="button" class="secondary">Annuler</a>
|
||||
<input type="submit" value="Envoyer">
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,76 @@
|
||||
{% extends "agenda_culturel/page.html" %}
|
||||
|
||||
{% block title %}{% block og_title %}Liste des organismes{% endblock %}{% endblock %}
|
||||
|
||||
{% block fluid %}{% endblock %}
|
||||
|
||||
{% load utils_extra %}
|
||||
{% load cat_extra %}
|
||||
{% block entete_header %}
|
||||
{% css_categories %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidemenu-bouton %}
|
||||
<li><a href="#contenu-principal" aria-label="Aller au contenu">{% picto_from_name "chevron-up" %}</a></li>
|
||||
<li><a href="#sidebar" aria-label="Aller au menu latéral">{% picto_from_name "chevron-down" %}</a></li>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
<header>
|
||||
{% if user.is_authenticated %}
|
||||
<div class="slide-buttons">
|
||||
<a href="{% url 'add_organisation' %}" role="button">Ajouter {% picto_from_name "plus-circle" %}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<h1>Liste des organismes</h1>
|
||||
</header>
|
||||
{% include "agenda_culturel/navigation.html" with page_obj=page_obj %}
|
||||
</article>
|
||||
|
||||
{% if object_list %}
|
||||
{% for organisation in object_list %}
|
||||
<article>
|
||||
<header>
|
||||
{% if user.is_authenticated %}
|
||||
<div class="slide-buttons">
|
||||
<a href="{% url 'edit_organisation' organisation.pk %}" role="button">Modifier {% picto_from_name "edit-3" %}</a>
|
||||
<a href="{% url 'delete_organisation' organisation.pk %}" role="button">Supprimer {% picto_from_name "trash-2" %}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<h2>{{ organisation.name }}</h2>
|
||||
</header>
|
||||
{% if organisation.website or organisation.principal_place %}
|
||||
<ul>
|
||||
{% if organisation.website %}
|
||||
<li><strong>Site internet :</strong> <a href="{{ organisation.website }}">{{ organisation.website }}</a></li>
|
||||
{% endif %}
|
||||
{% if organisation.principal_place %}
|
||||
<li><strong>Lieu principal :</strong> <a href="{{ organisation.principal_place.get_absolute_url }}">{{ organisation.principal_place }}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<footer>
|
||||
{% if organisation.organised_events.all|length > 1 %}
|
||||
<p class="slide-buttons"><a role="button" href="{{ organisation.get_absolute_url }}">voir les {{ organisation.organised_events.all|length }} événements {% picto_from_name "chevron-right" %}</a></p>
|
||||
{% else %}
|
||||
<p class="slide-buttons"><a role="button" href="{{ organisation.get_absolute_url }}">voir les détails {% picto_from_name "chevron-right" %}</a></p>
|
||||
{% endif %}
|
||||
<div style="clear: both"></div>
|
||||
</footer>
|
||||
</article>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p>Il n'y a aucun organisme défini.</p>
|
||||
{% endif %}
|
||||
|
||||
<article>
|
||||
<footer>
|
||||
{% include "agenda_culturel/navigation.html" with page_obj=page_obj %}
|
||||
</footer>
|
||||
|
||||
</article>
|
||||
|
||||
|
||||
{% endblock %}
|
@ -107,6 +107,9 @@
|
||||
<div>
|
||||
<a href="{% url 'view_places' %}">{% picto_from_name "map-pin" %} lieux</a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{% url 'view_places' %}">{% picto_from_name "users" %} organisateurs</a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{% url 'view_all_tags' %}">{% picto_from_name "tag" %} étiquettes</a>
|
||||
</div>
|
||||
|
@ -49,10 +49,28 @@
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</ul>
|
||||
{% if not object.description|html_vide %}
|
||||
|
||||
{% if object.description and not object.description|html_vide %}
|
||||
<h2>Description du lieu</h2>
|
||||
{{ object.description|safe }}
|
||||
{% endif %}
|
||||
|
||||
{% with object.organisation_set.all as organisations %}
|
||||
{% if organisations|length == 1 %}
|
||||
<p>L'organisme <a href="{{ organisations.0.get_absolute_url }}">{{ organisations.0 }}</a> organise régulièrement des événements dans ce lieu.</p>
|
||||
{% endif %}
|
||||
{% if organisations|length > 1 %}
|
||||
<p>Les organismes suivants utilisent régulièrement ce lieu :</p>
|
||||
<ul>
|
||||
{% for o in organisations %}
|
||||
<li><a href="{{ o.get_absolute_url }}">{{ o }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% endwith %}
|
||||
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<div id="map_location" style="width: 100%; aspect-ratio: 16/16"></div>
|
||||
@ -78,12 +96,12 @@
|
||||
<a href="{% url 'view_place_past' object.pk %}" role="button">Voir les événements passés</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if past %}
|
||||
<h2>Événements passés</h2>
|
||||
{% else %}
|
||||
<h2>Événements à venir</h2>
|
||||
{% endif %}
|
||||
{% if object_list %}
|
||||
{% if past %}
|
||||
<h2>Événements passés</h2>
|
||||
{% else %}
|
||||
<h2>Événements à venir</h2>
|
||||
{% endif %}
|
||||
|
||||
{% include "agenda_culturel/navigation.html" with page_obj=page_obj %}
|
||||
|
||||
|
@ -24,13 +24,16 @@
|
||||
<li><a {% if current == "activite" %}class="selected" {% endif %}href="{% url 'activite' %}">Résumé des activités</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% if perms.agenda_culturel.change_place %}
|
||||
<h3>Lieux</h3>
|
||||
{% if perms.agenda_culturel.change_place or perms.agenda_culturel.change_organisation %}
|
||||
<h3>Lieux et organisateurs</h3>
|
||||
<nav>
|
||||
<ul>
|
||||
{% if perms.agenda_culturel.change_place %}
|
||||
<li><a {% if current == "places" %}class="selected" {% endif %}href="{% url 'view_places_admin' %}">Liste des lieux</a></li>
|
||||
{% endif %}
|
||||
{% if perms.agenda_culturel.change_organisation %}
|
||||
<li><a {% if current == "organisations" %}class="selected" {% endif %}href="{% url 'view_organisations' %}">Liste des organisateurs</a></li>
|
||||
{% endif %}
|
||||
{% if perms.agenda_culturel.change_place and perms.agenda_culturel.change_event %}
|
||||
<li><a {% if current == "unknown_places" %}class="selected" {% endif %}href="{% url 'view_unknown_places' %}">Événements sans lieu</a>{% show_badge_unknown_places "left" %}</li>
|
||||
{% endif %}
|
||||
|
@ -83,8 +83,7 @@
|
||||
<p>{{ event.description |linebreaks2 | truncatewords:60 }}</p>
|
||||
</div>
|
||||
<div class="right bottom">
|
||||
<a role="button" href="{{ event.get_absolute_url }}">Voir l'événement <svg width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<use href="{% static 'images/feather-sprite.svg' %}#chevron-right" />
|
||||
<a role="button" href="{{ event.get_absolute_url }}">Voir l'événement {% picto_from_name "chevron-right" %}
|
||||
</svg></a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,6 +7,7 @@
|
||||
<article>
|
||||
<header>
|
||||
{% include "agenda_culturel/ephemeris-inc.html" with event=event filter=filter %}
|
||||
<div class="details-entete">
|
||||
{{ event.category | small_cat_recurrent:event.has_recurrences }}
|
||||
<h1>{{ event|picto_status }} {{ event.title }} {{ event|picto_visibility:user.is_authenticated }}</h1>
|
||||
<p>
|
||||
@ -18,6 +19,17 @@
|
||||
{% picto_from_name "map-pin" %}
|
||||
{% include "agenda_culturel/event-location-inc.html" with event=event %}
|
||||
</p>
|
||||
|
||||
{% with event.get_shown_organisers as organisers %}
|
||||
{% if organisers and organisers|length > 0 %}
|
||||
<p>{% picto_from_name "users" %} organisé par
|
||||
{% for o in organisers %}
|
||||
<a href="{{ o.get_absolute_url }}">{{ o }}</a>{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
|
||||
{% if event.other_versions %}
|
||||
@ -47,7 +59,7 @@
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</header>
|
||||
<div class="event-body">
|
||||
|
||||
|
@ -131,6 +131,16 @@ urlpatterns = [
|
||||
path("duplicates/<int:pk>/update/<int:epk>", update_duplicate_event, name="update_event"),
|
||||
path("404/", page_not_found, name="page_not_found"),
|
||||
path("500/", internal_server_error, name="internal_server_error"),
|
||||
|
||||
path("organisme/<int:pk>/past", OrganisationDetailViewPast.as_view(), name="view_organisation_past"),
|
||||
path("organisme/<int:pk>", OrganisationDetailView.as_view(), name="view_organisation"),
|
||||
path("organisme/<int:pk>-<extra>/past", OrganisationDetailViewPast.as_view(), name="view_organisation_past_fullname"),
|
||||
path("organisme/<int:pk>-<extra>", OrganisationDetailView.as_view(), name="view_organisation_fullname"),
|
||||
path("organisme/<int:pk>/edit", OrganisationUpdateView.as_view(), name="edit_organisation"),
|
||||
path("organisme/<int:pk>/delete", OrganisationDeleteView.as_view(), name="delete_organisation"),
|
||||
path("organismes/", OrganisationListView.as_view(), name="view_organisations"),
|
||||
path("organisme/add", OrganisationCreateView.as_view(), name="add_organisation"),
|
||||
|
||||
path("place/<int:pk>/past", PlaceDetailViewPast.as_view(), name="view_place_past"),
|
||||
path("place/<int:pk>", PlaceDetailView.as_view(), name="view_place"),
|
||||
path("place/<int:pk>-<extra>/past", PlaceDetailViewPast.as_view(), name="view_place_past_fullname"),
|
||||
|
@ -60,7 +60,8 @@ from .models import (
|
||||
CategorisationRule,
|
||||
remove_accents,
|
||||
Place,
|
||||
ReferenceLocation
|
||||
ReferenceLocation,
|
||||
Organisation
|
||||
)
|
||||
from django.utils import timezone
|
||||
from django.utils.html import escape
|
||||
@ -1229,9 +1230,12 @@ def update_duplicate_event(request, pk, epk):
|
||||
else:
|
||||
setattr(event, f["key"], sum(values, []))
|
||||
else:
|
||||
setattr(event, f["key"], getattr(selected, f["key"]))
|
||||
if f["key"] == "image":
|
||||
setattr(event, "local_image", getattr(selected, "local_image"))
|
||||
if f["key"] == 'organisers':
|
||||
event.organisers.set(selected.organisers.all())
|
||||
else:
|
||||
setattr(event, f["key"], getattr(selected, f["key"]))
|
||||
if f["key"] == "image":
|
||||
setattr(event, "local_image", getattr(selected, "local_image"))
|
||||
|
||||
event.other_versions.fix(event)
|
||||
event.save()
|
||||
@ -1839,6 +1843,69 @@ class PlaceFromEventCreateView(PlaceCreateView):
|
||||
return self.event.get_absolute_url()
|
||||
|
||||
|
||||
|
||||
#########################
|
||||
## Organisations
|
||||
#########################
|
||||
|
||||
|
||||
class OrganisationListView(ListView):
|
||||
model = Organisation
|
||||
paginate_by = 10
|
||||
ordering = ["name__unaccent"]
|
||||
|
||||
class OrganisationDetailView(ListView):
|
||||
model = Organisation
|
||||
template_name = "agenda_culturel/organisation_detail.html"
|
||||
paginate_by = 10
|
||||
|
||||
def get_queryset(self):
|
||||
self.organisation = Organisation.objects.filter(pk=self.kwargs["pk"]).prefetch_related('organised_events').first()
|
||||
return self.organisation.organised_events.filter(start_day__gte=datetime.now()).order_by("start_day")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["object"] = self.organisation
|
||||
return context
|
||||
|
||||
class OrganisationDetailViewPast(OrganisationDetailView):
|
||||
|
||||
def get_queryset(self):
|
||||
self.organisation = Organisation.objects.filter(pk=self.kwargs["pk"]).prefetch_related('organised_events').first()
|
||||
self.past = True
|
||||
return self.organisation.organised_events.filter(start_day__lte=datetime.now()).order_by("-start_day")
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["past"] = self.past
|
||||
return context
|
||||
|
||||
|
||||
|
||||
class OrganisationUpdateView(
|
||||
PermissionRequiredMixin, SuccessMessageMixin, UpdateView
|
||||
):
|
||||
model = Organisation
|
||||
permission_required = "agenda_culturel.change_organisation"
|
||||
success_message = _("The organisation has been successfully updated.")
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class OrganisationCreateView(
|
||||
PermissionRequiredMixin, SuccessMessageMixin, CreateView
|
||||
):
|
||||
model = Organisation
|
||||
permission_required = "agenda_culturel.add_organisation"
|
||||
success_message = _("The organisation has been successfully created.")
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class OrganisationDeleteView(PermissionRequiredMixin, DeleteView):
|
||||
model = Organisation
|
||||
permission_required = "agenda_culturel.delete_organisation"
|
||||
success_url = reverse_lazy("view_organisations")
|
||||
|
||||
#########################
|
||||
## Tags
|
||||
#########################
|
||||
|
Loading…
Reference in New Issue
Block a user