diff --git a/coldfront/core/allocation/forms.py b/coldfront/core/allocation/forms.py index fe1f02bed..e8de4b9c0 100644 --- a/coldfront/core/allocation/forms.py +++ b/coldfront/core/allocation/forms.py @@ -164,11 +164,11 @@ class AllocationApprovalForm(forms.Form): sheetcheck = forms.BooleanField( label='I have ensured that enough space is available on this resource.', - required=True + required=False, ) auto_create_opts = forms.ChoiceField( label='How will this allocation be created?', - required=True, + required=False, widget=forms.RadioSelect, choices=ALLOCATION_AUTOCREATE_OPTIONS, ) @@ -184,6 +184,29 @@ class AllocationApprovalForm(forms.Form): ), ) + def clean(self): + cleaned_data = super().clean() + auto_create_opts = cleaned_data.get('auto_create_opts') + automation_specifications = cleaned_data.get('automation_specifications') + # if the action is 'approve', make auto_create_opts and sheetcheck mandatory + if not auto_create_opts: + self.add_error( + 'auto_create_opts', + 'You must select an option for how the allocation will be created.' + ) + if auto_create_opts == '2': + if not automation_specifications: + self.add_error( + 'automation_specifications', + 'You must select at least one automation option if you choose to automatically create the allocation.' + ) + if not cleaned_data.get('sheetcheck'): + self.add_error( + 'sheetcheck', + 'You must confirm that you have checked the space availability on this resource.' + ) + return cleaned_data + class AllocationResourceChoiceField(forms.ModelChoiceField): def label_from_instance(self, obj): diff --git a/coldfront/core/allocation/management/commands/add_allocation_defaults.py b/coldfront/core/allocation/management/commands/add_allocation_defaults.py index 1cfb1f179..92d1ace0c 100644 --- a/coldfront/core/allocation/management/commands/add_allocation_defaults.py +++ b/coldfront/core/allocation/management/commands/add_allocation_defaults.py @@ -32,7 +32,7 @@ def handle(self, *args, **options): # 'Paid', 'Payment Pending', 'Payment Requested', # 'Payment Declined', 'Revoked', 'Renewal Requested', 'Unpaid', ): - choice_obj = AllocationStatusChoice.objects.get_or_create(name=choice) + choice_obj, created = AllocationStatusChoice.objects.get_or_create(name=choice) choice_obj.description = description choice_obj.save() diff --git a/coldfront/core/allocation/signals.py b/coldfront/core/allocation/signals.py index 4f4c67ba1..f98e7da2e 100644 --- a/coldfront/core/allocation/signals.py +++ b/coldfront/core/allocation/signals.py @@ -1,5 +1,8 @@ import django.dispatch +allocation_autocreate = django.dispatch.Signal() + #providing_args=["approval_form_data", "allocation_obj"] + allocation_activate = django.dispatch.Signal() #providing_args=["allocation_pk"] allocation_disable = django.dispatch.Signal() diff --git a/coldfront/core/allocation/templates/allocation/allocation_detail.html b/coldfront/core/allocation/templates/allocation/allocation_detail.html index 0bafce618..30f0e6e2e 100644 --- a/coldfront/core/allocation/templates/allocation/allocation_detail.html +++ b/coldfront/core/allocation/templates/allocation/allocation_detail.html @@ -310,9 +310,8 @@

Allocation Information

{% if request.user.is_superuser %}
- {% if not allocation.status.name in 'New,Renewal Requested' %} - - {% else %} + + {% if allocation.status.name in 'New,Renewal Requested,In Progress,On Hold' %} {% endif %} diff --git a/coldfront/core/allocation/views.py b/coldfront/core/allocation/views.py index 49f4317eb..a142696ed 100644 --- a/coldfront/core/allocation/views.py +++ b/coldfront/core/allocation/views.py @@ -58,6 +58,7 @@ AllocationUserNote, AllocationUserStatusChoice) from coldfront.core.allocation.signals import (allocation_activate, + allocation_autocreate, allocation_activate_user, allocation_disable, allocation_remove_user, @@ -77,9 +78,7 @@ if 'django_q' in settings.INSTALLED_APPS: from django_q.tasks import Task if 'coldfront.plugins.isilon' in settings.INSTALLED_APPS: - from coldfront.plugins.isilon.utils import ( - update_isilon_allocation_quota, create_isilon_allocation_quota - ) + from coldfront.plugins.isilon.utils import update_isilon_allocation_quota ALLOCATION_ENABLE_ALLOCATION_RENEWAL = import_from_settings( 'ALLOCATION_ENABLE_ALLOCATION_RENEWAL', True) @@ -257,7 +256,7 @@ def get(self, request, *args, **kwargs): if not self.request.user.is_superuser: form.fields['is_locked'].disabled = True form.fields['is_changeable'].disabled = True - elif allocation_obj.status.name == 'New': + elif allocation_obj.status.name in PENDING_ALLOCATION_STATUSES: approval_form = AllocationApprovalForm() context['approval_form'] = approval_form @@ -296,18 +295,24 @@ def post(self, request, *args, **kwargs): form_data = form.cleaned_data old_status = allocation_obj.status.name - approval_form = AllocationApprovalForm(request.POST) - - if action in ['update', 'approve', 'deny']: - allocation_obj.end_date = form_data.get('end_date') - allocation_obj.start_date = form_data.get('start_date') - allocation_obj.description = form_data.get('description') - allocation_obj.is_locked = form_data.get('is_locked') - allocation_obj.is_changeable = form_data.get('is_changeable') - allocation_obj.status = form_data.get('status') + allocation_obj.end_date = form_data.get('end_date') + allocation_obj.start_date = form_data.get('start_date') + allocation_obj.description = form_data.get('description') + allocation_obj.is_locked = form_data.get('is_locked') + allocation_obj.is_changeable = form_data.get('is_changeable') + allocation_obj.status = form_data.get('status') if 'approve' in action: + + approval_form = AllocationApprovalForm(request.POST) + if not approval_form.is_valid(): + context = self.get_context_data() + context['form'] = form + context['allocation'] = allocation_obj + context['approval_form'] = approval_form + return render(request, self.template_name, context) + err = None # ensure that Tier gets swapped out for storage volume resource = form_data.get('resource') @@ -322,68 +327,30 @@ def post(self, request, *args, **kwargs): messages.error(request, err) return HttpResponseRedirect(reverse('allocation-detail', kwargs={'pk': pk})) if action == 'approve': - approval_form = AllocationApprovalForm(request.POST) + # ensure that sheetcheck and auto_create_opts are selected autoapproval_choice = approval_form.data.get('auto_create_opts') if autoapproval_choice == '2': - resources_plugins = { - 'isilon': 'coldfront.plugins.isilon', - # 'lfs': 'coldfront.plugins.lustre', - } - rtype = next((k for k in resources_plugins if k in alloc_change_obj.allocation.get_parent_resource.name), None) - if not rtype: - err = ('non-isilon resource interactions are not automated ' - 'at this time. Please manually create the resource ' - 'before approving this request.') - messages.error(request, err) - return HttpResponseRedirect( - reverse('allocation-detail', kwargs={'pk': pk}) + error = None + try: + preactivation_responses = allocation_autocreate.send( + sender=self.__class__, + allocation_obj=allocation_obj, + resource=resource, + approval_form_data=approval_form.cleaned_data ) + preactivation_replies = [p[1] for p in preactivation_responses if p[1]] + if not preactivation_replies: + error = ('this allocation\'s resource has no autocreate options ' + 'at this time. Please manually create the resource ' + 'before approving this request.') + except Exception as e: + error = f"An error was encountered during autocreation: {e} Please contact your administrator." + logger.exception('A350: %s', e) + if error: + messages.error(request, error) + return HttpResponseRedirect(reverse('allocation-detail', kwargs={'pk': pk})) - if not approval_form.is_valid(): - messages.error(request, 'form is not valid.') - context = self.get_context_data() - context['form'] = form - context['allocation'] = allocation_obj - return render(request, self.template_name, context) - - approval_form_data = approval_form.cleaned_data - automation_specifications = approval_form_data.get('automation_specifications') - automation_kwargs = {k:True for k in automation_specifications} - - plugin = resources_plugins[rtype] - if plugin in settings.INSTALLED_APPS: - try: - option_exceptions = create_isilon_allocation_quota( - allocation_obj, resource, **automation_kwargs - ) - if option_exceptions: - err = f'some options failed to be created for new allocation {allocation_obj} ({allocation_obj.pk}): {option_exceptions}' - logger.error(err) - messages.error(request, f"{err}. Please contact Coldfront administration for further assistance.") - except Exception as e: - err = ("An error was encountered while auto-creating" - " the allocation. Please contact Coldfront " - f"administration and/or manually create the allocation: {e}") - logger.error(err) - ex_type, ex_value, ex_traceback = sys.exc_info() - stack_trace = list() - trace_back = traceback.extract_tb(ex_traceback) - for trace in trace_back: - stack_trace.append("File : %s , Line : %d, Func.Name : %s, Message : %s" % (trace[0], trace[1], trace[2], trace[3])) - logger.error(stack_trace) - messages.error(request, err) - return HttpResponseRedirect( - reverse('allocation-detail', kwargs={'pk': pk}) - ) - else: - err = ("There is an issue with the configuration of " - "Coldfront's auto-creation capabilities. Please contact Coldfront " - "administration and/or manually create the allocation.") - messages.error(request, err) - return HttpResponseRedirect( - reverse('allocation-detail', kwargs={'pk': pk}) - ) if 'Tier ' in allocation_obj.get_resources_as_string: # remove current resource from resources allocation_obj.resources.clear() @@ -394,6 +361,11 @@ def post(self, request, *args, **kwargs): elif action == 'deny': allocation_obj.status = AllocationStatusChoice.objects.get(name='Denied') + elif action == 'update': + if old_status in PENDING_ALLOCATION_STATUSES and allocation_obj.status.name not in PENDING_ALLOCATION_STATUSES: + err = "You can only use the 'update' option on new allocations to change their statuses to New, On Hold, or In Progress." + messages.error(request, err) + return HttpResponseRedirect(reverse('allocation-detail', kwargs={'pk': pk})) allocation_users = allocation_obj.allocationuser_set.exclude( status__name__in=['Removed', 'Error'] @@ -438,7 +410,7 @@ def post(self, request, *args, **kwargs): ) return HttpResponseRedirect(reverse('allocation-request-list')) - elif old_status != allocation_obj.status.name in ['Denied', 'New', 'Revoked']: + elif old_status != allocation_obj.status.name in ['Denied', 'Revoked']+PENDING_ALLOCATION_STATUSES: allocation_obj.start_date = None allocation_obj.end_date = None allocation_obj.save() diff --git a/coldfront/plugins/isilon/utils.py b/coldfront/plugins/isilon/utils.py index 578bcc396..d5491edfd 100644 --- a/coldfront/plugins/isilon/utils.py +++ b/coldfront/plugins/isilon/utils.py @@ -2,9 +2,11 @@ import isilon_sdk.v9_3_0 as isilon_api from isilon_sdk.v9_3_0.rest import ApiException +from django.dispatch import receiver from coldfront.core.utils.common import import_from_settings from coldfront.core.allocation.models import AllocationAttributeType, AllocationAttribute +from coldfront.core.allocation.signals import allocation_autocreate from coldfront.config.plugins.isilon import ISILON_AUTH_MODEL logger = logging.getLogger(__name__) @@ -379,3 +381,30 @@ def update_coldfront_quota_and_usage(alloc, usage_attribute_type, value_list): usage.value = value_list[1] usage.save() return usage_attribute + +@receiver(allocation_autocreate) +def activate_allocation(sender, **kwargs): + + approval_form_data = kwargs['approval_form_data'] + allocation_obj = kwargs['allocation_obj'] + resource = kwargs['resource'] + + automation_specifications = approval_form_data.get('automation_specifications') + automation_kwargs = {k:True for k in automation_specifications} + + if 'isilon' in resource.name: + try: + option_exceptions = create_isilon_allocation_quota( + allocation_obj, resource, **automation_kwargs + ) + if option_exceptions: + err = f'some options failed to be created for new allocation {allocation_obj} ({allocation_obj.pk}): {option_exceptions}' + logger.error(err) + raise ValueError(err) + except Exception as e: + err = ("An error was encountered while auto-creating the " + "allocation. Please contact Coldfront administration " + f"and/or manually create the allocation: {e}") + logger.error(err) + raise ValueError(err) + return 'isilon'