diff --git a/home/tests.py b/home/tests.py deleted file mode 100644 index a79ca8be..00000000 --- a/home/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -# from django.test import TestCase - -# Create your tests here. diff --git a/home/urls.py b/home/urls.py index 45d7fc54..0cafd655 100644 --- a/home/urls.py +++ b/home/urls.py @@ -13,6 +13,11 @@ views.metadata_specification_view, name="metadata_specification", ), + path( + "details//.csv", + views.details_view_csv, + name="details_csv", + ), path( "details//", views.details_view, diff --git a/home/views.py b/home/views.py index 3dd900ac..0e9fdbd4 100644 --- a/home/views.py +++ b/home/views.py @@ -1,9 +1,10 @@ +import csv from urllib.parse import urlparse from data_platform_catalogue.client.exceptions import EntityDoesNotExist from data_platform_catalogue.search_types import DomainOption from django.conf import settings -from django.http import Http404, HttpResponseBadRequest +from django.http import Http404, HttpResponse, HttpResponseBadRequest from django.shortcuts import render from django.utils.translation import gettext as _ from django.views.decorators.cache import cache_control @@ -15,6 +16,11 @@ DatabaseDetailsService, DatasetDetailsService, ) +from home.service.details_csv import ( + DashboardDetailsCsvFormatter, + DatabaseDetailsCsvFormatter, + DatasetDetailsCsvFormatter, +) from home.service.domain_fetcher import DomainFetcher from home.service.glossary import GlossaryService from home.service.metadata_specification import MetadataSpecificationService @@ -55,6 +61,36 @@ def details_view(request, result_type, urn): raise Http404(f"{result_type} '{urn}' does not exist") +@cache_control(max_age=300, private=True) +def details_view_csv(request, result_type, urn) -> HttpResponse: + if result_type == "table": + service = DatasetDetailsService(urn) + csv_formatter = DatasetDetailsCsvFormatter(service) + elif result_type == "database": + service = DatabaseDetailsService(urn) + csv_formatter = DatabaseDetailsCsvFormatter(service) + elif result_type == "dashboard": + service = DashboardDetailsService(urn) + csv_formatter = DashboardDetailsCsvFormatter(service) + else: + raise Http404("CSV not available") + + # In case there are any quotes in the filename, remove them in order to + # not to break the header. + unsavoury_characters = str.maketrans({'"': ""}) + filename = urn.translate(unsavoury_characters) + ".csv" + + response = HttpResponse( + content_type="text/csv", + headers={"Content-Disposition": f'attachment; filename="{filename}"'}, + ) + writer = csv.writer(response) + writer.writerow(csv_formatter.headers()) + writer.writerows(csv_formatter.data()) + + return response + + @cache_control(max_age=60, private=True) def search_view(request, page: str = "1"): new_search = request.GET.get("new", "") diff --git a/templates/details_dashboard.html b/templates/details_dashboard.html index 5e180fce..73aa5b9b 100644 --- a/templates/details_dashboard.html +++ b/templates/details_dashboard.html @@ -33,6 +33,11 @@ {% endfor %} +
+ +
{% else %}

{% translate "Dashboard content" %}

{% translate "This dashboard is missing chart information." %}

diff --git a/templates/details_database.html b/templates/details_database.html index 260f6300..15a7efd4 100644 --- a/templates/details_database.html +++ b/templates/details_database.html @@ -39,6 +39,11 @@ {% endfor %} +
+ +
{% else %}

{% translate "Database content" %}

{% translate "This database is missing table information." %}

diff --git a/templates/details_table.html b/templates/details_table.html index f257c8ef..7def2117 100644 --- a/templates/details_table.html +++ b/templates/details_table.html @@ -40,6 +40,12 @@ {% endfor %} + +
+ +
{% else %}

{% translate "Table schema" %}

{% translate "The schema for this table is not available." %}

diff --git a/tests/test_views.py b/tests/test_views.py index 0a0000e5..d6aa1469 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -51,9 +51,65 @@ def test_table(self, client, switch_bool): response = client.get( reverse("home:details", kwargs={"urn": "fake", "result_type": "table"}) ) + assert response.status_code == 200 assert response.headers["Cache-Control"] == "max-age=300, private" + @pytest.mark.django_db + def test_csv_output(self, client): + response = client.get( + reverse( + "home:details_csv", + kwargs={"urn": "fake", "result_type": "table"}, + ) + ) + assert response.status_code == 200 + assert ( + response.headers["Content-Disposition"] == 'attachment; filename="fake.csv"' + ) + assert response.content == ( + b"name,display_name,is_primary_key,type,nullable,description\r\n" + + b"urn,urn,True,string,False,description **with markdown**\r\n" + ) + + +class TestDatabaseView: + @pytest.mark.django_db + def test_csv_output(self, client): + response = client.get( + reverse( + "home:details_csv", + kwargs={"urn": "fake", "result_type": "database"}, + ) + ) + assert response.status_code == 200 + assert ( + response.headers["Content-Disposition"] == 'attachment; filename="fake.csv"' + ) + assert response.content == ( + b"urn,display_name,description\r\n" + + b"urn:li:dataset:fake_table,fake_table,table description\r\n" + ) + + +class TestDashboardView: + @pytest.mark.django_db + def test_csv_output(self, client): + response = client.get( + reverse( + "home:details_csv", + kwargs={"urn": "fake", "result_type": "dashboard"}, + ) + ) + assert response.status_code == 200 + assert ( + response.headers["Content-Disposition"] == 'attachment; filename="fake.csv"' + ) + assert response.content == ( + b"urn,display_name,description\r\n" + + b"urn:li:chart:fake_chart,fake_chart,chart description\r\n" + ) + class TestChartView: def test_chart(self, client):