Skip to content
Merged
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
25 changes: 17 additions & 8 deletions component_catalog/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from django.urls import path
from django.urls import reverse
from django.utils.html import format_html
from django.utils.html import mark_safe
from django.utils.http import urlencode
from django.utils.translation import gettext_lazy as _

Expand Down Expand Up @@ -504,7 +505,7 @@ def response_change(self, request, obj):
"This license change impacts component usage in a Product or in another "
"Component.<br>{}".format(", ".join(changelist_links))
)
self.message_user(request, format_html(msg), messages.WARNING)
self.message_user(request, mark_safe(msg), messages.WARNING)

return response

Expand Down Expand Up @@ -554,15 +555,23 @@ def get_actions(self, request):
del actions["set_policy"]
return actions

def log_deletion(self, request, object, object_repr):
def delete_model(self, request, obj):
"""
Handle single object deletion from the delete view.
Add the option to delete associated `Package` instances.
We use this method rather than `self.delete_model()` since we want to support both
the delete_view and the `delete_selected` action.
"""
super().log_deletion(request, object, object_repr)
if request.POST.get("delete_packages"):
object.packages.all().delete()
obj.packages.all().delete()
super().delete_model(request, obj)

def delete_queryset(self, request, queryset):
"""
Handle bulk deletion from the delete_selected action.
Add the option to delete associated `Package` instances.
"""
if request.POST.get("delete_packages"):
Package.objects.filter(component__in=queryset).delete()
super().delete_queryset(request, queryset)

def changeform_view(self, request, object_id=None, form_url="", extra_context=None):
"""
Expand Down Expand Up @@ -1053,7 +1062,7 @@ def collect_data_action(self, request, queryset):
if not_updated:
msg += f"<br>{not_updated} package(s) NOT updated (data already set or URL unavailable)"

self.message_user(request, format_html(msg), messages.SUCCESS)
self.message_user(request, mark_safe(msg), messages.SUCCESS)

@admin.display(
ordering="component",
Expand All @@ -1068,7 +1077,7 @@ def components_links(self, obj):
assigned_package.component.get_admin_link(target="_blank")
for assigned_package in obj.componentassignedpackage_set.all()
]
return format_html("<br>".join(component_links))
return mark_safe("<br>".join(component_links))

@admin.display(description="Inferred URL")
def inferred_url(self, obj):
Expand Down
10 changes: 5 additions & 5 deletions component_catalog/license_expression_dje.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from django.forms import widgets
from django.urls import reverse
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.html import mark_safe

from boolean.boolean import PARSE_ERRORS
from license_expression import ExpressionError
Expand Down Expand Up @@ -149,23 +149,23 @@ def normalize_and_validate_expression(
msg = str(ee)
if include_available:
msg += available_licenses_message(licensing)
raise ValidationError(format_html(msg), code="invalid")
raise ValidationError(mark_safe(msg), code="invalid")

except ParseError as pe:
msg = PARSE_ERRORS[pe.error_code]
if pe.token_string:
msg += ": " + pe.token_string
if include_available:
msg += available_licenses_message(licensing)
raise ValidationError(format_html(msg), code="invalid")
raise ValidationError(mark_safe(msg), code="invalid")

except (ValueError, TypeError) as ve:
msg = "Invalid reference licenses data.\n" + str(ve)
raise ValidationError(format_html(msg), code="invalid")
raise ValidationError(mark_safe(msg), code="invalid")

except Exception as e:
msg = "Invalid license expression.\n" + str(e)
raise ValidationError(format_html(msg), code="invalid")
raise ValidationError(mark_safe(msg), code="invalid")

# NOTE: we test for None because an expression cannot be resolved to
# a boolean and a plain "if parsed" would attempt to resolve the
Expand Down
3 changes: 2 additions & 1 deletion component_catalog/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from django.template.defaultfilters import filesizeformat
from django.utils.functional import cached_property
from django.utils.html import format_html
from django.utils.html import mark_safe
from django.utils.text import format_lazy
from django.utils.text import get_valid_filename
from django.utils.text import normalize_newlines
Expand Down Expand Up @@ -190,7 +191,7 @@ def get_license_expression(self, template="{symbol.key}", as_link=False, show_po
as_link=as_link,
show_policy=show_policy,
)
return format_html(rendered)
return mark_safe(rendered)

def get_license_expression_attribution(self):
# note: the fields use in the template must be available as attributes or
Expand Down
13 changes: 8 additions & 5 deletions component_catalog/tests/test_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2204,18 +2204,20 @@ def test_component_admin_delete_confirmation_include_associated_packages(self):
expected = "Would you also like to delete Packages associated with this Component"
self.assertNotContains(response, expected)

package1 = Package.objects.create(filename="package1.zip", dataspace=self.dataspace1)
package1 = make_package(dataspace=self.dataspace1, filename="package1.zip")
ComponentAssignedPackage.objects.create(
component=self.component1, package=package1, dataspace=self.dataspace1
)
package2 = Package.objects.create(filename="package2.zip", dataspace=self.dataspace1)
package2 = make_package(dataspace=self.dataspace1, filename="package2.zip")
ComponentAssignedPackage.objects.create(
component=self.component1, package=package2, dataspace=self.dataspace1
)
self.assertEqual(2, self.component1.packages.count())

self.assertTrue(self.component1.packages.exists())
response = self.client.get(delete_url)
self.assertContains(response, expected)
field = '<input type="checkbox" name="enable_delete_packages" id="enable_delete_packages">'
self.assertContains(response, field)

data = {
"post": "yes",
Expand All @@ -2224,9 +2226,10 @@ def test_component_admin_delete_confirmation_include_associated_packages(self):
response = self.client.post(delete_url, data=data, follow=True)
self.assertContains(response, "was deleted successfully.")
self.assertFalse(Component.objects.filter(pk=self.component1.pk).exists())
self.assertFalse(Package.objects.filter(pk__in=[package1.pk, package2.pk]).exists())
package_qs = Package.objects.filter(pk__in=[package1.pk, package2.pk])
self.assertFalse(package_qs.exists())

package3 = Package.objects.create(filename="package3.zip", dataspace=self.dataspace1)
package3 = make_package(dataspace=self.dataspace1, filename="package3.zip")
ComponentAssignedPackage.objects.create(
component=self.component2, package=package3, dataspace=self.dataspace1
)
Expand Down
2 changes: 1 addition & 1 deletion component_catalog/tests/test_importers.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ def test_component_import_url_without_scheme_validation(self):
self.assertTrue(importer.formset.is_valid())
importer.save_all()
added_component = importer.results["added"][0]
self.assertEqual("http://www.a.com", added_component.homepage_url)
self.assertEqual("https://www.a.com", added_component.homepage_url)

def test_component_import_strip_input_values(self):
file = os.path.join(TESTFILES_LOCATION, "valid_with_non_stripped_spaces.csv")
Expand Down
27 changes: 14 additions & 13 deletions component_catalog/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from django.utils.dateparse import parse_datetime
from django.utils.html import escape
from django.utils.html import format_html
from django.utils.html import mark_safe
from django.utils.text import normalize_newlines
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
Expand Down Expand Up @@ -275,7 +276,7 @@ def tab_vulnerabilities(self):

return {
"fields": [(None, context, None, self.template)],
"label": format_html(label),
"label": mark_safe(label),
}

def get_fixed_packages_html(self, vulnerability, dataspace):
Expand Down Expand Up @@ -311,7 +312,7 @@ def get_fixed_packages_html(self, vulnerability, dataspace):
if is_vulnerable:
display_value += package.get_html_link(
href=f"{absolute_url}#vulnerabilities",
value=format_html(vulnerability_icon),
value=mark_safe(vulnerability_icon),
)
else:
display_value += no_vulnerabilities_icon
Expand All @@ -336,7 +337,7 @@ def get_fixed_packages_html(self, vulnerability, dataspace):
)
fixed_packages_values.append(display_value)

return format_html("<br>".join(fixed_packages_values))
return mark_safe("<br>".join(fixed_packages_values))


class ComponentListView(
Expand Down Expand Up @@ -703,7 +704,7 @@ def tab_subcomponents(self):
if dataspace.show_usage_policy_in_user_views:
usage_policy = subcomponent.get_usage_policy_display_with_icon()
if not usage_policy:
usage_policy = format_html("&nbsp;")
usage_policy = mark_safe("&nbsp;")
fields_data["usage_policy"] = usage_policy

components.append(fields_data)
Expand Down Expand Up @@ -1053,7 +1054,7 @@ def post_add_to_component(self, form_class):
return redirect(request.path)

error_msg = f"Error assigning packages to a component.\n{form.errors}"
messages.error(request, format_html(error_msg))
messages.error(request, mark_safe(error_msg))
return redirect(request.path)

def post(self, request, *args, **kwargs):
Expand Down Expand Up @@ -1418,7 +1419,7 @@ def post_scan_to_package(self, form_class):
messages.warning(request, "No new values to assign.")
else:
error_msg = f"Error assigning values to the package.\n{form.errors}"
messages.error(request, format_html(error_msg))
messages.error(request, mark_safe(error_msg))

return redirect(f"{request.path}#essentials")

Expand Down Expand Up @@ -1593,7 +1594,7 @@ def package_create_ajax_view(request):
elif len_created > 1:
packages = "\n".join([package.get_absolute_link() for package in created])
msg = f"The following Packages were successfully created{scan_msg}:\n{packages}"
messages.success(request, format_html(msg))
messages.success(request, mark_safe(msg))

purls_wihtout_download_url = [package for package in created if not package.download_url]
if purls_wihtout_download_url:
Expand All @@ -1604,12 +1605,12 @@ def package_create_ajax_view(request):
"\nAlternatively, you can directly provide a download URL instead of a "
"package URL to create the packages.\n"
)
messages.warning(request, format_html(msg + "\n".join(packages)))
messages.warning(request, mark_safe(msg + "\n".join(packages)))

if errors:
messages.error(request, format_html("\n".join(errors)))
messages.error(request, mark_safe("\n".join(errors)))
if warnings:
messages.warning(request, format_html("\n".join(warnings)))
messages.warning(request, mark_safe("\n".join(warnings)))

return JsonResponse({"redirect_url": redirect_url})

Expand Down Expand Up @@ -2187,7 +2188,7 @@ def license_clarity_fields(license_clarity_score):
table.append(
{
"label": label,
"value": format_html(value),
"value": mark_safe(value),
"help_text": help_text,
"td_class": td_class,
"th_class": th_class,
Expand Down Expand Up @@ -2328,7 +2329,7 @@ def scan_detected_package_fields(self, key_files_packages):
for label, scan_field in ScanCodeIO.SCAN_PACKAGE_FIELD:
if value := self.detected_package_data.get(scan_field):
if isinstance(value, list):
value = format_html("<br>".join([escape(entry) for entry in value]))
value = mark_safe("<br>".join([escape(entry) for entry in value]))
else:
value = escape(value)
detected_package_fields.append((label, value))
Expand Down Expand Up @@ -2404,7 +2405,7 @@ def scan_summary_fields(self, scan_summary):
if entry.get("value")
]

scan_summary_fields.append((label, format_html("\n".join(values))))
scan_summary_fields.append((label, mark_safe("\n".join(values))))

scan_summary_fields.append(FieldSeparator)
return scan_summary_fields
Expand Down
14 changes: 13 additions & 1 deletion dejacode/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,9 @@ def get_fake_redis_connection(config, use_strict_redis):
# Although, this setting and registration views are needed for the user creation.
ACCOUNT_ACTIVATION_DAYS = 10

# django-altcha
ALTCHA_HMAC_KEY = env.str("DEJACODE_ALTCHA_HMAC_KEY", default="")

# https://github.com/zapier/django-rest-hooks
HOOK_FINDER = "notification.models.find_and_fire_hook"
HOOK_DELIVERER = "notification.tasks.deliver_hook_wrapper"
Expand Down Expand Up @@ -683,11 +686,20 @@ def get_fake_redis_connection(config, use_strict_redis):
MIDDLEWARE.append("debug_toolbar.middleware.DebugToolbarMiddleware")
INTERNAL_IPS = ["127.0.0.1"]

# drf-yasg DeprecationWarning
SWAGGER_USE_COMPAT_RENDERERS = False

# The default protocol in urlize and urlizetrunc will change from HTTP to HTTPS
# in Django 7.0.
# Set the transitional setting URLIZE_ASSUME_HTTPS to True to opt into assuming HTTPS
# during the Django 6.x release cycle.
URLIZE_ASSUME_HTTPS = env.bool("DEJACODE_URLIZE_ASSUME_HTTPS", default=True)

if IS_TESTS:
# Silent the django-axes logging during tests
LOGGING["loggers"].update({"axes": {"handlers": ["null"]}})
# Do not pollute the MEDIA_ROOT location while running the tests.
MEDIA_ROOT = tempfile.TemporaryDirectory().name
MEDIA_ROOT = tempfile.mkdtemp()
# Set a faster hashing algorithm for running the tests
# https://docs.djangoproject.com/en/dev/topics/testing/overview/#password-hashing
PASSWORD_HASHERS = [
Expand Down
3 changes: 2 additions & 1 deletion dejacode_toolkit/spdx.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import re
from dataclasses import dataclass
from dataclasses import field
from datetime import UTC
from datetime import datetime
from typing import List # Python 3.8 compatibility

Expand Down Expand Up @@ -106,7 +107,7 @@ class CreationInfo:
Format: YYYY-MM-DDThh:mm:ssZ
"""
created: str = field(
default_factory=lambda: datetime.utcnow().isoformat(timespec="seconds") + "Z",
default_factory=lambda: datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ"),
)

def as_dict(self):
Expand Down
Loading