First shot at a working backend
This commit is contained in:
		| @@ -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) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user