parent
3287085b70
commit
5dffc1f0b2
@ -327,6 +327,7 @@ class EventForm(GroupFormMixin, ModelForm):
|
|||||||
is_authenticated = kwargs.pop("is_authenticated", False)
|
is_authenticated = kwargs.pop("is_authenticated", False)
|
||||||
self.cloning = kwargs.pop("is_cloning", False)
|
self.cloning = kwargs.pop("is_cloning", False)
|
||||||
self.simple_cloning = kwargs.pop("is_simple_cloning", False)
|
self.simple_cloning = kwargs.pop("is_simple_cloning", False)
|
||||||
|
self.is_edit_from_moderation = kwargs.pop("is_edit_from_moderation", False)
|
||||||
self.is_moderation_expert = kwargs.pop("is_moderation_expert", False)
|
self.is_moderation_expert = kwargs.pop("is_moderation_expert", False)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if not is_authenticated:
|
if not is_authenticated:
|
||||||
|
18
src/agenda_culturel/migrations/0158_event_editing_start.py
Normal file
18
src/agenda_culturel/migrations/0158_event_editing_start.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.2.19 on 2025-03-14 20:26
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("agenda_culturel", "0157_auto_20250314_1645"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="event",
|
||||||
|
name="editing_start",
|
||||||
|
field=models.DateTimeField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
29
src/agenda_culturel/migrations/0159_event_editing_user.py
Normal file
29
src/agenda_culturel/migrations/0159_event_editing_user.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Generated by Django 4.2.19 on 2025-03-14 21:19
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
("agenda_culturel", "0158_event_editing_start"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="event",
|
||||||
|
name="editing_user",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_DEFAULT,
|
||||||
|
related_name="in_edition_events",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
verbose_name="Author currently editing/moderating the event",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -714,6 +714,17 @@ class Event(models.Model):
|
|||||||
modified_date = models.DateTimeField(blank=True, null=True)
|
modified_date = models.DateTimeField(blank=True, null=True)
|
||||||
moderated_date = models.DateTimeField(blank=True, null=True)
|
moderated_date = models.DateTimeField(blank=True, null=True)
|
||||||
|
|
||||||
|
editing_start = models.DateTimeField(blank=True, null=True)
|
||||||
|
editing_user = models.ForeignKey(
|
||||||
|
User,
|
||||||
|
verbose_name=_("Author currently editing/moderating the event"),
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
on_delete=models.SET_DEFAULT,
|
||||||
|
related_name="in_edition_events",
|
||||||
|
)
|
||||||
|
|
||||||
created_by_user = models.ForeignKey(
|
created_by_user = models.ForeignKey(
|
||||||
User,
|
User,
|
||||||
verbose_name=_("Author of the event creation"),
|
verbose_name=_("Author of the event creation"),
|
||||||
@ -920,6 +931,34 @@ class Event(models.Model):
|
|||||||
else:
|
else:
|
||||||
return self.end_day if self.end_day else self.start_day
|
return self.end_day if self.end_day else self.start_day
|
||||||
|
|
||||||
|
def is_modification_locked(self, now=None):
|
||||||
|
if now is None:
|
||||||
|
now = timezone.now()
|
||||||
|
limit = timezone.now() + timedelta(minutes=-10)
|
||||||
|
|
||||||
|
return self.editing_start is not None and self.editing_start > limit
|
||||||
|
|
||||||
|
def get_modification_lock(self, user):
|
||||||
|
now = timezone.now()
|
||||||
|
|
||||||
|
if not self.is_modification_locked(now):
|
||||||
|
self.editing_start = now
|
||||||
|
self.editing_user = user
|
||||||
|
self.save(update_fields=["editing_start", "editing_user"])
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def free_modification_lock(self, user, save=True):
|
||||||
|
if user != self.editing_user:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
self.editing_start = None
|
||||||
|
self.editing_user = None
|
||||||
|
if save:
|
||||||
|
self.save(update_fields=["editing_start", "editing_user"])
|
||||||
|
return True
|
||||||
|
|
||||||
def get_dates(self):
|
def get_dates(self):
|
||||||
first = self.start_day
|
first = self.start_day
|
||||||
last = self.get_consolidated_end_day()
|
last = self.get_consolidated_end_day()
|
||||||
@ -1351,6 +1390,10 @@ class Event(models.Model):
|
|||||||
self.moderated_date = now
|
self.moderated_date = now
|
||||||
self.moderated_by_user = self.processing_user
|
self.moderated_by_user = self.processing_user
|
||||||
|
|
||||||
|
# release editing lock
|
||||||
|
if self.processing_user is not None:
|
||||||
|
self.free_modification_lock(self.processing_user, False)
|
||||||
|
|
||||||
def get_recurrence_at_date(self, year, month, day):
|
def get_recurrence_at_date(self, year, month, day):
|
||||||
dtstart = timezone.make_aware(
|
dtstart = timezone.make_aware(
|
||||||
datetime(year, month, day, 0, 0), timezone.get_default_timezone()
|
datetime(year, month, day, 0, 0), timezone.get_default_timezone()
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load cat_extra %}
|
{% load cat_extra %}
|
||||||
{% load utils_extra %}
|
{% load utils_extra %}
|
||||||
|
{% load event_extra %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% block og_title %}
|
{% block og_title %}
|
||||||
{% if object %}
|
{% if object %}
|
||||||
@ -86,6 +87,21 @@
|
|||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</header>
|
</header>
|
||||||
|
{% if not form.is_clone_from_url and not form.is_simple_clone_from_url and not object|get_modification_lock:user %}
|
||||||
|
{% if object.editing_user == user %}
|
||||||
|
<div class="message info">
|
||||||
|
Attention, vous avez ouvert en édition ou modération cet
|
||||||
|
événement dans un autre onglet (le {{ object.editing_start }}), et toute modification intermédiaire sera effacée par la dernière.
|
||||||
|
Ce message peut aussi apparaître si vous avez utilisé la navigation arrière.
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="message warning">
|
||||||
|
Attention, <em>{{ object.editing_user }}</em> est en train de modifier ou de modérer cet
|
||||||
|
événement (depuis {{ object.editing_start }}), et toute modification de votre part pourrait être perdue ou écraser les modifications de l'autre personne.
|
||||||
|
Revenez plus tard !
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
<div class="grid moderate-preview">
|
<div class="grid moderate-preview">
|
||||||
<div id="event-preview">
|
<div id="event-preview">
|
||||||
{% include "agenda_culturel/single-event/event-single-inc.html" with event=event nobuttons=1 permalink=1 h=2 %}
|
{% include "agenda_culturel/single-event/event-single-inc.html" with event=event nobuttons=1 permalink=1 h=2 %}
|
||||||
@ -155,9 +171,15 @@
|
|||||||
{% if object %}
|
{% if object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid buttons">
|
<div class="grid buttons">
|
||||||
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{{ object.get_absolute_url }}{% endif %}"
|
{% if form.is_edit_from_moderation %}
|
||||||
role="button"
|
<a href="{% url 'moderate_event_force' event.pk %}"
|
||||||
class="secondary">Annuler</a>
|
class="secondary"
|
||||||
|
role="button">Annuler</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{{ object.get_absolute_url }}{% endif %}"
|
||||||
|
role="button"
|
||||||
|
class="secondary">Annuler</a>
|
||||||
|
{% endif %}
|
||||||
<input type="submit" value="Enregistrer">
|
<input type="submit" value="Enregistrer">
|
||||||
<input type="submit"
|
<input type="submit"
|
||||||
value="Enregistrer et passer au suivant >"
|
value="Enregistrer et passer au suivant >"
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load utils_extra %}
|
{% load utils_extra %}
|
||||||
{% load cat_extra %}
|
{% load cat_extra %}
|
||||||
|
{% load event_extra %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% block og_title %}Modération de l'événement {{ object.title }}{% endblock %}
|
{% block og_title %}Modération de l'événement {{ object.title }}{% endblock %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -41,11 +42,26 @@
|
|||||||
.
|
.
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
{% if not event|get_modification_lock:user %}
|
||||||
|
{% if event.editing_user == user %}
|
||||||
|
<div class="message info">
|
||||||
|
Attention, vous avez ouvert en édition ou modération cet
|
||||||
|
événement dans un autre onglet (le {{ event.editing_start }}), et toute modification intermédiaire sera effacée par la dernière.
|
||||||
|
Ce message peut aussi apparaître si vous avez utilisé la navigation arrière.
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="message warning">
|
||||||
|
Attention, <em>{{ event.editing_user }}</em> est en train de modifier ou de modérer cet
|
||||||
|
événement (le {{ event.editing_start }}), et toute modification de votre part pourrait être perdue ou écraser les modifications de l'autre personne.
|
||||||
|
Revenez plus tard !
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
<form method="post" enctype="multipart/form-data" id="moderate-form">
|
<form method="post" enctype="multipart/form-data" id="moderate-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="grid moderate-preview">
|
<div class="grid moderate-preview">
|
||||||
<div id="event-preview">
|
<div id="event-preview">
|
||||||
{% include "agenda_culturel/single-event/event-single-inc.html" with event=event onlyedit=1 permalink=1 h=2 %}
|
{% include "agenda_culturel/single-event/event-single-inc.html" with event=event editforce=1 permalink=1 h=2 %}
|
||||||
{% with event.get_concurrent_events as concurrent_events %}
|
{% with event.get_concurrent_events as concurrent_events %}
|
||||||
{% if concurrent_events %}
|
{% if concurrent_events %}
|
||||||
<article>
|
<article>
|
||||||
@ -95,13 +111,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="grid buttons">
|
<div class="grid buttons">
|
||||||
{% if pred %}
|
{% if pred %}
|
||||||
<a href="{% url 'moderate_event' pred %}"
|
<a href="{% url 'moderate_event_backstep' pred event.pk %}"
|
||||||
class="secondary"
|
class="secondary"
|
||||||
role="button">< Revenir au précédent</a>
|
role="button">< Revenir au précédent</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{% if request.META.HTTP_REFERER %}{{ request.META.HTTP_REFERER }}{% else %}{{ object.get_absolute_url }}{% endif %}"
|
<a href="{% url 'administration' %}" role="button" class="secondary">Annuler</a>
|
||||||
role="button"
|
|
||||||
class="secondary">Annuler</a>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<input type="submit" value="Enregistrer" name="save">
|
<input type="submit" value="Enregistrer" name="save">
|
||||||
<input type="submit"
|
<input type="submit"
|
||||||
|
@ -26,6 +26,17 @@
|
|||||||
{% css_categories %}
|
{% css_categories %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{% if event.is_modification_locked %}
|
||||||
|
{% if event.editing_user == user %}
|
||||||
|
<div class="message info">
|
||||||
|
Vous avez ouvert cet événement en édition ou en modération dans un autre onglet (le {{ event.editing_start }}).
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="message info">
|
||||||
|
<em>{{ event.editing_user }}</em> est en train de modifier ou de modérer cet événement (le {{ event.editing_start }}).
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
<div class="grid two-columns">
|
<div class="grid two-columns">
|
||||||
<div>
|
<div>
|
||||||
{% with cache_timeout=user.is_authenticated|yesno:"30,600" %}
|
{% with cache_timeout=user.is_authenticated|yesno:"30,600" %}
|
||||||
|
@ -205,7 +205,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% if not nobuttons %}
|
{% if not nobuttons %}
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
{% if onlyedit %}
|
{% if editforce %}
|
||||||
{% if event.pure_import %}
|
{% if event.pure_import %}
|
||||||
{% with event.get_local_version as local %}
|
{% with event.get_local_version as local %}
|
||||||
{% if local %}
|
{% if local %}
|
||||||
@ -215,7 +215,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{% url 'edit_event' event.id %}" role="button">modifier {% picto_from_name "edit-3" %}</a>
|
<a href="{% url 'edit_event_force' event.id %}" role="button">modifier {% picto_from_name "edit-3" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{% url 'export_event_ical' event.start_day.year event.start_day.month event.start_day.day event.id %}"
|
<a href="{% url 'export_event_ical' event.start_day.year event.start_day.month event.start_day.day event.id %}"
|
||||||
|
@ -233,3 +233,8 @@ def tw_badge(event):
|
|||||||
@register.filter
|
@register.filter
|
||||||
def get_image_uri(event, request):
|
def get_image_uri(event, request):
|
||||||
return event.get_image_url(request)
|
return event.get_image_url(request)
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def get_modification_lock(event, user):
|
||||||
|
return event.get_modification_lock(user)
|
||||||
|
@ -201,16 +201,29 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path("event/<int:pk>/", EventDetailView.as_view(), name="edit_event_pk"),
|
path("event/<int:pk>/", EventDetailView.as_view(), name="edit_event_pk"),
|
||||||
path("event/<int:pk>/edit", EventUpdateView.as_view(), name="edit_event"),
|
path("event/<int:pk>/edit", EventUpdateView.as_view(), name="edit_event"),
|
||||||
|
path(
|
||||||
|
"event/<int:pk>/edit-force", EventUpdateView.as_view(), name="edit_event_force"
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"event/<int:pk>/moderate",
|
"event/<int:pk>/moderate",
|
||||||
EventModerateView.as_view(),
|
EventModerateView.as_view(),
|
||||||
name="moderate_event",
|
name="moderate_event",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"event/<int:pk>/moderate-force",
|
||||||
|
EventModerateView.as_view(),
|
||||||
|
name="moderate_event_force",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"event/<int:pk>/moderate/after/<int:pred>",
|
"event/<int:pk>/moderate/after/<int:pred>",
|
||||||
EventModerateView.as_view(),
|
EventModerateView.as_view(),
|
||||||
name="moderate_event_step",
|
name="moderate_event_step",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"event/<int:pk>/moderate/back/<int:pred>",
|
||||||
|
EventModerateView.as_view(),
|
||||||
|
name="moderate_event_backstep",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"event/<int:pk>/moderate-next",
|
"event/<int:pk>/moderate-next",
|
||||||
moderate_event_next,
|
moderate_event_next,
|
||||||
|
@ -478,6 +478,7 @@ class EventUpdateView(
|
|||||||
self.request.user.userprofile.is_moderation_expert
|
self.request.user.userprofile.is_moderation_expert
|
||||||
)
|
)
|
||||||
kwargs["is_cloning"] = self.is_cloning
|
kwargs["is_cloning"] = self.is_cloning
|
||||||
|
kwargs["is_edit_from_moderation"] = self.is_edit_force()
|
||||||
kwargs["is_simple_cloning"] = self.is_simple_cloning
|
kwargs["is_simple_cloning"] = self.is_simple_cloning
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
@ -489,10 +490,15 @@ class EventUpdateView(
|
|||||||
)
|
)
|
||||||
return mark_safe(_("The event has been successfully modified.") + txt)
|
return mark_safe(_("The event has been successfully modified.") + txt)
|
||||||
|
|
||||||
|
def is_edit_force(self):
|
||||||
|
return "edit-force" in self.request.path.split("/")
|
||||||
|
|
||||||
def get_object(self, queryset=None):
|
def get_object(self, queryset=None):
|
||||||
event = super().get_object(queryset)
|
event = super().get_object(queryset)
|
||||||
if event.status == Event.STATUS.DRAFT:
|
if event.status == Event.STATUS.DRAFT:
|
||||||
event.status = Event.STATUS.PUBLISHED
|
event.status = Event.STATUS.PUBLISHED
|
||||||
|
if self.is_edit_force():
|
||||||
|
event.free_modification_lock(self.request.user)
|
||||||
return event
|
return event
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
@ -576,6 +582,12 @@ class EventModerateView(
|
|||||||
def is_moderate_next(self):
|
def is_moderate_next(self):
|
||||||
return "after" in self.request.path.split("/")
|
return "after" in self.request.path.split("/")
|
||||||
|
|
||||||
|
def is_moderate_back(self):
|
||||||
|
return "back" in self.request.path.split("/")
|
||||||
|
|
||||||
|
def is_moderate_force(self):
|
||||||
|
return "moderate-force" in self.request.path.split("/")
|
||||||
|
|
||||||
def is_starting_moderation(self):
|
def is_starting_moderation(self):
|
||||||
return "pk" not in self.kwargs
|
return "pk" not in self.kwargs
|
||||||
|
|
||||||
@ -627,6 +639,11 @@ class EventModerateView(
|
|||||||
event = super().get_object(queryset)
|
event = super().get_object(queryset)
|
||||||
if event.status == Event.STATUS.DRAFT:
|
if event.status == Event.STATUS.DRAFT:
|
||||||
event.status = Event.STATUS.PUBLISHED
|
event.status = Event.STATUS.PUBLISHED
|
||||||
|
if self.is_moderate_back():
|
||||||
|
pred = Event.objects.filter(pk=self.kwargs["pred"]).first()
|
||||||
|
pred.free_modification_lock(self.request.user)
|
||||||
|
if self.is_moderate_force():
|
||||||
|
event.free_modification_lock(self.request.user, False)
|
||||||
return event
|
return event
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
@ -670,6 +687,8 @@ def error_next_event(request, pk):
|
|||||||
def moderate_event_next(request, pk):
|
def moderate_event_next(request, pk):
|
||||||
# current event
|
# current event
|
||||||
obj = Event.objects.filter(pk=pk).first()
|
obj = Event.objects.filter(pk=pk).first()
|
||||||
|
# free lock
|
||||||
|
obj.free_modification_lock(request.user)
|
||||||
start_day = obj.start_day
|
start_day = obj.start_day
|
||||||
start_time = obj.start_time
|
start_time = obj.start_time
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user