manager: view to duplicate an agenda (#44127)
This commit is contained in:
parent
3ba233792d
commit
7433750574
|
@ -321,6 +321,32 @@ class Agenda(models.Model):
|
|||
|
||||
return created
|
||||
|
||||
def duplicate(self, label=None):
|
||||
# clone current agenda
|
||||
new_agenda = copy.deepcopy(self)
|
||||
new_agenda.pk = None
|
||||
new_agenda.label = label or _('Copy of %s') % self.label
|
||||
# reset slug
|
||||
new_agenda.slug = None
|
||||
new_agenda.save()
|
||||
|
||||
# clone related objects
|
||||
if self.kind == 'meetings':
|
||||
for meeting_type in self.meetingtype_set.all():
|
||||
meeting_type.duplicate(agenda_target=new_agenda)
|
||||
for desk in self.desk_set.all():
|
||||
desk.duplicate(agenda_target=new_agenda)
|
||||
new_agenda.resources.set(self.resources.all())
|
||||
elif self.kind == 'events':
|
||||
for event in self.event_set.all():
|
||||
event.duplicate(agenda_target=new_agenda)
|
||||
elif self.kind == 'virtual':
|
||||
for timeperiod in self.excluded_timeperiods.all():
|
||||
timeperiod.duplicate(agenda_target=new_agenda)
|
||||
for real_agenda in self.real_agendas.all():
|
||||
VirtualMember.objects.create(virtual_agenda=new_agenda, real_agenda=real_agenda)
|
||||
return new_agenda
|
||||
|
||||
def get_effective_time_periods(self):
|
||||
'''Regroup timeperiods by desks.
|
||||
|
||||
|
@ -513,12 +539,14 @@ class TimePeriod(models.Model):
|
|||
'end_time': self.end_time.strftime('%H:%M'),
|
||||
}
|
||||
|
||||
def duplicate(self, desk_target=None):
|
||||
def duplicate(self, desk_target=None, agenda_target=None):
|
||||
# clone current period
|
||||
new_period = copy.deepcopy(self)
|
||||
new_period.pk = None
|
||||
# set desk
|
||||
new_period.desk = desk_target or self.desk
|
||||
# set agenda
|
||||
new_period.agenda = agenda_target or self.agenda
|
||||
# store new period
|
||||
new_period.save()
|
||||
|
||||
|
@ -680,6 +708,17 @@ class MeetingType(models.Model):
|
|||
'duration': self.duration,
|
||||
}
|
||||
|
||||
def duplicate(self, agenda_target=None):
|
||||
new_meeting_type = copy.deepcopy(self)
|
||||
new_meeting_type.pk = None
|
||||
if agenda_target:
|
||||
new_meeting_type.agenda = agenda_target
|
||||
else:
|
||||
new_meeting_type.slug = None
|
||||
new_meeting_type.save()
|
||||
|
||||
return new_meeting_type
|
||||
|
||||
|
||||
class Event(models.Model):
|
||||
agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE)
|
||||
|
@ -829,6 +868,17 @@ class Event(models.Model):
|
|||
'pricing': self.pricing,
|
||||
}
|
||||
|
||||
def duplicate(self, agenda_target=None):
|
||||
new_event = copy.deepcopy(self)
|
||||
new_event.pk = None
|
||||
if agenda_target:
|
||||
new_event.agenda = agenda_target
|
||||
else:
|
||||
new_event.slug = None
|
||||
new_event.save()
|
||||
|
||||
return new_event
|
||||
|
||||
|
||||
class Booking(models.Model):
|
||||
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
||||
|
@ -956,14 +1006,16 @@ class Desk(models.Model):
|
|||
'exceptions': [exception.export_json() for exception in self.timeperiodexception_set.all()],
|
||||
}
|
||||
|
||||
def duplicate(self, label=None):
|
||||
def duplicate(self, label=None, agenda_target=None):
|
||||
# clone current desk
|
||||
new_desk = copy.deepcopy(self)
|
||||
new_desk.pk = None
|
||||
# set label
|
||||
new_desk.label = label or _('Copy of %s') % self.label
|
||||
# reset slug
|
||||
new_desk.slug = None
|
||||
if agenda_target:
|
||||
new_desk.agenda = agenda_target
|
||||
else:
|
||||
new_desk.slug = None
|
||||
# store new desk
|
||||
new_desk.save()
|
||||
|
||||
|
|
|
@ -414,3 +414,7 @@ class TimePeriodExceptionSourceReplaceForm(forms.ModelForm):
|
|||
|
||||
class AgendasImportForm(forms.Form):
|
||||
agendas_json = forms.FileField(label=_('Agendas Export File'))
|
||||
|
||||
|
||||
class AgendaDuplicateForm(forms.Form):
|
||||
label = forms.CharField(label=_('New label'), max_length=150, required=False)
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
{% extends "chrono/manager_agenda_view.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block breadcrumb %}
|
||||
{{ block.super }}
|
||||
<a href="">{% trans "Duplicate Agenda" %}</a>
|
||||
{% endblock %}
|
||||
|
||||
{% block appbar %}
|
||||
<h2>{% trans "Duplicate Agenda" %}</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-agenda-settings' pk=agenda.id %}">{% trans 'Cancel' %}</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -16,12 +16,16 @@
|
|||
</h2>
|
||||
<span class="actions">
|
||||
{% if user.is_staff %}
|
||||
<a class="extra-actions-menu-opener"></a>
|
||||
<a rel="popup" href="{% url 'chrono-manager-agenda-delete' pk=object.id %}">{% trans 'Delete' %}</a>
|
||||
{% endif %}
|
||||
<a download href="{% url 'chrono-manager-agenda-export' pk=object.id %}">{% trans 'Export' %}</a>
|
||||
<a rel="popup" href="{% url 'chrono-manager-agenda-edit' pk=object.id %}">{% trans 'Options' %}</a>
|
||||
{% block agenda-extra-management-actions %}
|
||||
{% endblock %}
|
||||
<ul class="extra-actions-menu">
|
||||
<li><a rel="popup" class="action-duplicate" href="{% url 'chrono-manager-agenda-duplicate' pk=object.pk %}">{% trans 'Duplicate' %}</a></li>
|
||||
</ul>
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ urlpatterns = [
|
|||
url(r'^agendas/(?P<pk>\d+)/delete$', views.agenda_delete, name='chrono-manager-agenda-delete'),
|
||||
url(r'^agendas/(?P<pk>\d+)/export$', views.agenda_export, name='chrono-manager-agenda-export'),
|
||||
url(r'^agendas/(?P<pk>\d+)/add-event$', views.agenda_add_event, name='chrono-manager-agenda-add-event'),
|
||||
url(r'^agendas/(?P<pk>\d+)/duplicate$', views.agenda_duplicate, name='chrono-manager-agenda-duplicate'),
|
||||
url(
|
||||
r'^agendas/(?P<pk>\d+)/import-events$',
|
||||
views.agenda_import_events,
|
||||
|
|
|
@ -81,6 +81,7 @@ from .forms import (
|
|||
ResourceAddForm,
|
||||
ResourceEditForm,
|
||||
AgendaResourceForm,
|
||||
AgendaDuplicateForm,
|
||||
)
|
||||
from .utils import import_site
|
||||
|
||||
|
@ -1142,6 +1143,22 @@ class AgendaExport(ManagedAgendaMixin, DetailView):
|
|||
agenda_export = AgendaExport.as_view()
|
||||
|
||||
|
||||
class AgendaDuplicate(ManagedAgendaMixin, FormView):
|
||||
form_class = AgendaDuplicateForm
|
||||
template_name = 'chrono/manager_agenda_duplicate_form.html'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.new_agenda.pk})
|
||||
|
||||
def form_valid(self, form):
|
||||
agenda = Agenda.objects.get(pk=self.kwargs['pk'])
|
||||
self.new_agenda = agenda.duplicate(label=form.cleaned_data['label'])
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
agenda_duplicate = AgendaDuplicate.as_view()
|
||||
|
||||
|
||||
class AgendaAddEventView(ManagedAgendaMixin, CreateView):
|
||||
template_name = 'chrono/manager_event_form.html'
|
||||
model = Event
|
||||
|
|
|
@ -844,3 +844,117 @@ def test_desk_duplicate():
|
|||
# duplicate again !
|
||||
new_desk = desk.duplicate()
|
||||
assert new_desk.slug == 'copy-of-desk-1'
|
||||
|
||||
|
||||
def test_agenda_meetings_duplicate():
|
||||
group = Group(name=u'Group')
|
||||
group.save()
|
||||
agenda = Agenda.objects.create(label='Agenda', kind='meetings', view_role=group)
|
||||
desk = Desk.objects.create(label='Desk', agenda=agenda)
|
||||
meeting_type = MeetingType.objects.create(agenda=agenda, label='meeting', duration=30)
|
||||
time_period = TimePeriod.objects.create(
|
||||
weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
|
||||
)
|
||||
TimePeriodExceptionSource.objects.create(desk=desk, ics_url='http://example.com/sample.ics')
|
||||
source2 = TimePeriodExceptionSource.objects.create(
|
||||
desk=desk,
|
||||
ics_filename='sample.ics',
|
||||
ics_file=ContentFile(ICS_SAMPLE_WITH_DURATION, name='sample.ics'),
|
||||
)
|
||||
time_period_exception = TimePeriodException.objects.create(
|
||||
label='Exception',
|
||||
desk=desk,
|
||||
start_datetime=now() + datetime.timedelta(days=1),
|
||||
end_datetime=now() + datetime.timedelta(days=2),
|
||||
)
|
||||
resource = Resource.objects.create(label=u'Foo bar')
|
||||
agenda.resources.add(resource)
|
||||
|
||||
new_agenda = agenda.duplicate()
|
||||
assert new_agenda.pk != agenda.pk
|
||||
assert new_agenda.label == 'Copy of Agenda'
|
||||
assert new_agenda.slug == 'copy-of-agenda'
|
||||
assert new_agenda.kind == 'meetings'
|
||||
assert new_agenda.view_role == group
|
||||
assert new_agenda.resources.first() == resource
|
||||
|
||||
new_meeting_type = new_agenda.meetingtype_set.first()
|
||||
assert new_meeting_type.pk != meeting_type.pk
|
||||
assert new_meeting_type.label == meeting_type.label
|
||||
assert new_meeting_type.duration == meeting_type.duration
|
||||
assert new_meeting_type.slug == meeting_type.slug
|
||||
|
||||
new_desk = new_agenda.desk_set.first()
|
||||
assert new_desk.pk != desk.pk
|
||||
assert new_desk.slug == desk.slug
|
||||
assert new_desk.timeperiod_set.count() == 1
|
||||
new_time_period = TimePeriod.objects.get(desk=new_desk)
|
||||
assert new_time_period.weekday == time_period.weekday
|
||||
assert new_time_period.start_time == time_period.start_time
|
||||
assert new_time_period.end_time == time_period.end_time
|
||||
assert new_desk.timeperiodexception_set.count() == 1
|
||||
new_time_period_exception = TimePeriodException.objects.get(desk=new_desk)
|
||||
assert new_time_period_exception.label == time_period_exception.label
|
||||
assert new_time_period_exception.start_datetime == time_period_exception.start_datetime
|
||||
assert new_time_period_exception.end_datetime == time_period_exception.end_datetime
|
||||
assert new_desk.timeperiodexceptionsource_set.count() == 2
|
||||
assert TimePeriodExceptionSource.objects.filter(
|
||||
desk=new_desk, ics_url='http://example.com/sample.ics'
|
||||
).exists()
|
||||
new_source2 = TimePeriodExceptionSource.objects.get(desk=new_desk, ics_filename='sample.ics')
|
||||
assert new_source2.ics_file.path != source2.ics_file.path
|
||||
|
||||
# duplicate again !
|
||||
new_agenda = agenda.duplicate()
|
||||
assert new_agenda.slug == 'copy-of-agenda-1'
|
||||
|
||||
|
||||
def test_agenda_events_duplicate():
|
||||
agenda = Agenda.objects.create(label='Agenda', kind='events')
|
||||
event = Event.objects.create(
|
||||
agenda=agenda,
|
||||
start_datetime=now() + datetime.timedelta(days=1),
|
||||
duration=10,
|
||||
places=10,
|
||||
label='event',
|
||||
slug='event',
|
||||
)
|
||||
|
||||
new_agenda = agenda.duplicate()
|
||||
assert new_agenda.pk != agenda.pk
|
||||
assert new_agenda.kind == 'events'
|
||||
|
||||
new_event = new_agenda.event_set.first()
|
||||
assert new_event.pk != event.pk
|
||||
assert new_event.label == event.label
|
||||
assert new_event.duration == event.duration
|
||||
assert new_event.duration == event.duration
|
||||
assert new_event.places == event.places
|
||||
assert new_event.start_datetime == event.start_datetime
|
||||
|
||||
|
||||
def test_agenda_virtual_duplicate():
|
||||
agenda1 = Agenda.objects.create(label=u'Agenda 1', kind='meetings')
|
||||
agenda2 = Agenda.objects.create(label=u'Agenda 2', kind='meetings')
|
||||
virt_agenda = Agenda.objects.create(label=u'Virtual agenda', kind='virtual')
|
||||
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=agenda1)
|
||||
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=agenda2)
|
||||
virt_agenda.save()
|
||||
|
||||
excluded_timeperiod = TimePeriod.objects.create(
|
||||
weekday=1, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), agenda=virt_agenda
|
||||
)
|
||||
|
||||
new_agenda = virt_agenda.duplicate()
|
||||
assert new_agenda.pk != virt_agenda.pk
|
||||
assert new_agenda.kind == 'virtual'
|
||||
|
||||
new_time_period = new_agenda.excluded_timeperiods.first()
|
||||
assert new_time_period.pk != excluded_timeperiod.pk
|
||||
assert new_time_period.agenda == new_agenda
|
||||
assert new_time_period.weekday == excluded_timeperiod.weekday
|
||||
assert new_time_period.start_time == excluded_timeperiod.start_time
|
||||
assert new_time_period.end_time == excluded_timeperiod.end_time
|
||||
|
||||
assert VirtualMember.objects.filter(virtual_agenda=new_agenda, real_agenda=agenda1).exists()
|
||||
assert VirtualMember.objects.filter(virtual_agenda=new_agenda, real_agenda=agenda2).exists()
|
||||
|
|
|
@ -3301,3 +3301,26 @@ def test_event_digit_slug(app, admin_user):
|
|||
resp.form['slug'] = 42
|
||||
resp = resp.form.submit()
|
||||
assert 'value cannot be a number' in resp.text
|
||||
|
||||
|
||||
def test_duplicate_agenda(app, admin_user):
|
||||
agenda = Agenda.objects.create(label=u'Foo Bar', slug='foo-bar', kind='meetings')
|
||||
assert Agenda.objects.count() == 1
|
||||
|
||||
app = login(app)
|
||||
resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
|
||||
resp = resp.click('Duplicate')
|
||||
resp = resp.form.submit()
|
||||
assert Agenda.objects.count() == 2
|
||||
|
||||
new_agenda = Agenda.objects.exclude(pk=agenda.pk).first()
|
||||
assert resp.location == '/manage/agendas/%s/settings' % new_agenda.pk
|
||||
assert new_agenda.pk != agenda.pk
|
||||
|
||||
resp = resp.follow()
|
||||
assert 'copy-of-foo-bar' in resp.text
|
||||
|
||||
resp = resp.click('Duplicate')
|
||||
resp.form['label'] = 'hop'
|
||||
resp = resp.form.submit().follow()
|
||||
assert 'hop' in resp.text
|
||||
|
|
Loading…
Reference in New Issue