Skip to content

Commit

Permalink
🐛(backend) generate certificates with product type certificate
Browse files Browse the repository at this point in the history
It is now possible to launch the generation of certificates
for orders with product of type certificate from the BO.

Fix #1063
  • Loading branch information
jonathanreveille committed Feb 24, 2025
1 parent 3e34abe commit bcda62c
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 13 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ and this project adheres to
- Remove `owner` and `is_main` in CreditCard model permanently
- Remove `is_active` on order group client serializer

## Fixed

- BO: generate certificates for orders with product of type certificate

## [2.16.0] - 2025-02-13

### Added
Expand Down
8 changes: 6 additions & 2 deletions src/backend/joanie/core/utils/course_product_relation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Utility methods to get all orders and/or certificates from a course product relation."""

from django.db.models import Q

from joanie.core.enums import ORDER_STATE_COMPLETED, PRODUCT_TYPE_CERTIFICATE_ALLOWED
from joanie.core.models import Certificate, Order

Expand All @@ -11,7 +13,8 @@ def get_orders(course_product_relation):
return [
str(order_id)
for order_id in Order.objects.filter(
course=course_product_relation.course,
Q(course=course_product_relation.course, enrollment__isnull=True)
| Q(course__isnull=True, enrollment__isnull=False),
product=course_product_relation.product,
product__type__in=PRODUCT_TYPE_CERTIFICATE_ALLOWED,
state=ORDER_STATE_COMPLETED,
Expand All @@ -27,8 +30,9 @@ def get_generated_certificates(course_product_relation):
Return certificates that were published for a course product relation.
"""
return Certificate.objects.filter(
Q(order__course=course_product_relation.course, order__enrollment__isnull=True)
| Q(order__course__isnull=True, order__enrollment__isnull=False),
order__product=course_product_relation.product,
order__course=course_product_relation.course,
order__certificate__isnull=False,
order__state=ORDER_STATE_COMPLETED,
)
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from django.utils import timezone

from joanie.core import enums, factories
from joanie.core.models import Certificate, CourseProductRelation
from joanie.core.models import Certificate, CourseProductRelation, CourseState, Order
from joanie.lms_handler.backends.dummy import DummyLMSBackend


Expand Down Expand Up @@ -683,3 +683,73 @@ def test_api_admin_course_product_relation_check_certificates_generation_complet
# Verify that certificates were generated
for order in orders:
self.assertTrue(Certificate.objects.filter(order=order).exists())

@mock.patch(
"joanie.lms_handler.backends.dummy.DummyLMSBackend.get_grades",
return_value={"passed": True},
)
def test_api_admin_course_product_relation_generate_certificate_product_certificate(
self, _mock_get_grades
):
"""
Authenticated admin user should be able to generate certificate for products of type
certificate. Once the generation is done, the cache data must be deleted, and
we should find our certificates.
"""
admin = factories.UserFactory(is_staff=True, is_superuser=True)
self.client.login(username=admin.username, password="password")

course_run = factories.CourseRunFactory(
is_listed=True, state=CourseState.ONGOING_OPEN
)
enrollments = factories.EnrollmentFactory.create_batch(
20, course_run=course_run
)
product = factories.ProductFactory(
price=0,
type=enums.PRODUCT_TYPE_CERTIFICATE,
)
relation = factories.CourseProductRelationFactory(
product=product, course=enrollments[0].course_run.course
)

for enrollment in enrollments:
factories.OrderFactory(
product=relation.product,
enrollment=enrollment,
course=None,
state=enums.ORDER_STATE_COMPLETED,
)

response = self.client.post(
f"/api/v1.0/admin/course-product-relations/{relation.id}/generate_certificates/",
content_type="application/json",
)

self.assertEqual(response.status_code, HTTPStatus.CREATED)
self.assertDictEqual(
response.json(),
{
"course_product_relation_id": str(relation.id),
"count_certificate_to_generate": 20,
"count_exist_before_generation": 0,
},
)

second_response = self.client.get(
f"/api/v1.0/admin/course-product-relations/{relation.id}"
"/check_certificates_generation_process/",
content_type="application/json",
)

self.assertEqual(second_response.status_code, HTTPStatus.NOT_FOUND)
self.assertIsNone(cache.get(f"celery_certificate_generation_{relation.id}"))

# Verify that all the certificates were generated
orders = Order.objects.filter(
product=relation.product,
state=enums.ORDER_STATE_COMPLETED,
certificate__isnull=False,
)
for order in orders:
self.assertTrue(Certificate.objects.filter(order=order).exists())
104 changes: 94 additions & 10 deletions src/backend/joanie/tests/core/test_utils_course_product_relation.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from django.utils import timezone

from joanie.core import enums, factories
from joanie.core.models import CourseProductRelation
from joanie.core.models import CourseProductRelation, CourseState
from joanie.core.utils.course_product_relation import (
get_generated_certificates,
get_orders,
Expand All @@ -16,10 +16,10 @@
class UtilsCourseProductRelationTestCase(TestCase):
"""Test suite utility methods for course product relation to get orders and certificates"""

def test_utils_course_product_relation_get_orders_made(self):
def test_utils_course_product_relation_get_orders_for_product_type_credential(self):
"""
It should return the amount of orders that are validated for this course product
relation.
It should return the list of orders that are completed for this course product relation
with a product of type credential.
"""
course = factories.CourseFactory(products=None)
product = factories.ProductFactory(
Expand All @@ -43,23 +43,61 @@ def test_utils_course_product_relation_get_orders_made(self):
course_product_relation = CourseProductRelation.objects.get(
product=product, course=course
)
# Generate orders for the course product relation
# Generate orders for the course product relation with the course
orders = factories.OrderFactory.create_batch(
10,
product=course_product_relation.product,
course=course_product_relation.course,
enrollment=None,
state=enums.ORDER_STATE_COMPLETED,
)
for order in orders:
order.init_flow()

result = get_orders(course_product_relation=course_product_relation)

self.assertEqual(len(result), 10)

def test_utils_course_product_relation_get_generated_certificates(self):
def test_utils_course_product_relation_get_orders_for_product_type_certificate(
self,
):
"""
It should return the list of orders that are completed for the course product relation
with a product of type certificate.
"""
course_run = factories.CourseRunFactory(
is_listed=True, state=CourseState.ONGOING_OPEN
)
enrollments = factories.EnrollmentFactory.create_batch(5, course_run=course_run)
product = factories.ProductFactory(
price=0,
type=enums.PRODUCT_TYPE_CERTIFICATE,
)
relation = factories.CourseProductRelationFactory(
product=product, course=enrollments[0].course_run.course
)

orders = get_orders(course_product_relation=relation)

self.assertEqual(len(orders), 0)

# Generate orders for the course product relation with the enrollments
for enrollment in enrollments:
factories.OrderFactory(
product=relation.product,
enrollment=enrollment,
course=None,
state=enums.ORDER_STATE_COMPLETED,
)

orders = get_orders(course_product_relation=relation)

self.assertEqual(len(orders), 5)

def test_utils_course_product_relation_get_generated_certificates_for_product_type_credential(
self,
):
"""
It should return the amount of certificates that were published for this course product
relation.
relation with a product of type credential.
"""
course = factories.CourseFactory(products=None)
product = factories.ProductFactory(
Expand Down Expand Up @@ -87,20 +125,66 @@ def test_utils_course_product_relation_get_generated_certificates(self):
generated_certificates_queryset = get_generated_certificates(
course_product_relation=course_product_relation
)

self.assertEqual(generated_certificates_queryset.count(), 0)

# Generate certificates for the course product relation
orders = factories.OrderFactory.create_batch(
5,
product=course_product_relation.product,
course=course_product_relation.course,
enrollment=None,
state=enums.ORDER_STATE_COMPLETED,
)
for order in orders:
order.init_flow()
factories.OrderCertificateFactory(order=order)

generated_certificates_queryset = get_generated_certificates(
course_product_relation=course_product_relation
)

self.assertEqual(generated_certificates_queryset.count(), 5)

def test_utils_course_product_relation_get_generated_certificated_for_product_type_certificate(
self,
):
"""
It should return the amount of certificates that were published for this course product
relation with a product of type certificate.
"""
course_run = factories.CourseRunFactory(
is_listed=True, state=CourseState.ONGOING_OPEN
)
enrollments = factories.EnrollmentFactory.create_batch(
10, course_run=course_run
)
product = factories.ProductFactory(
price=0,
type=enums.PRODUCT_TYPE_CERTIFICATE,
)
relation = factories.CourseProductRelationFactory(
product=product, course=enrollments[0].course_run.course
)

generated_certificates_queryset = get_generated_certificates(
course_product_relation=relation
)

self.assertEqual(generated_certificates_queryset.count(), 0)

# Generate certificates for the course product relation
for enrollment in enrollments:
factories.OrderCertificateFactory(
order=factories.OrderFactory(
product=relation.product,
enrollment=enrollment,
course=None,
state=enums.ORDER_STATE_COMPLETED,
)
)

generated_certificates_queryset = get_generated_certificates(
course_product_relation=relation
)

self.assertEqual(generated_certificates_queryset.count(), 10)

0 comments on commit bcda62c

Please sign in to comment.