segno qr code

This commit is contained in:
Jonas 12t 2021-11-08 15:42:25 +04:00
parent 40eb9c3ad2
commit f0ec02c295
15 changed files with 405 additions and 36 deletions

View File

@ -0,0 +1,8 @@
<div>
{{ img_svg | safe }}
</div>
<object type="image/svg+xml" data="data:image/svg+xml;base64,{{ img_svg64 }}">
fallback
</object>

View File

@ -3,21 +3,202 @@
{% load static %} {% load static %}
<meta charset="utf-8"> <meta charset="utf-8">
<link rel="stylesheet" href="{% static 'ticket/ticket.css' %}"/>
<title>Boarding ticket</title> <title>Boarding ticket</title>
<meta name="description" content="Boarding ticket"> <meta name="description" content="Boarding ticket">
<style>
@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: #fff;
display: flex;
font-family: Barlow Condensed, sans-serif;
height: 100%;
justify-content: center;
}
body {
background: #eef1f5;
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;
color: black;
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;
}
</style>
</head> </head>
<body> <body>
<div>
{{ img_svg | safe }}
</div>
<section id="informations"> <section id="informations">
<h1 id="name">{{ ticket.first_name }} {{ ticket.last_name }}</h1> <h1 id="name">{{ ticket.first_name }} {{ ticket.last_name }}</h1>
<h1 id="destination">{{ ticket.reservation.event.name }}</h1> <h1 id="destination">{{ ticket.reservation.event.name }}</h1>
<dl> <dl>
<dt>Flight</dt> <dt>IMG</dt>
<dd>DL31</dd> <dd>Prout</dd>
<dt>Gate</dt> <dt>Gate</dt>
<dd>29</dd> <dd>img_svg</dd>
<dt>Seat</dt> <dt>Seat</dt>
<dd>26E</dd> <dd>26E</dd>
<dt>Zone</dt> <dt>Zone</dt>
@ -32,7 +213,7 @@
</section> </section>
<section id="ticket"> <section id="ticket">
<p>1257797706706</p> <p>proutproutprout</p>
<h2>{{ ticket.first_name }} {{ ticket.last_name }}</h2> <h2>{{ ticket.first_name }} {{ ticket.last_name }}</h2>
<dl> <dl>
<dt>Flight</dt> <dt>Flight</dt>

View File

@ -150,6 +150,13 @@ class TicketPdf(WeasyTemplateView):
kwargs['ticket'] = ticket kwargs['ticket'] = ticket
kwargs['config'] = self.config kwargs['config'] = self.config
'''
context = {
'ticket': ticket,
'config': config,
}
'''
self.pdf_filename = ticket.pdf_filename() self.pdf_filename = ticket.pdf_filename()
return kwargs return kwargs

View File

@ -1,2 +0,0 @@
# Create your tasks here

View File

@ -7,6 +7,7 @@ from django.utils import timezone
from ApiBillet.thread_mailer import ThreadMaileur from ApiBillet.thread_mailer import ThreadMaileur
from BaseBillet.models import Reservation, LigneArticle, Ticket, Product, Configuration, Paiement_stripe from BaseBillet.models import Reservation, LigneArticle, Ticket, Product, Configuration, Paiement_stripe
from BaseBillet.tasks import ticket_celery_mailer
from TiBillet import settings from TiBillet import settings
@ -127,7 +128,6 @@ def check_paid(old_instance, new_instance):
# def send_billet_to_mail(sender, instance: Reservation, **kwargs): # def send_billet_to_mail(sender, instance: Reservation, **kwargs):
def send_billet_to_mail(old_instance, new_instance): def send_billet_to_mail(old_instance, new_instance):
# On active les tickets # On active les tickets
urls_for_attached_files = {}
if new_instance.tickets: if new_instance.tickets:
# On prend aussi ceux qui sont déja activé ( avec les Q() ) # On prend aussi ceux qui sont déja activé ( avec les Q() )
# pour pouvoir les envoyer par mail en cas de nouvelle demande # pour pouvoir les envoyer par mail en cas de nouvelle demande
@ -136,33 +136,16 @@ def send_billet_to_mail(old_instance, new_instance):
ticket.status = Ticket.NOT_SCANNED ticket.status = Ticket.NOT_SCANNED
ticket.save() ticket.save()
# on rajoute les urls du pdf pour le thread async
urls_for_attached_files[ticket.pdf_filename()] = ticket.pdf_url()
# import ipdb; ipdb.set_trace() # import ipdb; ipdb.set_trace()
# On vérifie qu'on a pas déja envoyé le mail # On vérifie qu'on a pas déja envoyé le mail
if not new_instance.mail_send : if not new_instance.mail_send :
logger.info(f" TRIGGER RESERVATION send_billet_to_mail {new_instance.status}") logger.info(f" TRIGGER RESERVATION send_billet_to_mail {new_instance.status}")
new_instance : Reservation new_instance : Reservation
config = Configuration.get_solo()
if new_instance.user_commande.email: 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,
},
urls_for_attached_files = urls_for_attached_files,
)
# import ipdb; ipdb.set_trace() # import ipdb; ipdb.set_trace()
mail.send_with_tread() task = ticket_celery_mailer.delay(new_instance.pk)
except Exception as e : # https://github.com/psf/requests/issues/5832
logger.error(f"{timezone.now()} Erreur envoie de mail pour reservation {new_instance} : {e}")
else : else :
logger.info(f" TRIGGER RESERVATION mail déja envoyé {new_instance} : {new_instance.mail_send} - status : {new_instance.status}") logger.info(f" TRIGGER RESERVATION mail déja envoyé {new_instance} : {new_instance.mail_send} - status : {new_instance.status}")

View File

@ -0,0 +1,169 @@
import os
from io import BytesIO
import base64
import segno
from weasyprint import HTML, CSS
from weasyprint.text.fonts import FontConfiguration
from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string, get_template
from django.utils import timezone
from BaseBillet.models import Configuration, Reservation, Ticket
from TiBillet.celery import app
import logging
logger = logging.getLogger(__name__)
class CeleryMailerClass():
def __init__(self,
email: str,
title: str,
text=None,
html=None,
template=None,
context=None,
attached_files=None,
):
self.title = title
self.email = email
self.text = text
self.html = html
self.config = Configuration.get_solo()
self.context = context
self.attached_files = attached_files
if template and context:
self.html = render_to_string(template, context=context)
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")
if self.attached_files:
for filename, file in self.attached_files.items():
mail.attach(filename, file, 'application/pdf')
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_return
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 create_ticket_pdf(ticket: Ticket):
qr = segno.make(f'{ticket.uuid}')
buffer_png = BytesIO()
qr.save(buffer_png, kind='PNG', scale=15)
img_str = base64.b64encode(buffer_png.getvalue()).decode('utf-8')
buffer_svg = BytesIO()
qr.save(buffer_svg, kind='svg', scale=10)
context = {
'ticket': ticket,
'config': Configuration.get_solo(),
'img_str': base64.b64encode(buffer_png.getvalue()).decode('utf-8'),
'img_svg': buffer_svg.getvalue().decode('utf-8'),
'img_svg64': base64.b64encode(buffer_svg.getvalue()).decode('utf-8'),
}
template_name = 'ticket/ticket.html'
# template_name = 'ticket/qrtest.html'
font_config = FontConfiguration()
template = get_template(template_name)
html = template.render(context)
css = CSS(string=
'''
@font-face {
font-family: Libre Barcode;
src: url(file:///DjangoFiles/ApiBillet/templates/ticket/librebarcode128-regular.ttf);
}
@font-face {
font-family: Barlow Condensed;
src: url(file:///DjangoFiles/ApiBillet/templates/ticket/barlowcondensed-regular.otf)
}
@font-face {
font-family: Barlow Condensed;
font-weight: 300;
src: url(file:///DjangoFiles/ApiBillet/templates/ticket/barlowcondensed-light.otf);
}
@font-face {
font-family: Barlow Condensed;
font-weight: 700;
src: url(file:///DjangoFiles/ApiBillet/templates/ticket/barlowcondensed-bold.otf);
}
''',
font_config=font_config)
pdf_binary = HTML(string=html).write_pdf(
stylesheets=[css],
font_config=font_config
)
return pdf_binary
@app.task
def ticket_celery_mailer(reservation_uuid: str):
'''
for ticket in reservation.tickets.filter(status=Ticket.NOT_SCANNED):
response = requests.get(ticket.pdf_url())
print(response.status_code)
'''
config = Configuration.get_solo()
reservation = Reservation.objects.get(pk=reservation_uuid)
attached_files = {}
for ticket in reservation.tickets.filter(status=Ticket.NOT_SCANNED):
attached_files[ticket.pdf_filename()] = create_ticket_pdf(ticket)
try:
mail = CeleryMailerClass(
reservation.user_commande.email,
f"Votre reservation pour {config.organisation}",
template='mails/buy_confirmation.html',
context={
'config': config,
'reservation': reservation,
},
attached_files=attached_files,
)
mail.send()
return True
except Exception as e:
logger.error(f"{timezone.now()} Erreur envoie de mail pour reservation {reservation} : {e}")
raise Exception

View File

@ -8,6 +8,14 @@ from rest_framework.permissions import AllowAny
from rest_framework.views import APIView from rest_framework.views import APIView
from BaseBillet.models import Configuration, Event, Ticket from BaseBillet.models import Configuration, Event, Ticket
import base64
import segno
from io import StringIO, BytesIO
from django.template import engines
from django.http import HttpResponse
from PIL import Image
class index(APIView): class index(APIView):
permission_classes = [AllowAny] permission_classes = [AllowAny]
@ -36,10 +44,24 @@ class Ticket_html_view(APIView):
permission_classes = [AllowAny] permission_classes = [AllowAny]
def get(self, request, pk_uuid): def get(self, request, pk_uuid):
ticket = get_object_or_404(Ticket, uuid=pk_uuid) ticket = get_object_or_404(Ticket, uuid=pk_uuid)
qr = segno.make(f'{ticket.uuid}')
buffer_png = BytesIO()
qr.save(buffer_png, kind='PNG', scale=3)
buffer_svg = BytesIO()
qr.save(buffer_svg, kind='svg', scale=10)
context = { context = {
'ticket': ticket, 'ticket': ticket,
'config': Configuration.get_solo(), 'config': Configuration.get_solo(),
'img_str': base64.b64encode(buffer_png.getvalue()).decode('utf-8'),
'img_svg': buffer_svg.getvalue().decode('utf-8'),
'img_svg64': base64.b64encode(buffer_svg.getvalue()).decode('utf-8'),
} }
return render(request, 'ticket/ticket.html', context=context) return render(request, 'ticket/ticket.html', context=context)
# return render(request, 'ticket/qrtest.html', context=context)

View File

@ -34,10 +34,10 @@ app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
@app.task(bind=True) @app.task
def add(x, y): def add(x, y):
return x + y return x + y
@app.task @app.task
def add2(x, y): def schema_name():
return x + y return connection.schema_name

View File

@ -216,11 +216,10 @@ EMAIL_USE_SSL = os.environ.get('EMAIL_USE_SSL', True)
CELERY_TIMEZONE=os.environ.get('TIME_ZONE', 'UTC') CELERY_TIMEZONE=os.environ.get('TIME_ZONE', 'UTC')
CELERY_TASK_TRACK_STARTED=True CELERY_TASK_TRACK_STARTED=True
CELERY_TASK_TIME_LIMIT=30 * 60 CELERY_TASK_TIME_LIMIT=30 * 60
BROKER_URL=os.environ.get('CELERY_BROKER') BROKER_URL=os.environ.get('CELERY_BROKER', 'redis://redis:6379/0')
RESULT_BACKEND=os.environ.get('CELERY_BROKER') CELERY_RESULT_BACKEND=os.environ.get('CELERY_BACKEND', 'redis://redis:6379/0')
CELERY_RESULT_BACKEND=os.environ.get('CELERY_BACKEND')
# DJANGO_CELERY_BEAT_TZ_AWARE=False # DJANGO_CELERY_BEAT_TZ_AWARE=False
# celery -A TiBillet worker -l INFO
# Jet Menu # Jet Menu
# -------------------------------------/ # -------------------------------------/

View File

@ -74,6 +74,8 @@ RUN pip install celery
RUN pip install redis RUN pip install redis
RUN pip install tenant-schemas-celery RUN pip install tenant-schemas-celery
RUN pip install segno
RUN apt-get -y clean RUN apt-get -y clean
RUN python --version RUN python --version