diff --git a/CHANGELOG.md b/CHANGELOG.md index 7719a9ba9..8ed794b7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to ### Changed +- Remove `generate_certificates` action in django admin views - Remove `owner` and `is_main` in CreditCard model permanently - Remove `is_active` on order group client serializer diff --git a/src/backend/joanie/core/admin.py b/src/backend/joanie/core/admin.py index b965bfb25..33985a99d 100644 --- a/src/backend/joanie/core/admin.py +++ b/src/backend/joanie/core/admin.py @@ -6,21 +6,18 @@ from django.contrib.admin.options import csrf_protect_m from django.contrib.auth import admin as auth_admin from django.contrib.sites.models import Site -from django.http import HttpResponseRedirect -from django.urls import re_path, reverse +from django.urls import reverse from django.utils.html import format_html from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext_lazy from admin_auto_filters.filters import AutocompleteFilter from adminsortable2.admin import SortableAdminBase, SortableInlineAdminMixin -from django_object_actions import DjangoObjectActions, takes_instance_or_queryset +from django_object_actions import DjangoObjectActions from parler.admin import TranslatableAdmin, TranslatableStackedInline -from joanie.core import forms, helpers, models -from joanie.core.enums import PRODUCT_TYPE_CERTIFICATE_ALLOWED +from joanie.core import forms, models -ACTION_NAME_GENERATE_CERTIFICATES = "generate_certificates" ACTION_NAME_CANCEL = "cancel" @@ -275,9 +272,7 @@ class CourseCourseRunsInline(admin.TabularInline): class CourseAdmin(DjangoObjectActions, TranslatableAdmin): """Admin class for the Course model""" - actions = (ACTION_NAME_GENERATE_CERTIFICATES,) autocomplete_fields = ["organizations"] - change_actions = (ACTION_NAME_GENERATE_CERTIFICATES,) change_form_template = "joanie/admin/translatable_change_form_with_actions.html" list_display = ("code", "title", "state") readonly_fields = ("course_runs",) @@ -305,18 +300,6 @@ class CourseAdmin(DjangoObjectActions, TranslatableAdmin): ) search_fields = ["code", "translations__title"] - @takes_instance_or_queryset - def generate_certificates(self, request, queryset): # pylint: disable no-self-use - """ - Custom action to generate certificates for a collection of courses - passed as a queryset - """ - certificate_generated_count = helpers.generate_certificates_for_orders( - models.Order.objects.filter(course__in=queryset) - ) - - summarize_certification_to_user(request, certificate_generated_count) - @admin.register(models.CourseRun) class CourseRunAdmin(TranslatableAdmin): @@ -479,67 +462,8 @@ class ProductAdmin( "id", "related_courses", ) - actions = (ACTION_NAME_GENERATE_CERTIFICATES,) - change_actions = (ACTION_NAME_GENERATE_CERTIFICATES,) search_fields = ["translations__title"] - def get_change_actions(self, request, object_id, form_url): - """ - Remove the generate_certificates action from list of actions - if the product instance is not certifying - """ - actions = super().get_change_actions(request, object_id, form_url) - actions = list(actions) - - if not self.model.objects.filter( - pk=object_id, type__in=PRODUCT_TYPE_CERTIFICATE_ALLOWED - ).exists(): - actions.remove(ACTION_NAME_GENERATE_CERTIFICATES) - - return actions - - def get_urls(self): - """ - Add url to trigger certificate generation for a course - product couple. - """ - url_patterns = super().get_urls() - - return [ - re_path( - r"^(?P.+)/generate-certificates/(?P.+)/$", - self.admin_site.admin_view(self.generate_certificates_for_course), - name=ACTION_NAME_GENERATE_CERTIFICATES, - ) - ] + url_patterns - - @takes_instance_or_queryset - def generate_certificates(self, request, queryset): # pylint: disable=no-self-use - """ - Custom action to generate certificates for a collection of products - passed as a queryset - """ - certificate_generated_count = helpers.generate_certificates_for_orders( - models.Order.objects.filter(product__in=queryset) - ) - - summarize_certification_to_user(request, certificate_generated_count) - - def generate_certificates_for_course(self, request, product_id, course_code): # pylint: disable=no-self-use - """ - A custom action to generate certificates for a course - product couple. - """ - certificate_generated_count = helpers.generate_certificates_for_orders( - models.Order.objects.filter( - product__id=product_id, course__code=course_code - ) - ) - - summarize_certification_to_user(request, certificate_generated_count) - - return HttpResponseRedirect( - reverse("admin:core_product_change", args=(product_id,)) - ) - @admin.display(description="Related courses") def related_courses(self, obj): # pylint: disable=no-self-use """ @@ -553,7 +477,6 @@ def get_related_courses_as_html(obj): # pylint: disable=no-self-use Get the html representation of the product's related courses """ related_courses = obj.courses.all() - is_certifying = obj.type in PRODUCT_TYPE_CERTIFICATE_ALLOWED if related_courses: items = [] @@ -562,26 +485,11 @@ def get_related_courses_as_html(obj): # pylint: disable=no-self-use "admin:core_course_change", args=(course.id,), ) - raw_html = ( '
  • ' f"{course.code} | {course.title}" + "
  • " ) - - if is_certifying: - # Add a button to generate certificate - generate_certificates_url = reverse( - f"admin:{ACTION_NAME_GENERATE_CERTIFICATES}", - kwargs={"product_id": obj.id, "course_code": course.code}, - ) - - raw_html += ( - f'' # pylint: disable=line-too-long - f"{_('Generate certificates')}" - "" - ) - - raw_html += "" items.append(raw_html) return format_html(f"") @@ -601,9 +509,8 @@ class DiscountAdmin(admin.ModelAdmin): class OrderAdmin(DjangoObjectActions, admin.ModelAdmin): """Admin class for the Order model""" - actions = (ACTION_NAME_CANCEL, ACTION_NAME_GENERATE_CERTIFICATES) + actions = (ACTION_NAME_CANCEL,) autocomplete_fields = ["course", "enrollment", "organization", "owner", "product"] - change_actions = (ACTION_NAME_GENERATE_CERTIFICATES,) list_display = ("id", "created_on", "organization", "owner", "product", "state") list_filter = [OwnerFilter, OrganizationFilter, ProductFilter, "state"] readonly_fields = ( @@ -621,15 +528,6 @@ def cancel(self, request, queryset): # pylint: disable=no-self-use for order in queryset: order.flow.cancel() - @takes_instance_or_queryset - def generate_certificates(self, request, queryset): # pylint: disable=no-self-use - """ - Custom action to launch generate_certificates management commands - over the order selected - """ - certificate_generated_count = helpers.generate_certificates_for_orders(queryset) - summarize_certification_to_user(request, certificate_generated_count) - def invoice(self, obj): # pylint: disable=no-self-use """Retrieve the root invoice related to the order.""" invoice = obj.invoices.get(parent__isnull=True) diff --git a/src/backend/joanie/tests/core/test_admin_course.py b/src/backend/joanie/tests/core/test_admin_course.py index aac6d36e5..6968d53ab 100644 --- a/src/backend/joanie/tests/core/test_admin_course.py +++ b/src/backend/joanie/tests/core/test_admin_course.py @@ -45,10 +45,3 @@ def test_admin_course_use_translatable_change_form_with_actions_template(self): # Django parler tabs should be displayed parler_tabs = html.cssselect(".parler-language-tabs span") self.assertEqual(len(parler_tabs), len(settings.LANGUAGES)) - - # Django object actions should be displayed - object_actions = html.cssselect(".objectaction-item") - self.assertEqual(len(object_actions), 1) - self.assertEqual( - object_actions[0].attrib["data-tool-name"], "generate_certificates" - ) diff --git a/src/backend/joanie/tests/core/test_admin_product.py b/src/backend/joanie/tests/core/test_admin_product.py index cf4d41866..b6768c59b 100644 --- a/src/backend/joanie/tests/core/test_admin_product.py +++ b/src/backend/joanie/tests/core/test_admin_product.py @@ -5,10 +5,8 @@ import random import uuid from http import HTTPStatus -from unittest import mock from django.conf import settings -from django.contrib.messages import get_messages from django.urls import reverse import lxml.html @@ -350,9 +348,9 @@ def test_admin_product_should_allow_to_generate_certificate_for_related_course( # - The related course should be displayed related_course = related_courses_field.cssselect("li") self.assertEqual(len(related_course), 1) - # - And it should contain two links + # - And it should contain one link links = related_course[0].cssselect("a") - self.assertEqual(len(links), 2) + self.assertEqual(len(links), 1) # - 1st a link to go to the related course change view self.assertEqual(links[0].text_content(), f"{course.code} | {course.title}") self.assertEqual( @@ -360,50 +358,6 @@ def test_admin_product_should_allow_to_generate_certificate_for_related_course( reverse("admin:core_course_change", args=(course.pk,)), ) - # - 2nd a link to generate certificate for the course - product couple - self.assertEqual(links[1].text_content(), "Generate certificates") - self.assertEqual( - links[1].attrib["href"], - reverse( - "admin:generate_certificates", - kwargs={"product_id": product.id, "course_code": course.code}, - ), - ) - - @mock.patch("joanie.core.helpers.generate_certificates_for_orders", return_value=0) - def test_admin_product_generate_certificate_for_course( - self, mock_generate_certificates - ): - """ - Product Admin should contain an endpoint which triggers the - `create_certificates` management command with product and course as options. - """ - user = factories.UserFactory(is_staff=True, is_superuser=True) - self.client.login(username=user.username, password="password") - - course = factories.CourseFactory() - product = factories.ProductFactory(courses=[course]) - - response = self.client.get( - reverse( - "admin:generate_certificates", - kwargs={"course_code": course.code, "product_id": product.id}, - ), - ) - - # - Generate certificates command should have been called - mock_generate_certificates.assert_called_once() - - # Check the presence of a confirmation message - messages = list(get_messages(response.wsgi_request)) - self.assertEqual(len(messages), 1) - self.assertEqual(str(messages[0]), "No certificates have been generated.") - - # - User should be redirected to the product change view - self.assertRedirects( - response, reverse("admin:core_product_change", args=(product.id,)) - ) - def test_admin_product_use_translatable_change_form_with_actions_template(self): """ The product admin change view should use a custom change form template