diff --git a/requirements.txt b/requirements.txt index 50cf9ab36269228d76171803b72a00b358c0eb07..d974c761791eccf6fc3aa8daf93c13a73fe9128d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,4 +13,5 @@ django-excel==0.0.9 pyexcel-xls==0.5.0 pyexcel==0.5.3 pycurl==7.43.0 +django-stronghold==0.2.9 diff --git a/smash/smash/settings.py b/smash/smash/settings.py index 98f536269cec9778a13523b618cfd12347eb5896..a034ce1086f8dff171c185d8bc3c5ea6c5884b7d 100644 --- a/smash/smash/settings.py +++ b/smash/smash/settings.py @@ -36,6 +36,7 @@ INSTALLED_APPS = [ 'django_otp.plugins.otp_totp', 'two_factor', 'web', + 'stronghold', 'debug_toolbar' ] @@ -50,6 +51,7 @@ MIDDLEWARE = [ 'django_otp.middleware.OTPMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'stronghold.middleware.LoginRequiredMiddleware', ] ROOT_URLCONF = 'smash.urls' @@ -120,9 +122,11 @@ STATIC_URL = '/static/' MEDIA_URL = '/media/' # Used for @login_required ecosystem -# LOGIN_URL = '/login' LOGIN_URL = 'two_factor:login' LOGIN_REDIRECT_URL = 'web.views.appointments' LOGOUT_REDIRECT_URL = 'web.views.appointments' +# Used for LoginRequiredMiddleware +STRONGHOLD_PUBLIC_NAMED_URLS = (LOGIN_URL,) + from local_settings import * diff --git a/smash/web/api_views/appointment.py b/smash/web/api_views/appointment.py index 2ef29f8d941f9333ba3fa9790bafddf4a043742c..13dd32595ac6cee510ea6323b9a2e8eb0704337e 100644 --- a/smash/web/api_views/appointment.py +++ b/smash/web/api_views/appointment.py @@ -1,7 +1,6 @@ import logging from datetime import datetime -from django.contrib.auth.decorators import login_required from django.http import JsonResponse from django.urls import reverse from django.utils import timezone @@ -17,7 +16,6 @@ from web.views.notifications import get_filter_locations, get_today_midnight_dat logger = logging.getLogger(__name__) -@login_required def get_appointment_columns(request, appointment_list_type): study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0] appointment_lists = AppointmentList.objects.filter(study=study, type=appointment_list_type) @@ -41,7 +39,6 @@ def get_appointment_columns(request, appointment_list_type): return JsonResponse({"columns": result}) -@login_required def get_appointments(request, appointment_type, min_date, max_date): if appointment_type == APPOINTMENT_LIST_GENERIC: result = Appointment.objects.filter(location__in=get_filter_locations(request.user)) @@ -115,7 +112,6 @@ def get_appointments_filtered(appointments_to_be_filtered, filters): return result -@login_required def appointments(request, appointment_type): # id of the query from dataTable: https://datatables.net/manual/server-side draw = int(request.GET.get("draw", "-1")) diff --git a/smash/web/api_views/appointment_type.py b/smash/web/api_views/appointment_type.py index 66f9af836adc5cd2f17cbd1a35cbd974fc6fa840..5fd8e80eb43be7dfbccef1a0964da12af9dc38f5 100644 --- a/smash/web/api_views/appointment_type.py +++ b/smash/web/api_views/appointment_type.py @@ -1,10 +1,8 @@ -from django.contrib.auth.decorators import login_required from django.http import JsonResponse from web.models import AppointmentType -@login_required def appointment_types(request): appointments = AppointmentType.objects.filter().all() result = [] diff --git a/smash/web/api_views/configuration.py b/smash/web/api_views/configuration.py index c332d385438cb274b8d8f0509f975d218ddd9739..12c420dc66c6bed578984b5929b3a3cbf59beba9 100644 --- a/smash/web/api_views/configuration.py +++ b/smash/web/api_views/configuration.py @@ -1,10 +1,8 @@ -from django.contrib.auth.decorators import login_required from django.http import JsonResponse from web.models import ConfigurationItem -@login_required def configuration_items(request): # id of the query from dataTable: https://datatables.net/manual/server-side draw = int(request.GET.get("draw", "-1")) @@ -33,7 +31,6 @@ def configuration_items(request): }) -@login_required def update_configuration_item(request): id = int(request.GET.get("id", "-1")) value = request.GET.get("value", None) diff --git a/smash/web/api_views/daily_planning.py b/smash/web/api_views/daily_planning.py index ad3c0a94387f1c64266194de086ae60a805bb9c5..ec08fdcd5b6b83a46694dfe13e6aeb96e667ca62 100644 --- a/smash/web/api_views/daily_planning.py +++ b/smash/web/api_views/daily_planning.py @@ -3,7 +3,6 @@ import json import logging from operator import itemgetter -from django.contrib.auth.decorators import login_required from django.http import JsonResponse from django.shortcuts import get_object_or_404 @@ -233,7 +232,6 @@ def get_generic_appointment_events(request, date): return result.values() -@login_required def events(request, date): appointments = Appointment.objects.filter( datetime_when__date=date, @@ -325,7 +323,6 @@ def availabilities(request, date): }) -@login_required def events_persist(request): try: event_links = json.loads(request.POST.get('events_to_persist')) diff --git a/smash/web/api_views/flying_team.py b/smash/web/api_views/flying_team.py index 26b4a166618606fe24e0f57736345126cbbf98eb..82000c57524aa406777396c0357592ea2bae89a6 100644 --- a/smash/web/api_views/flying_team.py +++ b/smash/web/api_views/flying_team.py @@ -1,10 +1,8 @@ -from django.contrib.auth.decorators import login_required from django.http import JsonResponse from web.models import FlyingTeam -@login_required def flying_teams(request): all_flying_teams = FlyingTeam.objects.all() data = [] diff --git a/smash/web/api_views/location.py b/smash/web/api_views/location.py index d5da64abfea495e4340409fb87012254bcf46e99..f6e09cede4f6c74464a831cd7fda72f3d753a4cb 100644 --- a/smash/web/api_views/location.py +++ b/smash/web/api_views/location.py @@ -1,10 +1,8 @@ -from django.contrib.auth.decorators import login_required from django.http import JsonResponse from web.models import Location -@login_required def locations(request): locations = Location.objects.all() data = [] diff --git a/smash/web/api_views/redcap.py b/smash/web/api_views/redcap.py index 677e43346030d6c3fbbcabe493108346bd6f3022..31124060743e4eb5f17b9017b91256b91c025e52 100644 --- a/smash/web/api_views/redcap.py +++ b/smash/web/api_views/redcap.py @@ -1,10 +1,8 @@ -from django.contrib.auth.decorators import login_required from django.http import JsonResponse from web.models import MissingSubject -@login_required def ignore_missing_subject(request, missing_subject_id): missing_subjects = MissingSubject.objects.filter(id=missing_subject_id) for missing_subject in missing_subjects: @@ -15,7 +13,6 @@ def ignore_missing_subject(request, missing_subject_id): }) -@login_required def unignore_missing_subject(request, missing_subject_id): missing_subjects = MissingSubject.objects.filter(id=missing_subject_id) for missing_subject in missing_subjects: diff --git a/smash/web/api_views/subject.py b/smash/web/api_views/subject.py index 2cf6c5ec449f24814e50260a1aba0feffd764ffb..a30d4c198348f47bf26f6672e4c323ae352d83d3 100644 --- a/smash/web/api_views/subject.py +++ b/smash/web/api_views/subject.py @@ -1,6 +1,5 @@ import logging -from django.contrib.auth.decorators import login_required from django.db.models import Count, Case, When, Min, Max from django.db.models import Q from django.http import JsonResponse @@ -19,7 +18,6 @@ logger = logging.getLogger(__name__) # noinspection PyUnusedLocal -@login_required def cities(request): result_subjects = Subject.objects.filter(city__isnull=False).values_list('city').distinct() return JsonResponse({ @@ -28,7 +26,6 @@ def cities(request): # noinspection PyUnusedLocal -@login_required def referrals(request): result_subjects = StudySubject.objects.filter(referral__isnull=False).values_list('referral').distinct() return JsonResponse({ @@ -36,7 +33,6 @@ def referrals(request): }) -@login_required def get_subject_columns(request, subject_list_type): study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0] study_subject_lists = StudySubjectList.objects.filter(study=study, type=subject_list_type) @@ -82,7 +78,6 @@ def get_subject_columns(request, subject_list_type): return JsonResponse({"columns": result}) -@login_required def get_subjects(request, type): if type == SUBJECT_LIST_GENERIC: return StudySubject.objects.all() @@ -271,7 +266,6 @@ def get_subjects_filtered(subjects_to_be_filtered, filters): return result -@login_required def subjects(request, type): try: # id of the query from dataTable: https://datatables.net/manual/server-side @@ -313,7 +307,6 @@ def subjects(request, type): # noinspection PyUnusedLocal -@login_required def types(request): data = [{"id": subject_type_id, "name": subject_type_name} for subject_type_id, subject_type_name in SUBJECT_TYPE_CHOICES.items()] diff --git a/smash/web/api_views/visit.py b/smash/web/api_views/visit.py index 8a7a75b7805504a5ca33158dd9689e6c804f30d5..e6fbe9693dc121643817b92a73d93463ff038716 100644 --- a/smash/web/api_views/visit.py +++ b/smash/web/api_views/visit.py @@ -1,6 +1,5 @@ import logging -from django.contrib.auth.decorators import login_required from django.db.models import Q from django.http import JsonResponse @@ -20,7 +19,6 @@ logger = logging.getLogger(__name__) # noinspection PyUnusedLocal -@login_required def get_visit_columns(request, visit_list_type): study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0] study_visit_lists = StudyVisitList.objects.filter(study=study, type=visit_list_type) @@ -61,7 +59,6 @@ def get_visit_columns(request, visit_list_type): # noinspection PyUnusedLocal -@login_required def get_visits(request, visit_type): if visit_type == VISIT_LIST_GENERIC: return Visit.objects.all() @@ -171,7 +168,6 @@ def get_visits_filtered(visits_to_be_filtered, filters): return result -@login_required def visits(request, visit_list_type): # id of the query from dataTable: https://datatables.net/manual/server-side draw = int(request.GET.get("draw", "-1")) diff --git a/smash/web/api_views/worker.py b/smash/web/api_views/worker.py index e3ca26040d94afab46ef7be02094923a841258ee..bdf249541b8f3ac637b1ca4b35e332806cc7c782 100644 --- a/smash/web/api_views/worker.py +++ b/smash/web/api_views/worker.py @@ -1,6 +1,5 @@ import datetime -from django.contrib.auth.decorators import login_required from django.http import JsonResponse from django.utils import timezone @@ -9,7 +8,6 @@ from web.api_views.daily_planning import get_workers_for_daily_planning, get_ava from ..models import Worker -@login_required def specializations(request): workers = Worker.objects.filter(specialization__isnull=False).values_list('specialization').distinct() return JsonResponse({ @@ -17,7 +15,6 @@ def specializations(request): }) -@login_required def units(request): workers = Worker.objects.filter(unit__isnull=False).values_list('unit').distinct() return JsonResponse({ @@ -25,7 +22,6 @@ def units(request): }) -@login_required def workers_for_daily_planning(request): workers = get_workers_for_daily_planning(request) workers_list_for_json = [] @@ -40,7 +36,6 @@ def workers_for_daily_planning(request): return JsonResponse(workers_list_for_json, safe=False) -@login_required def availabilities(request): result = [] min_date = request.GET.get("start_date") diff --git a/smash/web/templates/_base.html b/smash/web/templates/_base.html index b173fa287e9205eb2a88ab524af472a4b2ec1977..637f33b35fa6c4077396d609951f708d63466501 100644 --- a/smash/web/templates/_base.html +++ b/smash/web/templates/_base.html @@ -267,7 +267,7 @@ desired effect {% block footer %} <!-- To the right --> <div class="pull-right hidden-xs"> - Version: <strong>0.10.1</strong> (20 November 2017) + Version: <strong>0.10.2</strong> (11 January 2018) </div> <!-- Default to the left --> diff --git a/smash/web/tests/test_statistics.py b/smash/web/tests/test_statistics.py index af58590938e9870239cbfb1eb38c477e71b785b1..3c36dcf7fdbd483492f8adacaa51c6e3bbd3e22b 100644 --- a/smash/web/tests/test_statistics.py +++ b/smash/web/tests/test_statistics.py @@ -34,7 +34,7 @@ class TestStatistics(TestCase): self.assertEqual(12, previous_month) def test_get_statistics_for_month_one_appointment(self): - statistics = self.statistics_manager.get_statistics_for_month(self.visit_start.month, self.now.year) + statistics = self.statistics_manager.get_statistics_for_month(self.visit_start.month, self.visit_start.year) self.check_statistics(statistics, 1, 0, 0, {"C": [0, 0]}, ['Scheduled']) statistics = self.statistics_manager.get_statistics_for_month(self.now.month, self.now.year) diff --git a/smash/web/tests/view/test_worker.py b/smash/web/tests/view/test_worker.py index 6aba5ed6b67636165bc2ba3e02353d2ec2c9b773..dc1aca4dcf08cf8952ccc8240c88fdd9c6f37048 100644 --- a/smash/web/tests/view/test_worker.py +++ b/smash/web/tests/view/test_worker.py @@ -31,6 +31,17 @@ class WorkerViewTests(LoggedInTestCase): language = create_language() location = create_location() + form_data = self.create_add_worker_form_data(language, location) + + response = self.client.post(reverse('web.views.worker_add', kwargs={'worker_type': WORKER_STAFF}), + data=form_data) + logger.debug(response.content) + + self.assertEqual(response.status_code, 302) + + self.assertEqual(1, Worker.objects.all().count()) + + def create_add_worker_form_data(self, language, location): form_data = self.get_form_data(Worker()) form_data["first_name"] = "John" form_data["last_name"] = "Doe" @@ -41,14 +52,22 @@ class WorkerViewTests(LoggedInTestCase): form_data["languages"] = [language.id] form_data["locations"] = [location.id] form_data["role"] = ROLE_CHOICES_DOCTOR + return form_data - response = self.client.post(reverse('web.views.worker_add', kwargs={'worker_type': WORKER_STAFF}), - data=form_data) - logger.debug(response.content) + def test_security_in_worker_added_request(self): + self.client.logout() - self.assertEqual(response.status_code, 302) + language = create_language() + location = create_location() + count = Worker.objects.all().count() - self.assertEqual(1, Worker.objects.all().count()) + form_data = self.create_add_worker_form_data(language, location) + + self.client.post(reverse('web.views.worker_add', kwargs={'worker_type': WORKER_STAFF}), data=form_data) + + new_count = Worker.objects.all().count() + # new user shouldn't be added + self.assertEqual(count, new_count) @staticmethod def get_form_data(worker=None): diff --git a/smash/web/urls.py b/smash/web/urls.py index 9f26924b56f23a6d15bb98012914a9d5b686586e..3816e9c4a5129cccdc84351fd2818e115c285c92 100644 --- a/smash/web/urls.py +++ b/smash/web/urls.py @@ -16,7 +16,6 @@ Including another URLconf from django.conf import settings from django.conf.urls import include from django.conf.urls import url -from django.contrib.auth.decorators import login_required from django.contrib.auth.views import logout from django.views.defaults import page_not_found from django.views.generic import TemplateView @@ -163,7 +162,7 @@ urlpatterns = [ # DAILY PLANNING # #################### - url(r'^daily_planning$', login_required(TemplateView.as_view(template_name='daily_planning.html')), + url(r'^daily_planning$', TemplateView.as_view(template_name='daily_planning.html'), name='web.views.daily_planning'), #################### diff --git a/smash/web/views/__init__.py b/smash/web/views/__init__.py index 95ce4b72e2088348eeb767ead94b273aef365673..a0c8a353160dbc8b61c6ab861cfe113ab539fba9 100644 --- a/smash/web/views/__init__.py +++ b/smash/web/views/__init__.py @@ -1,6 +1,5 @@ # coding=utf-8 from django.conf import settings -from django.contrib.auth.decorators import login_required from django.shortcuts import redirect, render from django.utils.decorators import method_decorator from django.views.generic.base import ContextMixin @@ -36,7 +35,6 @@ def e400_bad_request(request, context=None): return render(request, "errors/400.html", context, status=400) -@login_required def wrap_response(request, template, params): final_params = extend_context(params, request) return render(request, template, final_params) @@ -54,7 +52,6 @@ def extend_context(params, request): return final_params -@method_decorator(login_required, name='dispatch') class WrappedView(ContextMixin): def get_context_data(self, **kwargs): context = super(WrappedView, self).get_context_data(**kwargs) diff --git a/smash/web/views/equipment.py b/smash/web/views/equipment.py index c7cf21bec25b7a95f521b71f58eff1df4334a0c3..ff498134cab372d0f026c0316f89295d60b83191 100644 --- a/smash/web/views/equipment.py +++ b/smash/web/views/equipment.py @@ -1,6 +1,5 @@ # coding=utf-8 from django.shortcuts import redirect, get_object_or_404 -from django.contrib.auth.decorators import login_required from . import wrap_response from ..models import Item @@ -16,7 +15,6 @@ def equipment(request): return wrap_response(request, "equipment_and_rooms/equipment/index.html", context) -@login_required def equipment_add(request): if request.method == 'POST': form = ItemForm(request.POST) @@ -29,7 +27,6 @@ def equipment_add(request): return wrap_response(request, 'equipment_and_rooms/equipment/add.html', {'form': form}) -@login_required def equipment_edit(request, equipment_id): the_item = get_object_or_404(Item, id=equipment_id) if request.method == 'POST': @@ -43,7 +40,6 @@ def equipment_edit(request, equipment_id): return wrap_response(request, 'equipment_and_rooms/equipment/edit.html', {'form': form}) -@login_required def equipment_delete(request, equipment_id): the_item = get_object_or_404(Item, id=equipment_id) the_item.delete() diff --git a/smash/web/views/export.py b/smash/web/views/export.py index b5b755e61ebd600e8043c9a50259fb8802815912..a55f25036dfcb2b0ca7a70f9c87daf78de3dc537 100644 --- a/smash/web/views/export.py +++ b/smash/web/views/export.py @@ -2,7 +2,6 @@ import csv import django_excel as excel -from django.contrib.auth.decorators import login_required from django.http import HttpResponse from notifications import get_today_midnight_date @@ -10,7 +9,6 @@ from . import e500_error, wrap_response from ..models import Subject, StudySubject, Appointment -@login_required def export_to_csv(request, data_type="subjects"): # Create the HttpResponse object with the appropriate CSV header. response = HttpResponse(content_type='text/csv') @@ -30,7 +28,6 @@ def export_to_csv(request, data_type="subjects"): return response -@login_required def export_to_excel(request, data_type="subjects"): filename = data_type + '-' + get_today_midnight_date().strftime("%Y-%m-%d") + ".xls" if data_type == "subjects": diff --git a/smash/web/views/flying_teams.py b/smash/web/views/flying_teams.py index 8a58cbf768abd76bbac00bff809541847974bbf2..dc6c4752045cef881778084f6ece845aa259ef2f 100644 --- a/smash/web/views/flying_teams.py +++ b/smash/web/views/flying_teams.py @@ -1,6 +1,5 @@ # coding=utf-8 from django.shortcuts import redirect, get_object_or_404 -from django.contrib.auth.decorators import login_required from . import wrap_response from ..models import FlyingTeam @@ -17,7 +16,6 @@ def flying_teams(request): "equipment_and_rooms/flying_teams/index.html", context) -@login_required def flying_teams_add(request): if request.method == 'POST': form = FlyingTeamAddForm(request.POST) @@ -30,7 +28,6 @@ def flying_teams_add(request): return wrap_response(request, 'equipment_and_rooms/flying_teams/add.html', {'form': form}) -@login_required def flying_teams_edit(request, flying_team_id): the_flying_team = get_object_or_404(FlyingTeam, id=flying_team_id) if request.method == 'POST': diff --git a/smash/web/views/mails.py b/smash/web/views/mails.py index 78d33d4189374f9776058715650dec560b13d0d9..1fc450b3a55b695190667cf55c5eea8b30749df0 100644 --- a/smash/web/views/mails.py +++ b/smash/web/views/mails.py @@ -3,7 +3,6 @@ import StringIO from wsgiref.util import FileWrapper from django.contrib import messages -from django.contrib.auth.decorators import login_required from django.http import HttpResponse from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy @@ -68,7 +67,6 @@ class MailTemplatesEditView(UpdateView, WrappedView): context_object_name = "mail_template" -@login_required def generate(request, mail_template_id, instance_id): mail_template = get_object_or_404(MailTemplate, id=mail_template_id) instance = get_object_or_404(CONTEXT_TYPES_MAPPING[mail_template.context], id=instance_id) diff --git a/smash/web/views/rooms.py b/smash/web/views/rooms.py index 124ff63bc68698fd9891b2c6ea31ca2f0b65e8d6..2ff626e95a9259a0769bcb7b8e840a40908952bc 100644 --- a/smash/web/views/rooms.py +++ b/smash/web/views/rooms.py @@ -1,10 +1,9 @@ # coding=utf-8 from django.shortcuts import redirect, get_object_or_404 -from django.contrib.auth.decorators import login_required from . import wrap_response -from ..models import Room from ..forms.forms import RoomForm +from ..models import Room def rooms(request): @@ -17,7 +16,7 @@ def rooms(request): "equipment_and_rooms/rooms/index.html", context) -@login_required + def rooms_add(request): if request.method == 'POST': form = RoomForm(request.POST) @@ -30,7 +29,6 @@ def rooms_add(request): return wrap_response(request, 'equipment_and_rooms/rooms/add.html', {'form': form}) -@login_required def rooms_edit(request, room_id): the_room = get_object_or_404(Room, id=room_id) if request.method == 'POST': @@ -44,7 +42,6 @@ def rooms_edit(request, room_id): return wrap_response(request, 'equipment_and_rooms/rooms/edit.html', {'form': form}) -@login_required def rooms_delete(request, room_id): the_room = get_object_or_404(Room, id=room_id) the_room.delete() diff --git a/smash/web/views/subject.py b/smash/web/views/subject.py index d891d0d65be6a3c07243f40d4704ee0c071d2103..f8df4c1af40bbf68dd9012676ad5d7fb516b4ce2 100644 --- a/smash/web/views/subject.py +++ b/smash/web/views/subject.py @@ -2,7 +2,6 @@ import logging from django.contrib import messages -from django.contrib.auth.decorators import login_required from django.shortcuts import redirect, get_object_or_404 from . import wrap_response @@ -23,7 +22,6 @@ def subjects(request): return wrap_response(request, 'subjects/index.html', context) -@login_required def subject_add(request): study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0] if request.method == 'POST': diff --git a/smash/web/views/uploaded_files.py b/smash/web/views/uploaded_files.py index e20a022a77488aabc8e7365352ff8f3b60990a54..f7f5f3dfa6936bac2592fdf16829947be3297eaa 100644 --- a/smash/web/views/uploaded_files.py +++ b/smash/web/views/uploaded_files.py @@ -3,7 +3,6 @@ import logging import ntpath from wsgiref.util import FileWrapper -from django.contrib.auth.decorators import login_required from django.http import HttpResponse from web.models.constants import FILE_STORAGE @@ -16,7 +15,6 @@ def path_to_filename(path): return tail or ntpath.basename(head) -@login_required def download(request): if request.GET and request.GET.get('file'): path = FILE_STORAGE.location + "/" + request.GET.get('file') diff --git a/smash/web/views/worker.py b/smash/web/views/worker.py index 6fab2239ac33adb9e702c0c9586e49c2e17b7631..f3b135734d8325c450e626997898ab440caad0f8 100644 --- a/smash/web/views/worker.py +++ b/smash/web/views/worker.py @@ -1,7 +1,6 @@ # coding=utf-8 import logging -from django.contrib.auth.decorators import login_required from django.shortcuts import redirect, get_object_or_404 from web.forms import AvailabilityAddForm, AvailabilityEditForm, HolidayAddForm @@ -66,7 +65,6 @@ def worker_disable(request, doctor_id): return worker_list(request) -@login_required def worker_availability_delete(request, availability_id): availability = Availability.objects.filter(id=availability_id) doctor_id = availability[0].person.id @@ -107,7 +105,6 @@ def worker_availability_edit(request, availability_id): }) -@login_required def worker_holiday_delete(request, holiday_id): holiday = Holiday.objects.filter(id=holiday_id) doctor_id = holiday[0].person.id