agendas: delete an exception source (#29209)

This commit is contained in:
Lauréline Guérin 2019-12-12 09:45:54 +01:00
parent a5a8a3fe3d
commit 2aed92d132
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
6 changed files with 525 additions and 1 deletions

View File

@ -0,0 +1,430 @@
.occupation-bar {
display: block;
position: absolute;
bottom: 0;
height: 3px;
background: #88e;
transition: max-width 300ms ease-in;
max-width: 0%;
width: 100%; }
.overbooking .occupation-bar {
background: #e33; }
li.not-bookable {
opacity: 0.7; }
li.full {
background: #f8f8fe; }
li span.duration {
font-size: 80%; }
li span.identifier {
font-size: 80%;
opacity: 0.6; }
h2 span.identifier {
font-size: 1rem;
opacity: 0.6; }
.time-only-picker thead {
display: none; }
.time-only-picker tbody td {
width: 200px; }
.timeperiods {
display: flex;
flex-wrap: wrap;
margin-right: -10px; }
.timeperiods .timeperiod {
flex: 1 0 auto;
width: 18%;
margin-right: 10px;
position: relative; }
.timeperiods .timeperiod h4 {
margin-top: 0; }
.timeperiods .timeperiod ul.objects-list {
margin-top: 0; }
.timeperiods .timeperiod:first-child:last-child ul.objects-list {
margin: -1rem; }
.timeperiods .timeperiod a.add::before {
content: "\f055";
/* plus-circle */
font-family: FontAwesome;
padding-right: 1ex; }
a.timeperiod-exception-all {
font-style: italic; }
.link-action-icon.upload::before {
content: "\f093";
/* upload-sign */ }
.dayview h2 a,
.monthview h2 a {
padding: 0 1ex; }
.dayview h2 > span {
display: inline-block;
min-width: 24ex; }
.monthview h2 > span {
display: inline-block;
min-width: 16ex; }
.agenda-table thead th {
width: 14vw;
padding-bottom: 1ex;
font-weight: normal; }
.agenda-table {
width: 100%; }
.agenda-table .desks-1 thead th {
width: 99%; }
.agenda-table {
width: 100%; }
.agenda-table .desks-2 thead th {
width: 49%; }
.agenda-table {
width: 100%; }
.agenda-table .desks-3 thead th {
width: 32.33333%; }
.agenda-table {
width: 100%; }
.agenda-table .desks-4 thead th {
width: 24%; }
.agenda-table {
width: 100%; }
.agenda-table .desks-5 thead th {
width: 19%; }
.agenda-table {
width: 100%; }
.agenda-table .desks-6 thead th {
width: 15.66667%; }
.agenda-table {
width: 100%; }
.agenda-table .desks-7 thead th {
width: 13.28571%; }
.agenda-table tbody tr th {
box-sizing: border-box;
padding: 1ex 2ex;
vertical-align: top;
width: 8ex;
font-weight: normal; }
.agenda-table tbody tr th.hour {
width: 5%;
text-align: left; }
.agenda-table tbody tr th a {
color: #000;
border: 0; }
.agenda-table tbody tr th.weekday {
width: 12.5%;
padding-top: 3rem; }
.agenda-table tbody tr th.weekday.today {
font-weight: bold; }
.agenda-table tbody tr:first-child th.weekday {
padding-top: 1ex; }
.agenda-table tbody tr.odd th.hour,
.agenda-table tbody tr.odd td {
background: #f0f0f0; }
@media print {
.agenda-table tbody tr.odd th.hour,
.agenda-table tbody tr.odd td {
border-top: 1px solid #aaa; } }
.agenda-table tbody tr.odd td.other-month {
background: #f8f8f8; }
.agenda-table tbody td {
padding: 0 1ex;
vertical-align: top;
position: relative;
border: 0; }
.agenda-table.month-view {
border-spacing: 0; }
.agenda-table.month-view tbody td {
border: 5px solid white;
border-width: 0 5px; }
table.hourspan-1 tbody td {
height: calc(1 * 2.5em); }
table.hourspan-2 tbody td {
height: calc(2 * 2.5em); }
table.hourspan-3 tbody td {
height: calc(3 * 2.5em); }
table.hourspan-4 tbody td {
height: calc(4 * 2.5em); }
table.hourspan-5 tbody td {
height: calc(5 * 2.5em); }
table.hourspan-6 tbody td {
height: calc(6 * 2.5em); }
table.hourspan-7 tbody td {
height: calc(7 * 2.5em); }
table.hourspan-8 tbody td {
height: calc(8 * 2.5em); }
table.hourspan-9 tbody td {
height: calc(9 * 2.5em); }
table.hourspan-10 tbody td {
height: calc(10 * 2.5em); }
table.hourspan-11 tbody td {
height: calc(11 * 2.5em); }
table.hourspan-12 tbody td {
height: calc(12 * 2.5em); }
table.hourspan-13 tbody td {
height: calc(13 * 2.5em); }
table.hourspan-14 tbody td {
height: calc(14 * 2.5em); }
table.hourspan-15 tbody td {
height: calc(15 * 2.5em); }
table.hourspan-16 tbody td {
height: calc(16 * 2.5em); }
table.hourspan-17 tbody td {
height: calc(17 * 2.5em); }
table.hourspan-18 tbody td {
height: calc(18 * 2.5em); }
table.hourspan-19 tbody td {
height: calc(19 * 2.5em); }
table.hourspan-20 tbody td {
height: calc(20 * 2.5em); }
table.hourspan-21 tbody td {
height: calc(21 * 2.5em); }
table.hourspan-22 tbody td {
height: calc(22 * 2.5em); }
table.hourspan-23 tbody td {
height: calc(23 * 2.5em); }
table.hourspan-24 tbody td {
height: calc(24 * 2.5em); }
table.hourspan-25 tbody td {
height: calc(25 * 2.5em); }
table.hourspan-26 tbody td {
height: calc(26 * 2.5em); }
table.hourspan-27 tbody td {
height: calc(27 * 2.5em); }
table.hourspan-28 tbody td {
height: calc(28 * 2.5em); }
table.hourspan-29 tbody td {
height: calc(29 * 2.5em); }
table.hourspan-30 tbody td {
height: calc(30 * 2.5em); }
table.hourspan-31 tbody td {
height: calc(31 * 2.5em); }
table.hourspan-32 tbody td {
height: calc(32 * 2.5em); }
table.hourspan-33 tbody td {
height: calc(33 * 2.5em); }
table.hourspan-34 tbody td {
height: calc(34 * 2.5em); }
table.hourspan-35 tbody td {
height: calc(35 * 2.5em); }
table.hourspan-36 tbody td {
height: calc(36 * 2.5em); }
table.hourspan-37 tbody td {
height: calc(37 * 2.5em); }
table.hourspan-38 tbody td {
height: calc(38 * 2.5em); }
table.hourspan-39 tbody td {
height: calc(39 * 2.5em); }
table.hourspan-40 tbody td {
height: calc(40 * 2.5em); }
table.hourspan-41 tbody td {
height: calc(41 * 2.5em); }
table.hourspan-42 tbody td {
height: calc(42 * 2.5em); }
table.hourspan-43 tbody td {
height: calc(43 * 2.5em); }
table.hourspan-44 tbody td {
height: calc(44 * 2.5em); }
table.hourspan-45 tbody td {
height: calc(45 * 2.5em); }
table.hourspan-46 tbody td {
height: calc(46 * 2.5em); }
table.hourspan-47 tbody td {
height: calc(47 * 2.5em); }
table.hourspan-48 tbody td {
height: calc(48 * 2.5em); }
table.hourspan-49 tbody td {
height: calc(49 * 2.5em); }
table.hourspan-50 tbody td {
height: calc(50 * 2.5em); }
table.hourspan-51 tbody td {
height: calc(51 * 2.5em); }
table.hourspan-52 tbody td {
height: calc(52 * 2.5em); }
table.hourspan-53 tbody td {
height: calc(53 * 2.5em); }
table.hourspan-54 tbody td {
height: calc(54 * 2.5em); }
table.hourspan-55 tbody td {
height: calc(55 * 2.5em); }
table.hourspan-56 tbody td {
height: calc(56 * 2.5em); }
table.hourspan-57 tbody td {
height: calc(57 * 2.5em); }
table.hourspan-58 tbody td {
height: calc(58 * 2.5em); }
table.hourspan-59 tbody td {
height: calc(59 * 2.5em); }
table.hourspan-60 tbody td {
height: calc(60 * 2.5em); }
.agenda-table tbody td div {
box-sizing: border-box;
padding: 1ex;
position: absolute;
overflow: hidden; }
.agenda-table tbody td div.opening-hours {
z-index: 1;
background: #b1ea4d linear-gradient(135deg, #b1ea4d 0%, #459522 100%);
opacity: 0.6;
left: 0.5ex;
width: calc(100% - 1ex); }
.agenda-table tbody td div.booking {
background: #eef linear-gradient(135deg, #eef 0%, #ddf 100%);
box-shadow: 0 0 1px 0 #2d2dad;
width: calc(100% - 2ex);
border: 1px solid #aaa;
z-index: 2; }
.agenda-table tbody td div.booking:hover {
z-index: 3;
height: auto !important; }
.monthview tbody td div.booking {
padding: 0;
transition: width 100ms ease-in, left 100ms ease-in, color 200ms ease-in;
text-indent: -9999px; }
.monthview tbody td div.booking:hover {
text-indent: 0;
color: inherit;
left: 0% !important;
width: 100% !important; }
.monthview tbody td div.booking span.desk {
display: block; }
span.start-time {
font-size: 80%; }
.date-title {
cursor: pointer; }
.date-title::after {
content: "\f073";
/* calendar */
font-family: FontAwesome;
padding-left: 1ex;
padding-right: 0ex;
opacity: 0.3;
font-size: 80%;
transition: opacity 200ms linear; }
.date-title:hover::after {
opacity: 0.8; }
.date-picker {
position: absolute;
background: #FAFAFA;
border: 1px solid #d0d0d0;
box-shadow: 0px 1px 1px 2px rgba(0, 0, 0, 0.04);
padding: 1ex 4ex;
left: 0;
top: 3ex;
z-index: 100; }
.date-picker button, .date-picker select {
font-size: 1rem; }
.date-picker::before {
position: absolute;
content: "";
display: block;
width: 1ex;
height: 1ex;
border: 1px solid #d0d0d0;
border-width: 1px 0 0 1px;
top: -0.6ex;
left: 5rem;
background: #FAFAFA;
transform: rotate(45deg); }
ul#id_weekdays {
margin: 0;
padding: 0;
list-style: none;
max-width: 42em; }
ul#id_weekdays li {
margin: 0;
padding: 0;
display: inline-block;
width: 10em; }

View File

@ -0,0 +1,19 @@
{% extends "chrono/manager_home.html" %}
{% load i18n %}
{% block appbar %}
<h2>{{ object }}</h2>
{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
<p>
{% blocktrans %}Are you sure you want to delete this exception source?{% endblocktrans %}
</p>
<div class="buttons">
<button class="delete-button">{% trans 'Delete' %}</button>
<a class="cancel" href="{% url 'chrono-manager-agenda-settings' object.desk.agenda_id %}">{% trans 'Cancel' %}</a>
</div>
</form>
{% endblock %}

View File

@ -35,7 +35,7 @@
{% if object.ics_filename %}{% trans "replace" %}{% else %}{% trans "refresh" %}{% endif %}
</a>
</td>
<td><a rel="popup" href="">{% trans "remove" %}</a></td>
<td><a rel="popup" href="{% url 'chrono-manager-time-period-exception-source-delete' object.pk %}">{% trans "remove" %}</a></td>
</tr>
{% endfor %}
</tbody>

View File

@ -100,6 +100,11 @@ urlpatterns = [
views.time_period_exception_list,
name='chrono-manager-time-period-exception-list',
),
url(
r'^time-period-exceptions-source/(?P<pk>\d+)/delete$',
views.time_period_exception_source_delete,
name='chrono-manager-time-period-exception-source-delete',
),
url(
r'^agendas/events.csv$',
views.agenda_import_events_sample_csv,

View File

@ -51,6 +51,7 @@ from chrono.agendas.models import (
TimePeriodException,
ICSError,
AgendaImportError,
TimePeriodExceptionSource,
)
from .forms import (
@ -914,6 +915,17 @@ class DeskImportTimePeriodExceptionsView(ManagedAgendaSubobjectMixin, UpdateView
desk_import_time_period_exceptions = DeskImportTimePeriodExceptionsView.as_view()
class TimePeriodExceptionSourceDeleteView(ManagedDeskSubobjectMixin, DeleteView):
template_name = 'chrono/manager_confirm_source_delete.html'
model = TimePeriodExceptionSource
def get_success_url(self):
return reverse('chrono-manager-agenda-settings', kwargs={'pk': self.desk.agenda_id})
time_period_exception_source_delete = TimePeriodExceptionSourceDeleteView.as_view()
def menu_json(request):
label = _('Agendas')
json_str = json.dumps(

View File

@ -1412,6 +1412,64 @@ def test_agenda_import_time_period_exception_from_remote_ics_with_ssl_error(mock
assert 'Failed to retrieve remote calendar (https://example.com/foo.ics, SSL error).' in resp.text
def test_meetings_agenda_delete_time_period_exception_source(app, admin_user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='Desk A')
MeetingType(agenda=agenda, label='Blah').save()
TimePeriod.objects.create(
weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
)
login(app)
# import a source
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
resp = resp.click('Settings')
resp = resp.click('upload')
ics_with_recurrent_exceptions = b"""BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//foo.bar//EN
BEGIN:VEVENT
DTSTART:20180101
DTEND:20180101
SUMMARY:New Year's Eve
RRULE:FREQ=YEARLY
END:VEVENT
END:VCALENDAR"""
resp.form['ics_file'] = Upload('exceptions.ics', ics_with_recurrent_exceptions, 'text/calendar')
resp = resp.form.submit(status=302).follow()
assert TimePeriodException.objects.filter(desk=desk).count() == 2
source1 = TimePeriodExceptionSource.objects.latest('pk')
assert source1.timeperiodexception_set.count() == 2
# import another one
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
resp = resp.click('Settings')
resp = resp.click('upload')
resp.form['ics_file'] = Upload('exceptions.ics', ics_with_recurrent_exceptions, 'text/calendar')
resp = resp.form.submit(status=302).follow()
assert TimePeriodException.objects.filter(desk=desk).count() == 4
source2 = TimePeriodExceptionSource.objects.latest('pk')
assert source2.timeperiodexception_set.count() == 2
# delete the second one
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
resp = resp.click('Settings')
resp = resp.click('upload')
resp = resp.click(href='/manage/time-period-exceptions-source/%d/delete' % source2.pk)
resp = resp.form.submit().follow()
assert TimePeriodException.objects.count() == 2
assert source1.timeperiodexception_set.count() == 2
assert TimePeriodExceptionSource.objects.filter(pk=source2.pk).exists() is False
# delete the first one
resp = app.get('/manage/agendas/%d/' % agenda.pk).follow()
resp = resp.click('Settings')
resp = resp.click('upload')
resp = resp.click(href='/manage/time-period-exceptions-source/%d/delete' % source1.pk)
resp = resp.form.submit().follow()
assert TimePeriodException.objects.count() == 0
assert TimePeriodExceptionSource.objects.filter(pk=source1.pk).exists() is False
def test_agenda_day_view(app, admin_user, manager_user, api_user):
agenda = Agenda.objects.create(label='New Example', kind='meetings')
desk = Desk.objects.create(agenda=agenda, label='New Desk')