agendas: delete an exception source (#29209)
This commit is contained in:
parent
a5a8a3fe3d
commit
2aed92d132
|
@ -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; }
|
|
@ -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 %}
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in New Issue