Skip to content

Commit

Permalink
✨(backend) Generate PDF certificate
Browse files Browse the repository at this point in the history
Use marion and howard to generate certificate for an order.
  • Loading branch information
sdemagny committed Jun 7, 2021
1 parent a37cb0a commit a08f138
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -10,6 +10,7 @@ and this project adheres to

### Added

- Use marion and howard to generate certificate for an order
- Use marion and howard to generate invoice for an order
- Implement Address model for billing and add routes API to get, create,
update and delete address.
Expand Down
2 changes: 1 addition & 1 deletion src/backend/joanie/core/admin.py
Expand Up @@ -20,7 +20,7 @@ class CertificateDefinitionAdmin(TranslatableAdmin):
class CertificateAdmin(admin.ModelAdmin):
"""Admin class for the Certificate model"""

list_display = ("certificate_definition", "order", "issued_on")
list_display = ("order", "issued_on")


@admin.register(models.Course)
Expand Down
3 changes: 3 additions & 0 deletions src/backend/joanie/core/factories.py
Expand Up @@ -39,6 +39,7 @@ class Meta:

title = factory.Sequence(lambda n: "Certificate definition %s" % n)
name = factory.Sequence(lambda n: "certificate-definition-%s" % n)
template = settings.MARION_CERTIFICATE_DOCUMENT_ISSUER


class OrganizationFactory(factory.django.DjangoModelFactory):
Expand All @@ -49,6 +50,8 @@ class Meta:

code = factory.Faker("ean", length=8)
title = factory.Sequence(lambda n: "Organization %s" % n)
signature = factory.django.FileField(filename="signature.png")
logo = factory.django.FileField(filename="logo.png")


class CourseFactory(factory.django.DjangoModelFactory):
Expand Down
37 changes: 37 additions & 0 deletions src/backend/joanie/core/migrations/0003_auto_20210607_0938.py
@@ -0,0 +1,37 @@
# Generated by Django 3.2 on 2021-06-07 09:38

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("core", "0002_auto_20210604_2357"),
]

operations = [
migrations.RemoveField(
model_name="certificate",
name="certificate_definition",
),
migrations.AddField(
model_name="organization",
name="logo",
field=models.ImageField(blank=True, upload_to="", verbose_name="logo"),
),
migrations.AddField(
model_name="organization",
name="representative",
field=models.CharField(
blank=True,
help_text="representative fullname (to sign certificate for example)",
max_length=100,
verbose_name="representative",
),
),
migrations.AddField(
model_name="organization",
name="signature",
field=models.ImageField(blank=True, upload_to="", verbose_name="signature"),
),
]
12 changes: 2 additions & 10 deletions src/backend/joanie/core/models/certifications.py
Expand Up @@ -7,8 +7,6 @@

from parler import models as parler_models

from . import products as products_models


class CertificateDefinition(parler_models.TranslatableModel):
"""
Expand Down Expand Up @@ -42,14 +40,8 @@ class Certificate(models.Model):
Certificate represents and records all user certificates issued as part of an order
"""

certificate_definition = models.ForeignKey(
CertificateDefinition,
verbose_name=_("certificate definition"),
related_name="certificates_issued",
on_delete=models.RESTRICT,
)
order = models.OneToOneField(
products_models.Order,
"core.Order",
verbose_name=_("order"),
on_delete=models.PROTECT,
)
Expand All @@ -63,4 +55,4 @@ class Meta:
verbose_name_plural = _("Certificates")

def __str__(self):
return f"{self.certificate_definition} for {self.order.owner}"
return f"Certificate for {self.order.owner}"
8 changes: 8 additions & 0 deletions src/backend/joanie/core/models/courses.py
Expand Up @@ -20,6 +20,14 @@ class Organization(parler_models.TranslatableModel):
translations = parler_models.TranslatedFields(
title=models.CharField(_("title"), max_length=255)
)
representative = models.CharField(
_("representative"),
help_text=_("representative fullname (to sign certificate for example)"),
max_length=100,
blank=True,
)
signature = models.ImageField(_("signature"), blank=True)
logo = models.ImageField(_("logo"), blank=True)

class Meta:
db_table = "joanie_organization"
Expand Down
29 changes: 29 additions & 0 deletions src/backend/joanie/core/models/products.py
Expand Up @@ -19,6 +19,7 @@

from .. import enums
from . import accounts as customers_models
from . import certifications as certifications_models
from . import courses as courses_models

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -268,6 +269,34 @@ def generate_invoice(self):
self.save()
return invoice

def deliver_certificate(self):
"""Deliver a pdf certificate for the owner of order"""
organization = self.course.organization
context_query = {
"student": {
"name": self.owner.get_full_name(),
},
"course": {
"name": self.product.title, # pylint: disable=no-member
"description": self.product.description, # pylint: disable=no-member
"organization": {
"name": organization.title,
"representative": organization.representative,
"signature": organization.signature.path,
"logo": organization.logo.path,
},
},
}
document_request = DocumentRequest.objects.create(
issuer=self.product.certificate_definition.template, # pylint: disable=no-member
context_query=context_query,
)
certificate, _ = certifications_models.Certificate.objects.update_or_create(
order=self,
defaults={"attachment": document_request.get_document_path().name},
)
return certificate


class OrderCourseRelation(models.Model):
"""
Expand Down
1 change: 1 addition & 0 deletions src/backend/joanie/settings.py
Expand Up @@ -232,6 +232,7 @@ class Base(Configuration):

MARION_DOCUMENT_ISSUER_CHOICES_CLASS = "howard.defaults.DocumentIssuerChoices"
MARION_INVOICE_DOCUMENT_ISSUER = "howard.issuers.InvoiceDocument"
MARION_CERTIFICATE_DOCUMENT_ISSUER = "howard.issuers.CertificateDocument"
JOANIE_CURRENCY = values.Value(
("EUR", "\N{euro sign}"), environ_name="JOANIE_CURRENCY"
)
Expand Down
17 changes: 17 additions & 0 deletions src/backend/joanie/tests/test_models_product.py
Expand Up @@ -60,3 +60,20 @@ def test_generate_invoice(self):
order.refresh_from_db()
now = timezone.localtime(timezone.now())
self.assertTrue(order.invoice_ref.startswith(now.strftime("%Y")))

def test_deliver_certificate(self):
"""Generate a certificate for a product order"""

course = factories.CourseFactory()
product = factories.ProductFactory(
courses=[course],
certificate_definition=factories.CertificateDefinitionFactory(),
)
order = factories.OrderFactory(product=product)

certificate = order.deliver_certificate()
self.assertEqual(DocumentRequest.objects.count(), 1)
self.assertEqual(
certificate.attachment.name,
f"{DocumentRequest.objects.get().document_id}.pdf",
)

0 comments on commit a08f138

Please sign in to comment.