djgentelella 0.3.29__py3-none-any.whl → 0.4.30__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.
Files changed (72) hide show
  1. djgentelella/__init__.py +1 -1
  2. djgentelella/admin.py +4 -3
  3. djgentelella/fields/files.py +27 -1
  4. djgentelella/firmador_digital/__init__.py +0 -0
  5. djgentelella/firmador_digital/config/__init__.py +0 -0
  6. djgentelella/firmador_digital/config/asgi_config.py +32 -0
  7. djgentelella/firmador_digital/config/asgi_worker.py +5 -0
  8. djgentelella/firmador_digital/config/websocket_urls.py +4 -0
  9. djgentelella/firmador_digital/consumers/__init__.py +0 -0
  10. djgentelella/firmador_digital/consumers/pdf_render.py +8 -0
  11. djgentelella/firmador_digital/consumers/sign.py +148 -0
  12. djgentelella/firmador_digital/forms.py +28 -0
  13. djgentelella/firmador_digital/gunicorn/config_asgi.py +12 -0
  14. djgentelella/firmador_digital/gunicorn/config_wsgi.py +13 -0
  15. djgentelella/firmador_digital/models.py +33 -0
  16. djgentelella/firmador_digital/signvalue_utils.py +41 -0
  17. djgentelella/firmador_digital/utils.py +154 -0
  18. djgentelella/firmador_digital/viewsets.py +77 -0
  19. djgentelella/locale/es/LC_MESSAGES/django.mo +0 -0
  20. djgentelella/locale/es/LC_MESSAGES/django.po +103 -6
  21. djgentelella/locale/es/LC_MESSAGES/djangojs.mo +0 -0
  22. djgentelella/locale/es/LC_MESSAGES/djangojs.po +107 -8
  23. djgentelella/management/commands/createbasejs.py +3 -0
  24. djgentelella/management/commands/loaddevstatic.py +18 -0
  25. djgentelella/migrations/0013_usersignatureconfig.py +25 -0
  26. djgentelella/serializers/firmador_digital.py +103 -0
  27. djgentelella/settings.py +4 -2
  28. djgentelella/static/djgentelella.readonly.vendors.min.css +1 -1
  29. djgentelella/static/djgentelella.readonly.vendors.min.js +1 -1
  30. djgentelella/static/djgentelella.vendors.header.min.js +1 -1
  31. djgentelella/static/djgentelella.vendors.min.css +2 -2
  32. djgentelella/static/gentelella/css/pdfviewer.css +147 -0
  33. djgentelella/static/gentelella/images/firmador.ico +0 -0
  34. djgentelella/static/gentelella/js/base/digital_signature.js +945 -0
  35. djgentelella/static/gentelella/js/base.js +947 -0
  36. djgentelella/static/gentelella/js/datatables.js +2 -2
  37. djgentelella/static/gentelella/js/widgets.js +72 -59
  38. djgentelella/static/vendors/friconix/friconix.js +1 -1
  39. djgentelella/static/vendors/pdfjs/images/altText_add.svg +3 -0
  40. djgentelella/static/vendors/pdfjs/images/altText_disclaimer.svg +3 -0
  41. djgentelella/static/vendors/pdfjs/images/altText_done.svg +3 -0
  42. djgentelella/static/vendors/pdfjs/images/altText_spinner.svg +30 -0
  43. djgentelella/static/vendors/pdfjs/images/altText_warning.svg +3 -0
  44. djgentelella/static/vendors/pdfjs/images/cursor-editorFreeHighlight.svg +6 -0
  45. djgentelella/static/vendors/pdfjs/images/cursor-editorFreeText.svg +3 -0
  46. djgentelella/static/vendors/pdfjs/images/cursor-editorInk.svg +4 -0
  47. djgentelella/static/vendors/pdfjs/images/cursor-editorTextHighlight.svg +8 -0
  48. djgentelella/static/vendors/pdfjs/images/editor-toolbar-delete.svg +5 -0
  49. djgentelella/static/vendors/pdfjs/images/loading-icon.gif +0 -0
  50. djgentelella/static/vendors/pdfjs/images/messageBar_closingButton.svg +3 -0
  51. djgentelella/static/vendors/pdfjs/images/messageBar_warning.svg +3 -0
  52. djgentelella/static/vendors/pdfjs/images/toolbarButton-editorHighlight.svg +6 -0
  53. djgentelella/static/vendors/pdfjs/images/toolbarButton-menuArrow.svg +3 -0
  54. djgentelella/static/vendors/timeline/css/timeline.css +1 -1
  55. djgentelella/static/vendors/timeline/js/timeline.js +1 -1
  56. djgentelella/templates/gentelella/registration/login.html +38 -42
  57. djgentelella/templates/gentelella/statics/javascript.html +75 -61
  58. djgentelella/templates/gentelella/statics/stylesheets.html +11 -10
  59. djgentelella/templates/gentelella/widgets/addtreeselect.html +1 -1
  60. djgentelella/templates/gentelella/widgets/chunkedupload.html +2 -2
  61. djgentelella/templates/gentelella/widgets/digital_signature.html +208 -0
  62. djgentelella/templates/gentelella/widgets/file.html +15 -16
  63. djgentelella/templatetags/gtsettings.py +4 -0
  64. djgentelella/urls.py +1 -1
  65. djgentelella/widgets/core.py +5 -2
  66. djgentelella/widgets/digital_signature.py +116 -0
  67. {djgentelella-0.3.29.dist-info → djgentelella-0.4.30.dist-info}/METADATA +18 -20
  68. {djgentelella-0.3.29.dist-info → djgentelella-0.4.30.dist-info}/RECORD +72 -35
  69. {djgentelella-0.3.29.dist-info → djgentelella-0.4.30.dist-info}/WHEEL +1 -1
  70. {djgentelella-0.3.29.dist-info → djgentelella-0.4.30.dist-info}/AUTHORS +0 -0
  71. {djgentelella-0.3.29.dist-info → djgentelella-0.4.30.dist-info}/LICENSE.txt +0 -0
  72. {djgentelella-0.3.29.dist-info → djgentelella-0.4.30.dist-info}/top_level.txt +0 -0
djgentelella/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.3.29'
1
+ __version__ = '0.4.30'
djgentelella/admin.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from django.contrib import admin
2
2
 
3
+ from djgentelella.firmador_digital.models import UserSignatureConfig
3
4
  from djgentelella.models import MenuItem, Help, GentelellaSettings, Notification, \
4
5
  ChunkedUpload
5
6
  from djgentelella.models import PermissionsCategoryManagement
@@ -9,7 +10,6 @@ from djgentelella.utils import clean_cache
9
10
  class MenuAdmin(admin.ModelAdmin):
10
11
  filter_horizontal = ['permission']
11
12
 
12
-
13
13
  class GentelellaSettingsAdmin(admin.ModelAdmin):
14
14
  list_display = ['key', 'value']
15
15
  search_fields = ['key']
@@ -21,18 +21,19 @@ class GentelellaSettingsAdmin(admin.ModelAdmin):
21
21
 
22
22
  clean_settings_cache.short_description = "Clean settings cache"
23
23
 
24
-
25
24
  class NotificationAdmin(admin.ModelAdmin):
26
25
  list_display = ['description', 'state', 'message_type']
27
26
  list_editable = ['state']
28
27
 
29
-
30
28
  class ChunkedUploadAdmin(admin.ModelAdmin):
31
29
  list_display = ('upload_id', 'filename', 'status', 'created_on')
32
30
  search_fields = ('filename', 'filename')
33
31
  list_filter = ('status',)
34
32
 
33
+ class UserSignatureConfigAdmin(admin.ModelAdmin):
34
+ list_display = ('id', 'user', 'config')
35
35
 
36
+ admin.site.register(UserSignatureConfig, UserSignatureConfigAdmin)
36
37
  admin.site.register(ChunkedUpload, ChunkedUploadAdmin)
37
38
  admin.site.register(MenuItem, MenuAdmin)
38
39
  admin.site.register(Help)
@@ -7,6 +7,7 @@ from django.utils.text import slugify
7
7
  from django.utils.translation import gettext_lazy as _
8
8
  from rest_framework import serializers
9
9
 
10
+ from djgentelella.firmador_digital.signvalue_utils import ValueDSParser
10
11
  from djgentelella.models import ChunkedUpload
11
12
 
12
13
 
@@ -68,7 +69,7 @@ class GTBase64FileField(serializers.FileField):
68
69
 
69
70
 
70
71
  class ChunkedFileField(serializers.FileField):
71
-
72
+
72
73
  def parse_value(self, value):
73
74
  """
74
75
  Parses the given value and returns the parsed result.
@@ -123,3 +124,28 @@ class ChunkedFileField(serializers.FileField):
123
124
  if data and value.name and value.storage.exists(value.name):
124
125
  name = Path(value.name).name
125
126
  return {'name': value.name, 'url': data, 'display_name': name}
127
+
128
+
129
+ class DigitalSignatureField(serializers.FileField, ValueDSParser):
130
+ def to_internal_value(self, data):
131
+ """
132
+ Converts the given data to internal value representation.
133
+
134
+ Args:
135
+ data (str): The data to be converted.
136
+
137
+ Returns:
138
+ The internal value representation of the data, or None if the data is invalid
139
+ or does not contain the required attributes.
140
+ """
141
+ dev = None
142
+ jsondata = self.get_json_file(data)
143
+ if jsondata:
144
+ return self.get_filefield(jsondata)
145
+ return dev
146
+
147
+ def to_representation(self, value):
148
+ data = super().to_representation(value)
149
+ if data and value.name and value.storage.exists(value.name):
150
+ name = Path(value.name).name
151
+ return {'name': value.name, 'url': data, 'display_name': name}
File without changes
File without changes
@@ -0,0 +1,32 @@
1
+
2
+ import os
3
+
4
+ from django.core.asgi import get_asgi_application
5
+ from channels.routing import ProtocolTypeRouter, URLRouter
6
+ from channels.auth import AuthMiddlewareStack
7
+ from djgentelella.firmador_digital.config.websocket_urls import websocket_urlpatterns
8
+
9
+ class AsgiConfig:
10
+
11
+ def __init__(self, settings_module: str):
12
+ # Configuramos la variable de entorno con el módulo de settings
13
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings_module)
14
+ # variable de la aplicación
15
+ self.application = self._build_application()
16
+
17
+ def _build_application(self):
18
+ """
19
+ Construye y retorna la aplicación ASGI compuesta.
20
+ """
21
+ return ProtocolTypeRouter({
22
+ "http": get_asgi_application(),
23
+ "websocket": AuthMiddlewareStack(
24
+ URLRouter(websocket_urlpatterns)
25
+ ),
26
+ })
27
+
28
+ def __call__(self, scope, receive, send):
29
+ """
30
+ Permite que la instancia sea llamada como una aplicación ASGI.
31
+ """
32
+ return self.application(scope, receive, send)
@@ -0,0 +1,5 @@
1
+ from uvicorn_worker import UvicornWorker as BaseUvicornWorker
2
+
3
+
4
+ class DjgentelellaUvicornWorker(BaseUvicornWorker):
5
+ CONFIG_KWARGS = {"lifespan": "off", "loop": "auto", "http": "auto"}
@@ -0,0 +1,4 @@
1
+ from django.urls import path
2
+ from djgentelella.firmador_digital.consumers.sign import SignConsumer
3
+
4
+ websocket_urlpatterns = [path("async/sign_document", SignConsumer.as_asgi())]
File without changes
@@ -0,0 +1,8 @@
1
+ from rest_framework.renderers import BaseRenderer
2
+
3
+ class PDFRenderer(BaseRenderer):
4
+ media_type = 'application/pdf'
5
+ format = 'pdf'
6
+
7
+ def render(self, data, accepted_media_type=None, renderer_context=None):
8
+ return data
@@ -0,0 +1,148 @@
1
+ import base64
2
+ import logging
3
+ from io import BytesIO
4
+
5
+ from PIL import Image
6
+ from PIL.Image import Resampling
7
+ from channels.generic.websocket import JsonWebsocketConsumer
8
+ from django.contrib.staticfiles import finders
9
+ from django.utils.translation import gettext_lazy as _
10
+
11
+ from djgentelella.firmador_digital.utils import RemoteSignerClient
12
+ from djgentelella.serializers.firmador_digital import (
13
+ WSRequest,
14
+ InitialSignatureSerializer,
15
+ CompleteSignatureSerializer,
16
+ )
17
+
18
+ logger = logging.getLogger("djgentelella")
19
+
20
+
21
+ class SignConsumer(JsonWebsocketConsumer):
22
+ def __init__(self, *args, **kwargs):
23
+ super().__init__(*args, **kwargs)
24
+ self.data = None
25
+
26
+ def connect(self):
27
+ self.accept()
28
+ self.signclient = RemoteSignerClient(None)
29
+
30
+ def get_serializer(self, content):
31
+ serializer = WSRequest(data=content)
32
+
33
+ if serializer.is_valid():
34
+ if serializer.validated_data["action"] == "initial_signature":
35
+ return InitialSignatureSerializer(data=content)
36
+ if serializer.validated_data["action"] == "complete_signature":
37
+ return CompleteSignatureSerializer(data=content)
38
+
39
+ def disconnect(self, close_code):
40
+ super().disconnect(close_code)
41
+
42
+ def receive_json(self, content, **kwargs):
43
+ """
44
+ Called with decoded JSON content.
45
+ """
46
+ try:
47
+ serializer = self.get_serializer(content)
48
+
49
+ if serializer.is_valid():
50
+
51
+ match serializer.validated_data["action"]:
52
+ case "initial_signature":
53
+ self.do_initial_signature(serializer)
54
+ case "complete_signature":
55
+ self.do_complete_signature(serializer)
56
+ case _:
57
+ self.do_default(serializer)
58
+ else:
59
+ # errors when serializing data
60
+ self.send_json({
61
+ "result": False,
62
+ "error": str(_("Invalid request.")),
63
+ "details": serializer.errors,
64
+ "status": 400,
65
+ "code": 11,
66
+ })
67
+ logger.error("Invalid request.")
68
+
69
+ except Exception as e:
70
+ # uncontrolled data serializing errors
71
+ self.send_json({
72
+ "result": False,
73
+ "error": str(_("An unexpected error occurred.")),
74
+ "details": str(e),
75
+ "status": 500,
76
+ "code": 999,
77
+ })
78
+ logger.error("An unexpected error occurred.", exc_info=e)
79
+
80
+ def do_initial_signature(self, serializer):
81
+ signer = RemoteSignerClient(self.scope["user"])
82
+
83
+ # data for the request to Firmador server
84
+ response = signer.send_document_to_sign(
85
+ instance=serializer.validated_data["instance"],
86
+ usertoken=serializer.validated_data["card"],
87
+ docsettings=serializer.validated_data["docsettings"],
88
+ )
89
+
90
+ # remove signer image
91
+ if "imageIcon" in response:
92
+ del response["imageIcon"]
93
+
94
+ # about writing the answer to add b64image
95
+ logo_url = serializer.validated_data["logo_url"]
96
+ if "b64image" in response and logo_url:
97
+ response["b64image"] = self.get_logo_base64(logo_url)
98
+
99
+ self.send_json(response)
100
+
101
+ def do_complete_signature(self, serializer):
102
+ try:
103
+ signer = RemoteSignerClient(self.scope["user"])
104
+ data = dict(serializer.validated_data)
105
+ response = signer.complete_signature(data)
106
+ self.send_json({"result": response})
107
+ except Exception as e:
108
+ logger.error("Complete the signature fail", exc_info=e)
109
+
110
+ def do_default(self, serializer):
111
+ pass
112
+
113
+ def get_logo_base64(self, path_logo, target_width=128):
114
+ if not path_logo:
115
+ print("Do not have logo")
116
+ return ""
117
+
118
+ # Si la ruta contiene el prefijo /static/, lo removemos
119
+ if path_logo.startswith('/static/'):
120
+ path_logo = path_logo[len('/static/'):]
121
+
122
+ # Buscar la ruta real del archivo en los directorios estáticos
123
+ real_path = finders.find(path_logo)
124
+ if not real_path:
125
+ print("Logo file not found in static directories.")
126
+ return ""
127
+
128
+ try:
129
+ with Image.open(real_path) as img:
130
+ # Calcular la altura de forma proporcional
131
+ ratio = target_width / float(img.width)
132
+ target_height = int(img.height * ratio)
133
+
134
+ # Redimensionar
135
+ img = img.resize((target_width, target_height), Resampling.LANCZOS)
136
+
137
+ # Guardar en memoria
138
+ buffer = BytesIO()
139
+ img.save(buffer, format='PNG')
140
+
141
+ # Convertir a base64
142
+ b64_logo = base64.b64encode(buffer.getvalue()).decode()
143
+ return b64_logo
144
+
145
+ except Exception as e:
146
+ print("Error leyendo logo:", e)
147
+ logger.error("Reading logo fail", exc_info=e)
148
+ return ""
@@ -0,0 +1,28 @@
1
+ import logging
2
+
3
+ from django import forms
4
+ from django.core.exceptions import ValidationError
5
+ from django.utils.translation import gettext_lazy as _
6
+
7
+ from djgentelella.firmador_digital.signvalue_utils import ValueDSParser
8
+ from djgentelella.forms.forms import GTForm
9
+ from djgentelella.widgets import core as genwidgets
10
+
11
+ logger = logging.getLogger('djgentelella')
12
+
13
+
14
+ class CardForm(GTForm):
15
+ card = forms.ChoiceField(choices=[], widget=genwidgets.Select, label=_("Card"))
16
+
17
+
18
+ class RenderValueForm(GTForm, ValueDSParser):
19
+ value = forms.CharField(required=True)
20
+
21
+ def clean_value(self):
22
+ value = self.cleaned_data['value']
23
+ jsondata = self.get_json_file(value)
24
+ if not jsondata:
25
+ raise ValidationError(
26
+ _("Invalid value not encoded as b64 o json parser error"),
27
+ code="invalid")
28
+ return jsondata
@@ -0,0 +1,12 @@
1
+ import os
2
+
3
+ from django.conf import settings
4
+
5
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE",
6
+ os.getenv("DJANGO_ASETTINGS_MODULE", "demo.asettings"))
7
+
8
+ bind = getattr(settings, "UVICORN_BIND", "127.0.0.1:9022")
9
+ wsgi_app = getattr(settings, "GUNICORN_ASGI_APP", "demo.asgi:application")
10
+ workers = getattr(settings, "UVICORN_WORKER", 1)
11
+ worker_class = getattr(settings, "UVICORN_WORKER_CLASS",
12
+ "demo.asgi_worker.UvicornWorker")
@@ -0,0 +1,13 @@
1
+ import os
2
+
3
+ from django.conf import settings
4
+
5
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings")
6
+
7
+ bind = getattr(settings, "GUNICORN_BIND", "unix:/run/supervisor/gunicorn_wsgi.sock")
8
+ wsgi_app = getattr(settings, "GUNICORN_WSGI_APP", "demo.wsgi:application")
9
+ workers = getattr(settings, "GUNICORN_WORKERS", 1)
10
+ worker_class = getattr(settings, "GUNICORN_WORKER_CLASS",
11
+ "demo.asgi_worker.UvicornWorker")
12
+ user = getattr(settings, "GUNICORN_USER", "demo")
13
+ group = getattr(settings, "GUNICORN_GROUP", "demo")
@@ -0,0 +1,33 @@
1
+ from django.contrib.auth import get_user_model
2
+ from django.db import models
3
+
4
+ def get_signature_default():
5
+ return {
6
+ "backgroundColor": "transparente",
7
+ "cAdESLevel": "LTA",
8
+ "contact": "",
9
+ "country": "CR",
10
+ "dateFormat": "dd/MM/yyyy hh\:mm\:ss a",
11
+ "defaultSignMessage": "Esta es una representación gráfica únicamente,\nverifique la validez de la firma.",
12
+ "font": "Nimbus Sans Regular",
13
+ "fontAlignment": "RIGHT",
14
+ "fontColor": "000000",
15
+ "fontSize": "7",
16
+ "image": "",
17
+ "language": "es",
18
+ "pAdESLevel": "LTA",
19
+ "place": "",
20
+ "portNumber": "3516",
21
+ "reason": "",
22
+ "signHeight": "33",
23
+ "signWidth": "133",
24
+ "signX": "40",
25
+ "signY": "60",
26
+ "xAdESLevel": "LTA",
27
+ "isVisibleSignature": False,
28
+ }
29
+
30
+
31
+ class UserSignatureConfig(models.Model):
32
+ user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
33
+ config = models.JSONField(default=get_signature_default)
@@ -0,0 +1,41 @@
1
+ import base64
2
+ import json
3
+ import logging
4
+
5
+ from django.contrib.contenttypes.models import ContentType
6
+
7
+ from djgentelella.models import ChunkedUpload
8
+
9
+ logger = logging.getLogger("djgentelella")
10
+
11
+
12
+ class ValueDSParser:
13
+
14
+ def get_file_from_token(self, token):
15
+ tmpupload = ChunkedUpload.objects.filter(upload_id=token).first()
16
+ dev = None
17
+ if tmpupload:
18
+ dev = tmpupload.get_uploaded_file()
19
+ return dev
20
+
21
+ def get_json_file(self, value):
22
+ jsondata = None
23
+ try:
24
+ instance = base64.b64decode(value.encode())
25
+ jsondata = json.loads(instance.decode())
26
+ except Exception as e:
27
+ logger.error("Validation of value on digital signature fail", exc_info=e)
28
+ return jsondata
29
+
30
+ def get_filefield(self, jsondata):
31
+ if 'field_name' in jsondata:
32
+ ccinstance = ContentType.objects.get_for_id(jsondata['contenttype'])
33
+ instance = ccinstance.get_object_for_this_type(pk=jsondata['pk'])
34
+ return self.get_instance_file(instance, jsondata['field_name'])
35
+ if 'token' in jsondata:
36
+ return self.get_file_from_token(jsondata['token'])
37
+ return None
38
+
39
+ def get_instance_file(self, instance, fieldname):
40
+ file_path = getattr(instance, fieldname)
41
+ return file_path
@@ -0,0 +1,154 @@
1
+ import base64
2
+ import io
3
+ import logging
4
+
5
+ import requests
6
+ from django.conf import settings
7
+ from django.core.files.base import ContentFile
8
+ from django.utils.timezone import now
9
+ from django.utils.translation import gettext_lazy as _
10
+ from requests import HTTPError, Timeout, RequestException
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class RemoteSignerClient:
16
+ def __init__(self, user):
17
+ self.user = user
18
+
19
+ def load_settings(self, docsettings):
20
+ from djgentelella.firmador_digital.models import UserSignatureConfig
21
+ sc = UserSignatureConfig.objects.filter(user=self.user).first()
22
+ settings = {}
23
+ if sc:
24
+ settings.update(sc.config)
25
+ settings.update(docsettings)
26
+ return settings
27
+
28
+ def load_certificate(self, certtoken):
29
+ return certtoken["certificate"]
30
+
31
+ def get_b64document(self, value):
32
+
33
+ return base64.b64encode(value).decode()
34
+
35
+ def send_document_to_sign(self, instance, usertoken, docsettings):
36
+ b64doc = self.get_b64document(instance['value'])
37
+
38
+ files = {
39
+ "b64Document": b64doc,
40
+ "DocumentExtension": ".pdf",
41
+ "settings": self.load_settings(docsettings),
42
+ "certToken": self.load_certificate(usertoken),
43
+ }
44
+
45
+ response = requests.post(
46
+ settings.FIRMADOR_SIGN_URL, json=files
47
+ )
48
+ return response.json()
49
+
50
+ def _finalize_signature(self, data_to_sign, task):
51
+ result = None
52
+ error_msg = _("An unexpected error occurred during the signing process.")
53
+
54
+ try:
55
+ logger.info("Sending request to signing service")
56
+ response = requests.post(settings.FIRMADOR_SIGN_COMPLETE, json=data_to_sign)
57
+ response.raise_for_status()
58
+ logger.info("Successfully finalized signature for the task.")
59
+ result = response.json()
60
+ except HTTPError as errh:
61
+ logger.error(
62
+ "HTTPError during signature finalization for task %s: %s",
63
+ task,
64
+ str(errh),
65
+ exc_info=errh
66
+ )
67
+ error_msg = _(
68
+ "An error occurred while communicating with the signing service."
69
+ )
70
+ result = self.get_error_response(error_msg, str(errh), 502, 2)
71
+
72
+ except ConnectionError as errc:
73
+ logger.error(
74
+ "ConnectionError during signature finalization for task %s: %s",
75
+ task,
76
+ str(errc),
77
+ exc_info=errc
78
+ )
79
+ error_msg = _("Unable to connect to the signing service.")
80
+ result = self.get_error_response(error_msg, str(errc), 503, 2)
81
+ except Timeout as errt:
82
+ logger.error(
83
+ "Timeout during signature finalization for task %s: %s",
84
+ task,
85
+ str(errt),
86
+ exc_info=errt
87
+ )
88
+ error_msg = _("The request to the signing service timed out.")
89
+ result = self.get_error_response(error_msg, str(errt), 408, 12)
90
+ except RequestException as errr:
91
+ logger.error(
92
+ "RequestException during signature finalization for task %s: %s",
93
+ task,
94
+ str(errr),
95
+ exc_info=errr
96
+ )
97
+ error_msg = _("An exception ocurred el request to the signing service.")
98
+ result = self.get_error_response(error_msg, str(errr), 500, 999)
99
+ except Exception as erre:
100
+ logger.error(
101
+ "Exception during signature finalization for task %s: %s",
102
+ task,
103
+ str(erre),
104
+ exc_info=erre
105
+ )
106
+ error_msg = _("An unexpected error occurred during the signing process.")
107
+ result = self.get_error_response(error_msg, str(erre), 500, 999)
108
+
109
+ return result
110
+
111
+ def get_error_response(self, error_msg, details, status, code):
112
+ return {
113
+ "result": False,
114
+ "error": error_msg,
115
+ "details": details,
116
+ "status": status,
117
+ "code": code,
118
+ }
119
+
120
+ def complete_signature(self, data_to_sign):
121
+ from djgentelella.models import ChunkedUpload
122
+ datatosign = {
123
+ "signature": data_to_sign["signature"],
124
+ "documentid": data_to_sign["documentid"],
125
+ "certificate": data_to_sign["certificate"],
126
+ }
127
+
128
+ instance = data_to_sign["instance"]
129
+ doc_info = self._finalize_signature(datatosign, instance)
130
+
131
+ if doc_info:
132
+ name = "%s_%s_%s.pdf" % (
133
+ instance['pk'],
134
+ self.user.pk,
135
+ now().strftime("%m%d%Y"),
136
+ )
137
+ chunk = self.convert_to_django_file(
138
+ doc_info["bytes"],
139
+ name,
140
+ )
141
+ chfile = ChunkedUpload.objects.create(
142
+ filename=name,
143
+ file=ContentFile(b'', name=name),
144
+ completed_on=now(),
145
+ created_on=now(),
146
+ user=self.user,
147
+ status=2 # this is complete, but I can import constants here
148
+ )
149
+ chfile.append_chunk(chunk, save=True)
150
+ return chfile.upload_id
151
+ return False
152
+
153
+ def convert_to_django_file(self, data, name):
154
+ return io.BytesIO(base64.b64decode(data))
@@ -0,0 +1,77 @@
1
+ from django.contrib.auth.decorators import login_required
2
+ from django.contrib.contenttypes.models import ContentType
3
+ from django.utils.decorators import method_decorator
4
+ from rest_framework import status
5
+ from rest_framework.response import Response
6
+ from rest_framework.views import APIView
7
+
8
+ from djgentelella.firmador_digital.consumers.pdf_render import PDFRenderer
9
+ from djgentelella.firmador_digital.forms import RenderValueForm
10
+ from djgentelella.models import ChunkedUpload
11
+
12
+
13
+ @method_decorator(login_required, name='dispatch')
14
+ class DigitalSignatureRenderFileAPIView(APIView):
15
+ renderer_classes = [PDFRenderer]
16
+
17
+ form_class = RenderValueForm
18
+
19
+ def check_user(self, cc, pk, user, token=None):
20
+ return True
21
+
22
+ def get_token_from_form(self, form):
23
+ jsondata = form.cleaned_data['value']
24
+ if 'token' in jsondata:
25
+ return jsondata['token']
26
+ return None
27
+
28
+ def get_file_from_token(self, token):
29
+ tmpupload = ChunkedUpload.objects.filter(upload_id=token).first()
30
+ dev = None
31
+ if tmpupload:
32
+ dev = tmpupload.get_uploaded_file()
33
+ return dev
34
+
35
+ def file_document(self, form, cc, pk):
36
+ jsondata = form.cleaned_data['value']
37
+ if 'field_name' in jsondata:
38
+ ccinstance = ContentType.objects.get_for_id(cc)
39
+ instance = ccinstance.get_object_for_this_type(pk=pk)
40
+ return self.get_instance_file(instance, jsondata['field_name'])
41
+ if 'token' in jsondata:
42
+ return self.get_file_from_token(jsondata['token'])
43
+ return None
44
+
45
+ def get(self, request, cc, pk, format=None):
46
+ """
47
+ Retrieve and serve the PDF file associated with a DigitalSignature instance.
48
+
49
+ Parameters:
50
+ request (HttpRequest): The incoming HTTP request.
51
+ pk (int or str): The primary key of the DigitalSignature instance.
52
+ format (optional): The format specifier (default is None).
53
+
54
+ Returns:
55
+ Response: An HTTP response containing the PDF file data with the
56
+ content type 'application/pdf'. If the DigitalSignature instance
57
+ is not found, returns a 404 Not Found response.
58
+ """
59
+ form = self.form_class(data=request.GET)
60
+ if not form.is_valid():
61
+ return Response({"detail": "File not found"},
62
+ status=status.HTTP_404_NOT_FOUND)
63
+ token = self.get_token_from_form(form)
64
+ if not self.check_user(cc, pk, request.user, token=token):
65
+ return Response({"detail": "File not found"},
66
+ status=status.HTTP_403_FORBIDDEN)
67
+
68
+ file_data = self.file_document(form, cc, pk)
69
+
70
+ return Response(file_data, content_type='application/pdf')
71
+
72
+ def get_instance_file(self, instance, fieldname):
73
+ file_path = getattr(instance, fieldname)
74
+ file_data = None
75
+ with open(file_path.path, 'rb') as f:
76
+ file_data = f.read()
77
+ return file_data