diff --git a/WAM/urls.py b/WAM/urls.py index a6709c4..60bd75c 100644 --- a/WAM/urls.py +++ b/WAM/urls.py @@ -24,10 +24,14 @@ from loads import views +from loads.views import CreateActivityGeneratorView +from loads.views import UpdateActivityGeneratorView +from loads.views import DeleteActivityGeneratorView from loads.views import CreateProgrammeView from loads.views import ProgrammeList from loads.views import UpdateProgrammeView from loads.views import DetailsProgrammeView +from loads.views import CreateProjectView from loads.views import CreateModuleView from loads.views import UpdateModuleView from loads.views import CreateTaskView @@ -69,6 +73,9 @@ name='generators_index'), re_path(r'^generators/generate_activities/(?P[0-9]+)$', views.generators_generate_activities, name='generators_generate_activities'), + re_path(r'^generators/create$', CreateActivityGeneratorView.as_view(), name='create generator'), + re_path(r'^generators/update/(?P[0-9]+)$', UpdateActivityGeneratorView.as_view(), name='update generator'), + re_path(r'^generators/delete/(?P[0-9]+)$', DeleteActivityGeneratorView.as_view(), name='delete generator'), re_path(r'^tasks/index/$', views.tasks_index, name='tasks_index'), re_path(r'^tasks/archived/index/$', views.archived_tasks_index, @@ -104,6 +111,7 @@ re_path(r'^programmes/details/(?P[0-9]+)$', DetailsProgrammeView.as_view(), name='view programme'), re_path(r'^programmes/delete/(?P[0-9]+)$', DeleteProgrammeView.as_view(), name='delete programme'), re_path(r'^projects/index/$', views.projects_index, name='projects_index'), + re_path(r'^projects/create/$', CreateProjectView.as_view(), name='create project'), re_path(r'^projects/detail/(?P[0-9]+)$', views.projects_details, name='projects_details'), re_path(r'^projects/generate_activities/(?P[0-9]+)$', views.projects_generate_activities, name='projects_generate_activities'), diff --git a/loads/forms.py b/loads/forms.py index 9f95235..d285e23 100644 --- a/loads/forms.py +++ b/loads/forms.py @@ -11,7 +11,7 @@ from django.db import transaction from django.core.exceptions import ValidationError -from .models import AssessmentResource, Task, Module +from .models import AssessmentResource, Task, Module, ActivityGenerator from .models import AssessmentStaff from .models import AssessmentStateSignOff from .models import Staff @@ -157,6 +157,16 @@ class ModulesIndexForm(forms.Form): # Forms based on Models +class ActivityGeneratorForm(ModelForm): + """Form for creating or editing activity generators""" + + class Meta: + model = ActivityGenerator + fields = ['name', 'hours', 'percentage', 'hours_percentage', 'semester', 'activity_type', + 'module', 'comment', 'package', 'details', 'targets', 'groups'] + widgets = {'details': forms.TextInput, + 'package': forms.HiddenInput()} + class AssessmentStaffForm(ModelForm): """Used for adding AssessmentStaff to a work package""" @@ -469,6 +479,7 @@ def save(self): return user +# Formsets class BaseModuleStaffByStaffFormSet(FancyModelFormSet): """ Enables altering teaching allocation for a member of staff from staff views """ diff --git a/loads/templates/loads/activities.html b/loads/templates/loads/activities/activities.html similarity index 100% rename from loads/templates/loads/activities.html rename to loads/templates/loads/activities/activities.html diff --git a/loads/templates/loads/activity_confirm_delete.html b/loads/templates/loads/activities/activity_confirm_delete.html similarity index 100% rename from loads/templates/loads/activity_confirm_delete.html rename to loads/templates/loads/activities/activity_confirm_delete.html diff --git a/loads/templates/loads/activity_form.html b/loads/templates/loads/activities/activity_form.html similarity index 100% rename from loads/templates/loads/activity_form.html rename to loads/templates/loads/activities/activity_form.html diff --git a/loads/templates/loads/activity_list.html b/loads/templates/loads/activities/activity_list.html similarity index 100% rename from loads/templates/loads/activity_list.html rename to loads/templates/loads/activities/activity_list.html diff --git a/loads/templates/loads/assessmentstaff_list.html b/loads/templates/loads/admin/assessmentstaff_list.html similarity index 100% rename from loads/templates/loads/assessmentstaff_list.html rename to loads/templates/loads/admin/assessmentstaff_list.html diff --git a/loads/templates/loads/generators/activitygenerator_confirm_delete.html b/loads/templates/loads/generators/activitygenerator_confirm_delete.html new file mode 100644 index 0000000..79edf11 --- /dev/null +++ b/loads/templates/loads/generators/activitygenerator_confirm_delete.html @@ -0,0 +1,41 @@ +{% extends "loads/base_automatic.html" %} + +{% block content %} + +

Delete generator

+

({{ object }})

+ +{% if object.package.in_the_past %} +
+

Warning: The Work Package for this item is in the past!

+

You should not be deleting past generators unless they are a correction.

+
+{% endif %} + +
+

+ You are deleting "{{ object }}" in year + "{{ object.package }}" +

+
+ Deleting generators will not delete activities they already created. +
+
+ +
+

+ This cannot be undone! Are you sure? +

+

+

+ {{ form }} + {% csrf_token %} +

+ + Cancel +

+
+

+
+ +{% endblock content %} \ No newline at end of file diff --git a/loads/templates/loads/generators/activitygenerator_form.html b/loads/templates/loads/generators/activitygenerator_form.html new file mode 100644 index 0000000..74235a1 --- /dev/null +++ b/loads/templates/loads/generators/activitygenerator_form.html @@ -0,0 +1,95 @@ +{% extends "loads/base_automatic.html" %} {% block content %} + +{% if form.instance.pk %} +

+ Update Activity Generator +

+{% else %} +

+ Create Activity Generator +

+

+ This form allows the creation of new Activity generators. +

+{% endif %} + +
+
+ {% csrf_token %} + {# Process hidden fields, we don't need to style them #} + {% for hidden in form.hidden_fields %} + {{ hidden }} + {% endfor %} + {% for field in form.visible_fields %} +
+ + {% if field.field.widget.input_type == "text" or field.field.widget.input_type == "number" %} +
+ + {# Show field errors as a list, one per line #} + {% if field.errors %} +
+ {% for error in field.errors %} +

{{ error|escape }}

+ {% endfor %} +
+ {% endif %} +
+ {% elif field.field.widget.input_type == "select" %} +
+ + {% if field.field.widget.allow_multiple_selected %} +
+ Hold down CTRL or CMD to select multiple options +
+ {% endif %} + {# Show field errors as a list, one per line #} + {% if field.errors %} +
+ {% for error in field.errors %} +

{{ error|escape }}

+ {% endfor %} +
+ {% endif %} +
+ {% else %} +
+ Unknown field type: {{ field.field.widget.input_type }} +
+ {% endif %} +
+ {% endfor %} +
+
+ + Cancel +
+ {% if form.instance.pk %} + + {% endif %} +
+ +
+
+ +{% endblock content %} \ No newline at end of file diff --git a/loads/templates/loads/generators/index.html b/loads/templates/loads/generators/index.html index dfff0fe..07c0049 100644 --- a/loads/templates/loads/generators/index.html +++ b/loads/templates/loads/generators/index.html @@ -49,7 +49,8 @@

Activity Generators

Generator name Activity type - Generate activities + Update + Activities @@ -57,7 +58,10 @@

Activity Generators

{{ generator.name }} {{ generator.activity_type }} - + + Update + + Generate @@ -74,5 +78,9 @@

Activity Generators

No activity generators are available in this work package.

{% endif %} +
+

Add activity generator

+ Click here to create a new activity generator +
{% endblock content %} \ No newline at end of file diff --git a/loads/templates/loads/loads.html b/loads/templates/loads/loads/loads.html similarity index 100% rename from loads/templates/loads/loads.html rename to loads/templates/loads/loads/loads.html diff --git a/loads/templates/loads/loads_charts.html b/loads/templates/loads/loads/loads_charts.html similarity index 100% rename from loads/templates/loads/loads_charts.html rename to loads/templates/loads/loads/loads_charts.html diff --git a/loads/templates/loads/module_confirm_delete.html b/loads/templates/loads/modules/module_confirm_delete.html similarity index 100% rename from loads/templates/loads/module_confirm_delete.html rename to loads/templates/loads/modules/module_confirm_delete.html diff --git a/loads/templates/loads/module_form.html b/loads/templates/loads/modules/module_form.html similarity index 100% rename from loads/templates/loads/module_form.html rename to loads/templates/loads/modules/module_form.html diff --git a/loads/templates/loads/programme_confirm_delete.html b/loads/templates/loads/programmes/programme_confirm_delete.html similarity index 100% rename from loads/templates/loads/programme_confirm_delete.html rename to loads/templates/loads/programmes/programme_confirm_delete.html diff --git a/loads/templates/loads/programme_detail.html b/loads/templates/loads/programmes/programme_detail.html similarity index 100% rename from loads/templates/loads/programme_detail.html rename to loads/templates/loads/programmes/programme_detail.html diff --git a/loads/templates/loads/programme_form.html b/loads/templates/loads/programmes/programme_form.html similarity index 100% rename from loads/templates/loads/programme_form.html rename to loads/templates/loads/programmes/programme_form.html diff --git a/loads/templates/loads/programme_list.html b/loads/templates/loads/programmes/programme_list.html similarity index 100% rename from loads/templates/loads/programme_list.html rename to loads/templates/loads/programmes/programme_list.html diff --git a/loads/templates/loads/projects/index.html b/loads/templates/loads/projects/index.html index 4b62bba..094d726 100644 --- a/loads/templates/loads/projects/index.html +++ b/loads/templates/loads/projects/index.html @@ -58,5 +58,9 @@

Projects

No unarchived projects are available.

{% endif %} + {% endblock content %} \ No newline at end of file diff --git a/loads/templates/loads/projects/project_form.html b/loads/templates/loads/projects/project_form.html new file mode 100644 index 0000000..055b8b1 --- /dev/null +++ b/loads/templates/loads/projects/project_form.html @@ -0,0 +1,97 @@ +{% extends "loads/base_automatic.html" %} {% block content %} + +{% if form.instance.pk %} +

+ Update Project +

+{% else %} +

+ Create Project +

+

+ This form allows the creation of new projects. +

+{% endif %} + +
+
+ {% csrf_token %} + {# Process hidden fields, we don't need to style them #} + {% for hidden in form.hidden_fields %} + {{ hidden }} + {% endfor %} + {% for field in form.visible_fields %} +
+ + {% if field.field.widget.input_type == "text" or field.field.widget.input_type == "number" or field.field.widget.input_type == "date" %} +
+ + {# Show field errors as a list, one per line #} + {% if field.errors %} +
+ {% for error in field.errors %} +

{{ error|escape }}

+ {% endfor %} +
+ {% endif %} +
+ {% elif field.field.widget.input_type == "select" %} +
+ + {% if field.field.widget.allow_multiple_selected %} +
+ Hold down CTRL or CMD to select multiple options +
+ {% endif %} + {# Show field errors as a list, one per line #} + {% if field.errors %} +
+ {% for error in field.errors %} +

{{ error|escape }}

+ {% endfor %} +
+ {% endif %} +
+ {% else %} +
+ Unknown field type: {{ field.field.widget.input_type }} +
+ {% endif %} +
+ {% endfor %} +
+
+ + Cancel +
+ {% comment %} + {% if form.instance.pk %} + + {% endif %} + {% endcomment %} +
+ +
+
+ +{% endblock content %} \ No newline at end of file diff --git a/loads/templates/loads/task_form.html b/loads/templates/loads/tasks/task_form.html similarity index 100% rename from loads/templates/loads/task_form.html rename to loads/templates/loads/tasks/task_form.html diff --git a/loads/tests_clients.py b/loads/tests_clients.py index 79b0c7c..3ca584e 100644 --- a/loads/tests_clients.py +++ b/loads/tests_clients.py @@ -519,6 +519,106 @@ def test_superuser_module_pages(self): self.assertEqual(response.status_code, 404) + def test_staff_no_role_programme_pages(self): + """This checks that a Staff member with no specific has appropriate programme views""" + + # Log the User in + user = User.objects.get(username='user') + staff = Staff.objects.get(user=user) + # force_login bypasses potential custom authentication back ends + self.client.force_login(user) + + programme = Programme.objects.get(programme_code="123") + + # These views should be response code 200 (OK) + response = self.client.get("/programmes/details/%u" % programme.id) + self.assertEqual(response.status_code, 200) + + # These views should be response code 403 (Forbidden) + response = self.client.get("/programmes/update/%u" % programme.id) + self.assertEqual(response.status_code, 403) + + # These views should be response code 404 (Not Found) + response = self.client.get("/programmes/update/9999") + self.assertEqual(response.status_code, 403) + + # These views should be response code 403 (Forbidden) + response = self.client.get("/programmes/create/") + self.assertEqual(response.status_code, 403) + + response = self.client.get("/programmes/delete/%u" % programme.id) + self.assertEqual(response.status_code, 403) + + # These views should be response code 404 (Not Found) + response = self.client.get("/programmes/details/9999") + self.assertEqual(response.status_code, 404) + + + def test_external_programme_pages(self): + """This checks that a Staff member with no specific has appropriate module views""" + + # Log the User in + user = User.objects.get(username='external') + staff = Staff.objects.get(user=user) + # force_login bypasses potential custom authentication back ends + self.client.force_login(user) + + programme = Programme.objects.get(programme_code="123") + + # These views should be response code 200 (OK) + response = self.client.get("/programmes/details/%u" % programme.id) + self.assertEqual(response.status_code, 200) + + # These views should be response code 403 (Forbidden) + response = self.client.get("/programmes/update/%u" % programme.id) + self.assertEqual(response.status_code, 403) + + response = self.client.get("/programmes/update/9999") + self.assertEqual(response.status_code, 403) + + response = self.client.get("/programmes/create/") + self.assertEqual(response.status_code, 403) + + response = self.client.get("/programmes/delete/%u" % programme.id) + self.assertEqual(response.status_code, 403) + + # These views should be response code 404 (Not Found) + response = self.client.get("/programmes/details/9999") + self.assertEqual(response.status_code, 404) + + + def test_superuser_programme_pages(self): + """This checks that a Staff member with no specific has appropriate module views""" + + # Log the User in + user = User.objects.get(username='admin') + staff = Staff.objects.get(user=user) + # force_login bypasses potential custom authentication back ends + self.client.force_login(user) + + programme = Programme.objects.get(programme_code="123") + + # These views should be response code 200 (OK) + response = self.client.get("/programmes/details/%u" % programme.id) + self.assertEqual(response.status_code, 200) + + response = self.client.get("/programmes/update/%u" % programme.id) + self.assertEqual(response.status_code, 200) + + response = self.client.get("/programmes/create/") + self.assertEqual(response.status_code, 200) + + response = self.client.get("/programmes/delete/%u" % programme.id) + self.assertEqual(response.status_code, 200) + + # These views should be response code 404 (Not Found) + response = self.client.get("/programmes/details/9999") + self.assertEqual(response.status_code, 404) + + response = self.client.get("/programmes/update/9999") + self.assertEqual(response.status_code, 404) + + def test_staff_task_pages(self): """This checks that a Staff member can access the various task views""" diff --git a/loads/views.py b/loads/views.py index 94b5425..ea4b2ae 100644 --- a/loads/views.py +++ b/loads/views.py @@ -41,6 +41,7 @@ from .models import ProjectStaff from .models import WorkPackage +from .forms import ActivityGeneratorForm from .forms import AssessmentResourceForm from .forms import AssessmentStaffForm from .forms import AssessmentStateSignOffForm @@ -368,7 +369,7 @@ def loads(request): else: average = 0 - template = loader.get_template('loads/loads.html') + template = loader.get_template('loads/loads/loads.html') context = { 'group_data': group_data, 'total': total, @@ -529,7 +530,7 @@ def loads_by_staff_chart(request): else: average = 0 - template = loader.get_template('loads/loads_charts.html') + template = loader.get_template('loads/loads/loads_charts.html') context = { 'form': form, 'sort_by_load': sort_by_load, @@ -749,7 +750,7 @@ def activities(request, staff_id): semester3_total = semester3_total * 100 / package.nominal_hours total = 100 * total / package.nominal_hours - template = loader.get_template('loads/activities.html') + template = loader.get_template('loads/activities/activities.html') context = { 'staff': staff, 'combined_list': combined_list, @@ -803,7 +804,7 @@ def assessmentstaff_index(request): form.fields['package'].initial = package form.fields['staff'].queryset = possible_assessment_staff - template = loader.get_template('loads/assessmentstaff_list.html') + template = loader.get_template('loads/admin/assessmentstaff_list.html') context = { 'staff': staff, 'package': package, @@ -1981,8 +1982,6 @@ def workpackage_change(request): except KeyError: next_page_push(request, reverse('loads')) - - return render(request, 'loads/workpackage.html', {'form': form, 'staff': staff}) @@ -2045,10 +2044,125 @@ def workpackage_migrate(request): # Class based views +@method_decorator(login_required, name='dispatch') +@method_decorator(staff_only, name='dispatch') +class CreateActivityGeneratorView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): + """View for creating a Module""" + permission_required = 'loads.add_activitygenerator' + model = ActivityGenerator + form_class = ActivityGeneratorForm + success_url = reverse_lazy('generators_index') + template_name = 'loads/generators/activitygenerator_form.html' + fields = ['name', 'hours', 'percentage', 'hours_percentage', 'semester', 'activity_type', + 'module', 'comment', 'package', 'details', 'targets', 'groups'] + + def get_form(self, form_class=ActivityGeneratorForm): + """We need to restrict form querysets""" + form = super(CreateActivityGeneratorView, self).get_form(form_class) + + # Work out the correct package and the staff within in + staff = get_object_or_404(Staff, user=self.request.user) + package = staff.package + package_staff = package.get_all_staff() + + # And restrict the querysets as appropriate + form.fields['package'].initial = package + form.fields['targets'].queryset = package_staff + form.fields['groups'].queryset = package.groups + return form + + def form_valid(self, form): + # Work out the correct package and the staff within in + staff = get_object_or_404(Staff, user=self.request.user) + package = staff.package + + self.object = form.save(commit=False) + self.object.package = package + response = super(CreateActivityGeneratorView, self).form_valid(form) + return response + + +@method_decorator(login_required, name='dispatch') +@method_decorator(staff_only, name='dispatch') +class UpdateActivityGeneratorView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): + """View for creating a Module""" + permission_required = 'loads.change_activitygenerator' + model = ActivityGenerator + form_class = ActivityGeneratorForm + success_url = reverse_lazy('generators_index') + template_name = 'loads/generators/activitygenerator_form.html' + fields = ['name', 'hours', 'percentage', 'hours_percentage', 'semester', 'activity_type', + 'module', 'comment', 'package', 'details', 'targets', 'groups'] + + def get_form(self, form_class=ActivityGeneratorForm): + """We need to restrict form querysets""" + form = super(UpdateActivityGeneratorView, self).get_form(form_class) + + # Work out the correct package and the staff within in + staff = get_object_or_404(Staff, user=self.request.user) + package = staff.package + package_staff = package.get_all_staff() + + # And restrict the querysets as appropriate + form.fields['package'].initial = package + form.fields['targets'].queryset = package_staff + form.fields['groups'].queryset = package.groups + return form + + def form_valid(self, form): + # Work out the correct package and the staff within in + staff = get_object_or_404(Staff, user=self.request.user) + package = staff.package + + self.object = form.save(commit=False) + self.object.package = package + response = super(UpdateActivityGeneratorView, self).form_valid(form) + return response + + +@method_decorator(login_required, name='dispatch') +@method_decorator(staff_only, name='dispatch') +class DeleteActivityGeneratorView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView): + """ + View for deleting a Module + + There are a number of import Models that will delete on Cascade from this so checks and warnings are important. + """ + permission_required = 'loads.delete_activitygenerator' + model = ActivityGenerator + template_name = 'loads/generators/activitygenerator_confirm_delete.html' + success_url = reverse_lazy('generators_index') + + def dispatch(self, request, *args, **kwargs): + # Check this Activity is the business of the logged in user + generator = self.get_object() + + try: + staff = Staff.objects.get(user=self.request.user) + except Staff.DoesNotExist: + raise PermissionDenied("""Your user has no matching staff object.""") + + if request.method == "POST": + action_verb = "confirming deletion of" + else: + action_verb = "seeking to delete" + + logger.warning("[%s] %s activity generator %s in package %s" % + (request.user, action_verb, generator.name, generator.package)) + + if not request.user.is_superuser: + if generator.package not in staff.get_all_packages(include_hidden=True): + logger.warning("[%s] permission denied, generator not in workpackages." % request.user) + raise PermissionDenied("""Sorry, this generator is not in your workpackages.""") + + return super().dispatch(request, *args, **kwargs) + + class CreateTaskView(LoginRequiredMixin, CreateView): """View for creating a task""" model = Task success_url = reverse_lazy('tasks_index') + template_name = 'loads/tasks/task_form.html' fields = ['name', 'category', 'details', 'deadline', 'archive', 'targets', 'groups'] def get_form(self, form_class=TaskForm): @@ -2083,6 +2197,7 @@ class UpdateTaskView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): permission_required = 'loads.change_task' model = Task success_url = reverse_lazy('tasks_index') + template_name = 'loads/tasks/task_form.html' fields = ['name', 'category', 'details', 'deadline', 'archive', 'targets', 'groups'] def get_form(self, form_class=None): @@ -2111,6 +2226,7 @@ class CreateModuleView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): """View for creating a Module""" permission_required = 'loads.add_module' model = Module + template_name = 'loads/modules/module_form.html' fields = ['module_code', 'module_name', 'campus', 'size', 'semester', 'contact_hours', 'admin_hours', 'assessment_hours', 'coordinator', 'moderators', 'programmes', 'lead_programme'] @@ -2153,6 +2269,7 @@ class UpdateModuleView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): """View for editing a Module""" permission_required = 'loads.change_module' model = Module + template_name = 'loads/modules/module_form.html' def get_form(self, form_class=ModuleForm): @@ -2188,6 +2305,7 @@ class DeleteModuleView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView): """ permission_required = 'loads.delete_module' model = Module + template_name = 'loads/modules/module_confirm_delete.html' success_url = reverse_lazy('modules_index') def dispatch(self, request, *args, **kwargs): @@ -2250,6 +2368,7 @@ class CreateProgrammeView(LoginRequiredMixin, PermissionRequiredMixin, CreateVie permission_required = 'loads.add_programme' model = Programme success_url = reverse_lazy('programmes_index') + template_name = 'loads/programmes/programme_form.html' fields = ['programme_code', 'programme_name', 'examiners', 'directors'] def get_form(self, form_class=None): @@ -2278,6 +2397,7 @@ class DetailsProgrammeView(LoginRequiredMixin, DetailView): """View for editing a Programme""" model = Programme success_url = reverse_lazy('programmes_index') + template_name = 'loads/programmes/programme_detail.html' fields = ['programme_code', 'programme_name', 'examiners', 'directors'] def get_queryset(self): @@ -2296,6 +2416,7 @@ class UpdateProgrammeView(LoginRequiredMixin, PermissionRequiredMixin, UpdateVie permission_required = 'loads.change_programme' model = Programme success_url = reverse_lazy('programmes_index') + template_name = 'loads/programmes/programme_form.html' fields = ['programme_code', 'programme_name', 'examiners', 'directors'] def get_form(self, form_class=None): @@ -2315,6 +2436,7 @@ class ProgrammeList(LoginRequiredMixin, ListView): """Generic view for Programme List""" model = Programme context_object_name = 'programmes' + template_name = 'loads/programmes/programme_list.html' def get_queryset(self): try: @@ -2350,6 +2472,7 @@ class DeleteProgrammeView(LoginRequiredMixin, PermissionRequiredMixin, DeleteVie """ permission_required = 'loads.delete_programme' model = Programme + template_name = 'loads/programmes/programme_confirm_delete.html' success_url = reverse_lazy('programmes_index') def dispatch(self, request, *args, **kwargs): @@ -2376,6 +2499,17 @@ def dispatch(self, request, *args, **kwargs): return super().dispatch(request, *args, **kwargs) +@method_decorator(login_required, name='dispatch') +@method_decorator(staff_only, name='dispatch') +class CreateProjectView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): + """View for creating a Project""" + permission_required = 'loads.add_project' + model = Project + form_class = ProjectForm + template_name = "loads/projects/project_form.html" + success_url = reverse_lazy('projects_index') + + # Looks like decorators execute before mixins, so if you don't call the login decorator, the staff_only may fail @method_decorator(login_required, name='dispatch') @method_decorator(staff_only, name='dispatch') @@ -2385,6 +2519,7 @@ class ActivityListView(LoginRequiredMixin, ListView): """ model = Activity context_object_name = 'activities' + template_name = 'loads/activities/activity_list.html' #TODO: Need more thought before paginate_by = 20 def get_queryset(self): @@ -2407,11 +2542,14 @@ def get_context_data(self, **kwargs): return context +@method_decorator(login_required, name='dispatch') +@method_decorator(staff_only, name='dispatch') class CreateActivityView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): """View for creating an Activity""" permission_required = 'loads.add_activity' model = Activity success_url = reverse_lazy('activities_index') + template_name = 'loads/activities/activity_form.html' fields = ['name', 'hours', 'percentage', 'hours_percentage', 'semester', 'activity_type', 'module', 'staff', 'comment'] @@ -2438,11 +2576,14 @@ def form_valid(self, form): return response +@method_decorator(login_required, name='dispatch') +@method_decorator(staff_only, name='dispatch') class UpdateActivityView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): """View for updating an Activity""" permission_required = 'loads.change_activity' model = Activity success_url = reverse_lazy('activities_index') + template_name = 'loads/activities/activity_form.html' fields = ['name', 'hours', 'percentage', 'hours_percentage', 'semester', 'activity_type', 'module', 'staff', 'comment'] @@ -2469,10 +2610,13 @@ def form_valid(self, form): return response +@method_decorator(login_required, name='dispatch') +@method_decorator(staff_only, name='dispatch') class DeleteActivityView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView): """View for deleting an Activity""" permission_required = 'loads.delete_activity' model = Activity + template_name = 'loads/activities/activity_confirm_delete.html' success_url = reverse_lazy('activities_index') def dispatch(self, request, *args, **kwargs):