From 16e7e8ae23665eb42b1afc837823c6e790b6336b Mon Sep 17 00:00:00 2001 From: Keshav Priyadarshi Date: Tue, 24 Mar 2026 12:22:41 +0530 Subject: [PATCH 1/2] wip Signed-off-by: Keshav Priyadarshi --- vulnerabilities/models.py | 6 +- .../templates/curation_detail.html | 210 ++++++++++++++++++ vulnerabilities/templates/curation_home.html | 148 ++++++++++++ vulnerabilities/templates/selected_page.html | 11 + vulnerabilities/templatetags/utils.py | 12 + vulnerabilities/views.py | 113 ++++++++++ vulnerablecode/urls.py | 11 +- 7 files changed, 509 insertions(+), 2 deletions(-) create mode 100644 vulnerabilities/templates/curation_detail.html create mode 100644 vulnerabilities/templates/curation_home.html create mode 100644 vulnerabilities/templates/selected_page.html diff --git a/vulnerabilities/models.py b/vulnerabilities/models.py index 2e69be49a..371165cd6 100644 --- a/vulnerabilities/models.py +++ b/vulnerabilities/models.py @@ -2515,7 +2515,7 @@ class AdvisoryToDoV2(models.Model): help_text="Advisory/ies where this TODO is applicable.", ) - issue_type = models.CharField( + issue_type = models.CharField( max_length=50, choices=ISSUE_TYPE_CHOICES, db_index=True, @@ -2552,6 +2552,8 @@ class AdvisoryToDoV2(models.Model): class Meta: unique_together = ("related_advisories_id", "issue_type") + #todo add index! + class AdvisorySeverity(models.Model): url = models.URLField( @@ -2981,6 +2983,8 @@ class AdvisoryV2(models.Model): choices=AdvisoryStatusType.choices, default=AdvisoryStatusType.PUBLISHED ) + # Note: Fields and relations below are not part of original upstream advisory. + exploitability = models.DecimalField( null=True, blank=True, diff --git a/vulnerabilities/templates/curation_detail.html b/vulnerabilities/templates/curation_detail.html new file mode 100644 index 000000000..43afd22c7 --- /dev/null +++ b/vulnerabilities/templates/curation_detail.html @@ -0,0 +1,210 @@ +{% extends "base.html" %} +{% load utils %} + +{% block title %} +Curation Dashboard +{% endblock %} + +{% block extrahead %} + + + +{% endblock %} + +{% block content %} + +
+ + +
+
+ + {% for adv in advisories %} + + {% endfor %} + + + +
+
+ + + +
+
+ +

Editor

+ +
+ {% csrf_token %} + +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ + +
+ +
+ +
+
+ +
+ +{% endblock %} + + +{% block scripts %} + +{% endblock %} \ No newline at end of file diff --git a/vulnerabilities/templates/curation_home.html b/vulnerabilities/templates/curation_home.html new file mode 100644 index 000000000..321671280 --- /dev/null +++ b/vulnerabilities/templates/curation_home.html @@ -0,0 +1,148 @@ +{% extends "base.html" %} +{% load utils %} + +{% block title %} +Advisory To-Dos +{% endblock %} + +{% block extrahead %} + +{% endblock %} + + +{% block content %} +
+
+
+ +
+
+

Advisory To-Dos

+
+
+
+
+
+ {{ form.search }} +
+
+ +
+
+
+ +
+ + + + + + + + + {% for schedule in schedule_list %} + + + + {% empty %} + + + + {% endfor %} + + +
+
+
+
Pipeline ID
+
Active
+
Interval
+
Status
+
Last Run End Time
+
Next Run Start
+
+
+
+ +
+ +
+ {{ schedule.issue_type }} +
+
{{ schedule.issue_type|yesno:"Yes,No" }}
+
+ {{ schedule.issue_type }} + +
+
+ + + +
+
+ {{ schedule.issue_type }} + +
+
+ {{ schedule.issue_type }} + +
+
+
+
No pipeline found.
+
+ {% if is_paginated %} + + {% endif %} +
+
+
+{% endblock %} \ No newline at end of file diff --git a/vulnerabilities/templates/selected_page.html b/vulnerabilities/templates/selected_page.html new file mode 100644 index 000000000..9196e9fd9 --- /dev/null +++ b/vulnerabilities/templates/selected_page.html @@ -0,0 +1,11 @@ +

Selected IDs

+ +{% if selected_ids %} + +{% else %} +

No items selected

+{% endif %} \ No newline at end of file diff --git a/vulnerabilities/templatetags/utils.py b/vulnerabilities/templatetags/utils.py index cea889808..c34c5dc37 100644 --- a/vulnerabilities/templatetags/utils.py +++ b/vulnerabilities/templatetags/utils.py @@ -39,3 +39,15 @@ def active_item(context, url_name): @register.filter def get_item(dictionary, key): return dictionary.get(key) + + +@register.simple_tag +def querystring(request, **kwargs): + query = request.GET.copy() + + for key, value in kwargs.items(): + if not value: + continue + query[key] = value + + return query.urlencode() diff --git a/vulnerabilities/views.py b/vulnerabilities/views.py index 860bde8eb..fe1044392 100644 --- a/vulnerabilities/views.py +++ b/vulnerabilities/views.py @@ -857,3 +857,116 @@ def get_context_data(self, **kwargs): context["site_title"] = "VulnerableCode site admin" context["site_header"] = "VulnerableCode Administration" return context + + +from django import forms + +from vulnerabilities.models import AdvisoryToDoV2 + +# class AdvisorySelectedView(View): +# def post(self, request): +# selected_ids = request.POST.getlist("selected_items") + +# # redirect with IDs in URL +# ids_str = ",".join(selected_ids) +# return redirect(f"/advisory/v2/selected/?ids={ids_str}") + +# def get(self, request): +# ids = request.GET.get("ids", "") +# selected_ids = ids.split(",") if ids else [] + +# return render(request, "selected_page.html", { +# "selected_ids": selected_ids +# }) + +from django.views.generic import DetailView +from vulnerabilities.pipes.export import serialize_advisory +import saneyaml + +class AdvisoryCurationDetailView(DetailView): + model = AdvisoryToDoV2 + template_name = "curation_detail.html" + context_object_name = "todo" + + # def get_context_data(self, **kwargs): + # context = super().get_context_data(**kwargs) + + # advisories = list( + # self.object.advisories.all() + # .prefetch_related("aliases", "references", "severities") + # ) + + # # get selected advisory from URL + # selected_id = self.request.GET.get("advisory") + + # selected = None + # if selected_id: + # selected = next((a for a in advisories if str(a.id) == selected_id), None) + + # if not selected: + # selected = advisories[0] if advisories else None + + # context["advisories"] = advisories + # context["selected_advisory"] = selected + + # # index for navigation + # if selected: + # idx = advisories.index(selected) + # context["prev_advisory"] = advisories[idx - 1] if idx > 0 else None + # context["next_advisory"] = ( + # advisories[idx + 1] if idx < len(advisories) - 1 else None + # ) + + # return context + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + advisories = self.object.advisories.all().prefetch_related( + "aliases", "references", "severities", "weaknesses" + ) + + context["advisories"] = [saneyaml.dump(serialize_advisory(i)) for i in advisories] + + return context + + +class AdvisoryCurationForm(forms.Form): + search = forms.CharField( + required=True, + label=False, + widget=forms.TextInput( + attrs={ + "placeholder": "Search ToDos...", + "class": "input ", + }, + ), + ) + + +class AdvisoryCurationView(ListView, FormMixin): + model = AdvisoryToDoV2 + context_object_name = "schedule_list" + template_name = "curation_home.html" + paginate_by = 20 + form_class = AdvisoryCurationForm + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs["data"] = self.request.GET + return kwargs + + def get_queryset(self): + form = self.form_class(self.request.GET) + qs = super().get_queryset().order_by("-created_at") + if form.is_valid() and (search := form.cleaned_data.get("search")): + return qs.filter(advisories__aliases__alias__icontains=search) + return qs + + # def get_context_data(self, **kwargs): + # context = super().get_context_data(**kwargs) + # context["active_pipeline_count"] = PipelineSchedule.objects.filter(is_active=True).count() + # context["disabled_pipeline_count"] = PipelineSchedule.objects.filter( + # is_active=False + # ).count() + # return context diff --git a/vulnerablecode/urls.py b/vulnerablecode/urls.py index 49948a3b9..62dd12806 100644 --- a/vulnerablecode/urls.py +++ b/vulnerablecode/urls.py @@ -26,9 +26,11 @@ from vulnerabilities.api_v2 import PackageV3ViewSet from vulnerabilities.api_v2 import PipelineScheduleV2ViewSet from vulnerabilities.api_v2 import VulnerabilityV2ViewSet -from vulnerabilities.views import AdminLoginView +from vulnerabilities.views import AdminLoginView, AdvisoryCurationDetailView +from vulnerabilities.views import AdvisoryCurationView from vulnerabilities.views import AdvisoryDetails from vulnerabilities.views import AdvisoryPackagesDetails +# from vulnerabilities.views import AdvisorySelectedView from vulnerabilities.views import ApiUserCreateView from vulnerabilities.views import HomePage from vulnerabilities.views import HomePageV2 @@ -89,6 +91,13 @@ def __init__(self, *args, **kwargs): PipelineScheduleListView.as_view(), name="dashboard", ), + path( + "advisory/v2/curation/", + AdvisoryCurationView.as_view(), + name="curation-home", + ), + # path("advisory/v2/selected/", AdvisorySelectedView.as_view(), name="advisory-curation-selected"), + path("todos//", AdvisoryCurationDetailView.as_view(), name="todo-detail"), path( "pipelines//runs/", PipelineRunListView.as_view(), From 85b994ea833343bac83fc0977eed73ad06060154 Mon Sep 17 00:00:00 2001 From: Keshav Priyadarshi Date: Thu, 2 Apr 2026 23:11:30 +0530 Subject: [PATCH 2/2] wip Signed-off-by: Keshav Priyadarshi --- vulnerabilities/models.py | 11 +- vulnerabilities/pipes/openssl.py | 4 +- vulnerabilities/templates/advisory_todos.html | 165 ++++++++++++++ .../templates/curation_detail.html | 210 ------------------ vulnerabilities/templates/curation_home.html | 148 ------------ .../templates/includes/pagination_v2.html | 65 ++++++ vulnerabilities/templates/navbar.html | 3 + vulnerabilities/templates/todo_curation.html | 190 ++++++++++++++++ vulnerabilities/templatetags/utils.py | 3 +- .../v2_importers/test_collect_fix_commit.py | 4 +- vulnerabilities/tests/test_api.py | 6 +- vulnerabilities/views.py | 83 +++++-- vulnerablecode/urls.py | 20 +- 13 files changed, 517 insertions(+), 395 deletions(-) create mode 100644 vulnerabilities/templates/advisory_todos.html delete mode 100644 vulnerabilities/templates/curation_detail.html delete mode 100644 vulnerabilities/templates/curation_home.html create mode 100644 vulnerabilities/templates/includes/pagination_v2.html create mode 100644 vulnerabilities/templates/todo_curation.html diff --git a/vulnerabilities/models.py b/vulnerabilities/models.py index 371165cd6..9b3acc9e2 100644 --- a/vulnerabilities/models.py +++ b/vulnerabilities/models.py @@ -1136,9 +1136,9 @@ def get_affecting_vulnerabilities(self): next_fixed_package_vulns = list(fixed_by_pkg.affected_by) fixed_by_package_details["fixed_by_purl"] = fixed_by_purl - fixed_by_package_details["fixed_by_purl_vulnerabilities"] = ( - next_fixed_package_vulns - ) + fixed_by_package_details[ + "fixed_by_purl_vulnerabilities" + ] = next_fixed_package_vulns fixed_by_pkgs.append(fixed_by_package_details) vuln_details["fixed_by_package_details"] = fixed_by_pkgs @@ -2515,7 +2515,7 @@ class AdvisoryToDoV2(models.Model): help_text="Advisory/ies where this TODO is applicable.", ) - issue_type = models.CharField( + issue_type = models.CharField( max_length=50, choices=ISSUE_TYPE_CHOICES, db_index=True, @@ -2552,7 +2552,8 @@ class AdvisoryToDoV2(models.Model): class Meta: unique_together = ("related_advisories_id", "issue_type") - #todo add index! + # todo add index! + # use uuid for todo identification. class AdvisorySeverity(models.Model): diff --git a/vulnerabilities/pipes/openssl.py b/vulnerabilities/pipes/openssl.py index 1dffdedc1..b240f416c 100644 --- a/vulnerabilities/pipes/openssl.py +++ b/vulnerabilities/pipes/openssl.py @@ -89,7 +89,9 @@ def get_reference(reference_name, tag, reference_url): ref_type = ( AdvisoryReference.COMMIT if "commit" in name or tag == "patch" - else AdvisoryReference.ADVISORY if "advisory" in name else AdvisoryReference.OTHER + else AdvisoryReference.ADVISORY + if "advisory" in name + else AdvisoryReference.OTHER ) return ReferenceV2( diff --git a/vulnerabilities/templates/advisory_todos.html b/vulnerabilities/templates/advisory_todos.html new file mode 100644 index 000000000..75b46d3fc --- /dev/null +++ b/vulnerabilities/templates/advisory_todos.html @@ -0,0 +1,165 @@ +{% extends "base.html" %} +{% load utils %} + +{% block title %} +Advisory To-Dos +{% endblock %} + +{% block extrahead %} + +{% endblock %} + + +{% block content %} +
+
+
+ +
+
+

Advisory To-Dos

+
+
+
+ +
+
+ {{ form.search }} + + {% if form.search.value %} + + ✕ + + {% endif %} +
+ +
+ +
+
+
+ +
+ + + + + + + + + + + + + {% for todo in todo_list %} + + + + {% empty %} + + + + {% endfor %} + +
+
+
+
CVE
+
Aliases
+
Resolved
+
# Advisories
+
Issue
+
+
+
+
+ + +
+
+
+ +
+
+ +
+
+ +
+ +
+
+ +
+
+
+
+
+ +
+
+ {{ identifiers|get_item:todo.id|get_item:"identifier" }} +
+
+ {{ identifiers|get_item:todo.id|get_item:"aliases"|default_if_none:""|join:", "|default:"NA" }} +
+
+ {{ todo.is_resolved|yesno:"Yes,No" }} +
+
+ {{ todo.advisories.all|length }} +
+
+ {{ todo.get_issue_type_display }} +
+
+
+
No pipeline found.
+
+ {% include "includes/pagination_v2.html" with page_obj=page_obj %} +
+
+
+{% endblock %} + diff --git a/vulnerabilities/templates/curation_detail.html b/vulnerabilities/templates/curation_detail.html deleted file mode 100644 index 43afd22c7..000000000 --- a/vulnerabilities/templates/curation_detail.html +++ /dev/null @@ -1,210 +0,0 @@ -{% extends "base.html" %} -{% load utils %} - -{% block title %} -Curation Dashboard -{% endblock %} - -{% block extrahead %} - - - -{% endblock %} - -{% block content %} - -
- - -
-
- - {% for adv in advisories %} - - {% endfor %} - - - -
-
- - - -
-
- -

Editor

- -
- {% csrf_token %} - -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- -
-
- -
- -
- - -
- -
- -
-
- -
- -{% endblock %} - - -{% block scripts %} - -{% endblock %} \ No newline at end of file diff --git a/vulnerabilities/templates/curation_home.html b/vulnerabilities/templates/curation_home.html deleted file mode 100644 index 321671280..000000000 --- a/vulnerabilities/templates/curation_home.html +++ /dev/null @@ -1,148 +0,0 @@ -{% extends "base.html" %} -{% load utils %} - -{% block title %} -Advisory To-Dos -{% endblock %} - -{% block extrahead %} - -{% endblock %} - - -{% block content %} -
-
-
- -
-
-

Advisory To-Dos

-
-
-
-
-
- {{ form.search }} -
-
- -
-
-
- -
- - - - - - - - - {% for schedule in schedule_list %} - - - - {% empty %} - - - - {% endfor %} - - -
-
-
-
Pipeline ID
-
Active
-
Interval
-
Status
-
Last Run End Time
-
Next Run Start
-
-
-
- -
- -
- {{ schedule.issue_type }} -
-
{{ schedule.issue_type|yesno:"Yes,No" }}
-
- {{ schedule.issue_type }} - -
-
- - - -
-
- {{ schedule.issue_type }} - -
-
- {{ schedule.issue_type }} - -
-
-
-
No pipeline found.
-
- {% if is_paginated %} - - {% endif %} -
-
-
-{% endblock %} \ No newline at end of file diff --git a/vulnerabilities/templates/includes/pagination_v2.html b/vulnerabilities/templates/includes/pagination_v2.html new file mode 100644 index 000000000..3afba9a53 --- /dev/null +++ b/vulnerabilities/templates/includes/pagination_v2.html @@ -0,0 +1,65 @@ +{% load utils %} + +{% if page_obj.has_other_pages %} + +{% endif %} \ No newline at end of file diff --git a/vulnerabilities/templates/navbar.html b/vulnerabilities/templates/navbar.html index 3d3fa0e91..fa19ccfc8 100644 --- a/vulnerabilities/templates/navbar.html +++ b/vulnerabilities/templates/navbar.html @@ -35,6 +35,9 @@