First shot at a working backend
This commit is contained in:
		@@ -1,3 +1,9 @@
 | 
			
		||||
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.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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'.
 | 
			
		||||
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
 | 
			
		||||
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
 | 
			
		||||
 | 
			
		||||
# 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!
 | 
			
		||||
DEBUG = True
 | 
			
		||||
@@ -37,6 +41,7 @@ INSTALLED_APPS = [
 | 
			
		||||
    'django.contrib.sessions',
 | 
			
		||||
    'django.contrib.messages',
 | 
			
		||||
    'django.contrib.staticfiles',
 | 
			
		||||
    'tikette',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
MIDDLEWARE = [
 | 
			
		||||
 
 | 
			
		||||
@@ -14,9 +14,16 @@ Including another URLconf
 | 
			
		||||
    1. Import the include() function: from django.urls import include, path
 | 
			
		||||
    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.urls import path
 | 
			
		||||
from django.views.generic.base import TemplateView
 | 
			
		||||
 | 
			
		||||
import tikette.views
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    path('admin/', admin.site.urls),
 | 
			
		||||
]
 | 
			
		||||
    path('list', tikette.views.index),
 | 
			
		||||
    path('', tikette.views.generate),
 | 
			
		||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user