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
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
!dejacode/
!dejacode_toolkit/
!dje/
!internal_notifications/
!license_library/
!notification/
!organization/
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Release notes
=============

### Version 6.1.0

**Upgrade prerequisite:** upgrade to DejaCode 6.0.x and run its migrations before upgrading to this version. The internal notifications app replaces the django-notifications-patched dependency and requires the 6.0.x database schema.

This release removes unused columns from the notifications table: back up the database before upgrading, as a rollback to an earlier version requires a database restore.

### Version 6.0.1

* fix: relink cloned codebase resources to the cloned inventory #601 by @tdruez in https://github.com/aboutcode-org/dejacode/pull/602
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ graft dejacode_toolkit
graft dje
graft docs
graft etc
graft internal_notifications
graft license_library
graft notification
graft organization
Expand Down
2 changes: 1 addition & 1 deletion component_catalog/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
from django.utils.encoding import force_str

from guardian.shortcuts import assign_perm
from notifications.models import Notification

from component_catalog.forms import ComponentAddToProductForm
from component_catalog.forms import ComponentForm
Expand Down Expand Up @@ -59,6 +58,7 @@
from dje.tests import create_superuser
from dje.tests import create_user
from dje.views import PaginationMixin
from internal_notifications.models import Notification
from license_library.models import License
from license_library.models import LicenseAssignedTag
from license_library.models import LicenseTag
Expand Down
1 change: 1 addition & 0 deletions compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ services:
- ./dejacode:/opt/dejacode/dejacode
- ./dejacode_toolkit:/opt/dejacode/dejacode_toolkit
- ./dje:/opt/dejacode/dje
- ./internal_notifications:/opt/dejacode/internal_notifications
- ./license_library:/opt/dejacode/license_library
- ./notification:/opt/dejacode/notification
- ./organization:/opt/dejacode/organization
Expand Down
2 changes: 1 addition & 1 deletion dejacode/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,6 @@ def gettext_noop(s):
"crispy_bootstrap5",
"guardian",
"django_filters",
"notifications",
"axes",
"django_otp",
"django_otp.plugins.otp_totp",
Expand All @@ -346,6 +345,7 @@ def gettext_noop(s):
"purldb",
"policy",
"notification",
"internal_notifications",
"vulnerabilities",
"vulnerabilities.triage",
]
Expand Down
5 changes: 2 additions & 3 deletions dje/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,10 @@
from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy as _

from notifications.models import Notification

from aboutcode.api_auth import AbstractAPIToken
from dje.fields import LastModifiedByField
from dje.tasks import send_mail_task
from internal_notifications.models import Notification

logger = logging.getLogger("dje")

Expand Down Expand Up @@ -1881,7 +1880,7 @@ def serialize_hook(self, hook):
}

def send_internal_notification(self, verb, **data):
"""Similar to ``notify.send`` without relying on the Signal system."""
"""Create an internal Notification sent by and to this user."""
return Notification.objects.create(
recipient=self,
actor=self,
Expand Down
3 changes: 1 addition & 2 deletions dje/templates/navbar/navbar_header_right_menu.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{% load i18n static %}
{% load has_notification from notifications_tags %}

{% block right-nav %}
<li class="nav-item">
Expand All @@ -23,7 +22,7 @@
<hr class="d-md-none my-2 text-white-50">
</li>
<li class="nav-item">
{% if user|has_notification %}
{% if user.notifications.unread.exists %}
<a class="nav-link px-md-3 position-relative" href="{% url 'notifications:unread' %}" title="Unread notifications" data-bs-toggle="tooltip" aria-label="Unread notifications">
<i class="fa-solid fa-bell"></i>
<span class="d-none d-md-inline position-absolute translate-middle p-1 bg-danger rounded-circle"></span>
Expand Down
5 changes: 1 addition & 4 deletions dje/templates/notifications/notice_content.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,9 @@
{% elif action_object_content_type|slugify == 'package' %}
<i>(object not found)</i>
{% endif %}
{% if notice.target %}
of {{ notice.target }}
{% endif %}
</div>
<small class="text-muted">
{{ notice.timesince }} ago
{{ notice.timestamp|timesince }} ago
</small>
</div>
{% if notice.description %}
Expand Down
45 changes: 44 additions & 1 deletion dje/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
from django.urls import reverse

from guardian.shortcuts import assign_perm
from notifications.models import Notification

from dje.models import Dataspace
from dje.models import DataspaceConfiguration
Expand All @@ -30,6 +29,7 @@
from dje.tests import create
from dje.tests import create_superuser
from dje.tests import create_user
from internal_notifications.models import Notification

Owner = apps.get_model("organization", "Owner")
Component = apps.get_model("component_catalog", "Component")
Expand Down Expand Up @@ -383,6 +383,49 @@ def test_mark_all_notifications_as_read_view(self):
self.assertRedirects(response, unread_url)
self.assertEqual(0, self.user.notifications.unread().count())

def test_notifications_list_views(self):
def get_verbs_by_model(grouped_notifications):
return {
content_type.model: [notice.verb for notice in notices]
for content_type, notices in grouped_notifications.items()
}

Notification.objects.create(
recipient=self.user,
actor=self.user,
verb="Read",
action_object=self.owner1,
unread=False,
)
Notification.objects.create(
recipient=self.user, actor=self.super_user, verb="Unread", action_object=self.owner1
)
Notification.objects.create(
recipient=self.user,
actor=self.super_user,
verb="Updated",
action_object=self.dataspace1,
)
Notification.objects.create(
recipient=self.super_user, actor=self.user, verb="Other", action_object=self.owner1
)
unread_url = reverse("notifications:unread")
all_url = reverse("notifications:all")

response = self.client.get(unread_url)
self.assertRedirects(response, f"{reverse('login')}?next={unread_url}")

self.client.login(username=self.user.username, password="secret")
response = self.client.get(unread_url)
expected = {"dataspace": ["Updated"], "owner": ["Unread"]}
self.assertEqual(expected, get_verbs_by_model(response.context["grouped_notifications"]))
self.assertEqual(3, response.context["all_count"])
self.assertEqual(2, response.context["unread_count"])

response = self.client.get(all_url)
expected = {"dataspace": ["Updated"], "owner": ["Unread", "Read"]}
self.assertEqual(expected, get_verbs_by_model(response.context["grouped_notifications"]))

@override_settings(REFERENCE_DATASPACE="Dataspace")
def test_global_search_list_view(self):
self.client.login(username=self.user.username, password="secret")
Expand Down
22 changes: 9 additions & 13 deletions dje/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@
from drf_yasg.views import get_schema_view
from grappelli.views.related import AutocompleteLookup
from grappelli.views.related import RelatedLookup
from notifications import views as notifications_views

from aboutcode.api_auth.views import BaseGenerateAPIKeyView
from aboutcode.api_auth.views import BaseRevokeAPIKeyView
Expand Down Expand Up @@ -2290,7 +2289,9 @@ def get_context_data(self, **kwargs):
return context_data


class NotificationsCountMixin:
class NotificationsListView(LoginRequiredMixin, ListView):
template_name = "notifications/list.html"

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
user_notifications = self.request.user.notifications
Expand All @@ -2306,24 +2307,19 @@ def get_context_data(self, **kwargs):
return context


class UnreadNotificationsList(
NotificationsCountMixin,
notifications_views.UnreadNotificationsList,
):
pass
class UnreadNotificationsList(NotificationsListView):
def get_queryset(self):
return self.request.user.notifications.unread()


class AllNotificationsList(
NotificationsCountMixin,
notifications_views.AllNotificationsList,
):
pass
class AllNotificationsList(NotificationsListView):
def get_queryset(self):
return self.request.user.notifications.all()


@login_required
@require_POST
def mark_all_notifications_as_read_view(request):
"""Replace the `notifications` library view, which marks as read on GET requests."""
request.user.notifications.mark_all_as_read()
return redirect("notifications:unread")

Expand Down
9 changes: 1 addition & 8 deletions etc/scripts/build_deb_docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,6 @@ def build_deb_with_docker():
# Get all dependencies
dependencies = project.get("dependencies", [])

filtered_dependencies = [
dep for dep in dependencies if "django_notifications_patched" not in dep
]

docker_cmd = [
"docker",
"run",
Expand Down Expand Up @@ -95,11 +91,8 @@ def build_deb_with_docker():
# Clean previous build artifacts
rm -rf build/

# Install non-PyPI dependencies
pip install https://github.com/dejacode/django-notifications-patched/archive/refs/tags/2.0.0.tar.gz

# Install dependencies directly
{" && ".join([f'pip install "{dep}"' for dep in filtered_dependencies])}
{" && ".join([f'pip install "{dep}"' for dep in dependencies])}

# Install build tool
pip install build
Expand Down
99 changes: 39 additions & 60 deletions etc/scripts/build_nix_docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,76 +242,55 @@ def create_defualt_nix(dependencies_list, meta_dict):
print("Processing {}/{}: {}".format(idx + 1, deps_size, dep["name"]))
name = dep["name"]
version = dep["version"]
# Handle 'django_notifications_patched' seperately
if name == "django_notifications_patched":
if name == "django_notifications_patched" and version == "2.0.0":
nix_content += " " + name + " = self.buildPythonPackage rec {\n"
nix_content += ' pname = "django_notifications_patched";\n'
nix_content += ' version = "2.0.0";\n'
nix_content += ' format = "setuptools";\n'
nix_content += " doCheck = false;\n"
nix_content += " src = pkgs.fetchFromGitHub {\n"
nix_content += ' owner = "dejacode";\n'
nix_content += ' repo = "django-notifications-patched";\n'
nix_content += ' rev = "2.0.0";\n'
nix_content += ' url = "https://github.com/dejacode/django-notifications-patched/archive/refs/tags/2.0.0.tar.gz";\n'
nix_content += (
' sha256 = "sha256-RDAp2PKWa2xA5ge25VqkmRm8HCYVS4/fq2xKc80LDX8=";\n'
)
nix_content += " };\n"
nix_content += " };\n"
else:
need_review_packages_list.append(dep)
else:
url = "https://pypi.org/pypi/{name}/{version}/json".format(name=name, version=version)
try:
response = requests.get(url, timeout=30)
response.raise_for_status()
data = response.json()

url_section = data.get("urls", [])
build_from_src = True
package_added = False

url = "https://pypi.org/pypi/{name}/{version}/json".format(name=name, version=version)
try:
response = requests.get(url, timeout=30)
response.raise_for_status()
data = response.json()

url_section = data.get("urls", [])
build_from_src = True
package_added = False

for component in url_section:
if component.get("packagetype") == "bdist_wheel":
whl_url = component.get("url")
if not is_compatible_wheel(whl_url, py_version_major, py_version_minor):
continue
whl_sha256 = get_sha256_hash(whl_url)
nix_content += " " + name + " = buildCustomPackage {\n"
nix_content += ' pname = "' + name + '";\n'
nix_content += ' version = "' + version + '";\n'
nix_content += ' format = "wheel";\n'
nix_content += " src = pkgs.fetchurl {\n"
nix_content += ' url = "' + whl_url + '";\n'
nix_content += ' sha256 = "' + whl_sha256 + '";\n'
nix_content += " };\n"
nix_content += " };\n"
build_from_src = False
package_added = True
break

if build_from_src:
for component in url_section:
if component.get("packagetype") == "bdist_wheel":
whl_url = component.get("url")
if not is_compatible_wheel(whl_url, py_version_major, py_version_minor):
continue
whl_sha256 = get_sha256_hash(whl_url)
if component.get("packagetype") == "sdist":
sdist_url = component.get("url")
sdist_sha256 = get_sha256_hash(sdist_url)
nix_content += " " + name + " = buildCustomPackage {\n"
nix_content += ' pname = "' + name + '";\n'
nix_content += ' version = "' + version + '";\n'
nix_content += ' format = "wheel";\n'
nix_content += ' format = "setuptools";\n'
nix_content += " src = pkgs.fetchurl {\n"
nix_content += ' url = "' + whl_url + '";\n'
nix_content += ' sha256 = "' + whl_sha256 + '";\n'
nix_content += ' url = "' + sdist_url + '";\n'
nix_content += ' sha256 = "' + sdist_sha256 + '";\n'
nix_content += " };\n"
nix_content += " };\n"
build_from_src = False
package_added = True
break

if build_from_src:
for component in url_section:
if component.get("packagetype") == "sdist":
sdist_url = component.get("url")
sdist_sha256 = get_sha256_hash(sdist_url)
nix_content += " " + name + " = buildCustomPackage {\n"
nix_content += ' pname = "' + name + '";\n'
nix_content += ' version = "' + version + '";\n'
nix_content += ' format = "setuptools";\n'
nix_content += " src = pkgs.fetchurl {\n"
nix_content += ' url = "' + sdist_url + '";\n'
nix_content += ' sha256 = "' + sdist_sha256 + '";\n'
nix_content += " };\n"
nix_content += " };\n"
package_added = True
break
if not package_added:
need_review_packages_list.append(dep)
except requests.exceptions.RequestException:
if not package_added:
need_review_packages_list.append(dep)
except requests.exceptions.RequestException:
need_review_packages_list.append(dep)

nix_content += """
};
Expand Down
Loading
Loading