html to pdf for ticket

This commit is contained in:
Jonas 12t 2021-10-31 13:56:57 +04:00
parent 41cd94e072
commit 8c768cfd5a
15 changed files with 1040 additions and 56 deletions

View File

@ -0,0 +1,149 @@
@font-face {
font-family: Libre Barcode;
src: url(librebarcode128-regular.ttf);
}
@font-face {
font-family: Barlow Condensed;
src: url(barlowcondensed-regular.otf);
}
@font-face {
font-family: Barlow Condensed;
font-weight: 300;
src: url(barlowcondensed-light.otf);
}
@font-face {
font-family: Barlow Condensed;
font-weight: 700;
src: url(barlowcondensed-bold.otf);
}
@page {
margin: 0;
size: landscape;
}
html {
align-content: center;
align-items: center;
background: #eef1f5;
display: flex;
font-family: Barlow Condensed, sans-serif;
height: 100%;
justify-content: center;
}
body {
background: #fff;
box-sizing: border-box;
color: #2A3239;
display: flex;
flex-wrap: wrap;
height: 8cm;
justify-content: space-between;
margin: 0;
width: 25cm;
}
section {
box-sizing: border-box;
}
dl {
columns: 4;
text-align: center;
}
dt {
font-size: 9pt;
font-weight: 700;
text-transform: uppercase;
}
dd {
margin-left: 0;
}
ul {
align-items: center;
display: flex;
list-style: none;
margin: 0;
padding-left: 0;
}
li {
font-weight: 700;
text-transform: uppercase;
}
#informations {
flex: 1;
padding: 0;
position: relative;
}
#informations h1 {
display: inline-block;
font-size: 25pt;
font-weight: 300;
text-transform: uppercase;
}
#informations #name {
margin-left: 1cm;
}
#informations #destination {
position: absolute;
right: 1cm;
}
#informations dl {
background: #2A3239;
color: #fff;
margin: 0;
padding: 1cm 0;
}
#informations dd {
border-left: 1pt solid #fff;
font-size: 35pt;
}
#informations dd:first-of-type {
border-left: 0;
}
#informations ul {
margin-left: 1cm;
}
#informations li {
font-weight: 300;
padding: 0.15cm;
}
#informations li:first-of-type {
background: #2A3239;
border-radius: 4pt;
color: #fff;
}
#informations li:last-of-type {
font-family: Libre Barcode, cursive;
font-size: 25pt;
margin-left: auto;
padding-right: 1cm;
padding-top: 0.5cm;
}
#ticket {
border-left: 1pt dashed #2A3239;
display: flex;
flex-direction: column;
height: 8cm;
justify-content: space-around;
padding: 0 1cm;
}
#ticket h2 {
font-weight: 300;
margin: 0;
text-transform: uppercase;
}
#ticket p {
font-family: Libre Barcode, cursive;
font-size: 25pt;
margin: 0;
text-align: center;
}
#ticket dl {
margin: 0;
}
#ticket li {
margin: 0 0.25cm;
}

View File

@ -0,0 +1,611 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="fr">
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
<!-- [ if !mso]> <!-->
<meta content="IE=edge" http-equiv="X-UA-Compatible"/>
<!-- <![endif] -->
<meta content="telephone=no" name="format-detection"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Vos billets</title>
<style type="text/css">
.ExternalClass {
width: 100%;
}
.ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div, .ExternalClass b, .ExternalClass br, .ExternalClass img {
line-height: 100% !important;
}
/* iOS BLUE LINKS */
.appleBody a {
color: #212121;
text-decoration: none;
}
.appleFooter a {
color: #212121 !important;
text-decoration: none !important;
}
/* END iOS BLUE LINKS */
img {
color: #ffffff;
text-align: center;
font-family: Open Sans, Helvetica, Arial, sans-serif;
display: block;
}
body {
margin: 0;
padding: 0;
-webkit-text-size-adjust: 100% !important;
-ms-text-size-adjust: 100% !important;
font-family: 'Open Sans', Helvetica, Arial, sans-serif !important;
}
body, #body_style {
background: #fffffe;
}
table td {
border-collapse: collapse;
border-spacing: 0 !important;
}
table tr {
border-collapse: collapse;
border-spacing: 0 !important;
}
table tbody {
border-collapse: collapse;
border-spacing: 0 !important;
}
table {
border-collapse: collapse;
border-spacing: 0 !important;
}
span.yshortcuts, a span.yshortcuts {
color: #000001;
background-color: none;
border: none;
}
span.yshortcuts:hover,
span.yshortcuts:active,
span.yshortcuts:focus {
color: #000001;
background-color: none;
border: none;
}
img {
-ms-interpolation-mode:: bicubic;
}
a[x-apple-data-detectors] {
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
/**** My desktop styles ****/
@media only screen and (min-width: 600px) {
.noDesk {
display: none !important;
}
.td-padding {
padding-left: 15px !important;
padding-right: 15px !important;
}
.padding-container {
padding: 0px 15px 0px 15px !important;
mso-padding-alt: 0px 15px 0px 15px !important;
}
.mobile-column-left-padding {
padding: 0px 0px 0px 0px !important;
mso-alt-padding: 0px 0px 0px 0px !important;
}
.mobile-column-right-padding {
padding: 0px 0px 0px 0px !important;
mso-alt-padding: 0px 0px 0px 0px !important;
}
.mobile {
display: none !important
}
}
/**** My mobile styles ****/
@media only screen and (max-width: 599px) and (-webkit-min-device-pixel-ratio: 1) {
*[class].wrapper {
width: 100% !important;
}
*[class].container {
width: 100% !important;
}
*[class].mobile {
width: 100% !important;
display: block !important;
}
*[class].image {
width: 100% !important;
height: auto;
}
*[class].center {
margin: 0 auto !important;
text-align: center !important;
}
*[class="mobileOff"] {
width: 0px !important;
display: none !important;
}
*[class*="mobileOn"] {
display: block !important;
max-height: none !important;
}
p[class="mobile-padding"] {
padding-left: 0px !important;
padding-top: 10px;
}
.padding-container {
padding: 0px 15px 0px 15px !important;
mso-padding-alt: 0px 15px 0px 15px !important;
}
.hund {
width: 100% !important;
height: auto !important;
}
.td-padding {
padding-left: 15px !important;
padding-right: 15px !important;
}
.mobile-column-left-padding {
padding: 18px 0px 18px 0px !important;
mso-alt-padding: 18px 0px 18px 0px !important;
}
.mobile-column-right-padding {
padding: 18px 0px 0px 0px !important;
mso-alt-padding: 18px 0px 0px 0px !important;
}
.stack {
width: 100% !important;
}
img {
width: 100% !important;
height: auto !important;
}
*[class="hide"] {
display: none !important
}
*[class="Gmail"] {
display: none !important
}
.Gmail {
display: none !important
}
.bottom-padding-fix {
padding: 0px 0px 18px 0px !important;
mso-alt-padding: 0px 0px 18px 0px;
}
}
.social, .social:active {
opacity: 1 !important;
transform: scale(1);
transition: all .2s !important;
}
.social:hover {
opacity: 0.8 !important;
transform: scale(1.1);
transition: all .2s !important;
}
.button.raised {
transition: box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1);
transition: all .2s;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);
}
.button.raised:hover {
box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2);
transition: all .2s;
-webkit-box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2);
transition: all .2s;
-moz-box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2);
transition: all .2s;
}
.card-1 {
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .16), 0 2px 10px 0 rgba(0, 0, 0, .12);
-webkit-box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .16), 0 2px 10px 0 rgba(0, 0, 0, .12);
-moz-box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .16), 0 2px 10px 0 rgba(0, 0, 0, .12);
transition: box-shadow .45s;
}
.card-1:hover {
box-shadow: 0 8px 17px 0 rgba(0, 0, 0, .2), 0 6px 20px 0 rgba(0, 0, 0, .19);
-webkit-box-shadow: 0 8px 17px 0 rgba(0, 0, 0, .2), 0 6px 20px 0 rgba(0, 0, 0, .19);
-moz-box-shadow: 0 8px 17px 0 rgba(0, 0, 0, .2), 0 6px 20px 0 rgba(0, 0, 0, .19);
transition: box-shadow .45s;
}
.ripplelink {
display: block
color: #fff;
text-decoration: none;
position: relative;
overflow: hidden;
-webkit-transition: all 0.2s ease;
-moz-transition: all 0.2s ease;
-o-transition: all 0.2s ease;
transition: all 0.2s ease;
z-index: 0;
}
.ripplelink:hover {
z-index: 1000;
}
.ink {
display: block;
position: absolute;
background: rgba(255, 255, 255, 0.3);
border-radius: 100%;
-webkit-transform: scale(0);
-moz-transform: scale(0);
-o-transform: scale(0);
transform: scale(0);
}
.animate {
-webkit-animation: ripple 0.65s linear;
-moz-animation: ripple 0.65s linear;
-ms-animation: ripple 0.65s linear;
-o-animation: ripple 0.65s linear;
animation: ripple 0.65s linear;
}
@-webkit-keyframes ripple {
100% {
opacity: 0;
-webkit-transform: scale(2.5);
}
}
@-moz-keyframes ripple {
100% {
opacity: 0;
-moz-transform: scale(2.5);
}
}
@-o-keyframes ripple {
100% {
opacity: 0;
-o-transform: scale(2.5);
}
}
@keyframes ripple {
100% {
opacity: 0;
transform: scale(2.5);
}
}
</style>
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
</head>
<body style="margin:0; padding:0; background-color: #eeeeee;" bgcolor="#eeeeee">
<!--[if mso]>
<style type="text/css">
body, table, td {
font-family: Arial, Helvetica, sans-serif !important;
}
</style>
<![endif]-->
<!-- START EMAIL -->
<table width="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#eeeeee">
<div class="Gmail"
style="height: 1px !important; margin-top: -1px !important; max-width: 600px !important; min-width: 600px !important; width: 600px !important;"></div>
<div style="display: none; max-height: 0px; overflow: hidden;">
Vos billets pour {{ config.organisation }}
</div>
<!-- Insert &zwnj;&nbsp; hack after hidden preview text -->
<div style="display: none; max-height: 0px; overflow: hidden;">
&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
</div>
<!-- START LOGO -->
<tr>
<td width="100%" valign="top" align="center" class="padding-container"
style="padding: 18px 0px 18px 0px!important; mso-padding-alt: 18px 0px 18px 0px;">
<table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="wrapper">
<tr>
<td align="center">
<table cellpadding="0" cellspacing="0" border="0">
<tr>
<td width="100%" valign="top" align="center">
<table width="600" cellpadding="0" cellspacing="0" border="0" align="center"
class="wrapper" bgcolor="#eeeeee">
<tr>
<td align="center">
<table width="600" cellpadding="0" cellspacing="0" border="0"
class="container" align="center">
<!-- START HEADER IMAGE -->
<tr>
<td align="center" class="hund" width="600">
<a href="www.tibillet.re">
<img src="https://wiki.tibillet.re/images/c/ca/Logo_Tibillet_Noir_Ombre.png"
width="200" alt="Logo" border="0"
style="max-width: 200px; display:block; ">
</a>
</td>
</tr>
<!-- END HEADER IMAGE -->
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- END LOGO -->
<!-- START CARD 1 -->
<tr>
<td width="100%" valign="top" align="center" class="padding-container"
style="padding-top: 0px!important; padding-bottom: 18px!important; mso-padding-alt: 0px 0px 18px 0px;">
<table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="wrapper">
<tr>
<td>
<table cellpadding="0" cellspacing="0" border="0">
<tr>
<td style="border-radius: 3px; border-bottom: 2px solid #d4d4d4;" class="card-1"
width="100%" valign="top" align="center">
<table style="border-radius: 3px;" width="600" cellpadding="0" cellspacing="0"
border="0" align="center" class="wrapper" bgcolor="#ffffff">
<tr>
<td align="center">
<table width="600" cellpadding="0" cellspacing="0" border="0"
class="container">
<!-- START HEADER IMAGE -->
<tr>
<td align="center" class="hund ripplelink" width="600">
<img align="center" width="600"
style="border-radius: 3px 3px 0px 0px; width: 100%; max-width: 600px!important"
class="hund"
src="{{ config.img.med }}">
</td>
</tr>
<!-- END HEADER IMAGE -->
<!-- START BODY COPY -->
<tr>
<td class="td-padding" align="left"
style="font-family: 'Roboto Mono', monospace; color: #212121!important; font-size: 24px; line-height: 30px; padding-top: 18px; padding-left: 18px!important; padding-right: 18px!important; padding-bottom: 0px!important; mso-line-height-rule: exactly; mso-padding-alt: 18px 18px 0px 13px;">
{{ config.organisation }}
</td>
</tr>
<tr>
<td class="td-padding" align="left"
style="font-family: 'Roboto Mono', monospace; color: #212121!important; font-size: 16px; line-height: 24px; padding-top: 18px; padding-left: 18px!important; padding-right: 18px!important; padding-bottom: 0px!important; mso-line-height-rule: exactly; mso-padding-alt: 18px 18px 0px 18px;">
Bonjour !
<br><br>
Nous vous confirmons votre achat
d'un total de {{ reservation.total_paid | floatformat:2 }}€.
<strong>Grand merci pour votre reservation !</strong>
<br><br>
{% if reservation.tickets %}
Vous trouverez vos tickets en pièce jointe.
{% else %}
Aucun ticket pour un concert ? Etrange etrange
{% endif %}
</td>
</tr>
<!-- END BODY COPY -->
<!-- BUTTON -->
<tr>
<td align="left"
style="padding: 18px 18px 18px 18px; mso-alt-padding: 18px 18px 18px 18px!important;">
<table width="100%" border="0" cellspacing="0"
cellpadding="0">
<tr>
<td>
<table border="0" cellspacing="0"
cellpadding="0">
<tr>
<td align="left"
style="border-radius: 3px;"
bgcolor="#17bef7">
<a class="button raised"
href="{{ config.domaine_cashless }}/rapport/invoice/{{ adhesion.commande }}"
target="_blank"
style="font-size: 14px; line-height: 14px; font-weight: 500; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; border-radius: 3px; padding: 10px 25px; border: 1px solid #17bef7; display: inline-block;">
TELECHARGER REÇU</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- END BUTTON -->
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- END CARD 1 -->
<!-- START CARD 2 -->
<tr>
<td width="100%" valign="top" align="center" class="padding-container"
style="padding-top: 0px!important; padding-bottom: 18px!important; mso-padding-alt: 0px 0px 18px 0px;">
<table width="600" cellpadding="0" cellspacing="0" border="0" align="center" class="wrapper">
<tr>
<td>
<table cellpadding="0" cellspacing="0" border="0">
<tr>
<td style="border-radius: 3px; border-bottom: 2px solid #d4d4d4;" class="card-1"
width="100%" valign="top" align="center">
<table style="border-radius: 3px;" width="600" cellpadding="0" cellspacing="0"
border="0" align="center" class="wrapper" bgcolor="#ffffff">
<tr>
<td align="center">
<table width="600" cellpadding="0" cellspacing="0" border="0"
class="container">
<!-- START HEADER IMAGE -->
<tr>
<td align="center" class="hund ripplelink" width="600">
<img align="center" width="600"
style="border-radius: 3px 3px 0px 0px; width: 100%; max-width: 600px!important"
class="hund"
src="https://wiki.tibillet.re/images/0/0d/Logo_Tibillet_Noir_Ombre_700px.png">
</td>
</tr>
<!-- END HEADER IMAGE -->
<!-- START BODY COPY -->
<tr>
<td class="td-padding" align="left"
style="font-family: 'Roboto Mono', monospace; color: #212121!important; font-size: 24px; line-height: 30px; padding-top: 18px; padding-left: 18px!important; padding-right: 18px!important; padding-bottom: 0px!important; mso-line-height-rule: exactly; mso-padding-alt: 18px 18px 0px 13px;">
Vous entrez maintenant dans le réseau TiBillet !
</td>
</tr>
<tr>
<td class="td-padding" align="left"
style="font-family: 'Roboto Mono', monospace; color: #212121!important; font-size: 16px; line-height: 24px; padding-top: 18px; padding-left: 18px!important; padding-right: 18px!important; padding-bottom: 0px!important; mso-line-height-rule: exactly; mso-padding-alt: 18px 18px 0px 18px;">
<a href="https://www.tibillet.re">TiBillet</a> est une
solution coopérative et open-source de gestion associative,
de cashless et de billetterie orientée économie
sociale et solidaire.
<br><br>
Vous pouvez acheter des billets dans tout le reseau avec
votre
carte cashless, recharger cette dernière en ligne ou sur
place,
et en profiter dans tout les lieux partenaires !
<br><br>
Vous êtes artiste ? organisateur ? Vous pouvez créer votre
espace
TiBillet en quelques clics et démarcher dans tout le reseau
coopératif !
<br><br>
Si vous voulez en savoir plus, n'hésitez pas à visiter notre
<a href="https://wiki.tibillet.re">wiki</a> !
<br><br>
</td>
</tr>
<!-- END BODY COPY -->
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- END CARD 2 -->
<!-- SPACER -->
<!--[if gte mso 9]>
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
<tr>
<td align="center" valign="top" width="600" height="18">
<![endif]-->
<tr>
<td height="18px"></td>
</tr>
<!--[if gte mso 9]>
</td>
</tr>
</table>
<![endif]-->
<!-- END SPACER -->
<!-- SPACER -->
<!--[if gte mso 9]>
<table align="center" border="0" cellspacing="0" cellpadding="0" width="600">
<tr>
<td align="center" valign="top" width="600" height="36">
<![endif]-->
<tr>
<td height="36px"></td>
</tr>
<!--[if gte mso 9]>
</td>
</tr>
</table>
<![endif]-->
<!-- END SPACER -->
</table>
<!-- END EMAIL -->
<div style="display:none; white-space:nowrap; font:15px courier; line-height:0;">
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
</div>
</body>
</html>

View File

@ -0,0 +1,53 @@
<html>
<head>
{% load static %}
<meta charset="utf-8">
<link rel="stylesheet" href="{% static 'ticket/ticket.css' %}"/>
<title>Boarding ticket</title>
<meta name="description" content="Boarding ticket">
</head>
<body>
<section id="informations">
<h1 id="name">Théodore Marcelin</h1>
<h1 id="destination">CDG ✈ LFLL</h1>
<dl>
<dt>Flight</dt>
<dd>DL31</dd>
<dt>Gate</dt>
<dd>29</dd>
<dt>Seat</dt>
<dd>26E</dd>
<dt>Zone</dt>
<dd>4</dd>
</dl>
<ul>
<li>5:10pm</li>
<li>Dec 15, 2018</li>
<li>Coach</li>
<li>1257797706706</li>
</ul>
</section>
<section id="ticket">
<p>1257797706706</p>
<h2>Théodore Marcelin</h2>
<dl>
<dt>Flight</dt>
<dd>DL31</dd>
<dt>Gate</dt>
<dd>29</dd>
<dt>Seat</dt>
<dd>26E</dd>
<dt>Zone</dt>
<dd>4</dd>
</dl>
<ul>
<li>CDG ✈ LFLL</li>
<li>5:10pm</li>
</ul>
</section>
</body>
</html>

Binary file not shown.

View File

@ -0,0 +1,98 @@
import os
import threading
from django.core.mail import send_mail, EmailMessage, EmailMultiAlternatives
from django.template.loader import render_to_string
from django.utils import timezone
from weasyprint import HTML
from BaseBillet.models import Configuration, Reservation, Ticket
import logging
logger = logging.getLogger(__name__)
'''
from ApiBillet.thread_mailer import ThreadMaileur
config = Configuration.get_solo()
context = {'config': config, }
mail = ThreadMaileur('jturbeaux@pm.me', "Vos Billets", template='mails/ticket.html', context=context)
mail.send_with_tread()
'''
class ThreadMaileur():
def __init__(self, email, title, text=None, html=None, template=None, context=None):
self.title = title
self.email = email
self.text = text
self.html = html
self.config = Configuration.get_solo()
self.context = None
if template and context :
self.html = render_to_string(template, context=context)
self.context = context
self.attached_file = self._attached_file()
def _attached_file(self):
attached_file = []
if self.context :
if self.context.get('reservation'):
reservation: Reservation = self.context.get('reservation')
tickets = reservation.tickets.filter(status=Ticket.NOT_SCANNED)
if len(tickets) > 0:
for ticket in tickets :
attached_file.append(render_to_string('ticket/ticket.html', context={'context': 'context'}))
return attached_file
def config_valid(self):
EMAIL_HOST = os.environ.get('EMAIL_HOST')
EMAIL_PORT = os.environ.get('EMAIL_PORT')
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD')
if EMAIL_HOST and EMAIL_PORT and EMAIL_HOST_USER and EMAIL_HOST_PASSWORD and self.config.email :
return True
else:
return False
def send(self):
if self.html and self.config_valid() :
logger.info(f' send_mail')
mail = EmailMultiAlternatives(
self.title,
self.text,
self.config.email,
[self.email,],
)
mail.attach_alternative(self.html, "text/html")
# msg = EmailMessage(subject, html_content, from_email, [to])
# msg.content_subtype = "html" # Main content is now text/html
# msg.send()
# import ipdb; ipdb.set_trace()
i=1
for file in self.attached_file:
html_before_pdf = HTML(string=file)
mail.attach(f'ticket_{i}.pdf', html_before_pdf.write_pdf(), 'application/pdf')
i += 1
mail_return = mail.send(fail_silently=False)
if mail_return == 1 :
logger.info(f' mail envoyé : {mail_return} - {self.email}')
else :
logger.error(f' mail non envoyé : {mail_return} - {self.email}')
return mail
else :
logger.error(f'Pas de contenu HTML ou de configuration email valide')
raise ValueError('Pas de contenu HTML ou de configuration email valide')
def send_with_tread(self):
self.send()
# logger.info(f'{timezone.now()} on lance le thread email {self.email}')
# thread_email = threading.Thread(target=self.send)
# thread_email.start()
# logger.info(f'{timezone.now()} Thread email lancé')

View File

@ -4,6 +4,8 @@ from django.urls import include, path, re_path
from ApiBillet import views as api_view
from rest_framework import routers
from ApiBillet.views import TicketPdf
router = routers.DefaultRouter()
router.register(r'events', api_view.EventsViewSet, basename='event')
router.register(r'products', api_view.ProductViewSet, basename='product')
@ -13,4 +15,6 @@ router.register(r'reservations', api_view.ReservationViewset, basename='reservat
urlpatterns = [
path('', include(router.urls)),
path('ticket/<uuid:pk_uuid>', TicketPdf.as_view()),
]

View File

@ -1,17 +1,25 @@
from datetime import datetime
from django.http import Http404
from django.shortcuts import render
# Create your views here.
from django.utils import timezone
from django_weasyprint import WeasyTemplateView
from rest_framework.generics import get_object_or_404
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from ApiBillet.serializers import EventSerializer, PriceSerializer, ProductSerializer, ReservationSerializer, \
ReservationValidator
from AuthBillet.models import TenantAdminPermission
from Customers.models import Client, Domain
from BaseBillet.models import Event, Price, Product, Reservation
from BaseBillet.models import Event, Price, Product, Reservation, Configuration, Ticket
from rest_framework import viewsets, permissions, status
import os
import logging
logger = logging.getLogger(__name__)
def new_tenants(schema_name):
@ -130,7 +138,28 @@ class ReservationViewset(viewsets.ViewSet):
return Response(validator.data, status=status.HTTP_201_CREATED)
return Response(validator.errors, status=status.HTTP_400_BAD_REQUEST)
def get_permissions(self):
permission_classes = [TenantAdminPermission]
return [permission() for permission in permission_classes]
class TicketPdf(WeasyTemplateView):
permission_classes = [AllowAny]
template_name = 'ticket/ticket.html'
def get_context_data(self, pk_uuid, **kwargs):
logger.info(f"{timezone.now()} création de pdf demandé. uuid : {pk_uuid}")
self.config = Configuration.get_solo()
ticket: Ticket = get_object_or_404(Ticket, uuid=pk_uuid)
kwargs['ticket'] = ticket
kwargs['config'] = self.config
self.nom_prenom = f"{ticket.first_name.upper()}_{ticket.last_name.capitalize()}"
return kwargs
def get_pdf_filename(self, **kwargs):
nom_prenom = self.nom_prenom
return f"Ticket_{nom_prenom}.pdf"
#

View File

@ -3,6 +3,7 @@ import uuid
import requests
from django.contrib.auth import get_user_model
from django.db import models
from django.db.models.aggregates import Sum
# Create your models here.
from django.db.models import Q
@ -314,8 +315,6 @@ class Event(models.Model):
verbose_name_plural = _('Evenements')
class Reservation(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True, db_index=True)
datetime = models.DateTimeField(auto_now=True)
@ -337,6 +336,7 @@ class Reservation(models.Model):
status = models.CharField(max_length=3, choices=TYPE_CHOICES, default=UNPAID,
verbose_name=_("Status de la réservation"))
mail_send = models.BooleanField(default=False)
# paiement = models.OneToOneField(Paiement_stripe, on_delete=models.PROTECT, blank=True, null=True,
# related_name='reservation')
@ -348,10 +348,30 @@ class Reservation(models.Model):
def user_mail(self):
return self.user_commande.email
def paiements_paid(self):
return self.paiements.filter(
Q(status=Paiement_stripe.PAID) | Q(status=Paiement_stripe.VALID)
)
def articles_paid(self):
articles_paid = []
for paiement in self.paiements_paid():
for ligne in paiement.lignearticle_set.filter(
Q(status=LigneArticle.PAID) | Q(status=LigneArticle.VALID)
):
articles_paid.append(ligne)
return articles_paid
def total_paid(self):
total_paid = 0
for article in self.articles_paid():
article: LigneArticle
total_paid += article.price.prix * article.qty
return total_paid
def __str__(self):
return f"{str(self.uuid).partition('-')[0]} - {self.user_commande.email}"
# def total_billet(self):
# total = 0
# for ligne in self.paiements.all():
@ -372,8 +392,6 @@ class Reservation(models.Model):
#
class Ticket(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True, db_index=True)
@ -410,7 +428,6 @@ class Ticket(models.Model):
ordering = ('-datetime',)
class Paiement_stripe(models.Model):
"""
La commande
@ -437,7 +454,8 @@ class Paiement_stripe(models.Model):
(CANCELED, 'Annulée'),
)
reservation = models.ForeignKey(Reservation, on_delete=models.PROTECT, blank=True, null=True)
reservation = models.ForeignKey(Reservation, on_delete=models.PROTECT, blank=True, null=True,
related_name="paiements")
status = models.CharField(max_length=1, choices=STATUT_CHOICES, default=NON, verbose_name="Statut de la commande")
@ -446,7 +464,8 @@ class Paiement_stripe(models.Model):
(QRCODE, _('Depuis scan QR-Code')),
(API_BILLETTERIE, _('Depuis billetterie')),
)
source = models.CharField(max_length=1, choices=SOURCE_CHOICES, default=API_BILLETTERIE, verbose_name="Source de la commande")
source = models.CharField(max_length=1, choices=SOURCE_CHOICES, default=API_BILLETTERIE,
verbose_name="Source de la commande")
total = models.FloatField(default=0)
@ -461,8 +480,6 @@ class Paiement_stripe(models.Model):
[f"{ligne.product.name} {ligne.qty * ligne.product.prix}" for ligne in self.lignearticle_set.all()])
class LigneArticle(models.Model):
uuid = models.UUIDField(primary_key=True, db_index=True, default=uuid.uuid4)
datetime = models.DateTimeField(auto_now=True)
@ -494,4 +511,3 @@ class LigneArticle(models.Model):
return self.paiement_stripe.status
else:
return _('no stripe send')

View File

@ -1,7 +1,11 @@
import requests
from django.db import connection
from django.db.models import Q
from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver
from django.utils import timezone
from ApiBillet.thread_mailer import ThreadMaileur
from BaseBillet.models import Reservation, LigneArticle, Ticket, Product, Configuration, Paiement_stripe
import logging
@ -20,21 +24,6 @@ def trigger_reservation(sender, instance: Reservation, created, **kwargs):
ticket.save()
@receiver(pre_save, sender=LigneArticle)
def trigger_LigneArticle(sender, instance: LigneArticle, update_fields=None, **kwargs):
# if not created
if not instance._state.adding:
old_instance = sender.objects.get(pk=instance.pk)
new_instance = pre_save_signal_status(old_instance, instance)
@receiver(pre_save, sender=Paiement_stripe)
def trigger_paiement_stripe(sender, instance: Paiement_stripe, update_fields=None, **kwargs):
# if not create
if not instance._state.adding:
old_instance = sender.objects.get(pk=instance.pk)
new_instance = pre_save_signal_status(old_instance, instance)
########################################################################
######################## SIGNAL PRE & POST SAVE ########################
@ -136,6 +125,8 @@ def expire_paiement_stripe(old_instance, new_instance):
def valide_stripe_paiement(old_instance, new_instance):
logger.info(f" TRIGGER PAIEMENT STRIPE valide_stripe_paiement {old_instance.status} to {new_instance.status}")
pass
@ -143,8 +134,28 @@ def valide_stripe_paiement(old_instance, new_instance):
def send_billet_to_mail(old_instance, new_instance):
logger.info(f" TRIGGER RESERVATION send_billet_to_mail {old_instance.status} to {new_instance.status}")
pass
if not new_instance.mail_send :
logger.info(f" TRIGGER RESERVATION send_billet_to_mail {old_instance.status} to {new_instance.status}")
new_instance : Reservation
config = Configuration.get_solo()
if new_instance.user_commande.email:
try:
mail = ThreadMaileur(
new_instance.user_commande.email,
f"Votre reservation pour {config.organisation}",
template='mails/buy_confirmation.html',
context={
'config': config,
'reservation': new_instance,
},
)
# import ipdb; ipdb.set_trace()
mail.send_with_tread()
except Exception as e :
logger.error(f"{timezone.now()} Erreur envoie de mail pour reservation {new_instance} : {e}")
else :
logger.info(f" TRIGGER RESERVATION mail déja envoyé {new_instance} : {new_instance.mail_send} - status : {old_instance.status} to {new_instance.status}")
######################## MOTEUR TRIGGER ########################
@ -165,7 +176,8 @@ TRANSITIONS = {
Reservation.PAID: send_billet_to_mail
},
Reservation.PAID: {
LigneArticle.PAID: send_billet_to_mail,
Reservation.VALID: send_billet_to_mail,
Reservation.PAID: send_billet_to_mail,
'_else_': error_regression,
},
Reservation.VALID: {
@ -202,21 +214,26 @@ TRANSITIONS = {
},
}
@receiver(pre_save)
def pre_save_signal_status(sender, instance, **kwargs):
# if not create
if not instance._state.adding:
sender_str = sender.__name__.upper()
dict_transition = TRANSITIONS.get(sender_str)
if dict_transition:
old_instance = sender.objects.get(pk=instance.pk)
new_instance = instance
def pre_save_signal_status(old_instance, new_instance):
sender_str = old_instance.__class__.__name__.upper()
dict_transition = TRANSITIONS.get(sender_str)
if dict_transition:
logger.info(f"dict_transition {sender_str} {new_instance} : {old_instance.status} to {new_instance.status}")
transitions = dict_transition.get(old_instance.status, None)
if transitions:
# Par ordre de préférence :
trigger_function = transitions.get('_all_', (
transitions.get(new_instance.status, (
transitions.get('_else_', None)
))))
logger.info(f"dict_transition {sender_str} {new_instance} : {old_instance.status} to {new_instance.status}")
transitions = dict_transition.get(old_instance.status, None)
if transitions:
# Par ordre de préférence :
trigger_function = transitions.get('_all_', (
transitions.get(new_instance.status, (
transitions.get('_else_', None)
))))
if trigger_function:
if not callable(trigger_function):
raise Exception(f'Fonction {trigger_function} is not callable. Disdonc !?')
trigger_function(old_instance, new_instance)
if trigger_function:
if not callable(trigger_function):
raise Exception(f'Fonction {trigger_function} is not callable. Disdonc !?')
trigger_function(old_instance, new_instance)

View File

@ -229,11 +229,12 @@ class retour_stripe(View):
return HttpResponseRedirect(f"/qr/{ligne_article.carte.uuid}#erreurpaiement")
elif paiement_stripe.source == Paiement_stripe.API_BILLETTERIE :
return HttpResponse(
'Coucou')
if paiement_stripe.status == Paiement_stripe.VALID :
return HttpResponse(
'Coucou')
else :
raise Http404('paiement_stripe.source ?')
raise Http404(f'{paiement_stripe.status}')
'''

View File

@ -61,11 +61,17 @@ RUN pip install django-stdimage
RUN pip install stripe
RUN apt-get install -y fonts-font-awesome
RUN apt-get install -y libffi-dev
RUN apt-get install -y libgdk-pixbuf2.0-0
RUN apt-get install -y libpango1.0-0
RUN apt-get install -y python-dev
# RUN apt-get install -y python-lxml
RUN apt-get install -y shared-mime-info
RUN apt-get install -y libcairo2
RUN pip install django-weasyprint
RUN apt-get -y clean
RUN python --version
RUN django-admin --version