diff --git a/packagedb/templates/base.html b/packagedb/templates/base.html new file mode 100644 index 00000000..637aaae0 --- /dev/null +++ b/packagedb/templates/base.html @@ -0,0 +1,15 @@ + + + + + + {% block title %}PurlDB{% endblock %} + + {% block extrahead %}{% endblock %} + + + {% block content %}{% endblock %} + + {% block scripts %}{% endblock %} + + \ No newline at end of file diff --git a/packagedb/templates/package/package_detail.html b/packagedb/templates/package/package_detail.html new file mode 100644 index 00000000..782af6ee --- /dev/null +++ b/packagedb/templates/package/package_detail.html @@ -0,0 +1,67 @@ +{% extends "base.html" %} + +{% block content %} +

Package Details

+ + + +

Dependencies

+{% if package.dependencies.all %} + +{% else %} +

No dependencies listed.

+{% endif %} + +

Source Packages

+{% if package.source_packages.all %} + +{% else %} +

No source packages linked.

+{% endif %} + +

Parties

+{% if package.parties.all %} + +{% else %} +

No parties listed.

+{% endif %} +{% endblock %} diff --git a/packagedb/templates/package/package_list.html b/packagedb/templates/package/package_list.html new file mode 100644 index 00000000..ab0ca158 --- /dev/null +++ b/packagedb/templates/package/package_list.html @@ -0,0 +1,71 @@ +{% extends "base.html" %} +{% block content %} +

Package List

+ + +

Package Filters

+
+ {{ filter.form.as_p }} + +
+ + +

Packages

+ + + + {% for column in columns_data %} + + {% endfor %} + + + + {% for package in object_list %} + + + + + + + + + {% empty %} + + + + {% endfor %} + +
{{ column.label }}
+ {{ package.package_url }} + + {{ package.name }} + + {{ package.type }} + + {{ package.other_license_expression_spdx }} + + Download + + {{ package.created_date }} +
No packages found.
+ + +{% endblock %} diff --git a/packagedb/urls.py b/packagedb/urls.py new file mode 100644 index 00000000..5a84bbeb --- /dev/null +++ b/packagedb/urls.py @@ -0,0 +1,23 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/aboutcode-org/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +from django.urls import path +from rest_framework.routers import DefaultRouter +from django.views.generic import RedirectView +from packagedb.views import PackageListView +from packagedb.views import PackageDetailView + +router = DefaultRouter() +router.register(r"packages", PackageListView, basename="package") + +urlpatterns = [ + path("", RedirectView.as_view(url="/packages")), + path("packages/", PackageListView.as_view(), name='package_list'), + path("packages//", PackageDetailView.as_view(), name='package_detail'), +] diff --git a/packagedb/views.py b/packagedb/views.py new file mode 100644 index 00000000..86d0527c --- /dev/null +++ b/packagedb/views.py @@ -0,0 +1,136 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# purldb is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/aboutcode-org/purldb for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +from packagedb.api import PackageFilterSet +from packagedb.models import Package + +from django.views.generic.detail import DetailView +from django_filters.views import FilterView + +PAGE_SIZE = 20 + +class TableColumnsMixin: + """ + table_columns = [ + "", + "", + { + "field_name": "", + "label": None, + "condition": None, + "sort_name": None, + "css_class": None, + }, + ] + """ + + table_columns = [] + + def get_columns_data(self): + """Return the columns data structure used in template rendering.""" + columns_data = [] + + sortable_fields = [] + active_sort = "" + filterset = getattr(self, "filterset", None) + if filterset and "sort" in filterset.filters: + sortable_fields = list(filterset.filters["sort"].param_map.keys()) + active_sort = filterset.data.get("sort", "") + + for column_definition in self.table_columns: + # Support for single "field_name" entry in columns list. + if not isinstance(column_definition, dict): + field_name = column_definition + column_data = {"field_name": field_name} + else: + field_name = column_definition.get("field_name") + column_data = column_definition.copy() + + condition = column_data.get("condition", None) + if condition is not None and not bool(condition): + continue + + if "label" not in column_data: + column_data["label"] = self.get_field_label(field_name) + + sort_name = column_data.get("sort_name") or field_name + if sort_name in sortable_fields: + is_sorted = sort_name == active_sort.lstrip("-") + + sort_direction = "" + if is_sorted and not active_sort.startswith("-"): + sort_direction = "-" + + column_data["is_sorted"] = is_sorted + column_data["sort_direction"] = sort_direction + query_dict = self.request.GET.copy() + query_dict["sort"] = f"{sort_direction}{sort_name}" + column_data["sort_query"] = query_dict.urlencode() + + if filter_fieldname := column_data.get("filter_fieldname"): + column_data["filter"] = filterset.form[filter_fieldname] + + columns_data.append(column_data) + + return columns_data + + @staticmethod + def get_field_label(field_name): + """Return a formatted label for display based on the `field_name`.""" + return field_name.replace("_", " ").capitalize().replace("url", "URL") + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["columns_data"] = self.get_columns_data() + context["request_query_string"] = self.request.GET.urlencode() + return context + +class PackageListView( + TableColumnsMixin, + FilterView +): + model = Package + filterset_class = PackageFilterSet + paginate_by = PAGE_SIZE + template_name = "package/package_list.html" + table_columns = [ + { + "field_name": "package_url", + "label": "Purl", + "sort_name": "projectmessages_count", + }, + "name", + "type", + { + "field_name": "other_license_expression_spdx", + "label": "License", + }, + { + "field_name": "download_url", + "label": "Download Url", + }, + { + "field_name": "created_date", + "label": "Created Date", + "sort_name": "created_date", + } + ] + +class PackageDetailView(DetailView): + model = Package + slug_field = "uuid" + slug_url_kwarg = "uuid" + template_name = "package/package_detail.html" + + def get_queryset(self): + return ( + super() + .get_queryset() + .prefetch_related("parties", "dependencies") + ) diff --git a/purldb_project/urls.py b/purldb_project/urls.py index 73bce815..76244551 100644 --- a/purldb_project/urls.py +++ b/purldb_project/urls.py @@ -57,7 +57,7 @@ path("api/", include((api_router.urls, "api"))), path("api/to_purl/", include((api_to_purl_router.urls, "api_to"))), path("api/from_purl/", include((api_from_purl_router.urls, "api_from"))), - path("", RedirectView.as_view(url="api/")), + path("", include("packagedb.urls")), path("api/schema/", SpectacularAPIView.as_view(), name="schema"), path( "api/docs/",