djgentelella 0.4.30__py3-none-any.whl → 0.5.1__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.
- djgentelella/__init__.py +4 -1
- djgentelella/admin.py +41 -2
- djgentelella/firmador_digital/consumers/sign.py +24 -3
- djgentelella/firmador_digital/forms.py +140 -0
- djgentelella/firmador_digital/models.py +19 -24
- djgentelella/firmador_digital/utils.py +11 -0
- djgentelella/firmador_digital/views.py +31 -0
- djgentelella/firmador_digital/viewsets.py +15 -4
- djgentelella/forms/forms.py +10 -2
- djgentelella/groute.py +4 -1
- djgentelella/history/api.py +145 -0
- djgentelella/history/filterset.py +23 -0
- djgentelella/history/serializers.py +50 -0
- djgentelella/history/utils.py +70 -0
- djgentelella/locale/es/LC_MESSAGES/django.mo +0 -0
- djgentelella/locale/es/LC_MESSAGES/django.po +105 -0
- djgentelella/locale/es/LC_MESSAGES/djangojs.mo +0 -0
- djgentelella/locale/es/LC_MESSAGES/djangojs.po +46 -0
- djgentelella/migrations/0014_trash.py +31 -0
- djgentelella/migrations/0015_trash_deleted_by.py +21 -0
- djgentelella/migrations/0016_trash_created_at.py +20 -0
- djgentelella/migrations/0017_alter_chunkedupload_status.py +18 -0
- djgentelella/migrations/0018_alter_chunkedupload_status.py +18 -0
- djgentelella/models.py +97 -0
- djgentelella/models_manager.py +40 -0
- djgentelella/serializers/firmador_digital.py +9 -1
- djgentelella/serializers/selects.py +14 -0
- djgentelella/static/djgentelella.readonly.vendors.min.css +9 -5
- djgentelella/static/djgentelella.readonly.vendors.min.js +1 -1
- djgentelella/static/gentelella/css/custom.css +23 -2
- djgentelella/static/gentelella/css/modern.css +15 -0
- djgentelella/static/gentelella/css/modern_black_white.css +15 -0
- djgentelella/static/gentelella/css/pdfviewer.css +43 -1
- djgentelella/static/gentelella/images/default.png +0 -0
- djgentelella/static/gentelella/js/base/digital_signature.js +318 -79
- djgentelella/static/gentelella/js/base/form.common.js +9 -4
- djgentelella/static/gentelella/js/base/select2_wrap.js +15 -1
- djgentelella/static/gentelella/js/base/select2related.js +5 -0
- djgentelella/static/gentelella/js/base.js +347 -83
- djgentelella/static/gentelella/js/custom.js +6 -1
- djgentelella/static/gentelella/js/datatables.js +15 -0
- djgentelella/static/gentelella/js/digital_signature_update.js +29 -0
- djgentelella/static/gentelella/js/obj_api_management.js +403 -344
- djgentelella/static/gentelella/js/widgets.js +10 -1
- djgentelella/static/vendors/timeline/css/timeline.css +9 -5
- djgentelella/static/vendors/timeline/js/timeline.js +1 -1
- djgentelella/templates/forms/as_grid.html +2 -2
- djgentelella/templates/forms/as_inline.html +3 -5
- djgentelella/templates/gentelella/app/sidebar.html +16 -13
- djgentelella/templates/gentelella/digital_signature/update_signature_settings.html +45 -0
- djgentelella/templates/gentelella/registration/reset_done.html +24 -0
- djgentelella/templates/gentelella/widgets/digital_signature.html +47 -11
- djgentelella/trash/api.py +77 -0
- djgentelella/trash/filterset.py +20 -0
- djgentelella/trash/serializer.py +45 -0
- djgentelella/urls.py +13 -2
- djgentelella/views/select2autocomplete.py +40 -0
- djgentelella/widgets/core.py +6 -5
- djgentelella/widgets/digital_signature.py +5 -21
- djgentelella/widgets/selects.py +47 -0
- {djgentelella-0.4.30.dist-info → djgentelella-0.5.1.dist-info}/METADATA +2 -2
- {djgentelella-0.4.30.dist-info → djgentelella-0.5.1.dist-info}/RECORD +66 -48
- {djgentelella-0.4.30.dist-info → djgentelella-0.5.1.dist-info}/AUTHORS +0 -0
- {djgentelella-0.4.30.dist-info → djgentelella-0.5.1.dist-info}/LICENSE.txt +0 -0
- {djgentelella-0.4.30.dist-info → djgentelella-0.5.1.dist-info}/WHEEL +0 -0
- {djgentelella-0.4.30.dist-info → djgentelella-0.5.1.dist-info}/top_level.txt +0 -0
djgentelella/__init__.py
CHANGED
djgentelella/admin.py
CHANGED
|
@@ -2,10 +2,12 @@ from django.contrib import admin
|
|
|
2
2
|
|
|
3
3
|
from djgentelella.firmador_digital.models import UserSignatureConfig
|
|
4
4
|
from djgentelella.models import MenuItem, Help, GentelellaSettings, Notification, \
|
|
5
|
-
ChunkedUpload
|
|
5
|
+
ChunkedUpload, Trash
|
|
6
6
|
from djgentelella.models import PermissionsCategoryManagement
|
|
7
7
|
from djgentelella.utils import clean_cache
|
|
8
|
-
|
|
8
|
+
from django.contrib.admin.models import LogEntry
|
|
9
|
+
from djgentelella.history.utils import ACTIONS
|
|
10
|
+
from django.utils.translation import gettext_lazy as _
|
|
9
11
|
|
|
10
12
|
class MenuAdmin(admin.ModelAdmin):
|
|
11
13
|
filter_horizontal = ['permission']
|
|
@@ -33,6 +35,41 @@ class ChunkedUploadAdmin(admin.ModelAdmin):
|
|
|
33
35
|
class UserSignatureConfigAdmin(admin.ModelAdmin):
|
|
34
36
|
list_display = ('id', 'user', 'config')
|
|
35
37
|
|
|
38
|
+
class TrashAdmin(admin.ModelAdmin):
|
|
39
|
+
list_display = ("id", "deleted_by", "content_type", "object_id", "object_repr", "created_at")
|
|
40
|
+
ordering = ("-created_at",)
|
|
41
|
+
search_fields = ("id",)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ActionFlagFilter(admin.SimpleListFilter):
|
|
45
|
+
title = _("Action")
|
|
46
|
+
parameter_name = "action_flag"
|
|
47
|
+
|
|
48
|
+
def lookups(self, request, model_admin):
|
|
49
|
+
return [(k, str(v)) for k, v in ACTIONS.items()]
|
|
50
|
+
|
|
51
|
+
def queryset(self, request, queryset):
|
|
52
|
+
val = self.value()
|
|
53
|
+
if not val:
|
|
54
|
+
return queryset
|
|
55
|
+
try:
|
|
56
|
+
return queryset.filter(action_flag=int(val))
|
|
57
|
+
except ValueError:
|
|
58
|
+
return queryset.none()
|
|
59
|
+
|
|
60
|
+
class LogEntryAdmin(admin.ModelAdmin):
|
|
61
|
+
verbose_name = _("History")
|
|
62
|
+
verbose_name_plural = _("History")
|
|
63
|
+
list_display = ("id", "action_time", "user", "content_type", "object_id", "object_repr", "action_label", "change_message")
|
|
64
|
+
search_fields = ("content_type__app_label", "content_type__model", "object_id", "user__username")
|
|
65
|
+
list_filter = (
|
|
66
|
+
ActionFlagFilter,
|
|
67
|
+
)
|
|
68
|
+
def action_label(self, obj):
|
|
69
|
+
return ACTIONS.get(obj.action_flag, obj.get_action_flag_display() or obj.action_flag)
|
|
70
|
+
action_label.short_description = _("Action")
|
|
71
|
+
|
|
72
|
+
|
|
36
73
|
admin.site.register(UserSignatureConfig, UserSignatureConfigAdmin)
|
|
37
74
|
admin.site.register(ChunkedUpload, ChunkedUploadAdmin)
|
|
38
75
|
admin.site.register(MenuItem, MenuAdmin)
|
|
@@ -40,3 +77,5 @@ admin.site.register(Help)
|
|
|
40
77
|
admin.site.register(PermissionsCategoryManagement)
|
|
41
78
|
admin.site.register(GentelellaSettings, GentelellaSettingsAdmin)
|
|
42
79
|
admin.site.register(Notification, NotificationAdmin)
|
|
80
|
+
admin.site.register(Trash, TrashAdmin)
|
|
81
|
+
admin.site.register(LogEntry, LogEntryAdmin)
|
|
@@ -12,7 +12,7 @@ from djgentelella.firmador_digital.utils import RemoteSignerClient
|
|
|
12
12
|
from djgentelella.serializers.firmador_digital import (
|
|
13
13
|
WSRequest,
|
|
14
14
|
InitialSignatureSerializer,
|
|
15
|
-
CompleteSignatureSerializer,
|
|
15
|
+
CompleteSignatureSerializer, ValidateDocumentSerializer,
|
|
16
16
|
)
|
|
17
17
|
|
|
18
18
|
logger = logging.getLogger("djgentelella")
|
|
@@ -35,24 +35,30 @@ class SignConsumer(JsonWebsocketConsumer):
|
|
|
35
35
|
return InitialSignatureSerializer(data=content)
|
|
36
36
|
if serializer.validated_data["action"] == "complete_signature":
|
|
37
37
|
return CompleteSignatureSerializer(data=content)
|
|
38
|
+
if serializer.validated_data["action"] == "validate_document":
|
|
39
|
+
return ValidateDocumentSerializer(data=content)
|
|
38
40
|
|
|
39
41
|
def disconnect(self, close_code):
|
|
40
42
|
super().disconnect(close_code)
|
|
43
|
+
logger.info(f"Disconnect {close_code}")
|
|
41
44
|
|
|
42
45
|
def receive_json(self, content, **kwargs):
|
|
43
46
|
"""
|
|
44
47
|
Called with decoded JSON content.
|
|
45
48
|
"""
|
|
49
|
+
socket_id = ""
|
|
46
50
|
try:
|
|
47
51
|
serializer = self.get_serializer(content)
|
|
48
52
|
|
|
49
53
|
if serializer.is_valid():
|
|
50
|
-
|
|
54
|
+
socket_id = serializer.validated_data['socket_id']
|
|
51
55
|
match serializer.validated_data["action"]:
|
|
52
56
|
case "initial_signature":
|
|
53
57
|
self.do_initial_signature(serializer)
|
|
54
58
|
case "complete_signature":
|
|
55
59
|
self.do_complete_signature(serializer)
|
|
60
|
+
case "validate_document":
|
|
61
|
+
self.do_validate_document(serializer)
|
|
56
62
|
case _:
|
|
57
63
|
self.do_default(serializer)
|
|
58
64
|
else:
|
|
@@ -63,6 +69,7 @@ class SignConsumer(JsonWebsocketConsumer):
|
|
|
63
69
|
"details": serializer.errors,
|
|
64
70
|
"status": 400,
|
|
65
71
|
"code": 11,
|
|
72
|
+
'socket_id': serializer.data.get('socket_id')
|
|
66
73
|
})
|
|
67
74
|
logger.error("Invalid request.")
|
|
68
75
|
|
|
@@ -74,9 +81,20 @@ class SignConsumer(JsonWebsocketConsumer):
|
|
|
74
81
|
"details": str(e),
|
|
75
82
|
"status": 500,
|
|
76
83
|
"code": 999,
|
|
84
|
+
"socket_id": socket_id
|
|
77
85
|
})
|
|
78
86
|
logger.error("An unexpected error occurred.", exc_info=e)
|
|
79
87
|
|
|
88
|
+
def do_validate_document(self, serializer):
|
|
89
|
+
signer = RemoteSignerClient(self.scope["user"])
|
|
90
|
+
|
|
91
|
+
response = signer.validate_document(
|
|
92
|
+
instance=serializer.validated_data["instance"],
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
response['socket_id'] = serializer.validated_data['socket_id']
|
|
96
|
+
self.send_json(response)
|
|
97
|
+
|
|
80
98
|
def do_initial_signature(self, serializer):
|
|
81
99
|
signer = RemoteSignerClient(self.scope["user"])
|
|
82
100
|
|
|
@@ -87,6 +105,8 @@ class SignConsumer(JsonWebsocketConsumer):
|
|
|
87
105
|
docsettings=serializer.validated_data["docsettings"],
|
|
88
106
|
)
|
|
89
107
|
|
|
108
|
+
response['socket_id'] = serializer.validated_data['socket_id']
|
|
109
|
+
|
|
90
110
|
# remove signer image
|
|
91
111
|
if "imageIcon" in response:
|
|
92
112
|
del response["imageIcon"]
|
|
@@ -103,7 +123,8 @@ class SignConsumer(JsonWebsocketConsumer):
|
|
|
103
123
|
signer = RemoteSignerClient(self.scope["user"])
|
|
104
124
|
data = dict(serializer.validated_data)
|
|
105
125
|
response = signer.complete_signature(data)
|
|
106
|
-
self.send_json({"result": response
|
|
126
|
+
self.send_json({"result": response,
|
|
127
|
+
'socket_id': serializer.validated_data['socket_id']})
|
|
107
128
|
except Exception as e:
|
|
108
129
|
logger.error("Complete the signature fail", exc_info=e)
|
|
109
130
|
|
|
@@ -1,12 +1,20 @@
|
|
|
1
|
+
import base64
|
|
1
2
|
import logging
|
|
3
|
+
from io import BytesIO
|
|
2
4
|
|
|
3
5
|
from django import forms
|
|
4
6
|
from django.core.exceptions import ValidationError
|
|
7
|
+
from django.core.files.uploadedfile import UploadedFile
|
|
8
|
+
from django.templatetags.static import static
|
|
9
|
+
from django.utils.safestring import mark_safe
|
|
5
10
|
from django.utils.translation import gettext_lazy as _
|
|
6
11
|
|
|
12
|
+
from djgentelella.firmador_digital.models import UserSignatureConfig, \
|
|
13
|
+
get_signature_default, FORMATS_DATE, FONT_ALIGNMENT
|
|
7
14
|
from djgentelella.firmador_digital.signvalue_utils import ValueDSParser
|
|
8
15
|
from djgentelella.forms.forms import GTForm
|
|
9
16
|
from djgentelella.widgets import core as genwidgets
|
|
17
|
+
from djgentelella.widgets.core import FileInput
|
|
10
18
|
|
|
11
19
|
logger = logging.getLogger('djgentelella')
|
|
12
20
|
|
|
@@ -26,3 +34,135 @@ class RenderValueForm(GTForm, ValueDSParser):
|
|
|
26
34
|
_("Invalid value not encoded as b64 o json parser error"),
|
|
27
35
|
code="invalid")
|
|
28
36
|
return jsondata
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class SignatureConfigForm(GTForm, forms.ModelForm):
|
|
40
|
+
contact = forms.CharField(
|
|
41
|
+
required=False,
|
|
42
|
+
max_length=100,
|
|
43
|
+
widget=genwidgets.TextInput,
|
|
44
|
+
label=_("Contact")
|
|
45
|
+
)
|
|
46
|
+
dateFormat = forms.ChoiceField(
|
|
47
|
+
widget=genwidgets.Select,
|
|
48
|
+
required=True,
|
|
49
|
+
choices=FORMATS_DATE,
|
|
50
|
+
label=_("Date format")
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
defaultSignMessage = forms.CharField(
|
|
54
|
+
widget=genwidgets.Textarea,
|
|
55
|
+
required=False,
|
|
56
|
+
max_length=100,
|
|
57
|
+
label=_("Signature message")
|
|
58
|
+
)
|
|
59
|
+
fontAlignment = forms.ChoiceField(
|
|
60
|
+
choices=FONT_ALIGNMENT,
|
|
61
|
+
widget=genwidgets.Select,
|
|
62
|
+
required=True,
|
|
63
|
+
label=_("Font alignment")
|
|
64
|
+
)
|
|
65
|
+
fontColor = forms.CharField(
|
|
66
|
+
max_length=50,
|
|
67
|
+
widget=genwidgets.ColorInput,
|
|
68
|
+
required=True,
|
|
69
|
+
label=_("Font color"),
|
|
70
|
+
initial="#FFFFFF",
|
|
71
|
+
)
|
|
72
|
+
fontSize = forms.IntegerField(
|
|
73
|
+
min_value=5,
|
|
74
|
+
max_value=28,
|
|
75
|
+
initial=7,
|
|
76
|
+
widget=genwidgets.NumberInput,
|
|
77
|
+
label=_("Font size")
|
|
78
|
+
)
|
|
79
|
+
place = forms.CharField(
|
|
80
|
+
required=False,
|
|
81
|
+
max_length=100,
|
|
82
|
+
widget=genwidgets.TextInput,
|
|
83
|
+
label=_("Place")
|
|
84
|
+
)
|
|
85
|
+
reason = forms.CharField(
|
|
86
|
+
required=False,
|
|
87
|
+
max_length=100,
|
|
88
|
+
widget=genwidgets.TextInput,
|
|
89
|
+
label=_("Reason")
|
|
90
|
+
)
|
|
91
|
+
isVisibleSignature = forms.BooleanField(
|
|
92
|
+
required=False,
|
|
93
|
+
widget=genwidgets.YesNoInput,
|
|
94
|
+
label=_("Visible signature")
|
|
95
|
+
)
|
|
96
|
+
image = forms.ImageField(
|
|
97
|
+
required=False,
|
|
98
|
+
widget=FileInput,
|
|
99
|
+
label=_("Signature image")
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
default_render_type = "as_grid"
|
|
103
|
+
|
|
104
|
+
grid_representation = [
|
|
105
|
+
[["contact"]],
|
|
106
|
+
[["place"]],
|
|
107
|
+
[["reason"]],
|
|
108
|
+
[["image"], ["preview_image"]],
|
|
109
|
+
[["dateFormat"], ["isVisibleSignature"]],
|
|
110
|
+
[["fontSize"], ["fontColor"], ["fontAlignment"]],
|
|
111
|
+
[["defaultSignMessage"]],
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
class Meta:
|
|
115
|
+
model = UserSignatureConfig
|
|
116
|
+
fields = []
|
|
117
|
+
|
|
118
|
+
def __init__(self, *args, **kwargs):
|
|
119
|
+
super().__init__(*args, **kwargs)
|
|
120
|
+
|
|
121
|
+
cfg = self.instance.config if getattr(self.instance, "config",
|
|
122
|
+
None) else get_signature_default()
|
|
123
|
+
for key, value in cfg.items():
|
|
124
|
+
if key in self.fields:
|
|
125
|
+
self.fields[key].initial = value
|
|
126
|
+
|
|
127
|
+
def preview_image(self):
|
|
128
|
+
label = _("Image preview")
|
|
129
|
+
src = static("gentelella/images/default.png")
|
|
130
|
+
|
|
131
|
+
if self.instance and self.instance.config:
|
|
132
|
+
image_b64 = self.instance.config.get("image")
|
|
133
|
+
if image_b64 and image_b64.startswith("data:image"):
|
|
134
|
+
src = image_b64
|
|
135
|
+
|
|
136
|
+
return mark_safe(f"""
|
|
137
|
+
<label for="image-preview"><strong>{label}:</strong></label><br>
|
|
138
|
+
<div class="d-flex justify-content-center">
|
|
139
|
+
<img id="image-preview" alt="{label}" src="{src}" style="max-height:100px; max-width:300px; display:block;">
|
|
140
|
+
</div>
|
|
141
|
+
""")
|
|
142
|
+
|
|
143
|
+
def save(self, commit=True):
|
|
144
|
+
data = get_signature_default()
|
|
145
|
+
prev_image = self.instance.config.get("image")
|
|
146
|
+
|
|
147
|
+
for key in data.keys():
|
|
148
|
+
if key in self.cleaned_data:
|
|
149
|
+
val = self.cleaned_data[key]
|
|
150
|
+
if key == "image":
|
|
151
|
+
if isinstance(val, UploadedFile):
|
|
152
|
+
# el usuario subió un nuevo archivo
|
|
153
|
+
buffered = BytesIO()
|
|
154
|
+
for chunk in val.chunks():
|
|
155
|
+
buffered.write(chunk)
|
|
156
|
+
mime = val.content_type
|
|
157
|
+
b64 = base64.b64encode(buffered.getvalue()).decode()
|
|
158
|
+
val = f"data:{mime};base64,{b64}"
|
|
159
|
+
else:
|
|
160
|
+
# no se subió archivo nuevo → conservamos imagen anterior
|
|
161
|
+
val = prev_image
|
|
162
|
+
|
|
163
|
+
if isinstance(data[key], str) and not isinstance(val, str):
|
|
164
|
+
val = str(val)
|
|
165
|
+
data[key] = val
|
|
166
|
+
|
|
167
|
+
self.instance.config = data
|
|
168
|
+
return super().save(commit=commit)
|
|
@@ -1,31 +1,26 @@
|
|
|
1
1
|
from django.contrib.auth import get_user_model
|
|
2
2
|
from django.db import models
|
|
3
|
+
from django.utils.translation import gettext_lazy as _
|
|
4
|
+
|
|
5
|
+
FORMATS_DATE = [("dd/MM/yyyy hh:mm:ss a", "dd/MM/yyyy hh:mm:ss a"),
|
|
6
|
+
("yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm:ss"),
|
|
7
|
+
("MM/dd/yyyy hh:mm:ss a", "MM/dd/yyyy hh:mm:ss a"),
|
|
8
|
+
("dd-MM-yyyy", "dd-MM-yyyy"), ]
|
|
9
|
+
|
|
10
|
+
FONT_ALIGNMENT = (('NONE', _('None')), ('RIGHT', _('Right')), ('LEFT', _('Left')),
|
|
11
|
+
('TOP', _('Top')), ('BOTTOM', _('Bottom')),)
|
|
12
|
+
|
|
3
13
|
|
|
4
14
|
def get_signature_default():
|
|
5
|
-
return {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
}
|
|
15
|
+
return {"backgroundColor": "transparent", "cAdESLevel": "LTA", "contact": "",
|
|
16
|
+
"country": "CR", "dateFormat": "dd/MM/yyyy hh:mm:ss a",
|
|
17
|
+
"defaultSignMessage": "Esta es una representación gráfica únicamente,\nverifique la validez de la firma.",
|
|
18
|
+
"font": "Nimbus Sans Regular", "fontAlignment": "None",
|
|
19
|
+
"fontColor": "000000",
|
|
20
|
+
"fontSize": "7", "image": "", "language": "es", "pAdESLevel": "LTA",
|
|
21
|
+
"place": "", "portNumber": "3516", "reason": "", "signHeight": "33",
|
|
22
|
+
"signWidth": "133", "signX": "40", "signY": "60", "xAdESLevel": "LTA",
|
|
23
|
+
"isVisibleSignature": False, "hideSignatureAdvice": False, }
|
|
29
24
|
|
|
30
25
|
|
|
31
26
|
class UserSignatureConfig(models.Model):
|
|
@@ -47,6 +47,17 @@ class RemoteSignerClient:
|
|
|
47
47
|
)
|
|
48
48
|
return response.json()
|
|
49
49
|
|
|
50
|
+
def validate_document(self, instance):
|
|
51
|
+
b64doc = self.get_b64document(instance['value'])
|
|
52
|
+
files = {
|
|
53
|
+
"b64Document": b64doc,
|
|
54
|
+
"DocumentExtension": ".pdf",
|
|
55
|
+
}
|
|
56
|
+
response = requests.post(
|
|
57
|
+
settings.FIRMADOR_VALIDA_URL, json=files
|
|
58
|
+
)
|
|
59
|
+
return response.json()
|
|
60
|
+
|
|
50
61
|
def _finalize_signature(self, data_to_sign, task):
|
|
51
62
|
result = None
|
|
52
63
|
error_msg = _("An unexpected error occurred during the signing process.")
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from django.contrib import messages
|
|
2
|
+
from django.contrib.auth.decorators import login_required
|
|
3
|
+
from django.shortcuts import render, get_object_or_404, redirect
|
|
4
|
+
from django.utils.translation import gettext_lazy as _
|
|
5
|
+
|
|
6
|
+
from djgentelella.firmador_digital.forms import SignatureConfigForm
|
|
7
|
+
from djgentelella.firmador_digital.models import UserSignatureConfig
|
|
8
|
+
|
|
9
|
+
@login_required
|
|
10
|
+
def update_signature_settings(request):
|
|
11
|
+
|
|
12
|
+
config, is_created = UserSignatureConfig.objects.get_or_create(user=request.user)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
if request.method == "POST":
|
|
16
|
+
form = SignatureConfigForm(request.POST, request.FILES, instance=config, render_type="as_grid")
|
|
17
|
+
if form.is_valid():
|
|
18
|
+
form.save()
|
|
19
|
+
messages.success(request, _("Updated signature settings successfully."))
|
|
20
|
+
return redirect("signature_config")
|
|
21
|
+
else:
|
|
22
|
+
form = SignatureConfigForm(instance=config, render_type="as_grid")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
return render(
|
|
26
|
+
request,
|
|
27
|
+
"gentelella/digital_signature/update_signature_settings.html",
|
|
28
|
+
context={
|
|
29
|
+
"form": form
|
|
30
|
+
}
|
|
31
|
+
)
|
|
@@ -70,8 +70,19 @@ class DigitalSignatureRenderFileAPIView(APIView):
|
|
|
70
70
|
return Response(file_data, content_type='application/pdf')
|
|
71
71
|
|
|
72
72
|
def get_instance_file(self, instance, fieldname):
|
|
73
|
+
# check if instance and fieldname are not None
|
|
74
|
+
if not instance or not fieldname:
|
|
75
|
+
return None
|
|
76
|
+
|
|
73
77
|
file_path = getattr(instance, fieldname)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
if not file_path:
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
# check if not file
|
|
82
|
+
try:
|
|
83
|
+
with open(file_path.path, 'rb') as f:
|
|
84
|
+
file_data = f.read()
|
|
85
|
+
|
|
86
|
+
return file_data
|
|
87
|
+
except (FileNotFoundError, OSError):
|
|
88
|
+
return None
|
djgentelella/forms/forms.py
CHANGED
|
@@ -69,6 +69,11 @@ class GTForm(forms.Form):
|
|
|
69
69
|
case _:
|
|
70
70
|
self.renderer.form_template_name = self.template_name_horizontal
|
|
71
71
|
|
|
72
|
+
def get_error_for_grid(self, name):
|
|
73
|
+
if name in self.errors:
|
|
74
|
+
return self.errors[name]
|
|
75
|
+
return None
|
|
76
|
+
|
|
72
77
|
@property
|
|
73
78
|
def grid(self):
|
|
74
79
|
"""
|
|
@@ -97,10 +102,13 @@ class GTForm(forms.Form):
|
|
|
97
102
|
row_list = []
|
|
98
103
|
for field in col:
|
|
99
104
|
if field in dic_fields and dic_fields[field]:
|
|
100
|
-
row_list.append(
|
|
105
|
+
row_list.append(
|
|
106
|
+
(dic_fields[field], self.get_error_for_grid(field))
|
|
107
|
+
)
|
|
101
108
|
else:
|
|
102
109
|
if hasattr(self, field):
|
|
103
|
-
row_list.append(getattr(self, field)()
|
|
110
|
+
row_list.append((getattr(self, field)(),
|
|
111
|
+
self.get_error_for_grid(field)))
|
|
104
112
|
col_list.append(row_list)
|
|
105
113
|
grid.append(col_list)
|
|
106
114
|
return grid
|
djgentelella/groute.py
CHANGED
|
@@ -18,7 +18,10 @@ def register_lookups(prefix='', basename=None):
|
|
|
18
18
|
def decore(klass):
|
|
19
19
|
if basename in ['userbase', 'groupbase']:
|
|
20
20
|
from djgentelella import settings
|
|
21
|
-
if
|
|
21
|
+
if klass.__module__ == 'djgentelella.gtselects':
|
|
22
|
+
if settings.REGISTER_DEFAULT_USER_API:
|
|
23
|
+
routes.register(prefix, klass, basename=basename)
|
|
24
|
+
else:
|
|
22
25
|
routes.register(prefix, klass, basename=basename)
|
|
23
26
|
else:
|
|
24
27
|
routes.register(prefix, klass, basename=basename)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from djgentelella.objectmanagement import AuthAllPermBaseObjectManagement
|
|
2
|
+
from django.utils.translation import gettext_lazy as _
|
|
3
|
+
from djgentelella.history.utils import add_log
|
|
4
|
+
from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION
|
|
5
|
+
from djgentelella.history.serializers import HistoryDataTableSerializer
|
|
6
|
+
from djgentelella.history.filterset import HistoryFilterSet
|
|
7
|
+
from rest_framework.authentication import SessionAuthentication
|
|
8
|
+
from rest_framework.filters import SearchFilter, OrderingFilter
|
|
9
|
+
from rest_framework.pagination import LimitOffsetPagination
|
|
10
|
+
from django_filters.rest_framework import DjangoFilterBackend
|
|
11
|
+
from rest_framework.response import Response
|
|
12
|
+
from django.conf import settings
|
|
13
|
+
from django.db.models import Q
|
|
14
|
+
from django.contrib.contenttypes.models import ContentType
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BaseViewSetWithLogs(AuthAllPermBaseObjectManagement):
|
|
19
|
+
|
|
20
|
+
def perform_create(self, serializer):
|
|
21
|
+
super().perform_create(serializer)
|
|
22
|
+
new_instance = serializer.instance
|
|
23
|
+
|
|
24
|
+
add_log(
|
|
25
|
+
self.request.user,
|
|
26
|
+
new_instance,
|
|
27
|
+
ADDITION,
|
|
28
|
+
new_instance._meta.verbose_name.title().lower(),
|
|
29
|
+
[],
|
|
30
|
+
change_message=_("Created"),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def perform_update(self, serializer):
|
|
34
|
+
# get the instance before the update
|
|
35
|
+
instance = self.get_object()
|
|
36
|
+
old_values = {
|
|
37
|
+
field: getattr(instance, field)
|
|
38
|
+
for field in serializer.validated_data.keys()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
super().perform_update(serializer)
|
|
42
|
+
|
|
43
|
+
new_instance = serializer.instance
|
|
44
|
+
# get changed fields
|
|
45
|
+
changed_fields = []
|
|
46
|
+
|
|
47
|
+
for field in serializer.validated_data.keys():
|
|
48
|
+
old_value = old_values.get(field)
|
|
49
|
+
new_value = getattr(new_instance, field)
|
|
50
|
+
if old_value != new_value:
|
|
51
|
+
changed_fields.append(field)
|
|
52
|
+
|
|
53
|
+
add_log(
|
|
54
|
+
self.request.user,
|
|
55
|
+
new_instance,
|
|
56
|
+
CHANGE,
|
|
57
|
+
new_instance._meta.verbose_name.title().lower(),
|
|
58
|
+
changed_data=changed_fields,
|
|
59
|
+
change_message=_("Updated"),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def perform_destroy(self, instance):
|
|
63
|
+
instance = self.get_object()
|
|
64
|
+
if instance._meta.verbose_name.title() in self.models_log:
|
|
65
|
+
add_log(
|
|
66
|
+
self.request.user,
|
|
67
|
+
instance,
|
|
68
|
+
DELETION,
|
|
69
|
+
instance._meta.verbose_name.title().lower(),
|
|
70
|
+
change_message=_("Deleted"),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
super().perform_destroy(instance)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class HistoryViewSet(AuthAllPermBaseObjectManagement):
|
|
77
|
+
authentication_classes = [SessionAuthentication]
|
|
78
|
+
serializer_class = HistoryDataTableSerializer
|
|
79
|
+
queryset = LogEntry.objects.all()
|
|
80
|
+
pagination_class = LimitOffsetPagination
|
|
81
|
+
filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter)
|
|
82
|
+
search_fields = ["object_repr"]
|
|
83
|
+
filterset_class = HistoryFilterSet
|
|
84
|
+
ordering_fields = ["-action_time"]
|
|
85
|
+
ordering = ("-action_time",)
|
|
86
|
+
perms = {"list": ["admin.view_logentry"]}
|
|
87
|
+
|
|
88
|
+
def get_queryset(self):
|
|
89
|
+
queryset = self.queryset
|
|
90
|
+
|
|
91
|
+
# check allowed models in settings
|
|
92
|
+
allowed = getattr(settings, "GT_HISTORY_ALLOWED_MODELS", None)
|
|
93
|
+
|
|
94
|
+
if allowed:
|
|
95
|
+
allowed_ctypes = self.contenttypes_from_settings(allowed)
|
|
96
|
+
|
|
97
|
+
if not allowed_ctypes.exists():
|
|
98
|
+
return queryset.none()
|
|
99
|
+
|
|
100
|
+
queryset = queryset.filter(content_type__in=allowed_ctypes).distinct()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# check contenttype param in form
|
|
104
|
+
ctypes_param = self.request.GET.get("contenttype")
|
|
105
|
+
print(allowed)
|
|
106
|
+
if ctypes_param and ctypes_param in allowed:
|
|
107
|
+
|
|
108
|
+
ctypes_qs = self.contenttypes_from_settings([ctypes_param])
|
|
109
|
+
print(ctypes_qs)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
if not ctypes_qs.exists():
|
|
113
|
+
return queryset.none()
|
|
114
|
+
|
|
115
|
+
queryset = queryset.filter(content_type__in=ctypes_qs).distinct()
|
|
116
|
+
|
|
117
|
+
# default values
|
|
118
|
+
return queryset
|
|
119
|
+
|
|
120
|
+
def contenttypes_from_settings(self, entries):
|
|
121
|
+
q = Q()
|
|
122
|
+
for item in entries:
|
|
123
|
+
if isinstance(item, str) and "." in item:
|
|
124
|
+
app_label, model_name = item.split(".", 1)
|
|
125
|
+
app_label = app_label.strip()
|
|
126
|
+
model_key = model_name.strip().lower()
|
|
127
|
+
q |= Q(app_label=app_label, model=model_key)
|
|
128
|
+
|
|
129
|
+
if not q:
|
|
130
|
+
return ContentType.objects.none()
|
|
131
|
+
return ContentType.objects.filter(q)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def list(self, request, *args, **kwargs):
|
|
135
|
+
queryset = self.filter_queryset(self.get_queryset())
|
|
136
|
+
page = self.paginate_queryset(queryset)
|
|
137
|
+
data = page if page is not None else queryset
|
|
138
|
+
|
|
139
|
+
response = {
|
|
140
|
+
"data": data,
|
|
141
|
+
"recordsTotal": LogEntry.objects.count(),
|
|
142
|
+
"recordsFiltered": queryset.count(),
|
|
143
|
+
"draw": self.request.GET.get("draw", 1),
|
|
144
|
+
}
|
|
145
|
+
return Response(self.get_serializer(response).data)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from django.contrib.admin.models import LogEntry
|
|
2
|
+
from django_filters.rest_framework import FilterSet, ChoiceFilter
|
|
3
|
+
from django_filters import DateTimeFromToRangeFilter
|
|
4
|
+
from djgentelella.fields.drfdatetime import DateTimeRangeTextWidget
|
|
5
|
+
from djgentelella.history.utils import ACTIONS
|
|
6
|
+
|
|
7
|
+
class HistoryFilterSet(FilterSet):
|
|
8
|
+
action_time = DateTimeFromToRangeFilter(
|
|
9
|
+
widget=DateTimeRangeTextWidget(attrs={"placeholder": "DD/MM/YYYY/"})
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
action_flag = ChoiceFilter(
|
|
13
|
+
field_name="action_flag",
|
|
14
|
+
choices=[(k, str(v)) for k, v in ACTIONS.items()], # added 4 y 5
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
class Meta:
|
|
18
|
+
model = LogEntry
|
|
19
|
+
fields = {
|
|
20
|
+
"object_repr": ["icontains"],
|
|
21
|
+
"change_message": ["icontains"],
|
|
22
|
+
"user": ["exact"],
|
|
23
|
+
}
|