-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve role_apply view to allow better licence handling in CEDA Serv…
…ices Portal. (#128) * Change role_apply to a class based view. * Remove licence handling code from jasmin-services. It moves to the ceda services portal.
- Loading branch information
Showing
6 changed files
with
175 additions
and
150 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ``/<category>/<service>/apply/<role>/``. | ||
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters