Skip to content

Commit

Permalink
feat: add backed for tracking agreement and implement DPA
Browse files Browse the repository at this point in the history
  • Loading branch information
nijel committed Nov 14, 2024
1 parent 634997c commit bdc7936
Show file tree
Hide file tree
Showing 9 changed files with 702 additions and 6 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@
venv
/node_modules/
/invoices/
/agreements/
/.coverage
/junit.xml
43 changes: 43 additions & 0 deletions weblate_web/legal/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Generated by Django 5.1.2 on 2024-11-14 11:26

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):
initial = True

dependencies = [
("payments", "0037_alter_customer_vat"),
]

operations = [
migrations.CreateModel(
name="Agreement",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("signed", models.DateTimeField(auto_now_add=True)),
(
"kind",
models.IntegerField(
choices=[(1, "Data Processing Agreement")], default=1
),
),
(
"customer",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
to="payments.customer",
),
),
],
),
]
100 changes: 99 additions & 1 deletion weblate_web/legal/models.py
Original file line number Diff line number Diff line change
@@ -1 +1,99 @@
# Create your models here.
#
# Copyright © Michal Čihař <[email protected]>
#
# This file is part of Weblate <https://weblate.org/>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
from __future__ import annotations

from shutil import copyfile
from typing import TYPE_CHECKING

from django.conf import settings
from django.db import models
from django.template.loader import render_to_string
from django.utils.translation import override

from weblate_web.pdf import render_pdf

if TYPE_CHECKING:
from pathlib import Path

Check warning on line 32 in weblate_web/legal/models.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/legal/models.py#L32

Added line #L32 was not covered by tests


class AgreementKind(models.IntegerChoices):
DPA = 1, "Data Processing Agreement"


class Agreement(models.Model):
customer = models.ForeignKey("payments.Customer", on_delete=models.deletion.PROTECT)
signed = models.DateTimeField(auto_now_add=True)
kind = models.IntegerField(choices=AgreementKind, default=AgreementKind.DPA)

def __str__(self) -> str:
return f"{self.kind.name} {self.customer.name} {self.shortdate}"

Check failure on line 45 in weblate_web/legal/models.py

View workflow job for this annotation

GitHub Actions / mypy

"int" has no attribute "name"

Check failure on line 45 in weblate_web/legal/models.py

View workflow job for this annotation

GitHub Actions / mypy

"int" has no attribute "name"

def save( # type: ignore[override]
self,
*,
force_insert: bool = False,
force_update: bool = False,
using=None,
update_fields=None,
):
super().save(
force_insert=force_insert,
force_update=force_update,
using=using,
update_fields=update_fields,
)
self.generate_files()

@property
def shortdate(self) -> str:
return self.signed.date().isoformat()

@property
def filename(self) -> str:
"""PDF filename."""
return f"Weblate_{self.kind.name}_{self.shortdate}_{self.pk}.pdf"

Check failure on line 70 in weblate_web/legal/models.py

View workflow job for this annotation

GitHub Actions / mypy

"int" has no attribute "name"

Check failure on line 70 in weblate_web/legal/models.py

View workflow job for this annotation

GitHub Actions / mypy

"int" has no attribute "name"

@property
def path(self) -> Path:
"""PDF path object."""
return settings.AGREEMENTS_PATH / self.filename

def generate_files(self) -> None:
self.generate_pdf()
if settings.AGREEMENTS_COPY_PATH:
copyfile(self.path, settings.AGREEMENTS_COPY_PATH / self.filename)

Check warning on line 80 in weblate_web/legal/models.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/legal/models.py#L80

Added line #L80 was not covered by tests

def generate_pdf(self) -> None:
# Create directory to store agreements
settings.AGREEMENTS_PATH.mkdir(exist_ok=True)
render_pdf(
html=self.render_html(),
output=settings.AGREEMENTS_PATH / self.filename,
)

def render_html(self) -> str:
with override("en_GB"):
return render_to_string(
"pdf/dpa.html",
{
"customer": self.customer,
"signed": self.signed,
"title": self.get_kind_display(),
},
)
461 changes: 461 additions & 0 deletions weblate_web/legal/templates/pdf/dpa.html

Large diffs are not rendered by default.

65 changes: 63 additions & 2 deletions weblate_web/legal/templates/pdf/legal.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,25 @@ header {

h1 {
font-size: 14pt;
}
header h1 {
padding-top: 3mm;
margin: 0;
position: absolute;
top: 0;
right: 0;
}
h1.page {
break-before: page;
counter-reset: tos_chapter;
}
img {
max-width: 30%;
}
.preamble p,
.tos h1 {
text-align: center;
}
.tos {
counter-reset: tos_chapter;
}
Expand All @@ -47,7 +57,12 @@ img {
}

.tos h2 {
counter-reset: tos_item;
counter-reset: tos_item tos_subchapter;
}

.tos h3::before {
counter-increment: tos_subchapter;
content: counter(tos_chapter) "." counter(tos_subchapter) ". ";
}

.tos p.item,
Expand All @@ -59,6 +74,10 @@ img {
.tos dl {
padding: 0 0 0 3.5em;
}
.dpa.tos p,
.dpa.tos dl {
padding: 0 0 0 1.5em;
}
.tos p.item {
counter-reset: tos_subitem;
}
Expand All @@ -76,6 +95,10 @@ img {
left: 0;
}

.dpa.tos p.item::before {
content: counter(tos_item) ". ";
}

.tos p.subitem::before {
counter-increment: tos_subitem;
content: counter(tos_chapter) "." counter(tos_item) "." counter(tos_subitem)
Expand All @@ -92,9 +115,20 @@ img {
left: 0;
}

.tos ul,
.tos ol {
padding: 0 0 0 5em;
}
.dpa.tos ul,
.dap.tos ol {
padding: 0 0 0 2.5em;
}

.dpa ul li {
margin-top: 0.5em;
}
.tos ol {
counter-reset: tos_list;
padding: 0 5em;
}
.tos ol > li {
list-style: none;
Expand All @@ -115,3 +149,30 @@ img {
.tos a:hover {
text-decoration: none;
}

table.signatures {
width: 100%;
border-spacing: 5mm;
}
table.signatures td {
width: 50%;
text-align: center;
}
div.placeholder,
table.signatures tr.signatures td {
border-bottom: 0.1mm solid #14213d;
}
table.signatures tr.signatures td {
height: 3cm;
}
div.placeholders {
display: flex;
flex-direction: row;
width: 100%;
}
div.placeholders div {
white-space: pre;
}
div.placeholder {
flex-grow: 8;
}
24 changes: 24 additions & 0 deletions weblate_web/legal/tests.py
Original file line number Diff line number Diff line change
@@ -1 +1,25 @@
# Create your tests here.
from weblate_web.payments.models import Customer
from weblate_web.tests import UserTestCase

from .models import Agreement, AgreementKind


class InvoiceTestCase(UserTestCase):
def create_customer(self, *, vat: str = "") -> Customer:
return Customer.objects.create(
name="Zkušební zákazník",
address="Street 42",
city="City",
postcode="424242",
country="cz",
user_id=-1,
vat=vat,
)

def test_agreement(self):
agreement = Agreement.objects.create(
customer=self.create_customer(), kind=AgreementKind.DPA
)
self.assertTrue(agreement.path.exists())
self.assertIn("DPA", str(agreement))
4 changes: 2 additions & 2 deletions weblate_web/payments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ class Customer(models.Model):
tax = models.CharField(
max_length=200,
blank=True,
verbose_name=gettext_lazy("Tax registration"),
verbose_name=gettext_lazy("Company identification number"),
help_text=gettext_lazy(
"Please fill in your tax registration if it should "
"Please fill in your company registration number if it should "
"appear on the invoice."
),
)
Expand Down
7 changes: 6 additions & 1 deletion weblate_web/pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@

from pathlib import Path

from django.conf import settings
from django.contrib.staticfiles import finders
from weasyprint import CSS, HTML
from weasyprint.text.fonts import FontConfiguration

SIGNATURE_URL = "signature:"
INVOICES_URL = "invoices:"
LEGAL_URL = "legal:"
STATIC_URL = "static:"
Expand All @@ -34,7 +36,10 @@
def url_fetcher(url: str) -> dict[str, str | bytes]:
path_obj: Path
result: dict[str, str | bytes]
if url.startswith(INVOICES_URL):

if url == SIGNATURE_URL:
path_obj = settings.AGREEMENTS_SIGNATURE_PATH

Check failure on line 41 in weblate_web/pdf.py

View workflow job for this annotation

GitHub Actions / mypy

Incompatible types in assignment (expression has type "Path | None", variable has type "Path")

Check failure on line 41 in weblate_web/pdf.py

View workflow job for this annotation

GitHub Actions / mypy

Incompatible types in assignment (expression has type "Path | None", variable has type "Path")
elif url.startswith(INVOICES_URL):
path_obj = INVOICES_TEMPLATES_PATH / url.removeprefix(INVOICES_URL)
elif url.startswith(LEGAL_URL):
path_obj = LEGAL_TEMPLATES_PATH / url.removeprefix(LEGAL_URL)
Expand Down
3 changes: 3 additions & 0 deletions weblate_web/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,9 @@
PAYMENT_FAKTURACE = (Path.home() / "weblate" / "tmp-fakturace").as_posix()
INVOICES_PATH = Path(BASE_DIR) / "invoices"
INVOICES_COPY_PATH: Path | None = None
AGREEMENTS_PATH = Path(BASE_DIR) / "agreements"
AGREEMENTS_COPY_PATH: Path | None = None
AGREEMENTS_SIGNATURE_PATH: Path | None = None

LOGIN_URL = "/saml2/login/"
LOGIN_REDIRECT_URL = "/user/"
Expand Down

0 comments on commit bdc7936

Please sign in to comment.