mirror of
https://framagit.org/la-chariotte/la-chariotte.git
synced 2025-05-05 13:21:49 +02:00
generate pdf list with weasyprint
This commit is contained in:
parent
fb84d11e54
commit
3946b1a6b4
7 changed files with 103 additions and 79 deletions
|
@ -3,57 +3,59 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>
|
<title>
|
||||||
{{ name }} - Liste des commandes
|
{{ grouped_order.name }} - Liste des commandes
|
||||||
</title>
|
</title>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
@page {
|
@page {
|
||||||
{% if items|length > 6 %}
|
{% if items|length > 6 %}
|
||||||
size: letter landscape;
|
size: letter landscape;
|
||||||
{% else %}
|
{% else %}
|
||||||
size: A4;
|
size: A4;
|
||||||
{% endif %}
|
{% endif %}
|
||||||
margin: 2cm 1.5cm;
|
margin: 2cm 1.5cm;
|
||||||
@frame footer {
|
@bottom-right{
|
||||||
-pdf-frame-content: footer;
|
font-size: 10pt;
|
||||||
bottom: 0cm;
|
font-family: sans-serif;
|
||||||
height: 1.5cm;
|
content: "Liste générée par la Chariotte - chariotte.fr | Page " counter(page)"/" counter(pages);
|
||||||
right: 0cm;
|
}
|
||||||
width: 10cm;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
font-size: 11pt;
|
font-size: 11pt;
|
||||||
}
|
|
||||||
|
|
||||||
.bold {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
border: 1px black solid;
|
border: 1px black solid;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
th, td {
|
th, td {
|
||||||
vertical-align: center;
|
vertical-align: center;
|
||||||
padding: 3px 2px 2px 2px;
|
padding: 3px 2px 2px 2px;
|
||||||
line-height: 0.8em;
|
border: 1px black solid;
|
||||||
|
font-weight: normal;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
th {
|
th {
|
||||||
text-align: center;
|
page-break-inside: avoid;
|
||||||
page-break-inside: avoid;
|
word-break: break-all;
|
||||||
word-break: break-all;
|
word-wrap: break-word;
|
||||||
word-wrap: break-word;
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-top: -1em;
|
||||||
|
margin-bottom: 2em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1 style="text-align: center">
|
<h2 style="text-align: center">
|
||||||
{{ name }} - {{ delivery_date }}
|
{{ grouped_order.name }} - {{ grouped_order.delivery_date }}
|
||||||
</h1>
|
</h2>
|
||||||
{% if items %}
|
{% if items %}
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -69,12 +71,13 @@
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr style="background-color: #bababa">
|
<tr style="background-color: #bababa">
|
||||||
<th style="text-align: left">Prix unitaire</th>
|
<td style="text-align: left">Prix unitaire</td>
|
||||||
{% for item in items %}
|
{% for item in items %}
|
||||||
<th>
|
<td>
|
||||||
{{ item.price }} €
|
{{ item.price }} €
|
||||||
</th>
|
</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr style="background-color: #bababa">
|
<tr style="background-color: #bababa">
|
||||||
<th style="text-align: left">TOTAL</th>
|
<th style="text-align: left">TOTAL</th>
|
||||||
|
@ -83,21 +86,21 @@
|
||||||
{{ item.ordered_nb }}
|
{{ item.ordered_nb }}
|
||||||
</th>
|
</th>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<th>{{ total_price }} €</th>
|
<th>{{ grouped_order.total_price }} €</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for order, ordered_items in orders_dict.items %}
|
{% for order, ordered_items in orders_dict.items %}
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<td>
|
||||||
{{ order.author }}
|
{{ order.author }}
|
||||||
</th>
|
</td>
|
||||||
{% for ordered_item in ordered_items %}
|
{% for ordered_item in ordered_items %}
|
||||||
<th>
|
<td>
|
||||||
{{ ordered_item }}
|
{{ ordered_item }}
|
||||||
</th>
|
</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<th>
|
<td>
|
||||||
{{ order.price }} €
|
{{ order.price }} €
|
||||||
</th>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -106,11 +109,5 @@
|
||||||
Aucun produit n'a été commandé
|
Aucun produit n'a été commandé
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div id="footer">
|
|
||||||
Liste générée par la Chariotte - chariotte.fr | Page
|
|
||||||
<pdf:pagenumber />
|
|
||||||
/
|
|
||||||
<pdf:pagecount />
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1132,7 +1132,7 @@ class TestItemCreateView:
|
||||||
|
|
||||||
class TestGroupedOrderSheetView:
|
class TestGroupedOrderSheetView:
|
||||||
def test_get__not_orga(self, client_log, other_user):
|
def test_get__not_orga(self, client_log, other_user):
|
||||||
"""A user that is not organiszer of the GO accesses the sheet view.
|
"""A user that is not organizer of the GO accesses the sheet view.
|
||||||
They get a 403 error"""
|
They get a 403 error"""
|
||||||
grouped_order = create_grouped_order(
|
grouped_order = create_grouped_order(
|
||||||
days_before_delivery_date=5,
|
days_before_delivery_date=5,
|
||||||
|
@ -1149,7 +1149,7 @@ class TestGroupedOrderSheetView:
|
||||||
response = client_log.get(generate_sheet_url)
|
response = client_log.get(generate_sheet_url)
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
|
||||||
def test_get__not_orga(self, client, other_user):
|
def test_get__anonymous(self, client, other_user):
|
||||||
"""An anonymous user accesses the sheet view.
|
"""An anonymous user accesses the sheet view.
|
||||||
They get redirected to login page"""
|
They get redirected to login page"""
|
||||||
grouped_order = create_grouped_order(
|
grouped_order = create_grouped_order(
|
||||||
|
@ -1184,14 +1184,15 @@ class TestGroupedOrderSheetView:
|
||||||
)
|
)
|
||||||
response = client_log.get(generate_sheet_url)
|
response = client_log.get(generate_sheet_url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.context["name"] == "gr order test"
|
assert response.context["grouped_order"] == grouped_order
|
||||||
assert response.context["delivery_date"] == grouped_order.delivery_date
|
|
||||||
assert response.context["items"].count() == 0
|
assert response.context["items"].count() == 0
|
||||||
|
assert len(response.context["orders_dict"]) == 0
|
||||||
|
|
||||||
|
# we order some items in the grouped order
|
||||||
order = order_items_in_grouped_order(grouped_order)
|
order = order_items_in_grouped_order(grouped_order)
|
||||||
response = client_log.get(generate_sheet_url)
|
response = client_log.get(generate_sheet_url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.context["name"] == "gr order test"
|
assert response.context["grouped_order"] == grouped_order
|
||||||
assert response.context["delivery_date"] == grouped_order.delivery_date
|
|
||||||
assert response.context["items"].count() == 2
|
assert response.context["items"].count() == 2
|
||||||
assert response.context["orders_dict"][order] == [3, 2]
|
assert response.context["orders_dict"][order] == [3, 2]
|
||||||
assert response.context["total_price"] == 24
|
assert response.context["grouped_order"].total_price == 24
|
||||||
|
|
|
@ -37,7 +37,7 @@ urlpatterns = [
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"<int:pk>/gerer/imprimer",
|
"<int:pk>/gerer/imprimer",
|
||||||
views.GroupedOrderSheetView.as_view(),
|
views.DownloadGroupedOrderSheetView.as_view(),
|
||||||
name="grouped_order_sheet",
|
name="grouped_order_sheet",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -8,6 +8,8 @@ from django.template.loader import get_template
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
|
from django_weasyprint import WeasyTemplateResponseMixin
|
||||||
|
from django_weasyprint.views import WeasyTemplateResponse, WeasyTemplateView
|
||||||
from xhtml2pdf import pisa
|
from xhtml2pdf import pisa
|
||||||
|
|
||||||
from .forms import GroupedOrderForm, ItemCreateForm
|
from .forms import GroupedOrderForm, ItemCreateForm
|
||||||
|
@ -266,11 +268,21 @@ class OrderDetailView(generic.DetailView):
|
||||||
model = Order
|
model = Order
|
||||||
|
|
||||||
|
|
||||||
class GroupedOrderSheetMixin:
|
class GroupedOrderSheetView(UserPassesTestMixin, generic.DetailView):
|
||||||
"""Mixin for grouped order info export (pdf sheet and csv)"""
|
"""View for gathering information about the groupedorder, in order to download it
|
||||||
|
in the DownloadGroupedOrderSheetView"""
|
||||||
|
|
||||||
def get_grouped_order_infos(self, grouped_order_id):
|
model = GroupedOrder
|
||||||
grouped_order = get_object_or_404(GroupedOrder, id=grouped_order_id)
|
template_name = "order/grouped_order_sheet.html"
|
||||||
|
context_object_name = "grouped_order"
|
||||||
|
|
||||||
|
def test_func(self):
|
||||||
|
"""Accessible only if the requesting user is the grouped order organizer"""
|
||||||
|
return self.get_object().orga == self.request.user
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(GroupedOrderSheetView, self).get_context_data(**kwargs)
|
||||||
|
grouped_order = self.get_object()
|
||||||
|
|
||||||
# Get ordered items
|
# Get ordered items
|
||||||
items = grouped_order.item_set.filter(ordered_nb__gt=0).order_by("name")
|
items = grouped_order.item_set.filter(ordered_nb__gt=0).order_by("name")
|
||||||
|
@ -287,27 +299,16 @@ class GroupedOrderSheetMixin:
|
||||||
ordered_values.append(value)
|
ordered_values.append(value)
|
||||||
orders_dict[order] = ordered_values # order as key of the dict
|
orders_dict[order] = ordered_values # order as key of the dict
|
||||||
|
|
||||||
return {
|
context["items"] = items
|
||||||
"name": grouped_order.name,
|
context["orders_dict"] = orders_dict
|
||||||
"delivery_date": grouped_order.delivery_date,
|
|
||||||
"items": items,
|
return context
|
||||||
"orders_dict": orders_dict,
|
|
||||||
"total_price": grouped_order.total_price,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class GroupedOrderSheetView(GroupedOrderOverview, GroupedOrderSheetMixin):
|
class DownloadGroupedOrderSheetView(WeasyTemplateResponseMixin, GroupedOrderSheetView):
|
||||||
"""View to get a pdf sheet summing up the grouped order for delivery"""
|
"""View for downloading the grouped order sheet,
|
||||||
|
using django-weasyprint : https://pypi.org/project/django-weasyprint/"""
|
||||||
|
|
||||||
template_name = "order/grouped_order_sheet.html"
|
def get_pdf_filename(self):
|
||||||
|
# filename for download
|
||||||
def get(self, request, pk):
|
return f"{self.get_object().delivery_date} - {self.get_object().name}"
|
||||||
grouped_order_sheet_info = self.get_grouped_order_infos(pk)
|
|
||||||
# should_include_prices = True if request.GET.get("with_prices", False) else False
|
|
||||||
# delivery_sheet_info["include_prices"] = should_include_prices - à ajouter plus tard
|
|
||||||
template = get_template(self.template_name)
|
|
||||||
html = template.render(grouped_order_sheet_info)
|
|
||||||
|
|
||||||
result = BytesIO()
|
|
||||||
pdf = pisa.pisaDocument(BytesIO(html.encode("UTF-8")), result)
|
|
||||||
return http.HttpResponse(result.getvalue(), content_type="application/pdf")
|
|
||||||
|
|
|
@ -89,7 +89,7 @@ WSGI_APPLICATION = "la_chariotte.wsgi.application"
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "django.db.backends.postgresql",
|
"ENGINE": "django.db.backends.postgresql",
|
||||||
"NAME": os.getenv("DB_NAME", "chariotte-db"),
|
"NAME": os.getenv("DB_NAME", "chariotte"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10400,3 +10400,26 @@ a.navbar-item:hover {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@media print {
|
||||||
|
body {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
a::after {
|
||||||
|
content: " (" attr(href) ") ";
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
@page {
|
||||||
|
margin: 0.75in;
|
||||||
|
size: Letter;
|
||||||
|
@top-right {
|
||||||
|
content: counter(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@page :first {
|
||||||
|
@top-right {
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -100,5 +100,7 @@ webencodings==0.5.1
|
||||||
# cssselect2
|
# cssselect2
|
||||||
# html5lib
|
# html5lib
|
||||||
# tinycss2
|
# tinycss2
|
||||||
|
weasyprint==58.0 #django-weasyprint doesn't work with weasyprint 59.0
|
||||||
|
django-weasyprint==2.2.0
|
||||||
xhtml2pdf==0.2.11
|
xhtml2pdf==0.2.11
|
||||||
# via la-chariotte (pyproject.toml)
|
# via la-chariotte (pyproject.toml)
|
||||||
|
|
Loading…
Reference in a new issue