manager: allow duplication of events (#67292)
This commit is contained in:
parent
963871ce43
commit
bca73caaea
|
@ -1709,9 +1709,13 @@ class Event(models.Model):
|
|||
'duration': self.duration,
|
||||
}
|
||||
|
||||
def duplicate(self, agenda_target=None, primary_event=None):
|
||||
def duplicate(self, agenda_target=None, primary_event=None, label=None, start_datetime=None):
|
||||
new_event = copy.deepcopy(self)
|
||||
new_event.pk = None
|
||||
if label:
|
||||
new_event.label = label
|
||||
if start_datetime:
|
||||
new_event.start_datetime = start_datetime
|
||||
if agenda_target:
|
||||
new_event.agenda = agenda_target
|
||||
else:
|
||||
|
|
|
@ -352,6 +352,27 @@ class EventForm(NewEventForm):
|
|||
return self.instance
|
||||
|
||||
|
||||
class EventDuplicateForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Event
|
||||
fields = [
|
||||
'label',
|
||||
'start_datetime',
|
||||
]
|
||||
field_classes = {
|
||||
'start_datetime': SplitDateTimeField,
|
||||
}
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
with transaction.atomic():
|
||||
self.instance = self.instance.duplicate(
|
||||
label=self.cleaned_data["label"], start_datetime=self.cleaned_data["start_datetime"]
|
||||
)
|
||||
if self.instance.recurrence_days:
|
||||
self.instance.create_all_recurrences()
|
||||
return self.instance
|
||||
|
||||
|
||||
class EventsTypeForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = EventsType
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{% extends "chrono/manager_agenda_view.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="{{ agenda.get_settings_url }}">{% trans 'Settings' %}</a>
|
||||
<a href="">{% trans "Duplicate Event" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans "Duplicate Event" %}</h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<div class="buttons">
|
||||
<button class="submit-button">{% trans "Duplicate" %}</button>
|
||||
<a class="cancel" href="{% url 'chrono-manager-event-edit' pk=agenda.id event_pk=object.pk %}">{% trans 'Cancel' %}</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -20,6 +20,9 @@
|
|||
{% if not object.primary_event %}
|
||||
<a rel="popup" href="{% url 'chrono-manager-event-delete' pk=object.agenda.id event_pk=object.id %}?next={{view.request.GET.next}}">{% trans 'Delete' %}</a>
|
||||
{% endif %}
|
||||
{% if not object.primary_event %}
|
||||
<a rel="popup" href="{% url 'chrono-manager-event-duplicate' pk=object.agenda.id event_pk=object.id %}">{% trans 'Duplicate' %}</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
{% else %}
|
||||
<h2>{% trans "New Event" %}</h2>
|
||||
|
|
|
@ -199,6 +199,11 @@ urlpatterns = [
|
|||
views.event_edit,
|
||||
name='chrono-manager-event-edit',
|
||||
),
|
||||
url(
|
||||
r'^agendas/(?P<pk>\d+)/events/(?P<event_pk>\d+)/duplicate$',
|
||||
views.event_duplicate,
|
||||
name='chrono-manager-event-duplicate',
|
||||
),
|
||||
url(
|
||||
r'^agendas/(?P<pk>\d+)/events/(?P<event_pk>\d+)/delete$',
|
||||
views.event_delete,
|
||||
|
|
|
@ -111,6 +111,7 @@ from .forms import (
|
|||
DeskExceptionsImportForm,
|
||||
DeskForm,
|
||||
EventCancelForm,
|
||||
EventDuplicateForm,
|
||||
EventForm,
|
||||
EventsTimesheetForm,
|
||||
ImportEventsForm,
|
||||
|
@ -1782,6 +1783,24 @@ class AgendaAddEventView(ManagedAgendaMixin, CreateView):
|
|||
agenda_add_event = AgendaAddEventView.as_view()
|
||||
|
||||
|
||||
class AgendaEventDuplicateView(ManagedAgendaMixin, UpdateView):
|
||||
model = Event
|
||||
queryset = Event.objects.filter(primary_event__isnull=True)
|
||||
pk_url_kwarg = 'event_pk'
|
||||
form_class = EventDuplicateForm
|
||||
template_name = 'chrono/manager_event_duplicate_form.html'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('chrono-manager-event-edit', kwargs={'pk': self.agenda.id, 'event_pk': self.object.id})
|
||||
|
||||
def form_valid(self, form):
|
||||
messages.success(self.request, _('Event successfully duplicated.'))
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
event_duplicate = AgendaEventDuplicateView.as_view()
|
||||
|
||||
|
||||
class AgendaImportEventsSampleView(TemplateView):
|
||||
template_name = 'chrono/manager_sample_events.txt'
|
||||
content_type = 'text/csv'
|
||||
|
|
|
@ -2442,6 +2442,103 @@ def test_duplicate_agenda(app, admin_user):
|
|||
assert 'hop' in resp.text
|
||||
|
||||
|
||||
def test_duplicate_event(app, admin_user):
|
||||
agenda = Agenda.objects.create(label='Foo Bar', slug='foo-bar', kind='meetings')
|
||||
event = Event.objects.create(
|
||||
agenda=agenda,
|
||||
start_datetime=now(),
|
||||
places=10,
|
||||
waiting_list_places=50,
|
||||
label='Foo',
|
||||
duration=45,
|
||||
pricing='200€',
|
||||
url="http://example.com",
|
||||
description='foo',
|
||||
)
|
||||
|
||||
assert Event.objects.count() == 1
|
||||
|
||||
new_datetime = localtime(event.start_datetime).replace(
|
||||
year=event.start_datetime.year + 1, hour=17, minute=0
|
||||
)
|
||||
|
||||
app = login(app)
|
||||
resp = app.get(f'/manage/agendas/{agenda.pk}/events/{event.pk}/edit')
|
||||
resp = resp.click('Duplicate')
|
||||
resp.form['label'] = "Bar"
|
||||
resp.form['start_datetime_0'] = str(new_datetime.date())
|
||||
resp.form['start_datetime_1'] = '17:00'
|
||||
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert Event.objects.count() == 2
|
||||
|
||||
duplicate = Event.objects.latest('pk')
|
||||
|
||||
assert duplicate.start_datetime == new_datetime
|
||||
assert duplicate.agenda == event.agenda
|
||||
|
||||
updated_fields = {"label", "start_datetime"}
|
||||
identical_fields = {f.name for f in Event._meta.fields} - updated_fields - {'id', 'slug'}
|
||||
for field in identical_fields:
|
||||
assert getattr(duplicate, field) == getattr(event, field)
|
||||
|
||||
assert 'Event successfully duplicated.' in resp.text
|
||||
|
||||
|
||||
def test_duplicate_event_creates_recurrences(app, admin_user):
|
||||
agenda = Agenda.objects.create(label='Foo Bar', slug='foo-bar', kind='meetings')
|
||||
event_start = now().replace(hour=1, minute=0)
|
||||
recurring_event = Event.objects.create(
|
||||
agenda=agenda,
|
||||
start_datetime=event_start,
|
||||
recurrence_days=[0, 1, 2, 3, 4, 5, 6],
|
||||
recurrence_end_date=event_start + datetime.timedelta(days=6),
|
||||
places=10,
|
||||
label='Foo',
|
||||
)
|
||||
|
||||
assert Event.objects.count() == 1
|
||||
|
||||
app = login(app)
|
||||
resp = app.get(f'/manage/agendas/{agenda.pk}/events/{recurring_event.pk}/edit')
|
||||
resp = resp.click('Duplicate')
|
||||
resp.form['label'] = "Bar"
|
||||
resp.form['start_datetime_0'] = str(event_start.date())
|
||||
resp.form['start_datetime_1'] = '17:00'
|
||||
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
duplicate = Event.objects.filter(primary_event__isnull=True).latest('pk')
|
||||
|
||||
assert duplicate != recurring_event
|
||||
assert duplicate.recurrences.count() == 6
|
||||
|
||||
|
||||
def test_cannot_duplicate_secondary_event(app, admin_user):
|
||||
agenda = Agenda.objects.create(label='Foo Bar', slug='foo-bar', kind='meetings')
|
||||
recurring_event = Event.objects.create(
|
||||
agenda=agenda,
|
||||
start_datetime=now(),
|
||||
places=10,
|
||||
label='Foo',
|
||||
)
|
||||
# this event was created automatically as part of a recurring event
|
||||
# it can't be duplicated
|
||||
event = Event.objects.create(
|
||||
agenda=agenda,
|
||||
start_datetime=now(),
|
||||
places=10,
|
||||
primary_event=recurring_event,
|
||||
)
|
||||
|
||||
app = login(app)
|
||||
resp = app.get(f'/manage/agendas/{agenda.pk}/events/{event.pk}/edit')
|
||||
assert 'Duplicate' not in resp.text
|
||||
|
||||
app.get(f'/manage/agendas/{agenda.pk}/events/{event.pk}/duplicate', status=404)
|
||||
|
||||
|
||||
def test_booking_cancellation_meetings_agenda(app, admin_user, manager_user, managers_group, api_user):
|
||||
agenda = Agenda.objects.create(label='Passeports', kind='meetings', view_role=managers_group)
|
||||
desk = Desk.objects.create(agenda=agenda, label='Desk A')
|
||||
|
|
Loading…
Reference in New Issue