From 83c85dfa10608638e04574559c23912fca608e76 Mon Sep 17 00:00:00 2001 From: Nick Santos Date: Thu, 16 Nov 2023 16:03:51 -0800 Subject: [PATCH] New permission to restrict modification of model runs New permission prevents creation or modification (deletion, renaming) of existing model runs based on a model_area level setting. Not applied by default to any model areas, but we have a READ_ONLY feature package included in this commit that sets that flag and we can load it to a new model area --- waterspout_api/feature_packages.py | 4 +++ ...apreferences_create_model_runs_and_more.py | 23 ++++++++++++++ waterspout_api/models.py | 5 +++ waterspout_api/permissions.py | 31 +++++++++++++++++++ waterspout_api/views.py | 4 +-- 5 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 waterspout_api/migrations/0056_modelareapreferences_create_model_runs_and_more.py diff --git a/waterspout_api/feature_packages.py b/waterspout_api/feature_packages.py index 9375265..afac084 100644 --- a/waterspout_api/feature_packages.py +++ b/waterspout_api/feature_packages.py @@ -15,6 +15,7 @@ 'allow_static_regions': False, 'allow_removed_regions': False, 'allow_linear_scaled_regions': False, + 'create_model_runs': True, 'shared_model_runs': True, # should model runs be visible to all organization members? } @@ -40,6 +41,9 @@ DEBUG['include_net_revenue'] = True DEBUG['allow_model_run_creation_code_view'] = True +READ_ONLY = copy.deepcopy(FULL_PUBLIC) # READ_ONLY means they can't create new models, but they can use all the other tools +READ_ONLY['create_or_modify_model_runs'] = False + DAP_DSC = copy.deepcopy(FULL_PUBLIC) # a few special plans - we'll copy them for now in case they have modifications later diff --git a/waterspout_api/migrations/0056_modelareapreferences_create_model_runs_and_more.py b/waterspout_api/migrations/0056_modelareapreferences_create_model_runs_and_more.py new file mode 100644 index 0000000..5f0d54a --- /dev/null +++ b/waterspout_api/migrations/0056_modelareapreferences_create_model_runs_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1.9 on 2023-11-16 23:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('waterspout_api', '0055_load_dap_multipliers'), + ] + + operations = [ + migrations.AddField( + model_name='modelareapreferences', + name='create_or_modify_model_runs', + field=models.BooleanField(default=True), + ), + migrations.AlterField( + model_name='regiongroup', + name='regions', + field=models.ManyToManyField(related_name='groups', to='waterspout_api.region'), + ), + ] diff --git a/waterspout_api/models.py b/waterspout_api/models.py index a853ad5..9628a9a 100644 --- a/waterspout_api/models.py +++ b/waterspout_api/models.py @@ -189,6 +189,11 @@ class ModelAreaPreferences(models.Model): # should all users of this model area be able to see the model runs of all other users? shared_model_runs = models.BooleanField(default=True) + # should users of this model area be able to create new model runs in the model area or just view model + # runs we've already loaded? Useful to disable when we want to premake some model runs then release + # the results without releasing the tools to create model runs + create_or_modify_model_runs = models.BooleanField(default=True) + # prevent users from reducing price/yield below the value that would make profits negative # basically forces stormchaser to create cards for crops when All Crops # goes to negative profits for the crop diff --git a/waterspout_api/permissions.py b/waterspout_api/permissions.py index 7a5b71f..cc04ab7 100644 --- a/waterspout_api/permissions.py +++ b/waterspout_api/permissions.py @@ -73,3 +73,34 @@ def _check_org_info(self, request_user, request_data, view): # request_data["calibration_set"] = calibration_set # replace it with the object so we can assign it later return True + + +class CanCreateOrModifyModelRuns(permissions.BasePermission): + def has_permission(self, request, view): + if request.method in permissions.SAFE_METHODS: # allow them to read model runs with this permission + return True + else: # but if they want to create, we need to check if the ModelArea allows creation + if "pk" in view.kwargs: # then we're checking against an existing object + item_id = view.kwargs['pk'] + item_class = view.serializer_class.Meta.model + try: + item = item_class.objects.get(pk=item_id) + except item_class.DoesNotExist: + return PermissionError("Model Run doesn't exist") + + if hasattr(item, "model_area"): + model_area = item.model_area + elif hasattr(item, "calibration_set"): + model_area = item.calibration_set.model_area + else: + raise RuntimeError(f"Can't get model area from {view.serializer_class}") + else: + if type(request.data) is not dict: + request_data = json.loads(request.data) + else: + request_data = request.data + calibration_set = models.CalibrationSet.objects.get(pk=request_data["calibration_set"]) + model_area = calibration_set.model_area + + preferences = model_area.preferences + return preferences.create_or_modify_model_runs \ No newline at end of file diff --git a/waterspout_api/views.py b/waterspout_api/views.py index 8ba9236..af892c8 100644 --- a/waterspout_api/views.py +++ b/waterspout_api/views.py @@ -255,9 +255,9 @@ class ModelRunViewSet(viewsets.ModelViewSet): Test - Permissions: Must be in same organization to specifically request an item + Permissions: Must be in same organization to specifically request an item, and ModelArea must allow model run creation to send destructive actions to the model run """ - permission_classes = [permissions.IsInSameOrganization] + permission_classes = [permissions.IsInSameOrganization, permissions.CanCreateOrModifyModelRuns] serializer_class = serializers.ModelRunSerializer def get_queryset(self):