First shot at a working backend
This commit is contained in:
parent
471fad53e1
commit
0bbe9d9fd9
@ -1,3 +1,9 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import Tikette, Tikategory, Tisub
|
||||||
|
|
||||||
|
admin.site.register(Tikette)
|
||||||
|
admin.site.register(Tikategory)
|
||||||
|
admin.site.register(Tisub)
|
||||||
|
|
||||||
# Register your models here.
|
# Register your models here.
|
||||||
|
61
backend/zetikettes/tikette/generate.py
Normal file
61
backend/zetikettes/tikette/generate.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
from .makesticker import makesticker
|
||||||
|
from .makeplanche import makeplanche
|
||||||
|
|
||||||
|
OUT_DIR = tempfile.gettempdir()
|
||||||
|
DEFAULT_DPI = 300
|
||||||
|
|
||||||
|
def inkscapize(svg_in, pdf_out):
|
||||||
|
png_out = tempfile.mktemp(suffix='.png')
|
||||||
|
try:
|
||||||
|
cmd = ['inkscape', '--export-type=png', f'--export-filename={png_out}',
|
||||||
|
f'--export-dpi={DEFAULT_DPI}', svg_in]
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
|
||||||
|
cmd = ['convert', png_out, pdf_out]
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
finally:
|
||||||
|
if os.path.exists(png_out):
|
||||||
|
os.unlink(png_out)
|
||||||
|
|
||||||
|
|
||||||
|
def generate(request, out_dir):
|
||||||
|
""" Generate a sticker sheet.
|
||||||
|
|
||||||
|
request: dict-like with the following fields:
|
||||||
|
sticker: mandatory. filename for the sticker
|
||||||
|
subs: mandatory. dict-like with key-value template subtitution fields
|
||||||
|
landscape: optional. defaults to False
|
||||||
|
|
||||||
|
out_dir: you get it
|
||||||
|
"""
|
||||||
|
|
||||||
|
# fill in sticker details
|
||||||
|
sticker_out = tempfile.mktemp(suffix='.svg')
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(sticker_out, 'w') as stickout:
|
||||||
|
landscape = request.get('landscape', False)
|
||||||
|
makesticker(os.path.join(out_dir, request['sticker']), stickout,
|
||||||
|
request['subs'], landscape=landscape)
|
||||||
|
|
||||||
|
# make sticker sheet
|
||||||
|
planche_out = tempfile.mktemp(suffix='.svg')
|
||||||
|
with open(sticker_out, 'r') as stickin:
|
||||||
|
with open(planche_out, 'w') as planchout:
|
||||||
|
makeplanche(stickin, planchout)
|
||||||
|
|
||||||
|
# process to printable pdf
|
||||||
|
pdf_out = tempfile.mktemp(dir=out_dir, suffix='.pdf')
|
||||||
|
inkscapize(planche_out, pdf_out)
|
||||||
|
|
||||||
|
return os.path.basename(pdf_out)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if os.path.exists(sticker_out):
|
||||||
|
os.unlink(sticker_out)
|
||||||
|
if 'planche_out' in locals() and os.path.exists(planche_out):
|
||||||
|
os.unlink(planche_out)
|
60
backend/zetikettes/tikette/makeplanche.py
Executable file
60
backend/zetikettes/tikette/makeplanche.py
Executable file
@ -0,0 +1,60 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
from string import Template
|
||||||
|
import sys
|
||||||
|
|
||||||
|
DEFAULT_SHEET_TEMPLATE = Path(__file__).parent / 'planche.svg.in'
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Make a sheet with 12 stickers')
|
||||||
|
parser.add_argument('--template',
|
||||||
|
'-t',
|
||||||
|
default=DEFAULT_SHEET_TEMPLATE,
|
||||||
|
help='path to the sheet template')
|
||||||
|
parser.add_argument('--out',
|
||||||
|
'-o',
|
||||||
|
default=sys.stdout,
|
||||||
|
type=argparse.FileType('w'),
|
||||||
|
help='output path (default: stdout)')
|
||||||
|
parser.add_argument('sticker',
|
||||||
|
type=argparse.FileType('r'),
|
||||||
|
default=sys.stdin,
|
||||||
|
nargs='?',
|
||||||
|
help='path to the sticker SVG (default: stdin)')
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def makeplanche(sticker, out, template=DEFAULT_SHEET_TEMPLATE):
|
||||||
|
with open(template) as tpl:
|
||||||
|
tpl_data = tpl.read()
|
||||||
|
|
||||||
|
lines = sticker.readlines()
|
||||||
|
if lines[0].startswith('<?xml'):
|
||||||
|
lines = lines[1:]
|
||||||
|
sticker_data = ''.join(lines)
|
||||||
|
|
||||||
|
subs = {
|
||||||
|
'left0': sticker_data,
|
||||||
|
'left1': sticker_data,
|
||||||
|
'left2': sticker_data,
|
||||||
|
'left3': sticker_data,
|
||||||
|
'left4': sticker_data,
|
||||||
|
'left5': sticker_data,
|
||||||
|
'right0': sticker_data,
|
||||||
|
'right1': sticker_data,
|
||||||
|
'right2': sticker_data,
|
||||||
|
'right3': sticker_data,
|
||||||
|
'right4': sticker_data,
|
||||||
|
'right5': sticker_data,
|
||||||
|
}
|
||||||
|
|
||||||
|
out.write(Template(tpl_data).substitute(subs))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
makeplanche(**vars(parse_args()))
|
58
backend/zetikettes/tikette/makesticker.py
Executable file
58
backend/zetikettes/tikette/makesticker.py
Executable file
@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from string import Template
|
||||||
|
import sys
|
||||||
|
import typing
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
parser = argparse.ArgumentParser(description='Fill in sticker details')
|
||||||
|
parser.add_argument('--out',
|
||||||
|
'-o',
|
||||||
|
default=sys.stdout,
|
||||||
|
type=argparse.FileType('w'),
|
||||||
|
help='output path (default: stdout)')
|
||||||
|
parser.add_argument('--dluo',
|
||||||
|
'-d',
|
||||||
|
required=True,
|
||||||
|
help='Date Limite d\'Utilisation Optimale')
|
||||||
|
parser.add_argument('--lot', '-l', required=True, help='Numéro de lot')
|
||||||
|
parser.add_argument('--quantite', '-q', required=False, help='Quantité (volume ou masse)')
|
||||||
|
parser.add_argument('--teneur', '-t', required=False, help='Teneur en sucre')
|
||||||
|
parser.add_argument('--fruit', '-f', required=False, help='Quantité de fruits')
|
||||||
|
parser.add_argument('--size', '-s', required=False, help='Masse de produit')
|
||||||
|
parser.add_argument('--landscape',
|
||||||
|
action='store_true',
|
||||||
|
help='input sticker is in landscape orientation')
|
||||||
|
parser.add_argument('sticker', help='path to the sticker template')
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def makesticker(sticker: str, out: typing.IO, subs: dict, landscape=False):
|
||||||
|
with open(sticker) as fin:
|
||||||
|
lines = fin.readlines()
|
||||||
|
if lines[0].startswith('<?xml'):
|
||||||
|
lines = lines[1:]
|
||||||
|
sticker_data = ''.join(lines)
|
||||||
|
|
||||||
|
if landscape:
|
||||||
|
# Rotate the sticker 90 degrees
|
||||||
|
sticker_data = "<g transform=\"rotate(-90,0,0) translate(-102,0)\">{}</g>".format(sticker_data)
|
||||||
|
|
||||||
|
out.write(Template(sticker_data).substitute(subs))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
args = vars(parse_args())
|
||||||
|
subs = {
|
||||||
|
'dluo': args.pop('dluo'),
|
||||||
|
'lot': args.pop('lot'),
|
||||||
|
'teneur': args.pop('teneur'),
|
||||||
|
'fruit': args.pop('fruit'),
|
||||||
|
'qty': args.pop('quantite'),
|
||||||
|
'size': args.pop('size'),
|
||||||
|
}
|
||||||
|
args['subs'] = subs
|
||||||
|
makesticker(**args)
|
@ -1,3 +1,38 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
class Tikategory(models.Model):
|
||||||
|
name = models.CharField(max_length=50)
|
||||||
|
landscape = models.BooleanField()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = "tikategoriez"
|
||||||
|
|
||||||
|
|
||||||
|
class Tisub(models.Model):
|
||||||
|
name = models.CharField(max_length=50)
|
||||||
|
descritpion = models.TextField()
|
||||||
|
default = models.TextField()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = "tisubz"
|
||||||
|
|
||||||
|
|
||||||
|
class Tikette(models.Model):
|
||||||
|
title = models.CharField(max_length=100)
|
||||||
|
category = models.ForeignKey(Tikategory, on_delete=models.CASCADE)
|
||||||
|
svg = models.FileField()
|
||||||
|
subs = models.ManyToManyField(Tisub)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = "tikettz"
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
|
24
backend/zetikettes/tikette/planche.svg.in
Normal file
24
backend/zetikettes/tikette/planche.svg.in
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 210 297"
|
||||||
|
height="297mm"
|
||||||
|
width="210mm">
|
||||||
|
<g transform="translate(3, 4.5)">
|
||||||
|
<g transform="translate(-42,144) rotate(-90,144,0)">
|
||||||
|
<g transform="translate( 0,0)">$right0</g>
|
||||||
|
<g transform="translate( 48,0)">$right1</g>
|
||||||
|
<g transform="translate( 96,0)">$right2</g>
|
||||||
|
<g transform="translate(144,0)">$right3</g>
|
||||||
|
<g transform="translate(192,0)">$right4</g>
|
||||||
|
<g transform="translate(240,0)">$right5</g>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(-42,144) rotate(90,144,0)">
|
||||||
|
<g transform="translate( 0,0)">$left0</g>
|
||||||
|
<g transform="translate( 48,0)">$left1</g>
|
||||||
|
<g transform="translate( 96,0)">$left2</g>
|
||||||
|
<g transform="translate(144,0)">$left3</g>
|
||||||
|
<g transform="translate(192,0)">$left4</g>
|
||||||
|
<g transform="translate(240,0)">$left5</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 896 B |
@ -1,3 +1,31 @@
|
|||||||
from django.shortcuts import render
|
import json
|
||||||
|
|
||||||
# Create your views here.
|
from django.conf import settings
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
|
from . import generate as stickersheet
|
||||||
|
from .models import Tikette, Tikategory
|
||||||
|
|
||||||
|
CORS={'access-control-allow-origin': '*'}
|
||||||
|
|
||||||
|
def index(request):
|
||||||
|
tikettes = [{
|
||||||
|
'title': x.title,
|
||||||
|
'category': x.category.name,
|
||||||
|
'sticker': x.svg.name,
|
||||||
|
'landscape': x.category.landscape,
|
||||||
|
'subs': {x.name: x.default for x in x.subs.all()},
|
||||||
|
} for x in Tikette.objects.all()]
|
||||||
|
return JsonResponse({'status': 'ok', 'tikettes': tikettes}, headers=CORS)
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
def generate(request):
|
||||||
|
if request.method != "POST":
|
||||||
|
return JsonResponse({'status': 'notok', 'message': 'this isn\'t the way'})
|
||||||
|
|
||||||
|
payload = json.loads(request.body)
|
||||||
|
pdfpath = stickersheet.generate(payload, out_dir=settings.TIKETTE_OUT_DIR)
|
||||||
|
|
||||||
|
return JsonResponse({'status': 'ok', 'file': pdfpath}, headers=CORS)
|
||||||
|
@ -15,12 +15,16 @@ from pathlib import Path
|
|||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
#TIKETTE_OUT_DIR = BASE_DIR / 'data'
|
||||||
|
TIKETTE_OUT_DIR = Path('/data')
|
||||||
|
MEDIA_ROOT = TIKETTE_OUT_DIR
|
||||||
|
MEDIA_URL = 'data/'
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
SECRET_KEY = 'django-insecure-a$!y$504(^om%yac)!!vb68arv*e*+r^t-4%y=d%8fda5x38=o'
|
SECRET_KEY = 'django-insecure-64qxpe55#9wy=5@#dl0)3w7ywxh48m!f&!slp9e7v4lh@hjdct'
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
@ -37,6 +41,7 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
'tikette',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
@ -14,9 +14,16 @@ Including another URLconf
|
|||||||
1. Import the include() function: from django.urls import include, path
|
1. Import the include() function: from django.urls import include, path
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls.static import static
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
from django.views.generic.base import TemplateView
|
||||||
|
|
||||||
|
import tikette.views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
]
|
path('list', tikette.views.index),
|
||||||
|
path('', tikette.views.generate),
|
||||||
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
Loading…
Reference in New Issue
Block a user