epok-toolkit 1.12.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,246 @@
1
+ from reportlab.lib.utils import ImageReader
2
+ from reportlab.pdfbase import pdfmetrics
3
+ from datetime import date, datetime
4
+ from reportlab.pdfbase.ttfonts import TTFont
5
+ from reportlab.pdfgen import canvas
6
+ from dataclasses import dataclass
7
+ from typing import Optional
8
+ from PIL import Image
9
+ from uuid import uuid4, UUID
10
+ import os
11
+ import qrcode
12
+ from io import BytesIO
13
+ import importlib.resources
14
+
15
+
16
+
17
+ fuente_path = os.path.join(os.path.dirname(__file__), "fuentes", "Kollektif-Bold.ttf")
18
+ pdfmetrics.registerFont(TTFont("kollektif", fuente_path))
19
+
20
+
21
+ def hex_to_rgb(hex_color):
22
+ hex_color = hex_color.lstrip('#')
23
+ lv = len(hex_color)
24
+ return tuple(int(hex_color[i:i + lv // 3], 16) / 255.0 for i in range(0, lv, lv // 3))
25
+
26
+
27
+ class Config:
28
+ base_path = os.path.dirname(__file__)
29
+ plantillas = base_path + "/plantillas/"
30
+ plantilla_path = importlib.resources.files("epok_toolkit.pdf.plantillas").joinpath("Ticket_congrats.png")
31
+ output_path = os.path.join(base_path, "ticket_final.pdf")
32
+ fuente = "Helvetica"
33
+ fuente_bold = "kollektif"
34
+ #bg_color = "#0082FF"
35
+ bg_color = "#FFFFFF"
36
+ font_color = "#000000"
37
+
38
+
39
+ class Contenedor:
40
+ def __init__(self, x, y, w, h, bold: bool = False):
41
+ self.x = x
42
+ self.y = y
43
+ self.w = w
44
+ self.h = h
45
+ self.color = Config.bg_color
46
+ self.font_size = 28
47
+ self.font_color = Config.font_color
48
+ self.fuente = Config.fuente_bold if bold else Config.fuente
49
+
50
+ def dibujar(self, c):
51
+ c.setFillColorRGB(*hex_to_rgb(self.color))
52
+ c.rect(self.x, self.y, self.w, self.h, stroke=0, fill=1)
53
+
54
+
55
+ def dibujar_texto(self, c, texto):
56
+ c.setFillColorRGB(*hex_to_rgb(self.font_color))
57
+ c.setFont(self.fuente, self.font_size)
58
+ ascent = pdfmetrics.getAscent(self.fuente) * self.font_size / 1000
59
+ descent = abs(pdfmetrics.getDescent(self.fuente) * self.font_size / 1000)
60
+ text_h = ascent + descent
61
+ baseline_x = self.x + self.w / 2
62
+ baseline_y = self.y + (self.h - text_h) / 2 + descent
63
+ c.drawCentredString(baseline_x, baseline_y, texto)
64
+
65
+
66
+ class ContenedorTexto(Contenedor):
67
+ def __init__(self, x, y, w, h, bold: bool = False, texto="Demo",
68
+ font_size=28,
69
+ font_color=None,
70
+ color=None,
71
+ fuente=None):
72
+ super().__init__(x, y, w, h, bold=bold)
73
+ self.texto = texto
74
+ if font_size:
75
+ self.font_size = font_size
76
+ if font_color:
77
+ self.font_color = font_color
78
+ if color:
79
+ self.color = color
80
+ if fuente:
81
+ self.fuente = fuente
82
+
83
+ def dibujar(self, c):
84
+ super().dibujar(c)
85
+ self.dibujar_texto(c, self.texto)
86
+
87
+
88
+ class ContenedorQR(Contenedor):
89
+ def __init__(self, x, y, w, h, texto="QR vacío", color=None):
90
+ super().__init__(x, y, w, h)
91
+ self.texto = texto
92
+ if color:
93
+ self.color = color
94
+
95
+ def dibujar(self, c):
96
+ super().dibujar(c)
97
+ qr = qrcode.QRCode(
98
+ version=3,
99
+ error_correction=qrcode.constants.ERROR_CORRECT_H, # type: ignore
100
+ box_size=10,
101
+ border=1,
102
+ )
103
+ qr.add_data(self.texto)
104
+ qr.make(fit=True)
105
+ qr_img = qr.make_image(fill_color="black", back_color="white").convert("RGB") # type: ignore
106
+ qr_img = qr_img.resize((self.w, self.h), Image.LANCZOS) # type: ignore
107
+ buffer = BytesIO()
108
+ qr_img.save(buffer, format="PNG")
109
+ buffer.seek(0)
110
+ c.drawImage(ImageReader(buffer), self.x, self.y, width=self.w, height=self.h)
111
+
112
+
113
+ @dataclass
114
+ class TicketPDF:
115
+ nombre_evento: str
116
+ fecha: Optional[date]
117
+ titulo_ticket: str
118
+ precio: float
119
+ edad_min: int
120
+ tipo_evento: str
121
+ direccion: str
122
+ ticket_actual: int
123
+ total_tickets: int
124
+ nombre_persona: str
125
+ uuid: Optional[UUID] = None
126
+ hora_evento: Optional[str] = None
127
+ qr : Optional[str] = None
128
+
129
+ def __post_init__(self):
130
+ if isinstance(self.fecha, str):
131
+ self.fecha = datetime.strptime(self.fecha, "%Y-%m-%d %H:%M")
132
+ elif isinstance(self.fecha, date) and not isinstance(self.fecha, datetime):
133
+ hora = self.hora_evento or "00:00"
134
+ self.fecha = datetime.combine(self.fecha, datetime.strptime(hora, "%H:%M").time())
135
+ if not self.uuid:
136
+ raise ValueError("UUID no puede ser None")
137
+
138
+ self.hora_evento = self.fecha.strftime("%H:%M") # type: ignore
139
+ self.width = 0
140
+ self.height = 0
141
+ self.font_size = 28
142
+ self.font_color = Config.font_color
143
+ self.color = Config.bg_color
144
+ self.fuente = Config.fuente
145
+ with Config.plantilla_path.open("rb") as f:
146
+ self.img = Image.open(f)
147
+ self.img.load()
148
+ self.width, self.height = self.img.size
149
+ self.buffer = BytesIO()
150
+ self.c = canvas.Canvas(self.buffer, pagesize=(self.width, self.height))
151
+
152
+
153
+ def generate_ticket(self):
154
+ def y(px): return self.height - px
155
+ with Config.plantilla_path.open("rb") as f:
156
+ self.c.drawImage(ImageReader(f), 0, 0, width=self.width, height=self.height, mask='auto')
157
+
158
+ # NOMBRE EVENTO
159
+ nombre_evento = ContenedorTexto(x=260, y=y(210), w=1400, h=90, texto=self.nombre_evento, font_size=78, bold=True)
160
+ nombre_evento.dibujar(self.c)
161
+
162
+ # FECHA
163
+ SPANISH_DAYS = {
164
+ 'Monday': 'Lunes', 'Tuesday': 'Martes', 'Wednesday': 'Miercoles',
165
+ 'Thursday': 'Jueves', 'Friday': 'Viernes', 'Saturday': 'Sabado', 'Sunday': 'Domingo'
166
+ }
167
+ SPANISH_MONTHS = {
168
+ 'January': 'Enero', 'February': 'Febrero', 'March': 'Marzo', 'April': 'Abril',
169
+ 'May': 'Mayo', 'June': 'Junio', 'July': 'Julio', 'August': 'Agosto',
170
+ 'September': 'Septiembre', 'October': 'Octubre', 'November': 'Noviembre', 'December': 'Diciembre'
171
+ }
172
+ fecha_obj = self.fecha # type: ignore
173
+ day_name = SPANISH_DAYS[fecha_obj.strftime('%A')] # type: ignore
174
+ month_name = SPANISH_MONTHS[fecha_obj.strftime('%B')] # type: ignore
175
+ fecha = f"{day_name}, {fecha_obj.day} {month_name}".upper() #type: ignore
176
+ fecha = ContenedorTexto(x=570, y=y(320), w=900, h=90, texto=fecha, font_size=38, bold=True)
177
+ fecha.dibujar(self.c)
178
+
179
+ # TICKET TITULO
180
+ ticket_titulo = ContenedorTexto(x=260, y=y(525), w=950, h=90, texto=self.titulo_ticket, font_size=68, bold=True)
181
+ ticket_titulo.dibujar(self.c)
182
+
183
+ # PRECIO
184
+ precio = f"{self.precio:,.2f} MXN"
185
+ precio = ContenedorTexto(x=450, y=y(650), w=600, h=90, texto=precio, font_size=58, bold=True)
186
+ precio.dibujar(self.c)
187
+
188
+ # EDAD MINIMA
189
+ edad_min = str(self.edad_min)
190
+ edad_min = ContenedorTexto(x=345, y=y(760), w=80, h=30, texto=edad_min, font_size=18, bold=True)
191
+ edad_min.dibujar(self.c)
192
+
193
+ # HORA DE ACCESO
194
+ hora_evento = ContenedorTexto(x=670, y=y(760), w=80, h=30, texto=self.hora_evento, font_size=18, bold=True) # type: ignore
195
+ hora_evento.dibujar(self.c)
196
+
197
+ # TIPO DE EVENTO
198
+ tipo_evento = ContenedorTexto(x=995, y=y(760), w=150, h=30, texto=self.tipo_evento, font_size=18, bold=True)
199
+ tipo_evento.dibujar(self.c)
200
+
201
+ # DIRECCION
202
+ direccion = ContenedorTexto(x=200, y=y(950), w=1400, h=50, texto=self.direccion, font_size=38, bold=True)
203
+ direccion.dibujar(self.c)
204
+
205
+ # TICKET ACTUAL
206
+ texto = f"Ticket {self.ticket_actual} de {self.total_tickets}"
207
+ ticket_actual = ContenedorTexto(x=680, y=y(1035), w=340, h=50, texto=texto, font_size=28, bold=True)
208
+ ticket_actual.dibujar(self.c)
209
+
210
+ # NOMBRE PERSONA
211
+ nombre_persona = ContenedorTexto(x=1080, y=y(1035), w=760, h=60, texto=self.nombre_persona, font_size=48, bold=True)
212
+ nombre_persona.dibujar(self.c)
213
+
214
+ # UUID
215
+ uuid_string = self.uuid
216
+ # UUID
217
+ uuid_string = str(self.uuid)
218
+ uuid = ContenedorTexto(x=1390, y=y(770), w=400, h=30, texto=uuid_string, font_size=18, bold=True)
219
+ uuid.dibujar(self.c)
220
+ tam = 400
221
+ qr = ContenedorQR(x=1390, y=y(740), w=tam, h=tam, texto=uuid_string, color=Config.bg_color)
222
+ qr.dibujar(self.c)
223
+
224
+ self.c.showPage()
225
+ self.c.save()
226
+ self.buffer.seek(0)
227
+ return self.buffer.getvalue()
228
+
229
+
230
+
231
+
232
+ if __name__ == "__main__":
233
+ ticket = TicketPDF(
234
+ nombre_evento="14 aniversario Cerveza Libertad",
235
+ fecha=datetime(2025, 5, 31, 14, 30),
236
+ titulo_ticket="General",
237
+ precio=410,
238
+ edad_min=18,
239
+ tipo_evento="Aniversario",
240
+ direccion="Restaurante dentro de Hacienda del Conde",
241
+ ticket_actual=2,
242
+ total_tickets=5,
243
+ nombre_persona="Carolina Franco Medina",
244
+ uuid=uuid4(),
245
+ )
246
+ ticket.generate_ticket()
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.4
2
+ Name: epok-toolkit
3
+ Version: 1.12.8
4
+ Summary: Una herramienta para la gestión de tareas y procesos en Django con Celery.
5
+ Author-email: Fernando Leon Franco <fernanlee2131@gmail.com>
6
+ License: MIT
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Framework :: Django
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.12.9
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: Django>=5.0.2
15
+ Requires-Dist: djangorestframework>=3.16.0
16
+ Requires-Dist: celery[redis]>=5.5.3
17
+ Requires-Dist: django-celery-beat>=2.6.0
18
+ Requires-Dist: colorstreak>=2.1.3
19
+ Requires-Dist: PyJWT>=2.10.1
20
+ Requires-Dist: reportlab>=4.4.3
21
+ Requires-Dist: qrcode>=8.2
22
+ Dynamic: license-file
23
+
24
+
25
+
26
+ # EPOK Toolkit 'v1.12.6'
27
+
28
+ EPOK Toolkit es una librería de utilidades para proyectos Django y Python, diseñada para facilitar tareas comunes como envío de emails, mensajería WhatsApp, generación de PDFs y manejo avanzado de caché en APIs.
29
+
30
+ ---
31
+
32
+
33
+ ## ✉️ Email
34
+ Módulo para envío de correos electrónicos con plantillas HTML y texto plano, registro dinámico y configuración visual.
35
+
36
+ 👉 [Documentación completa del módulo Email](epok_toolkit/email/README.md)
37
+
38
+ ---
39
+
40
+ ## 🟢 Django
41
+ Utilidades avanzadas para proyectos Django y DRF: caché, campos personalizados, managers, respuestas, viewsets y utilidades extra.
42
+
43
+ � [Documentación completa del módulo Django](epok_toolkit/django/README.md)
44
+
45
+ ---
46
+
47
+ ## 💬 Mensajería WhatsApp
48
+ Módulo para envío de mensajes y archivos por WhatsApp, con manejo de conexión y errores.
49
+
50
+ � [Documentación completa del módulo Messaging](epok_toolkit/messaging/README.md)
51
+
52
+ ---
53
+
54
+ ## 🧾 PDF / Ticket Generator
55
+ Módulo para generación de tickets PDF personalizados usando ReportLab y plantillas gráficas.
56
+
57
+ � [Documentación completa del módulo PDF](epok_toolkit/pdf/README.md)
58
+
59
+ ---
60
+
61
+ ## ⚙️ Configuración
62
+
63
+ Toda la configuración se centraliza en `default_settings.py`, permitiendo personalizar colores, datos de empresa, y más.
64
+
65
+ ---
66
+
67
+ ## 🚀 Instalación
68
+
69
+ ```bash
70
+ pip install epok-toolkit
71
+ ```
72
+
73
+ ---
74
+
75
+ ¿Quieres agregar ejemplos avanzados o documentación de otro módulo? ¡Solicítalo!
@@ -0,0 +1,32 @@
1
+ epok_toolkit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ epok_toolkit/apps.py,sha256=O3q3CcucJOHjlYIS0VgbKsbtim2hpng_FxpKEG_MlWs,486
3
+ epok_toolkit/default_settings.py,sha256=GdDpwMPzRYNDTDK0zcQKUwgiGYVVhj1Pi1NGAxTE1Io,890
4
+ epok_toolkit/django/__init__.py,sha256=noq8F7jlKE_9ikkOt4VnoYHspxm6zGJ-_N2qF_MvLX4,136
5
+ epok_toolkit/django/cache.py,sha256=gfYCF7HW2ofdJGb1tLTYNQN4f5EVS_izTpHVW3LDqZQ,4121
6
+ epok_toolkit/django/fields.py,sha256=-ajP5qx-4bt9Qz9yW48gTlinTxD1xWPKOEkslqx8cSM,1089
7
+ epok_toolkit/django/manager.py,sha256=3MZcA9wQY4E1KD8XlgZQbzf4wlF9vA8Pntl_gKDWfpA,1350
8
+ epok_toolkit/django/models.py,sha256=E1zj2KetRGszksQvLk3HN5kWkYkEYzJRRmAUOaLJmw8,1090
9
+ epok_toolkit/django/response.py,sha256=O8OHBaKgUQjBeYLLbgTTs669l_4D6swUgAOwswjc-88,1716
10
+ epok_toolkit/django/viewsets.py,sha256=bycP2NyzldHl0pi5vZPHo8x1DzGN7n16kJFTXt_sNZI,3650
11
+ epok_toolkit/django/utils/__init__.py,sha256=zDuoqm_eksZQpL-Bvd_q2KGMtSXfprBjf4TScZiwV6k,25
12
+ epok_toolkit/django/utils/magic_link.py,sha256=GiDuy0kAGdYohGPlBL7rwpKPMpXuB1wJa1k5LTAOm4w,889
13
+ epok_toolkit/email/__init__.py,sha256=pyJwysyVoq6DuYAG72fulsKFoOuAfjw3aBH7FhmYGHc,35
14
+ epok_toolkit/email/email_async.py,sha256=oC0WowWNUpTpXdxo6hJag5gWUaStxI6VBEArEKQxXko,1521
15
+ epok_toolkit/email/engine.py,sha256=IIifqRI9z76pHdrO5oSSZ25aP5txOTAgrj1JuVVPlMY,2387
16
+ epok_toolkit/email/templates.py,sha256=8aQ5E3A0q1Uqc7WZf7Tm_ssj6DgAGNHiLsAd6mrZff4,6136
17
+ epok_toolkit/messaging/__init__.py,sha256=lo0URCOr8tR62ljc0MWTtYZ_YR-tecQaAhhAbyGcOxs,53
18
+ epok_toolkit/messaging/whatsapp.py,sha256=TrMSiKzvnhWOotSDEGil1BGgOJ7jLK7h3MXKdW3zCJw,14114
19
+ epok_toolkit/messaging/whatsapp_instanced.py,sha256=4NVhxkjAi8cxDUwwLWjb6IwjJESSNoVjct8DMiW2SwI,1368
20
+ epok_toolkit/pdf/__init__.py,sha256=Scb1iOYnVIUEiUVHLNaPmcigyD-jOSBs3ws5RmolMKE,33
21
+ epok_toolkit/pdf/ticket_pdf.py,sha256=jktxxjEL4gFfnvXZW7P7y35bllOe9sJ3S-eOkgPcEQw,8913
22
+ epok_toolkit/pdf/fuentes/Kollektif-Bold.ttf,sha256=MiaucCL_aPGhbDl6M0xA2g2nf84MXHGciOd-XSw0XRo,78780
23
+ epok_toolkit/pdf/fuentes/Kollektif-BoldItalic.ttf,sha256=hPMiWYQ0fLFfOKvV_OXFCwAaHnNMYzic_DPiqWPQL5w,71912
24
+ epok_toolkit/pdf/fuentes/Kollektif-Italic.ttf,sha256=1CXPyw43il9u0tQ_7aRzsEaVtg3x3_oJ1vuMMvEaDp8,42048
25
+ epok_toolkit/pdf/fuentes/Kollektif.ttf,sha256=7wTLkVVNUm1giLjIZcWRUH5r2r3o0GjdKic4V1A-pNQ,51128
26
+ epok_toolkit/pdf/plantillas/Ticket_congrats.png,sha256=OSQhVR0j_nLHE6kSJ33BTR-77HM1fNAfJBe2EuX6wVk,157141
27
+ epok_toolkit/pdf/plantillas/Ticket_congrats2.png,sha256=1RBogBdo-8WSMpD3H73HoLgJtr5EC5oVKfOCIWOxPSo,373605
28
+ epok_toolkit-1.12.8.dist-info/licenses/LICENSE,sha256=iLDbGXdLSIOT5OsxzHCvtmxHtonE21GiFlS3LNkug4A,128
29
+ epok_toolkit-1.12.8.dist-info/METADATA,sha256=9vJv3rMmgvtbTBmIP_Pe5-I7wSQUExL4PP61nbl-Al4,2217
30
+ epok_toolkit-1.12.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
+ epok_toolkit-1.12.8.dist-info/top_level.txt,sha256=Wo72AqIFcfWwBGM5F5iGFw9PrO3WBnTSprFZIJk_pNg,13
32
+ epok_toolkit-1.12.8.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,5 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Fernando León
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy...
@@ -0,0 +1 @@
1
+ epok_toolkit