diff --git a/core b/core
deleted file mode 100644
index e69de29..0000000
diff --git a/jasmin_services/templates/jasmin_services/role_apply.html b/jasmin_services/templates/jasmin_services/role_apply.html
index 8cf12cd..a24b39d 100644
--- a/jasmin_services/templates/jasmin_services/role_apply.html
+++ b/jasmin_services/templates/jasmin_services/role_apply.html
@@ -33,22 +33,6 @@
-
- {% if licence_url %}
-
diff --git a/jasmin_services/urls.py b/jasmin_services/urls.py
index 5291120..2223772 100644
--- a/jasmin_services/urls.py
+++ b/jasmin_services/urls.py
@@ -35,12 +35,12 @@
),
path(
"//apply//",
- views.role_apply,
+ views.RoleApplyView.as_view(),
name="role_apply",
),
path(
"//apply////",
- views.role_apply,
+ views.RoleApplyView.as_view(),
name="role_apply",
),
path("request//decide/", views.request_decide, name="request_decide"),
diff --git a/jasmin_services/views/__init__.py b/jasmin_services/views/__init__.py
index 66d56f5..09d19e0 100644
--- a/jasmin_services/views/__init__.py
+++ b/jasmin_services/views/__init__.py
@@ -3,7 +3,7 @@
from .my_services import my_services
from .request_decide import request_decide
from .reverse_dns_check import reverse_dns_check
-from .role_apply import role_apply
+from .role_apply import RoleApplyView
from .service_details import service_details
from .service_list import service_list
from .service_message import service_message
@@ -16,7 +16,7 @@
"my_services",
"request_decide",
"reverse_dns_check",
- "role_apply",
+ "RoleApplyView",
"service_details",
"service_list",
"service_message",
diff --git a/jasmin_services/views/role_apply.py b/jasmin_services/views/role_apply.py
index 46cb659..cd2204b 100644
--- a/jasmin_services/views/role_apply.py
+++ b/jasmin_services/views/role_apply.py
@@ -1,158 +1,195 @@
import logging
from datetime import date
+import django.contrib.auth.mixins
+import django.core.exceptions
import django.http
-import requests as reqs
+import django.views.generic
+import django.views.generic.edit
+import requests
from dateutil.relativedelta import relativedelta
from django.conf import settings
from django.contrib import messages
-from django.contrib.auth.decorators import login_required
from django.db import transaction
-from django.shortcuts import render
-from django.views.decorators.http import require_http_methods
+from .. import models
from ..models import Access, Grant, Group, Request, RequestState, Role
from . import common
_log = logging.getLogger(__name__)
-@require_http_methods(["GET", "POST"])
-@login_required
-@common.with_service
-def role_apply(request, service, role, bool_grant=None, previous=None):
+class RoleApplyView(
+ django.contrib.auth.mixins.LoginRequiredMixin, django.views.generic.edit.FormView
+):
"""Handle for ``///apply//``.
- Responds to GET and POST requests. The user must be authenticated.
-
Collects the necessary information to raise a request for a role.
"""
- # Prevent users who are not allowed to apply for this service from doing so.
- user_may_apply = common.user_may_apply(request.user, service)
- if not user_may_apply[0]:
- return django.http.HttpResponseForbidden(
- "You do not have permission to apply for this service."
- )
- try:
- role = Role.objects.get(service=service, name=role)
- except Role.DoesNotExist:
- messages.error(request, "Role does not exist")
- return common.redirect_to_service(service)
-
- previous_grant = None
- previous_request = None
- # bool_grant = 1 if the new request is being made from a previous grant
- if bool_grant == 1:
- previous_grant = Grant.objects.get(pk=previous)
- previous_request = (
- Request.objects.filter_active().filter(previous_grant=previous_grant).first()
+ @staticmethod
+ def get_service(category_name, service_name):
+ """Get a service from it's category and name."""
+ try:
+ return models.Service.objects.get(name=service_name, category__name=category_name)
+ except models.Service.DoesNotExist as err:
+ raise django.http.Http404("Service does not exist.") from err
+
+ @staticmethod
+ def get_previous_request_and_grant(bool_grant, previous):
+ """Get the previous request or grant from the id supplied."""
+ error = None
+ previous_grant = None
+ previous_request = None
+ # bool_grant = 1 if the new request is being made from a previous grant
+ if bool_grant == 1:
+ previous_grant = Grant.objects.get(pk=previous)
+ previous_request = (
+ Request.objects.filter_active().filter(previous_grant=previous_grant).first()
+ )
+ # bool_grant = 0 if the new request is being made from a previous request
+ elif bool_grant == 0:
+ previous_request = Request.objects.get(pk=previous)
+ if previous_request.previous_grant:
+ previous_grant = previous_request.previous_grant
+
+ # If the user has a more recent request or grant for this chain they must use that
+ if (previous_request and hasattr(previous_request, "next_request")) or (
+ previous_grant and hasattr(previous_grant, "next_grant")
+ ):
+ error = "Please use the most recent request or grant"
+
+ # If the user has an active request for this chain it must be rejected
+ elif previous_request and previous_request.state != RequestState.REJECTED:
+ error = "You have already have an active request for the specified grant"
+
+ return error, previous_grant, previous_request
+
+ def setup(self, request, *args, **kwargs):
+ """Set up extra class attributes depending on the service and role we are dealing with.
+
+ These are needed throughout and not just in the context.
+ """
+ # pylint: disable=attribute-defined-outside-init
+ super().setup(request, *args, **kwargs)
+ self.redirect_to_service = True
+
+ # Get the service from the request.
+ self.service = self.get_service(kwargs["category"], kwargs["service"])
+ if self.service.disabled:
+ raise django.http.Http404("Service has been retired.")
+
+ # Prevent users who are not allowed to apply for this service from doing so.
+ user_may_apply = common.user_may_apply(request.user, self.service)
+ if not user_may_apply[0]:
+ raise django.core.exceptions.PermissionDenied(
+ "You do not have permission to apply for this service."
+ )
+
+ # Get the role we are applying for from the request.
+ try:
+ self.role = Role.objects.get(service=self.service, name=kwargs["role"])
+ except Role.DoesNotExist:
+ messages.error(request, "Role does not exist")
+ self.redirect_to_service = True
+ return
+
+ # Get the previous request or grant, if it is supplies.
+ error, self.previous_request, self.previous_grant = self.get_previous_request_and_grant(
+ kwargs.get("bool_grant", None), kwargs.get("previous", None)
)
- # bool_grant = 0 if the new request is being made from a previous request
- elif bool_grant == 0:
- previous_request = Request.objects.get(pk=previous)
- if previous_request.previous_grant:
- previous_grant = previous_request.previous_grant
-
- # If the user has a more recent request or grant for this chain they must use that
- if (previous_request and hasattr(previous_request, "next_request")) or (
- previous_grant and hasattr(previous_grant, "next_grant")
- ):
- messages.info(request, "Please use the most recent request or grant")
- return common.redirect_to_service(service)
-
- # If the user has an active request for this chain it must be rejected
- if previous_request and previous_request.state != RequestState.REJECTED:
- messages.info(request, "You have already have an active request for the specified grant")
- return common.redirect_to_service(service)
-
- # ONLY FOR CEDA SERVICES: Get licence url
- licence_url = None
- if settings.LICENCE_REQUIRED:
- groups = [b for b in role.behaviours.all() if isinstance(b, Group)]
- if groups:
- group = groups[0]
- response = reqs.get(
- settings.licence_url,
- params={"group": group.name},
+ if error is not None:
+ messages.info(request, error)
+ self.redirect_to_service = True
+ return
+
+ # If we have sucessfuly got all the info, we do not need to redirect to the service.
+ self.redirect_to_service = False
+
+ def dispatch(self, request, *args, **kwargs):
+ """Check the user may apply for the role before dispatching."""
+ if self.redirect_to_service:
+ return common.redirect_to_service(self.service)
+
+ return super().dispatch(request, *args, **kwargs)
+
+ def get_form_class(self):
+ """Use the form class for the role being applied for.
+
+ This includes the correct metadata forms for the role.
+ """
+ return self.role.metadata_form_class
+
+ def get_initial(self):
+ """Add previous requests to inital form data."""
+ initial = {}
+ if self.previous_request is not None:
+ for datum in self.previous_request.metadata.all():
+ initial[datum.key] = datum.value
+ return initial
+
+ def get_template_names(self):
+ """Allow overriding the template at multiple levels."""
+ return [
+ f"jasmin_services/{self.service.category.name}/{self.service.name}/{self.role.name}/role_apply.html",
+ f"jasmin_services/{self.service.category.name}/{self.service.name}/role_apply.html",
+ f"jasmin_services/{self.service.category.name}/role_apply.html",
+ "jasmin_services/role_apply.html",
+ ]
+
+ def get_context_data(self, **kwargs):
+ """Add the role, grant and request to the context."""
+ context = super().get_context_data(**kwargs)
+ context |= {
+ "role": self.role,
+ "grant": self.previous_grant,
+ "req": self.previous_request,
+ }
+ return context
+
+ def form_valid(self, form):
+ """Handle the form and create the role request."""
+ with transaction.atomic():
+ access, _ = Access.objects.get_or_create(
+ role=self.role,
+ user=self.request.user,
)
- json_response = response.json()
- licence_url = json_response["licence"]
-
- # Otherwise, attempt to do something
- form_class = role.metadata_form_class
- if request.method == "POST":
- form = form_class(data=request.POST)
- if form.is_valid():
- with transaction.atomic():
- access, _ = Access.objects.get_or_create(
- role=role,
- user=request.user,
+ # If the role is set to auto accept, grant before saving
+ if self.role.auto_accept:
+ req = Request.objects.create(
+ access=access,
+ requested_by=self.request.user.username,
+ state=RequestState.APPROVED,
+ )
+ req.resulting_grant = Grant.objects.create(
+ access=access,
+ granted_by="automatic",
+ expires=date.today() + relativedelta(years=1),
)
- # If the role is set to auto accept, grant before saving
- if role.auto_accept:
- req = Request.objects.create(
- access=access,
- requested_by=request.user.username,
- state=RequestState.APPROVED,
- )
- req.resulting_grant = Grant.objects.create(
- access=access,
- granted_by="automatic",
- expires=date.today() + relativedelta(years=1),
- )
-
- if previous_request:
- req.previous_request = previous_request
- req.save()
-
- if previous_grant:
- req.resulting_grant.previous_grant = previous_grant
- req.previous_grant = previous_grant
- req.resulting_grant.save()
+ if self.previous_request is not None:
+ req.previous_request = self.previous_request
req.save()
- form.save(req)
- req.copy_metadata_to(req.resulting_grant)
- else:
- req = Request.objects.create(access=access, requested_by=request.user.username)
- if previous_request:
- previous_request.next_request = req
- previous_request.save()
+ if self.previous_grant is not None:
+ req.resulting_grant.previous_grant = self.previous_grant
+ req.previous_grant = self.previous_grant
+ req.resulting_grant.save()
- if previous_grant:
- req.previous_grant = previous_grant
+ req.save()
+ form.save(req)
+ req.copy_metadata_to(req.resulting_grant)
+ else:
+ req = Request.objects.create(access=access, requested_by=self.request.user.username)
- req.save()
- form.save(req)
- messages.success(request, "Request submitted successfully")
- return common.redirect_to_service(service)
- else:
- messages.error(request, "Error with one or more fields")
- else:
- # Set the initial data to the metadata attached to the active request
- initial = {}
- if previous_request:
- for datum in previous_request.metadata.all():
- initial[datum.key] = datum.value
- form = form_class(initial=initial)
- templates = [
- "jasmin_services/{}/{}/{}/role_apply.html".format(
- service.category.name, service.name, role.name
- ),
- "jasmin_services/{}/{}/role_apply.html".format(service.category.name, service.name),
- "jasmin_services/{}/role_apply.html".format(service.category.name),
- "jasmin_services/role_apply.html",
- ]
- return render(
- request,
- templates,
- {
- "role": role,
- "grant": previous_grant,
- "req": previous_request,
- "form": form,
- "licence_url": licence_url,
- },
- )
+ if self.previous_request is not None:
+ self.previous_request.next_request = req
+ self.previous_request.save()
+
+ if self.previous_grant is not None:
+ req.previous_grant = self.previous_grant
+ req.save()
+ form.save(req)
+
+ messages.success(self.request, "Request submitted successfully")
+ return common.redirect_to_service(self.service)
diff --git a/pyproject.toml b/pyproject.toml
index 0bb1c4c..32febdb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -40,6 +40,7 @@ isort = "^5.10.1"
mypy = "^1.2.0"
types-python-dateutil = "^2.8.19.12"
django-stubs = {extras = ["compatible-mypy"], version = "^4.2.0"}
+pylint-django = "^2.5.5"
[tool.poetry.group.test.dependencies]
django-oauth-toolkit = "^1.7.0"
@@ -62,6 +63,9 @@ django_settings_module = "tests.settings"
[tool.pydocstyle]
ignore = ['D106']
+[tool.pylint.main]
+load-plugins = 'pylint_django'
+
[tool.pylint.'MESSAGES CONTROL']
disable = ['too-few-public-methods', 'missing-class-docstring']