Implement PDF generator
Improve mail view Implement voucher generation
This commit is contained in:
parent
29231bcfc9
commit
a3d31c5cc3
16 changed files with 247 additions and 21 deletions
|
@ -1,5 +1,5 @@
|
|||
#!/bin/bash
|
||||
apt install libpq-dev build-essential libpython3-dev postgis python3-pip python3-venv libgdal-dev -y
|
||||
apt install libpq-dev build-essential libpython3-dev postgis python3-pip python3-venv libgdal-dev wkhtmltopdf -y
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -Ur requirements.txt
|
|
@ -51,6 +51,6 @@ class VerificationView(SuperUserRequiredMixin, FormView):
|
|||
|
||||
return HttpResponseRedirect(resolve_url("localauth:verify"))
|
||||
|
||||
class EmailVerificationHTMLMailView(MailView):
|
||||
class EmailVerificationMailView(MailView):
|
||||
template_name = "localauth/mail/verify.html"
|
||||
|
|
@ -9,28 +9,41 @@ from html.parser import HTMLParser
|
|||
from bs4 import BeautifulSoup
|
||||
|
||||
class MailView(ContextMixin):
|
||||
def __init__(self, template_name=None, html_template_name=None, text_template_name=None):
|
||||
self.html_template_name = html_template_name
|
||||
self.text_template_name = text_template_name
|
||||
template_name = None
|
||||
|
||||
if template_name:
|
||||
self._template_name = template_name
|
||||
|
||||
if len(template_name.split("/")[-1].split(".")[-1] in ("html", "txt")):
|
||||
@property
|
||||
def html_template_name(self):
|
||||
if self.template_name:
|
||||
if self.template_name.split("/")[-1].split(".")[-1] in ("html", "txt"):
|
||||
basename = template_name.rsplit(".", 1)[0]
|
||||
else:
|
||||
basename = template_name
|
||||
|
||||
for ext in ("html", "txt"):
|
||||
try:
|
||||
path = f"{basename}.{ext}"
|
||||
render_to_string(path)
|
||||
if ext == "html":
|
||||
self.html_template_name = self.html_template_name or path
|
||||
else:
|
||||
self.text_template_name = self.text_template_name or path
|
||||
except TemplateDoesNotExist:
|
||||
pass
|
||||
try:
|
||||
path = f"{basename}.html"
|
||||
render_to_string(path)
|
||||
return path
|
||||
except TemplateDoesNotExist:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
@property
|
||||
def text_template_name(self):
|
||||
if self.template_name:
|
||||
if self.template_name.split("/")[-1].split(".")[-1] in ("html", "txt"):
|
||||
basename = template_name.rsplit(".", 1)[0]
|
||||
else:
|
||||
basename = template_name
|
||||
|
||||
try:
|
||||
path = f"{basename}.txt"
|
||||
render_to_string(path)
|
||||
return path
|
||||
except TemplateDoesNotExist:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
def render_to_html(self, **kwargs):
|
||||
if self.html_template_name:
|
||||
|
|
|
@ -7,5 +7,6 @@ app_name = "payment"
|
|||
urlpatterns = [
|
||||
path('gateways/paypal/', include("payment.paypal.urls"), name="paypal"),
|
||||
path('gateways/sepa/', include("payment.sepa.urls"), name="sepa"),
|
||||
path('gateways/voucher/', include("payment.voucher.urls"), name="voucher"),
|
||||
path('status/<slug:uuid>/', PaymentStatusView.as_view(), name="status"),
|
||||
]
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
from django.db import models
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
from .helpers import generate_voucher_code
|
||||
from ..const import PAYMENT_STATUS_SUCCESS, PAYMENT_STATUS_FAILED, PAYMENT_STATUS_REFUNDED
|
||||
from ..models import InvoicePayment
|
||||
|
||||
from payment.const import PAYMENT_STATUS_SUCCESS, PAYMENT_STATUS_FAILED, PAYMENT_STATUS_REFUNDED
|
||||
from clients.models import ClientProfile
|
||||
|
||||
from dbsettings.functions import getValue
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
import uuid
|
||||
|
||||
class Voucher(models.Model):
|
||||
code = models.IntegerField(default=generate_voucher_code)
|
||||
code = models.BigIntegerField(default=generate_voucher_code, unique=True)
|
||||
value = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
|
@ -19,6 +27,21 @@ class Voucher(models.Model):
|
|||
except VoucherActivation.DoesNotExist:
|
||||
return False
|
||||
|
||||
def get_absolute_url(self):
|
||||
return VoucherDownloadURL.objects.create(voucher=self).get_absolute_url()
|
||||
|
||||
class VoucherDownloadURL(models.Model):
|
||||
uuid = models.UUIDField(default=uuid.uuid4, primary_key=True)
|
||||
voucher = models.ForeignKey(Voucher, models.CASCADE)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
@property
|
||||
def expiry(self):
|
||||
return self.created + timedelta(seconds=getValue("payment.voucher.urlvalidity", 300))
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy("payment:voucher:download")
|
||||
|
||||
class VoucherActivation(models.Model):
|
||||
voucher = models.OneToOneField(Voucher, models.PROTECT)
|
||||
user = models.ForeignKey(get_user_model(), models.PROTECT)
|
||||
|
|
10
payment/voucher/urls.py
Normal file
10
payment/voucher/urls.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from django.urls import path
|
||||
|
||||
from .views import AdminGenerateVoucherView, DownloadVoucherView
|
||||
|
||||
app_name = "voucher"
|
||||
|
||||
urlpatterns = [
|
||||
path('generate/', AdminGenerateVoucherView.as_view(), name="generate"),
|
||||
path('download/<slug:pk>/', DownloadVoucherView.as_view(), name="download"),
|
||||
]
|
38
payment/voucher/views.py
Normal file
38
payment/voucher/views.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
from django.views.generic import CreateView, DetailView
|
||||
from django.shortcuts import redirect
|
||||
from django.utils import timezone
|
||||
from django.http import Http404, HttpResponse
|
||||
|
||||
from localauth.mixins import SuperUserRequiredMixin
|
||||
from pdf.views import PDFView
|
||||
|
||||
from .models import VoucherDownloadURL
|
||||
|
||||
class AdminGenerateVoucherView(SuperUserRequiredMixin, CreateView):
|
||||
pass
|
||||
|
||||
class VoucherPDFView(PDFView):
|
||||
template_name = "payment/voucher/pdfvoucher.html"
|
||||
voucher = None
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
return super().get_context_data(voucher=self.voucher, **kwargs)
|
||||
|
||||
class DownloadVoucherView(DetailView):
|
||||
model = VoucherDownloadURL
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
obj = self.get_object()
|
||||
|
||||
if (not obj) or obj.expiry < timezone.now():
|
||||
raise Http404()
|
||||
|
||||
pdfview = VoucherPDFView()
|
||||
pdfview.voucher = obj.voucher
|
||||
pdf = pdfview.render()
|
||||
|
||||
response = HttpResponse(pdf, content_type="application/pdf")
|
||||
|
||||
response['Content-Disposition'] = 'inline; filename=voucher.pdf'
|
||||
|
||||
return response
|
0
pdf/__init__.py
Normal file
0
pdf/__init__.py
Normal file
3
pdf/admin.py
Normal file
3
pdf/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
6
pdf/apps.py
Normal file
6
pdf/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class PdfConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'pdf'
|
3
pdf/models.py
Normal file
3
pdf/models.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
3
pdf/tests.py
Normal file
3
pdf/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
23
pdf/views.py
Normal file
23
pdf/views.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from django.views.generic.base import ContextMixin
|
||||
from django.template.loader import render_to_string
|
||||
from django.template.exceptions import TemplateDoesNotExist
|
||||
from django.core.mail import EmailMultiAlternatives
|
||||
from django.conf import settings
|
||||
|
||||
from html.parser import HTMLParser
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
import pdfkit
|
||||
|
||||
class PDFView(ContextMixin):
|
||||
template_name = None
|
||||
|
||||
def render_to_html(self, **kwargs):
|
||||
context = self.get_context_data(**kwargs)
|
||||
return render_to_string(self.template_name, context)
|
||||
|
||||
def render(self, **kwargs):
|
||||
html = self.render_to_html(**kwargs)
|
||||
pdf = pdfkit.from_string(html, False)
|
||||
return pdf
|
|
@ -23,4 +23,5 @@ django-filtersignals
|
|||
python-magic
|
||||
bs4
|
||||
django-starfield
|
||||
pdfkit
|
||||
git+https://kumig.it/kumisystems/PyInvoice
|
||||
|
|
BIN
static/payment/voucher/gift-card-img.jpg
Normal file
BIN
static/payment/voucher/gift-card-img.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 236 KiB |
102
templates/payment/voucher/pdfvoucher.html
Normal file
102
templates/payment/voucher/pdfvoucher.html
Normal file
|
@ -0,0 +1,102 @@
|
|||
{% load static %}
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<style>
|
||||
html {
|
||||
margin-top: 0in !important;
|
||||
margin-left: 0in !important;
|
||||
}
|
||||
|
||||
@page {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 1.4em;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.voucher {
|
||||
width: 8in;
|
||||
height: 3.77in;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
.voucher-img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.field-amount {
|
||||
position: absolute;
|
||||
top: 236px;
|
||||
right: 353px;
|
||||
color: #fff;
|
||||
font-size: 57px;
|
||||
line-height: 40px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.field-content {
|
||||
position: absolute;
|
||||
width: 40%;
|
||||
right: 10px;
|
||||
top: 48px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.demo-title {
|
||||
font-size: 35px;
|
||||
font-weight: 300;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.demo-p {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.field-code {
|
||||
padding: 15px;
|
||||
border: 1px dotted;
|
||||
margin: 35px 0 15px;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.field-expiry {
|
||||
padding: 8px 13px;
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.field-customer {
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="voucher">
|
||||
<img class="voucher-img" src="{% static "payment/voucher/gift-card-img.jpg" %}" />
|
||||
|
||||
<span class="field-amount">{{ voucher.value }}</span>
|
||||
|
||||
<div class="field-content">
|
||||
<div class="demo-title">JourneyJoker-Gutschein</div>
|
||||
<div class="demo-p">Buche noch heute deinen nächsten Traumurlaub.</div>
|
||||
|
||||
<div class="field-code">{{ voucher.code }}</div>
|
||||
<div class="field-expiry">Gutschein erstellt am {{ voucher.created_at }}</div>
|
||||
<div class="field-customer" style="display:none;">Gekauft von {{ voucher.customer.user.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in a new issue