Skip to content

Create UI for purldb packages #20 #615

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions packagedb/templates/base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}PurlDB{% endblock %}</title>

{% block extrahead %}{% endblock %}
</head>
<body class="container">
{% block content %}{% endblock %}

{% block scripts %}{% endblock %}
</body>
</html>
67 changes: 67 additions & 0 deletions packagedb/templates/package/package_detail.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{% extends "base.html" %}

{% block content %}
<h1>Package Details</h1>

<ul>
<li><strong>Package URL:</strong> {{ package.package_url }}</li>
<li><strong>Filename:</strong> {{ package.filename }}</li>
<li><strong>Download URL:</strong> {{ package.download_url }}</li>
<li><strong>Size:</strong> {{ package.size }}</li>
<li><strong>Release Date:</strong> {{ package.release_date }}</li>
<li><strong>Primary language:</strong> {{ package.primary_language }}</li>
<li><strong>Description:</strong> {{ package.description }}</li>
<li><strong>Type:</strong> {{ package.type }}</li>
<li><strong>Namespace:</strong> {{ package.namespace }}</li>
<li><strong>Name:</strong> {{ package.name }}</li>
<li><strong>Version:</strong> {{ package.version }}</li>
<li><strong>Homepage URL:</strong> {{ package.homepage_url }}</li>
<li><strong>SHA1:</strong> {{ package.sha1 }}</li>
<li><strong>Declared license:</strong> {{ package.declared_license_expression_spdx }}</li>
</ul>

<h2>Dependencies</h2>
{% if package.dependencies.all %}
<ul>
{% for dep in package.dependencies.all %}
<li>{{ dep }}</li>
{% endfor %}
</ul>
{% else %}
<p>No dependencies listed.</p>
{% endif %}

<h2>Source Packages</h2>
{% if package.source_packages.all %}
<ul>
{% for sp in package.source_packages.all %}
<li>
<strong>{{ sp.name }}</strong>
{% if sp.version %} — v{{ sp.version }}{% endif %}
{% if sp.package_url %}<br>PURL: {{ sp.package_url }}{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<p>No source packages linked.</p>
{% endif %}

<h2>Parties</h2>
{% if package.parties.all %}
<ul>
{% for party in package.parties.all %}
<li>
<strong>Type:</strong> {{ party.type }}<br>
<strong>Role:</strong> {{ party.role }}<br>
<strong>Name:</strong> {{ party.name }}<br>
<strong>Email:</strong> {{ party.email }}<br>
{% if party.url %}
<strong>URL:</strong> <a href="{{ party.url }}">{{ party.url }}</a>
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<p>No parties listed.</p>
{% endif %}
{% endblock %}
71 changes: 71 additions & 0 deletions packagedb/templates/package/package_list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{% extends "base.html" %}
{% block content %}
<h1>Package List</h1>

<!-- Filter form -->
<h2>Package Filters</h2>
<form method="get">
{{ filter.form.as_p }}
<button type="submit">Filter</button>
</form>

<!-- Table to display the packages -->
<h2>Packages</h2>
<table>
<thead>
<tr>
{% for column in columns_data %}
<th>{{ column.label }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for package in object_list %}
<tr>
<td>
<a href="{% url 'package_detail' package.uuid %}"> {{ package.package_url }} </a>
</td>
<td>
{{ package.name }}
</td>
<td>
{{ package.type }}
</td>
<td>
{{ package.other_license_expression_spdx }}
</td>
<td>
<a href="{{ package.download_url }}"> Download </a>
</td>
<td>
{{ package.created_date }}
</td>
</tr>
{% empty %}
<tr>
<td colspan="{{ table_columns|length }}">No packages found.</td>
</tr>
{% endfor %}
</tbody>
</table>

<div class="pagination">
{% if page_obj.has_previous %}
<a href="?page=1">First</a>
<a href="?page={{ page_obj.previous_page_number }}">Previous</a>
{% endif %}

{% for num in paginator.page_range %}
{% if num == page_obj.number %}
<strong>{{ num }}</strong>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<a href="?page={{ num }}">{{ num }}</a>
{% endif %}
{% endfor %}

{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">Next</a>
<a href="?page={{ page_obj.paginator.num_pages }}">Last</a>
{% endif %}
</div>
{% endblock %}
23 changes: 23 additions & 0 deletions packagedb/urls.py
Original file line number Diff line number Diff line change
@@ -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/<uuid:uuid>/", PackageDetailView.as_view(), name='package_detail'),
]
136 changes: 136 additions & 0 deletions packagedb/views.py
Original file line number Diff line number Diff line change
@@ -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>",
"<field_name>",
{
"field_name": "<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")
)
2 changes: 1 addition & 1 deletion purldb_project/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/",
Expand Down