From f50e1b04acf35ab78f00d295f590fb3299f69a3b Mon Sep 17 00:00:00 2001 From: Michael Collins <15347726+michaeljcollinsuk@users.noreply.github.com> Date: Fri, 6 Sep 2024 12:04:33 +0100 Subject: [PATCH] Feature/filter databases (#268) * Filter database list by user access * Refactor filtering to a mixin and include Allow filtering of databases and tables by the users access * Super-linter * Disable HTML super linter * Only catch AttributeError in templatetags * Disable debug in prod * Bump chart version --- .github/workflows/super-linter.yml | 1 + ap/database_access/models/access.py | 12 ++++ ap/database_access/templatetags/__init__.py | 0 .../templatetags/database_access_tags.py | 23 ++++++++ ap/database_access/views.py | 56 ++++++++++++++++--- ap/settings/common.py | 5 +- chart/Chart.yaml | 4 +- .../database_access/database/detail.html | 51 +++++++++-------- .../database/includes/filter_form.html | 11 ++++ templates/database_access/database/list.html | 7 ++- 10 files changed, 136 insertions(+), 34 deletions(-) create mode 100644 ap/database_access/templatetags/__init__.py create mode 100644 ap/database_access/templatetags/database_access_tags.py create mode 100644 templates/database_access/database/includes/filter_form.html diff --git a/.github/workflows/super-linter.yml b/.github/workflows/super-linter.yml index 50298c13..9c3286fb 100644 --- a/.github/workflows/super-linter.yml +++ b/.github/workflows/super-linter.yml @@ -46,3 +46,4 @@ jobs: VALIDATE_CHECKOV: false # TODO failures to remediate at later date VALIDATE_PYTHON_PYINK: false # we are using Black instead VALIDATE_HTML_PRETTIER: false # incompatible with django templating language + VALIDATE_HTML: false # issues with django templating language, need to revist diff --git a/ap/database_access/models/access.py b/ap/database_access/models/access.py index b0ac4c0e..db8fdb4b 100644 --- a/ap/database_access/models/access.py +++ b/ap/database_access/models/access.py @@ -63,6 +63,9 @@ def database_details(self): def target_database(self): return self.database_details.get("TargetDatabase", {}) + def get_absolute_url(self): + return reverse("database_access:detail", kwargs={"database_name": self.name}) + def grant_lakeformation_permissions(self, create_hybrid_opt_in=False): lake_formation = aws.LakeFormationService() quicksight_user = lake_formation.arn( @@ -152,6 +155,15 @@ def get_absolute_url(self, viewname: str = "database_access:manage_table_access" def get_absolute_revoke_url(self): return self.get_absolute_url(viewname="database_access:revoke_table_access") + def get_absolute_table_detail_url(self): + return reverse( + viewname="database_access:table_detail", + kwargs={ + "database_name": self.database_access.name, + "table_name": self.name, + }, + ) + def grant_lakeformation_permissions(self, create_hybrid_opt_in=False): lake_formation = aws.LakeFormationService() quicksight_user = lake_formation.arn( diff --git a/ap/database_access/templatetags/__init__.py b/ap/database_access/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ap/database_access/templatetags/database_access_tags.py b/ap/database_access/templatetags/database_access_tags.py new file mode 100644 index 00000000..a6118c5c --- /dev/null +++ b/ap/database_access/templatetags/database_access_tags.py @@ -0,0 +1,23 @@ +from django import template +from django.urls import reverse + +register = template.Library() + + +@register.simple_tag +def database_detail_url(database): + try: + return database.get_absolute_url() + except AttributeError: + return reverse("database_access:detail", kwargs={"database_name": database["Name"]}) + + +@register.simple_tag +def table_detail_url(table, database_name): + try: + return table.get_absolute_table_detail_url() + except AttributeError: + return reverse( + "database_access:table_detail", + kwargs={"database_name": database_name, "table_name": table["Name"]}, + ) diff --git a/ap/database_access/views.py b/ap/database_access/views.py index 2a57f917..eaddadd2 100644 --- a/ap/database_access/views.py +++ b/ap/database_access/views.py @@ -5,6 +5,7 @@ from django.http import Http404, HttpResponse from django.urls import reverse from django.views.generic import CreateView, DeleteView, DetailView, TemplateView, UpdateView +from django.views.generic.base import ContextMixin from django.views.generic.detail import SingleObjectMixin import botocore @@ -16,24 +17,64 @@ from . import models -class DatabaseListView(OIDCLoginRequiredMixin, TemplateView): - template_name = "database_access/database/list.html" +class FilterAccessMixin(ContextMixin): + context_list_name = "objects" + + def get_user_access_objects(self): + raise NotImplementedError() + + def get_all_objects(self): + raise NotImplementedError() def get_context_data(self, **kwargs: Any) -> dict[str, Any]: context = super().get_context_data(**kwargs) - context["databases"] = aws.GlueService().get_database_list() + filter_by = self.request.GET.get("filter", {}) + match filter_by: + case "my-access": + context[self.context_list_name] = self.get_user_access_objects() + case "all": + context[self.context_list_name] = self.get_all_objects() + case _: + context[self.context_list_name] = self.get_all_objects() + return context -class DatabaseDetailView(OIDCLoginRequiredMixin, DetailView): +class DatabaseListView(OIDCLoginRequiredMixin, FilterAccessMixin, TemplateView): + template_name = "database_access/database/list.html" + context_list_name = "databases" + + def get_all_objects(self): + return aws.GlueService().get_database_list() + + def get_user_access_objects(self): + return models.DatabaseAccess.objects.filter(user=self.request.user) + + +class DatabaseDetailView(OIDCLoginRequiredMixin, FilterAccessMixin, TemplateView): template_name = "database_access/database/detail.html" - context_object_name = "database" + context_list_name = "tables" - def get_object(self): + def get_database(self): + database = aws.GlueService().get_database_detail(database_name=self.kwargs["database_name"]) + if not database: + raise Http404("Database not found") + return database + + def get_all_objects(self): tables = aws.GlueService().get_table_list(database_name=self.kwargs["database_name"]) if not tables: raise Http404("Database not found") - return {"name": self.kwargs["database_name"], "tables": tables} + return tables + + def get_user_access_objects(self): + database = aws.GlueService().get_database_detail(database_name=self.kwargs["database_name"]) + if not database: + raise Http404("Database not found") + return models.TableAccess.objects.filter( + database_access__name=self.kwargs["database_name"], + database_access__user=self.request.user, + ) class TableDetailView(OIDCLoginRequiredMixin, DetailView): @@ -46,6 +87,7 @@ def get_object(self): ) if not table: raise Http404("Table not found") + return { "name": self.kwargs["table_name"], "is_registered_with_lake_formation": table.get("IsRegisteredWithLakeFormation"), diff --git a/ap/settings/common.py b/ap/settings/common.py index b2931865..579a923d 100644 --- a/ap/settings/common.py +++ b/ap/settings/common.py @@ -31,7 +31,7 @@ PROJECT_ROOT = dirname(DJANGO_ROOT) # Name of the deployment environment (dev/prod) -ENV = os.environ.get("APP_ENVIRONMENT", "dev") +ENV = os.environ.get("APP_ENVIRONMENT", "development") # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ @@ -42,6 +42,9 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = os.environ.get("DEBUG", True) +if ENV == "production": + DEBUG = False + # Application definition INSTALLED_APPS = [ diff --git a/chart/Chart.yaml b/chart/Chart.yaml index fbc64925..4aed347d 100644 --- a/chart/Chart.yaml +++ b/chart/Chart.yaml @@ -3,8 +3,8 @@ apiVersion: v2 name: analytical-platform-ui description: Analytical Platform UI type: application -version: 0.1.7 -appVersion: 0.1.7 +version: 0.2.0 +appVersion: 0.2.0 icon: https://upload.wikimedia.org/wikipedia/en/thumb/4/4a/Ministry_of_Justice_logo_%28United_Kingdom%29.svg/611px-Ministry_of_Justice_logo_%28United_Kingdom%29.svg.png maintainers: - name: moj-data-platform-robot diff --git a/templates/database_access/database/detail.html b/templates/database_access/database/detail.html index 78ba9e05..8d303b6b 100644 --- a/templates/database_access/database/detail.html +++ b/templates/database_access/database/detail.html @@ -1,31 +1,38 @@ {% extends "base.html" %} +{% load database_access_tags %} {% block title %}Analytical Platform - Tables for {{ database.name }}{% endblock title %} {% block content %} -
-Table Name | -Actions | -
---|
{{ table.Name }} | -- View - | +Table Name | +Actions |
---|
No tables found.
-{% endif %} + + + {% for table in tables %} +No tables found.
+ {% endif %} + +