epok-toolkit 1.11.1__py3-none-any.whl → 1.12.0__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.

Potentially problematic release.


This version of epok-toolkit might be problematic. Click here for more details.

@@ -12,4 +12,22 @@ EMAIL_HOST_PASSWORD = "your-email-password"
12
12
  # Configuración de whatsApp
13
13
  API_KEY = "your-whatsapp-api-key"
14
14
  INSTANCE = "instance-id"
15
- SERVER_URL = "https://your-server-url.com/"
15
+ SERVER_URL = "https://your-server-url.com/"
16
+
17
+
18
+
19
+ # ---------- TEMPLATE DE CORREO ELECTRÓNICO ---------- #
20
+ TEMPLATES_SETTINGS = {
21
+ "company": {
22
+ "name": "Congrats",
23
+ "email": "info@compania.com",
24
+ "eslogan": "Eslogan sin definir",
25
+ "footer": "¡Nos vemos pronto!<br><em> El equipo de Congrats 🥳</em>"
26
+ },
27
+ "colors": {
28
+ "background": "#f9fafb",
29
+ "primary": "#4f46e5",
30
+ "text": "#374151",
31
+ "white": "#ffffff"
32
+ }
33
+ }
@@ -1,5 +1,6 @@
1
+ from .cache import *
1
2
  from .fields import *
2
3
  from .manager import *
4
+ from .models import *
3
5
  from .response import *
4
- from .cache import *
5
- from .viewsets import *
6
+ from .viewsets import *
@@ -0,0 +1,42 @@
1
+ from uuid import uuid4
2
+ from django.conf import settings
3
+ from django.db import models
4
+
5
+
6
+
7
+ class UUID4Mixin(models.Model):
8
+ id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
9
+
10
+ class Meta:
11
+ abstract = True
12
+
13
+
14
+
15
+ class TimeStampMixin(models.Model):
16
+ created_at = models.DateTimeField(auto_now_add=True)
17
+ updated_at = models.DateTimeField(auto_now=True)
18
+
19
+ class Meta:
20
+ abstract = True
21
+
22
+
23
+
24
+ class SoftDeleteMixin(models.Model):
25
+ is_deleted = models.BooleanField(default=False)
26
+
27
+ class Meta:
28
+ abstract = True
29
+ indexes = [
30
+ models.Index(fields=['is_deleted']),
31
+ ]
32
+
33
+
34
+
35
+ class CreatorsMixin(models.Model):
36
+ created_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True,
37
+ on_delete=models.CASCADE, related_name='%(class)s_created')
38
+ updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True,
39
+ on_delete=models.CASCADE, related_name='%(class)s_updated')
40
+
41
+ class Meta:
42
+ abstract = True
@@ -1,8 +1,13 @@
1
1
  # core/viewsets.py
2
2
  from rest_framework import viewsets
3
3
  from rest_framework.pagination import PageNumberPagination
4
+ from rest_framework.permissions import IsAuthenticated
4
5
 
5
6
  class DefaultPagination(PageNumberPagination):
7
+ """
8
+ Clase de paginación por defecto para los ViewSets.
9
+ Puedes personalizarla según tus necesidades.
10
+ """
6
11
  page_size = 10
7
12
  page_size_query_param = 'page_size'
8
13
  max_page_size = 100
@@ -10,32 +15,51 @@ class DefaultPagination(PageNumberPagination):
10
15
 
11
16
 
12
17
  class BaseOptimizedViewSet(viewsets.ModelViewSet):
13
- """
14
- ViewSet base que alterna entre .full() y .simple() automáticamente
15
- y permite serializers diferentes para list y detail.
16
- """
17
- pagination_class = DefaultPagination
18
+ queryset = None
19
+ write_serializer_class = None
20
+ update_serializer_class = None
18
21
  simple_serializer_class = None
19
22
  full_serializer_class = None
23
+ serializer_class = full_serializer_class
24
+ extensions_auto_optimize = True
25
+
26
+ permission_classes = [IsAuthenticated]
20
27
 
21
- # def get_queryset(self):
22
- # qs = super().get_queryset()
28
+ pagination_class = DefaultPagination
29
+
30
+ filterset_fields = []
31
+ search_fields = []
32
+ ordering_fields = []
33
+ ordering = []
34
+
35
+ def get_queryset(self):
36
+ qs = super().get_queryset()
37
+ model_cls = qs.model
38
+ manager = model_cls._default_manager
39
+
40
+ if hasattr(manager, 'simple') and self.action == 'list':
41
+ return manager.simple()
42
+ elif hasattr(manager, 'full'):
43
+ return manager.full()
44
+
45
+
46
+ def get_serializer_class(self):
47
+
48
+ match self.action:
49
+ case 'create':
50
+ return self.write_serializer_class
51
+ case 'update' | 'partial_update':
52
+ return self.update_serializer_class
53
+ case 'list':
54
+ return self.simple_serializer_class
55
+ case 'retrieve':
56
+ return self.full_serializer_class
57
+ case _:
58
+ return super().get_serializer_class()
59
+
60
+
61
+ def perform_create(self, serializer):
62
+ serializer.save(created_by=self.request.user, updated_by=self.request.user)
23
63
 
24
- # if not (hasattr(qs, 'full') and hasattr(qs, 'simple')):
25
- # raise NotImplementedError(
26
- # f"❌ El modelo {qs.model.__name__} no está usando un OptimizedManager con full() y simple()."
27
- # )
28
-
29
- # if self.action == 'list':
30
- # return qs.simple()
31
- # elif self.action in ['retrieve', 'update', 'partial_update']:
32
- # return qs.full()
33
- # return qs
34
-
35
- # def get_serializer_class(self):
36
- # # Cambia el serializer según la acción
37
- # if self.action == 'list' and self.simple_serializer_class:
38
- # return self.simple_serializer_class
39
- # if self.action in ['retrieve', 'update', 'partial_update'] and self.full_serializer_class:
40
- # return self.full_serializer_class
41
- # return super().get_serializer_class()
64
+ def perform_update(self, serializer):
65
+ serializer.save(updated_by=self.request.user)
@@ -1,29 +1,49 @@
1
1
  """
2
- notifications/method/email_templates.py
3
- Catálogo central de plantillas de correo con estilo corporativo “morado minimalista”.
4
- Cada plantilla declara las variables que necesita y se renderiza vía EmailTemplate.
2
+
5
3
  """
6
4
 
7
5
  from dataclasses import dataclass
8
6
  from typing import Dict, List
7
+ from django.conf import settings
8
+ import re
9
+
10
+
11
+ # ============================
12
+ # Config base
13
+ # ============================
14
+
15
+ TEMPLATES_SETTINGS = getattr(settings, "TEMPLATE_SETTINGS", {})
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class Colors:
20
+ BACKGROUND: str = TEMPLATES_SETTINGS.get("colors", {}).get("background", "#f9fafb")
21
+ PRIMARY: str = TEMPLATES_SETTINGS.get("colors", {}).get("primary", "#4f46e5")
22
+ TEXT: str = TEMPLATES_SETTINGS.get("colors", {}).get("text", "#374151")
23
+ WHITE: str = TEMPLATES_SETTINGS.get("colors", {}).get("white", "#ffffff")
24
+
25
+ @dataclass(frozen=True)
26
+ class Company:
27
+ name: str = TEMPLATES_SETTINGS.get("company", {}).get("name", "Congrats")
28
+ email: str = TEMPLATES_SETTINGS.get("company", {}).get("email", "info@compania.com")
29
+ eslogan: str = TEMPLATES_SETTINGS.get("company", {}).get("eslogan", "Eslogan sin definir")
30
+ footer: str = TEMPLATES_SETTINGS.get("company", {}).get("footer", "¡Nos vemos pronto!<br><em>El equipo de Congrats 🥳</em>")
31
+
9
32
 
10
33
 
11
- # ---------------------------------------------------------------------------#
12
- # Shared purple‑minimalist wrapper
13
- # ---------------------------------------------------------------------------#
34
+ # ============================
35
+ # Grupper (envoltorio HTML)
36
+ # ============================
37
+
14
38
  def wrap_html(content: str) -> str:
15
- """
16
- Envuelve el bloque `content` en el layout HTML corporativo de Congrats.
17
- """
18
39
  return f"""
19
- <div style="font-family: Arial, Helvetica, sans-serif; background-color: #f9fafb; padding: 24px;">
20
- <table width="100%" cellpadding="0" cellspacing="0" style="max-width: 600px; margin: 0 auto; background: #ffffff; border-radius: 8px; overflow: hidden;">
40
+ <div style="font-family: Arial, Helvetica, sans-serif; background-color: {Colors.BACKGROUND}; padding: 24px;">
41
+ <table width="100%" cellpadding="0" cellspacing="0" style="max-width: 600px; margin: 0 auto; background: {Colors.WHITE}; border-radius: 8px; overflow: hidden;">
21
42
  <tr>
22
- <td style="background: #4f46e5; padding: 20px 24px; text-align: center;">
23
- <h1 style="color: #ffffff; margin: 0; font-size: 24px;">Congrats 🎉</h1>
43
+ <td style="background: {Colors.PRIMARY}; padding: 20px 24px; text-align: center;">
44
+ <h1 style="color: {Colors.WHITE}; margin: 0; font-size: 24px;">{Company.name} 🎉</h1>
24
45
  </td>
25
46
  </tr>
26
-
27
47
  <tr>
28
48
  <td style="padding: 32px 24px;">
29
49
  {content}
@@ -34,24 +54,18 @@ def wrap_html(content: str) -> str:
34
54
  """
35
55
 
36
56
 
37
- # ---------------------------------------------------------------------------#
38
- # Core dataclasses
39
- # ---------------------------------------------------------------------------#
57
+ # ============================
58
+ # Tipos
59
+ # ============================
60
+
40
61
  @dataclass(frozen=True)
41
62
  class RenderedEmail:
42
63
  subject: str
43
64
  plain: str
44
65
  html: str
45
66
 
46
-
47
67
  @dataclass(frozen=True)
48
68
  class EmailTemplate:
49
- """
50
- subject – Cadena con placeholders, e.g. 'Hola {name}'
51
- plain_body – Versión texto plano
52
- html_body – HTML completo (usa wrap_html)
53
- required_vars – Lista de variables obligatorias
54
- """
55
69
  subject: str
56
70
  plain_body: str
57
71
  html_body: str
@@ -61,125 +75,99 @@ class EmailTemplate:
61
75
  missing = [v for v in self.required_vars if v not in context]
62
76
  if missing:
63
77
  raise ValueError(f"Faltan llaves en contexto: {missing}")
64
-
65
78
  return RenderedEmail(
66
79
  subject=self.subject.format(**context),
67
80
  plain=self.plain_body.format(**context),
68
81
  html=self.html_body.format(**context),
69
82
  )
83
+
84
+
85
+ # ============================
86
+ # Registro dinámico
87
+ # ============================
70
88
 
89
+ class TemplateRegistry:
90
+ def __init__(self):
91
+ self._templates: Dict[str, EmailTemplate] = {}
92
+
93
+ def _html_to_plain(self, html: str) -> str:
94
+ return re.sub(r"<[^>]*>", "", html).strip()
71
95
 
72
- # ---------------------------------------------------------------------------#
73
- # Template catalogue
74
- # ---------------------------------------------------------------------------#
75
- TEMPLATES: Dict[str, EmailTemplate] = {
76
- "password_reset": EmailTemplate(
77
- subject="🎉 Restablecimiento de Contraseña – Congrats 🎉",
78
- plain_body=(
79
- "¡Hola {name}! 🎉\n\n"
80
- "Para poner tu fiesta de contraseñas en marcha, haz clic aquí:\n"
81
- "{reset_link} 🎊\n\n"
82
- "Si no fuiste tú quien pidió cambio, relájate y ignora este correo. 😉\n\n"
83
- "¡Nos vemos en la pista de baile!\nEl equipo de Congrats 🥳"
84
- ),
85
- html_body=wrap_html(
86
- "<p style='font-size:16px;'>¡Hola <strong>{name}</strong>! 🎉</p>"
87
- "<p style='font-size:16px;margin:24px 0;'>"
88
- "Haz clic en el botón para restablecer tu contraseña y unirte a la celebración:</p>"
89
- "<p style='text-align:center;margin:24px 0;'>"
90
- "<a href='{reset_link}' style='display:inline-block;padding:12px 24px;font-size:16px;"
91
- "color:#ffffff;background-color:#4f46e5;text-decoration:none;border-radius:5px;'>"
92
- "🔒 Restablecer Contraseña 🎊</a>"
93
- "</p>"
94
- "<p style='font-size:16px;'>"
95
- "Si eso no funciona, copia y pega este enlace en tu navegador:<br>"
96
- "<span style='word-break:break-all;font-size:14px;'>{reset_link}</span>"
97
- "</p>"
98
- "<p style='font-size:16px;margin-top:24px;'>"
99
- "Si no solicitaste esto, tranquilo, nada cambió. 😌<br><br>"
100
- "¡A celebrar pronto!<br><em>El equipo de Congrats 🥳</em>"
101
- "</p>"
102
- ),
103
- required_vars=["name", "reset_link"],
104
- ),
96
+ def register_template(self, key: str, subject: str, html_body: str, required_vars: List[str], plain_body: str | None = None) -> None:
97
+ if key in self._templates:
98
+ raise ValueError(f"Template con clave '{key}' ya está registrado.")
99
+ plain = plain_body or self._html_to_plain(html_body)
100
+ wrapped_html = wrap_html(html_body)
101
+ self._templates[key] = EmailTemplate(
102
+ subject=subject,
103
+ plain_body=plain,
104
+ html_body=wrapped_html,
105
+ required_vars=required_vars
106
+ )
107
+
108
+ @property
109
+ def templates(self) -> Dict[str, EmailTemplate]:
110
+ return self._templates
111
+
112
+ registry = TemplateRegistry()
105
113
 
106
- "welcome": EmailTemplate(
107
- subject="¡Bienvenido a Congrats, {name}! 🎉🥳",
108
- plain_body=(
109
- "¡Hola {name}! 🎈\n\n"
110
- "Gracias por registrarte en Congrats. Prepárate para la mejor fiesta de eventos. 😎\n\n"
111
- "¡Nos vemos pronto!\nEl equipo de Congrats 🥳"
112
- ),
113
- html_body=wrap_html(
114
- "<p style='font-size:16px;'>¡Hola <strong>{name}</strong>! 🎈</p>"
115
- "<p style='font-size:16px;margin:24px 0;'>"
116
- "Gracias por unirte a <strong>Congrats 🎉</strong>. Prepárate para la mejor fiesta de eventos. 😎"
117
- "</p>"
118
- "<p style='font-size:16px;margin-top:24px;'>"
119
- "¡Nos vemos pronto!<br><em>El equipo de Congrats 🥳</em>"
120
- "</p>"
121
- ),
122
- required_vars=["name"],
114
+ # ============================
115
+ # Registrar plantillas base usando el mismo builder
116
+ # ============================
117
+
118
+ registry.register_template(
119
+ key="password_reset",
120
+ subject=f"🔑 Restablecimiento de Contraseña – {Company.name}",
121
+ html_body=(
122
+ "<p style='font-size:16px;'>¡Hola <strong>{name}</strong>! 🎉</p>"
123
+ "<p style='font-size:16px;margin:24px 0;'>Haz clic en el botón para restablecer tu contraseña:</p>"
124
+ "<p style='text-align:center;margin:24px 0;'>"
125
+ f"<a href='{{reset_link}}' style='display:inline-block;padding:12px 24px;font-size:16px;color:{Colors.WHITE};background-color:{Colors.PRIMARY};text-decoration:none;border-radius:5px;'>🔒 Restablecer Contraseña 🎊</a>"
126
+ "</p>"
127
+ "<p style='font-size:16px;'>Si no funciona, copia este enlace:<br>"
128
+ "<span style='word-break:break-all;font-size:14px;'>{reset_link}</span></p>"
129
+ f"<p style='font-size:16px;margin-top:24px;'>{Company.footer}</p>"
123
130
  ),
124
-
125
-
126
- "password_reset_success": EmailTemplate(
127
- subject="🔑 Contraseña restablecida – Congrats 🥳",
128
- plain_body=(
129
- "¡Hola {name}! 🔑\n\n"
130
- "Tu contraseña ya está lista para seguir la fiesta. 🎉\n\n"
131
- "Si no fuiste tú, ponte alerta. 😉\n\n"
132
- "Saludos festivos,\nEl equipo de Congrats 🥳"
133
- ),
134
- html_body=wrap_html(
135
- "<p style='font-size:16px;'>¡Hola <strong>{name}</strong>! 🔑</p>"
136
- "<p style='font-size:16px;margin:24px 0;'>"
137
- "Tu contraseña ha sido restablecida con éxito. Ahora vuelve a la pista de baile. 🎉"
138
- "</p>"
139
- "<p style='font-size:16px;margin-top:24px;'>"
140
- "Si no fuiste tú, ignora o avísanos. 🤔<br><em>El equipo de Congrats 🥳</em>"
141
- "</p>"
142
- ),
143
- required_vars=["name"],
131
+ required_vars=["name", "reset_link"]
132
+ )
133
+
134
+ registry.register_template(
135
+ key="welcome",
136
+ subject=f"🎉 Bienvenido a {Company.name}, {{name}}",
137
+ html_body=(
138
+ "<p style='font-size:16px;'>¡Hola <strong>{name}</strong>! 🎈</p>"
139
+ "<p style='font-size:16px;margin:24px 0;'>Gracias por unirte a <strong>{Company.name}</strong>.</p>"
140
+ f"<p style='font-size:16px;margin-top:24px;'>{Company.footer}</p>"
144
141
  ),
145
-
146
-
147
- "ticket_created": EmailTemplate(
148
- subject="🎫 ¡Tus tickets para {reunion_name} están listos! 🎉",
149
- plain_body=(
150
- "¡Hola {name}! 🎟️\n\n"
151
- "Has comprado {tickets} tickets para “{reunion_name}”. ¡A vivir la experiencia! 🎊\n\n"
152
- "¡Disfruta al máximo!\nEl equipo de Congrats 🥳"
153
- ),
154
- html_body=wrap_html(
155
- "<p style='font-size:16px;'>¡Hola <strong>{name}</strong>! 🎟️</p>"
156
- "<p style='font-size:16px;margin:24px 0;'>"
157
- "Tus {tickets} tickets para <strong>{reunion_name}</strong> están listos. ¡Nos vemos en la fiesta! 🎊"
158
- "</p>"
159
- "<p style='font-size:16px;margin-top:24px;'>"
160
- "¡Que lo disfrutes!<br><em>El equipo de Congrats 🥳</em>"
161
- "</p>"
162
- ),
163
- required_vars=["name", "reunion_name", "tickets"],
142
+ required_vars=["name"]
143
+ )
144
+
145
+ registry.register_template(
146
+ key="password_reset_success",
147
+ subject=f"🔑 Contraseña restablecida – {Company.name}",
148
+ html_body=(
149
+ "<p style='font-size:16px;'>¡Hola <strong>{name}</strong>! 🔑</p>"
150
+ "<p style='font-size:16px;margin:24px 0;'>Tu contraseña ha sido restablecida con éxito 🎉</p>"
151
+ f"<p style='font-size:16px;margin-top:24px;'>{Company.footer}</p>"
164
152
  ),
165
-
166
-
167
- "test_app_running": EmailTemplate(
168
- subject="🚀 Test OK – Congrats 🎉",
169
- plain_body=(
170
- "¡Hola {name}! 🚀\n\n"
171
- "Tu aplicación Congrats está activa y rockeando. 🤘\n\n"
172
- "Sigue brillando,\nEl equipo de Congrats 🥳"
173
- ),
174
- html_body=wrap_html(
175
- "<p style='font-size:16px;'>¡Hola <strong>{name}</strong>! 🚀</p>"
176
- "<p style='font-size:16px;margin:24px 0;'>"
177
- "La prueba de funcionamiento pasó. Tu app está lista para la fiesta. 🎉"
178
- "</p>"
179
- "<p style='font-size:16px;margin-top:24px;'>"
180
- "Sigue brillando.<br><em>El equipo de Congrats 🥳</em>"
181
- "</p>"
182
- ),
183
- required_vars=["name"],
153
+ required_vars=["name"]
154
+ )
155
+
156
+ registry.register_template(
157
+ key="test_app_running",
158
+ subject=f"🚀 Test OK – {Company.name}",
159
+ html_body=(
160
+ "<p style='font-size:16px;'>¡Hola <strong>{name}</strong>! 🚀</p>"
161
+ "<p style='font-size:16px;margin:24px 0;'>La prueba de funcionamiento pasó. Tu app está lista para rockear. 🎉</p>"
162
+ f"<p style='font-size:16px;margin-top:24px;'>{Company.footer}</p>"
184
163
  ),
164
+ required_vars=["name"]
165
+ )
166
+
167
+ # ============================
168
+ # Catálogo final
169
+ # ============================
170
+
171
+ TEMPLATES: Dict[str, EmailTemplate] = {
172
+ **registry.templates,
185
173
  }
@@ -4,10 +4,8 @@ from datetime import date, datetime
4
4
  from reportlab.pdfbase.ttfonts import TTFont
5
5
  from reportlab.pdfgen import canvas
6
6
  from dataclasses import dataclass
7
- from datetime import datetime
8
7
  from typing import Optional
9
8
  from PIL import Image
10
- import locale
11
9
  from uuid import uuid4, UUID
12
10
  import os
13
11
  import qrcode
@@ -15,8 +13,6 @@ from io import BytesIO
15
13
  import importlib.resources
16
14
 
17
15
 
18
- # pip install reportlab qrcode
19
-
20
16
 
21
17
  fuente_path = os.path.join(os.path.dirname(__file__), "fuentes", "Kollektif-Bold.ttf")
22
18
  pdfmetrics.registerFont(TTFont("kollektif", fuente_path))
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.4
2
+ Name: epok-toolkit
3
+ Version: 1.12.0
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>=0.1.0
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.11.2'
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!
@@ -1,31 +1,32 @@
1
1
  epok_toolkit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  epok_toolkit/apps.py,sha256=O3q3CcucJOHjlYIS0VgbKsbtim2hpng_FxpKEG_MlWs,486
3
- epok_toolkit/default_settings.py,sha256=hxsRmsAcjk4ar-pRz_X6AI17mJ3192Ljbx-xBuP5rdY,452
4
- epok_toolkit/django/__init__.py,sha256=k8Fs3tI_DAJPmhZ_fIcWwZWPog3KgYyg4-a2cJWwDYc,113
3
+ epok_toolkit/default_settings.py,sha256=GdDpwMPzRYNDTDK0zcQKUwgiGYVVhj1Pi1NGAxTE1Io,890
4
+ epok_toolkit/django/__init__.py,sha256=noq8F7jlKE_9ikkOt4VnoYHspxm6zGJ-_N2qF_MvLX4,136
5
5
  epok_toolkit/django/cache.py,sha256=R179_Hrlw1k0tsry9EN0zGl4-Of02L-VzE-xqFuXjaU,4107
6
6
  epok_toolkit/django/fields.py,sha256=-ajP5qx-4bt9Qz9yW48gTlinTxD1xWPKOEkslqx8cSM,1089
7
7
  epok_toolkit/django/manager.py,sha256=3MZcA9wQY4E1KD8XlgZQbzf4wlF9vA8Pntl_gKDWfpA,1350
8
+ epok_toolkit/django/models.py,sha256=E1zj2KetRGszksQvLk3HN5kWkYkEYzJRRmAUOaLJmw8,1090
8
9
  epok_toolkit/django/response.py,sha256=O8OHBaKgUQjBeYLLbgTTs669l_4D6swUgAOwswjc-88,1716
9
- epok_toolkit/django/viewsets.py,sha256=6OkQc7WgUQA-OlXPzm65w59vZOjlzQ0vuAorDwd8B5Q,1466
10
+ epok_toolkit/django/viewsets.py,sha256=qzq0TMmzVee65PLYWTsL2NJq0iZjBVUTZuFRatxfl3Q,1932
10
11
  epok_toolkit/django/utils/__init__.py,sha256=zDuoqm_eksZQpL-Bvd_q2KGMtSXfprBjf4TScZiwV6k,25
11
12
  epok_toolkit/django/utils/magic_link.py,sha256=GiDuy0kAGdYohGPlBL7rwpKPMpXuB1wJa1k5LTAOm4w,889
12
13
  epok_toolkit/email/__init__.py,sha256=pyJwysyVoq6DuYAG72fulsKFoOuAfjw3aBH7FhmYGHc,35
13
14
  epok_toolkit/email/email_async.py,sha256=oC0WowWNUpTpXdxo6hJag5gWUaStxI6VBEArEKQxXko,1521
14
15
  epok_toolkit/email/engine.py,sha256=IIifqRI9z76pHdrO5oSSZ25aP5txOTAgrj1JuVVPlMY,2387
15
- epok_toolkit/email/templates.py,sha256=uO3gYn2iiGxcjxioaG066gGPts1m-3C1Gp7XatGgVOg,7578
16
+ epok_toolkit/email/templates.py,sha256=8aQ5E3A0q1Uqc7WZf7Tm_ssj6DgAGNHiLsAd6mrZff4,6136
16
17
  epok_toolkit/messaging/__init__.py,sha256=lo0URCOr8tR62ljc0MWTtYZ_YR-tecQaAhhAbyGcOxs,53
17
18
  epok_toolkit/messaging/whatsapp.py,sha256=TrMSiKzvnhWOotSDEGil1BGgOJ7jLK7h3MXKdW3zCJw,14114
18
19
  epok_toolkit/messaging/whatsapp_instanced.py,sha256=q1CR--9N7IsR5MSf6Fps_l0zpHzTBFcJnB5nw14a28U,1414
19
20
  epok_toolkit/pdf/__init__.py,sha256=Scb1iOYnVIUEiUVHLNaPmcigyD-jOSBs3ws5RmolMKE,33
20
- epok_toolkit/pdf/ticket_pdf.py,sha256=ce21N7OyaSQkfZvOL4c0mEFv7XSGajlOzL-gNb8vBro,8989
21
+ epok_toolkit/pdf/ticket_pdf.py,sha256=jktxxjEL4gFfnvXZW7P7y35bllOe9sJ3S-eOkgPcEQw,8913
21
22
  epok_toolkit/pdf/fuentes/Kollektif-Bold.ttf,sha256=MiaucCL_aPGhbDl6M0xA2g2nf84MXHGciOd-XSw0XRo,78780
22
23
  epok_toolkit/pdf/fuentes/Kollektif-BoldItalic.ttf,sha256=hPMiWYQ0fLFfOKvV_OXFCwAaHnNMYzic_DPiqWPQL5w,71912
23
24
  epok_toolkit/pdf/fuentes/Kollektif-Italic.ttf,sha256=1CXPyw43il9u0tQ_7aRzsEaVtg3x3_oJ1vuMMvEaDp8,42048
24
25
  epok_toolkit/pdf/fuentes/Kollektif.ttf,sha256=7wTLkVVNUm1giLjIZcWRUH5r2r3o0GjdKic4V1A-pNQ,51128
25
26
  epok_toolkit/pdf/plantillas/Ticket_congrats.png,sha256=OSQhVR0j_nLHE6kSJ33BTR-77HM1fNAfJBe2EuX6wVk,157141
26
27
  epok_toolkit/pdf/plantillas/Ticket_congrats2.png,sha256=1RBogBdo-8WSMpD3H73HoLgJtr5EC5oVKfOCIWOxPSo,373605
27
- epok_toolkit-1.11.1.dist-info/licenses/LICENSE,sha256=iLDbGXdLSIOT5OsxzHCvtmxHtonE21GiFlS3LNkug4A,128
28
- epok_toolkit-1.11.1.dist-info/METADATA,sha256=UAJxsK-UvYi4xFUP4k1gQPNlK-iW3NszfIJHcef_J80,768
29
- epok_toolkit-1.11.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
30
- epok_toolkit-1.11.1.dist-info/top_level.txt,sha256=Wo72AqIFcfWwBGM5F5iGFw9PrO3WBnTSprFZIJk_pNg,13
31
- epok_toolkit-1.11.1.dist-info/RECORD,,
28
+ epok_toolkit-1.12.0.dist-info/licenses/LICENSE,sha256=iLDbGXdLSIOT5OsxzHCvtmxHtonE21GiFlS3LNkug4A,128
29
+ epok_toolkit-1.12.0.dist-info/METADATA,sha256=MPahT1yQzaukPNXmJmEAml3SGllxJMFQgrupyOoaK2M,2217
30
+ epok_toolkit-1.12.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
+ epok_toolkit-1.12.0.dist-info/top_level.txt,sha256=Wo72AqIFcfWwBGM5F5iGFw9PrO3WBnTSprFZIJk_pNg,13
32
+ epok_toolkit-1.12.0.dist-info/RECORD,,
@@ -1,25 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: epok-toolkit
3
- Version: 1.11.1
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>=0.1.0
19
- Requires-Dist: PyJWT>=2.10.1
20
- Dynamic: license-file
21
-
22
- # EPOK Toolkit
23
-
24
- Esta libreria contiene utilidades
25
-