wagtail-enap-designsystem 1.2.1.125__py3-none-any.whl → 1.2.1.127__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,132 @@
1
+ # Generated by Django 5.1.6 on 2025-09-04 22:04
2
+
3
+ import wagtail.fields
4
+ from django.db import migrations
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ (
11
+ "enap_designsystem",
12
+ "0387_alter_areaaluno_body_alter_enapcomponentes_body_and_more",
13
+ ),
14
+ ]
15
+
16
+ operations = [
17
+ migrations.AddField(
18
+ model_name="sistemavotacaopage",
19
+ name="configuracoes_votacao",
20
+ field=wagtail.fields.StreamField(
21
+ [
22
+ ("recaptcha", 5),
23
+ ("texto_instrucoes", 6),
24
+ ("aviso_importante", 10),
25
+ ("separador", 11),
26
+ ],
27
+ blank=True,
28
+ block_lookup={
29
+ 0: (
30
+ "wagtail.blocks.ChoiceBlock",
31
+ [],
32
+ {
33
+ "choices": [
34
+ ("v2", "reCAPTCHA v2 (checkbox visível)"),
35
+ ("v3", "reCAPTCHA v3 (invisível)"),
36
+ ("v2_invisible", "reCAPTCHA v2 invisível"),
37
+ ],
38
+ "help_text": "Tipo de reCAPTCHA",
39
+ },
40
+ ),
41
+ 1: (
42
+ "wagtail.blocks.ChoiceBlock",
43
+ [],
44
+ {
45
+ "choices": [("light", "Claro"), ("dark", "Escuro")],
46
+ "help_text": "Tema visual (apenas v2)",
47
+ },
48
+ ),
49
+ 2: (
50
+ "wagtail.blocks.ChoiceBlock",
51
+ [],
52
+ {
53
+ "choices": [("normal", "Normal"), ("compact", "Compacto")],
54
+ "help_text": "Tamanho do widget (apenas v2)",
55
+ },
56
+ ),
57
+ 3: (
58
+ "wagtail.blocks.CharBlock",
59
+ (),
60
+ {"help_text": "Classes CSS adicionais", "required": False},
61
+ ),
62
+ 4: (
63
+ "wagtail.blocks.CharBlock",
64
+ (),
65
+ {
66
+ "default": "submit",
67
+ "help_text": "Ação para reCAPTCHA v3 (ex: login, submit, homepage)",
68
+ "required": False,
69
+ },
70
+ ),
71
+ 5: (
72
+ "wagtail.blocks.StructBlock",
73
+ [
74
+ [
75
+ ("tipo", 0),
76
+ ("tema", 1),
77
+ ("tamanho", 2),
78
+ ("css_classes", 3),
79
+ ("acao_v3", 4),
80
+ ]
81
+ ],
82
+ {},
83
+ ),
84
+ 6: (
85
+ "wagtail.blocks.RichTextBlock",
86
+ (),
87
+ {
88
+ "help_text": "Instruções sobre como votar",
89
+ "icon": "doc-full",
90
+ "label": "Texto de Instruções",
91
+ },
92
+ ),
93
+ 7: (
94
+ "wagtail.blocks.CharBlock",
95
+ (),
96
+ {"label": "Título do Aviso", "max_length": 100},
97
+ ),
98
+ 8: ("wagtail.blocks.RichTextBlock", (), {"label": "Mensagem"}),
99
+ 9: (
100
+ "wagtail.blocks.ChoiceBlock",
101
+ [],
102
+ {
103
+ "choices": [
104
+ ("info", "Informação"),
105
+ ("warning", "Aviso"),
106
+ ("success", "Sucesso"),
107
+ ("danger", "Perigo"),
108
+ ],
109
+ "label": "Tipo de Aviso",
110
+ },
111
+ ),
112
+ 10: (
113
+ "wagtail.blocks.StructBlock",
114
+ [[("titulo", 7), ("mensagem", 8), ("tipo", 9)]],
115
+ {"icon": "warning", "label": "Aviso/Alerta"},
116
+ ),
117
+ 11: (
118
+ "wagtail.blocks.static_block.StaticBlock",
119
+ (),
120
+ {
121
+ "admin_text": "Separador visual entre seções",
122
+ "icon": "horizontalrule",
123
+ "label": "Separador",
124
+ "template": "blocks/separador.html",
125
+ },
126
+ ),
127
+ },
128
+ help_text="Adicione reCAPTCHA, instruções e outros elementos para a página de votação",
129
+ verbose_name="Configurações e Elementos da Votação",
130
+ ),
131
+ ),
132
+ ]
@@ -0,0 +1,81 @@
1
+ # Generated by Django 5.1.6 on 2025-09-04 22:13
2
+
3
+ import wagtail.fields
4
+ from django.db import migrations
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ("enap_designsystem", "0388_sistemavotacaopage_configuracoes_votacao"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AlterField(
15
+ model_name="sistemavotacaopage",
16
+ name="configuracoes_votacao",
17
+ field=wagtail.fields.StreamField(
18
+ [("recaptcha", 5)],
19
+ blank=True,
20
+ block_lookup={
21
+ 0: (
22
+ "wagtail.blocks.ChoiceBlock",
23
+ [],
24
+ {
25
+ "choices": [
26
+ ("v2", "reCAPTCHA v2 (checkbox visível)"),
27
+ ("v3", "reCAPTCHA v3 (invisível)"),
28
+ ("v2_invisible", "reCAPTCHA v2 invisível"),
29
+ ],
30
+ "help_text": "Tipo de reCAPTCHA",
31
+ },
32
+ ),
33
+ 1: (
34
+ "wagtail.blocks.ChoiceBlock",
35
+ [],
36
+ {
37
+ "choices": [("light", "Claro"), ("dark", "Escuro")],
38
+ "help_text": "Tema visual (apenas v2)",
39
+ },
40
+ ),
41
+ 2: (
42
+ "wagtail.blocks.ChoiceBlock",
43
+ [],
44
+ {
45
+ "choices": [("normal", "Normal"), ("compact", "Compacto")],
46
+ "help_text": "Tamanho do widget (apenas v2)",
47
+ },
48
+ ),
49
+ 3: (
50
+ "wagtail.blocks.CharBlock",
51
+ (),
52
+ {"help_text": "Classes CSS adicionais", "required": False},
53
+ ),
54
+ 4: (
55
+ "wagtail.blocks.CharBlock",
56
+ (),
57
+ {
58
+ "default": "submit",
59
+ "help_text": "Ação para reCAPTCHA v3 (ex: login, submit, homepage)",
60
+ "required": False,
61
+ },
62
+ ),
63
+ 5: (
64
+ "wagtail.blocks.StructBlock",
65
+ [
66
+ [
67
+ ("tipo", 0),
68
+ ("tema", 1),
69
+ ("tamanho", 2),
70
+ ("css_classes", 3),
71
+ ("acao_v3", 4),
72
+ ]
73
+ ],
74
+ {},
75
+ ),
76
+ },
77
+ help_text="Adicione reCAPTCHA para a página de votação",
78
+ verbose_name="Configurações e Elementos da Votação",
79
+ ),
80
+ ),
81
+ ]
@@ -19,6 +19,7 @@ from wagtail.blocks import URLBlock
19
19
  from wagtail.search import index
20
20
  import requests
21
21
  import os
22
+ from .blocks.html_blocks import RecaptchaBlock
22
23
  from django.db.models.signals import pre_delete
23
24
  from django.dispatch import receiver
24
25
  import shutil
@@ -4858,6 +4859,15 @@ class SistemaVotacaoPage(Page):
4858
4859
  help_text="Data/hora de encerramento da votação (opcional)"
4859
4860
  )
4860
4861
 
4862
+ configuracoes_votacao = StreamField([
4863
+ ('recaptcha', RecaptchaBlock()),
4864
+ ],
4865
+ blank=True,
4866
+ use_json_field=True,
4867
+ verbose_name="Configurações e Elementos da Votação",
4868
+ help_text="Adicione reCAPTCHA para a página de votação"
4869
+ )
4870
+
4861
4871
  footer = models.ForeignKey(
4862
4872
  "EnapFooterSnippet",
4863
4873
  null=True,
@@ -4876,6 +4886,8 @@ class SistemaVotacaoPage(Page):
4876
4886
  FieldPanel('navbar'),
4877
4887
  FieldPanel('footer'),
4878
4888
  ], heading="Conteúdo do Header"),
4889
+
4890
+ FieldPanel('configuracoes_votacao'),
4879
4891
 
4880
4892
  MultiFieldPanel([
4881
4893
  FieldPanel('mostrar_progresso'),
@@ -253,6 +253,7 @@
253
253
  grid-template-columns: 1fr 1fr;
254
254
  gap: 30px;
255
255
  margin-bottom: 40px;
256
+ max-height: 600px;
256
257
  }
257
258
 
258
259
  .news-grid-main {
@@ -336,7 +337,7 @@
336
337
  display: flex;
337
338
  flex-direction: column;
338
339
  gap: 20px;
339
- max-height: 550px;
340
+ max-height: 600px;
340
341
  }
341
342
 
342
343
 
@@ -0,0 +1,338 @@
1
+ <!-- templates/blocks/recaptcha.html -->
2
+ <div class="recaptcha-container {{ value.css_classes }}" data-recaptcha-id="{{ block.id }}">
3
+
4
+ {% if has_recaptcha_keys %}
5
+ <!-- reCAPTCHA v2 (checkbox + desafio) -->
6
+ <div class="g-recaptcha"
7
+ data-sitekey="{{ recaptcha_public_key }}"
8
+ data-theme="{{ value.tema }}"
9
+ data-size="{{ value.tamanho }}"
10
+ data-callback="onRecaptchaSuccess{{ block.id }}"
11
+ data-expired-callback="onRecaptchaExpired{{ block.id }}"
12
+ data-error-callback="onRecaptchaError{{ block.id }}"
13
+ id="recaptcha-{{ block.id }}">
14
+ </div>
15
+
16
+ <!-- Status da verificação -->
17
+ <div id="status-{{ block.id }}" class="recaptcha-status mt-3" style="display: none;">
18
+ <div class="alert alert-success">
19
+ <i class="fas fa-check-circle"></i>
20
+ <strong>Verificação completa!</strong>
21
+ Você passou no teste reCAPTCHA.
22
+ </div>
23
+ </div>
24
+
25
+ <!-- Status de erro -->
26
+ <div id="error-{{ block.id }}" class="recaptcha-error mt-3" style="display: none;">
27
+ <div class="alert alert-danger">
28
+ <i class="fas fa-exclamation-triangle"></i>
29
+ <strong>Erro no reCAPTCHA!</strong>
30
+ Tente novamente.
31
+ </div>
32
+ </div>
33
+
34
+ {% else %}
35
+ <div class="alert alert-warning">
36
+ <i class="fas fa-exclamation-triangle"></i>
37
+ <strong>reCAPTCHA não configurado!</strong>
38
+ <br><small>Configure RECAPTCHA_PUBLIC_KEY no settings.py</small>
39
+ </div>
40
+ {% endif %}
41
+ </div>
42
+
43
+ <script>
44
+ // Callback quando o usuário completa o reCAPTCHA
45
+ window.onRecaptchaSuccess{{ block.id }} = function(response) {
46
+ console.log('✅ reCAPTCHA completado com sucesso!');
47
+ console.log('Token recebido:', response);
48
+
49
+ // Mostrar mensagem de sucesso
50
+ const statusDiv = document.getElementById('status-{{ block.id }}');
51
+ const errorDiv = document.getElementById('error-{{ block.id }}');
52
+
53
+ if (statusDiv) statusDiv.style.display = 'block';
54
+ if (errorDiv) errorDiv.style.display = 'none';
55
+
56
+ // Marcar como verificado
57
+ const container = document.querySelector('[data-recaptcha-id="{{ block.id }}"]');
58
+ container.setAttribute('data-verified', 'true');
59
+ container.setAttribute('data-response', response);
60
+ container.classList.add('recaptcha-verified');
61
+ container.classList.remove('recaptcha-error');
62
+
63
+ // Disparar evento personalizado para integração
64
+ document.dispatchEvent(new CustomEvent('recaptchaCompleted', {
65
+ detail: {
66
+ blockId: '{{ block.id }}',
67
+ response: response,
68
+ success: true
69
+ }
70
+ }));
71
+
72
+ // Habilitar botões de submit se existirem
73
+ const submitButtons = document.querySelectorAll('button[type="submit"], input[type="submit"]');
74
+ submitButtons.forEach(btn => {
75
+ btn.disabled = false;
76
+ btn.classList.remove('disabled');
77
+ });
78
+ };
79
+
80
+ // Callback quando expira
81
+ window.onRecaptchaExpired{{ block.id }} = function() {
82
+ console.log('⚠️ reCAPTCHA expirado');
83
+
84
+ const statusDiv = document.getElementById('status-{{ block.id }}');
85
+ const errorDiv = document.getElementById('error-{{ block.id }}');
86
+
87
+ if (statusDiv) statusDiv.style.display = 'none';
88
+ if (errorDiv) {
89
+ errorDiv.querySelector('.alert').innerHTML = `
90
+ <i class="fas fa-clock"></i>
91
+ <strong>reCAPTCHA expirado!</strong>
92
+ Clique novamente para verificar.
93
+ `;
94
+ errorDiv.style.display = 'block';
95
+ }
96
+
97
+ const container = document.querySelector('[data-recaptcha-id="{{ block.id }}"]');
98
+ container.setAttribute('data-verified', 'false');
99
+ container.classList.remove('recaptcha-verified');
100
+ container.classList.add('recaptcha-error');
101
+
102
+ // Desabilitar botões de submit
103
+ const submitButtons = document.querySelectorAll('button[type="submit"], input[type="submit"]');
104
+ submitButtons.forEach(btn => {
105
+ btn.disabled = true;
106
+ btn.classList.add('disabled');
107
+ });
108
+ };
109
+
110
+ // Callback de erro
111
+ window.onRecaptchaError{{ block.id }} = function() {
112
+ console.log('❌ Erro no reCAPTCHA');
113
+
114
+ const statusDiv = document.getElementById('status-{{ block.id }}');
115
+ const errorDiv = document.getElementById('error-{{ block.id }}');
116
+
117
+ if (statusDiv) statusDiv.style.display = 'none';
118
+ if (errorDiv) {
119
+ errorDiv.querySelector('.alert').innerHTML = `
120
+ <i class="fas fa-exclamation-triangle"></i>
121
+ <strong>Erro no reCAPTCHA!</strong>
122
+ Verifique sua conexão e tente novamente.
123
+ `;
124
+ errorDiv.style.display = 'block';
125
+ }
126
+
127
+ const container = document.querySelector('[data-recaptcha-id="{{ block.id }}"]');
128
+ container.setAttribute('data-verified', 'false');
129
+ container.classList.remove('recaptcha-verified');
130
+ container.classList.add('recaptcha-error');
131
+ };
132
+
133
+ // Utilitários públicos para JavaScript
134
+ window.recaptchaUtils{{ block.id }} = {
135
+ isVerified: function() {
136
+ const container = document.querySelector('[data-recaptcha-id="{{ block.id }}"]');
137
+ return container && container.getAttribute('data-verified') === 'true';
138
+ },
139
+
140
+ getResponse: function() {
141
+ const container = document.querySelector('[data-recaptcha-id="{{ block.id }}"]');
142
+ return container ? (container.getAttribute('data-response') || '') : '';
143
+ },
144
+
145
+ reset: function() {
146
+ if (typeof grecaptcha !== 'undefined') {
147
+ try {
148
+ grecaptcha.reset();
149
+ console.log('🔄 reCAPTCHA resetado');
150
+ } catch (e) {
151
+ console.log('⚠️ Erro ao resetar reCAPTCHA:', e);
152
+ }
153
+ }
154
+
155
+ const container = document.querySelector('[data-recaptcha-id="{{ block.id }}"]');
156
+ if (container) {
157
+ container.setAttribute('data-verified', 'false');
158
+ container.classList.remove('recaptcha-verified', 'recaptcha-error');
159
+ }
160
+
161
+ const statusDiv = document.getElementById('status-{{ block.id }}');
162
+ const errorDiv = document.getElementById('error-{{ block.id }}');
163
+
164
+ if (statusDiv) statusDiv.style.display = 'none';
165
+ if (errorDiv) errorDiv.style.display = 'none';
166
+ },
167
+
168
+ execute: function() {
169
+ if (typeof grecaptcha !== 'undefined') {
170
+ try {
171
+ grecaptcha.execute();
172
+ console.log('▶️ reCAPTCHA executado');
173
+ } catch (e) {
174
+ console.log('⚠️ Erro ao executar reCAPTCHA:', e);
175
+ }
176
+ }
177
+ }
178
+ };
179
+
180
+ // Carregar script do Google reCAPTCHA (apenas uma vez)
181
+ if (!document.querySelector('script[src*="recaptcha/api.js"]') && {{ has_recaptcha_keys|yesno:"true,false" }}) {
182
+ const script = document.createElement('script');
183
+ script.src = 'https://www.google.com/recaptcha/api.js';
184
+ script.async = true;
185
+ script.defer = true;
186
+
187
+ script.onload = function() {
188
+ console.log('📡 reCAPTCHA carregado com sucesso!');
189
+ };
190
+
191
+ script.onerror = function() {
192
+ console.log('❌ Erro ao carregar reCAPTCHA do Google');
193
+
194
+ const errorDiv = document.getElementById('error-{{ block.id }}');
195
+ if (errorDiv) {
196
+ errorDiv.querySelector('.alert').innerHTML = `
197
+ <i class="fas fa-wifi"></i>
198
+ <strong>Erro de conexão!</strong>
199
+ Não foi possível carregar o reCAPTCHA.
200
+ `;
201
+ errorDiv.style.display = 'block';
202
+ }
203
+ };
204
+
205
+ document.head.appendChild(script);
206
+ console.log('📡 Carregando reCAPTCHA do Google...');
207
+ }
208
+
209
+ // Event listener global para debug
210
+ document.addEventListener('recaptchaCompleted', function(e) {
211
+ console.log('🎉 Evento reCAPTCHA global:', e.detail);
212
+ });
213
+
214
+ // Debug: informações da configuração
215
+ console.log('🔍 reCAPTCHA Config:');
216
+ console.log('- Block ID:', '{{ block.id }}');
217
+ console.log('- Public Key:', '{{ recaptcha_public_key|truncatechars:20 }}...');
218
+ console.log('- Has Keys:', {{ has_recaptcha_keys|yesno:"true,false" }});
219
+ console.log('- Tema:', '{{ value.tema }}');
220
+ console.log('- Tamanho:', '{{ value.tamanho }}');
221
+ </script>
222
+
223
+ <style>
224
+ .recaptcha-container {
225
+ margin: 20px 0;
226
+ padding: 20px;
227
+ display: flex;
228
+ flex-direction: column;
229
+ align-items: center;
230
+ border: 1px solid #e9ecef;
231
+ border-radius: 8px;
232
+ background: #f8f9fa;
233
+ transition: all 0.3s ease;
234
+ }
235
+
236
+ .recaptcha-container .g-recaptcha {
237
+ margin: 10px 0;
238
+ }
239
+
240
+ /* Estado verificado */
241
+ .recaptcha-container.recaptcha-verified {
242
+ border-color: #28a745;
243
+ background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
244
+ }
245
+
246
+ .recaptcha-container.recaptcha-verified::before {
247
+ content: '✅ Verificado com Sucesso';
248
+ display: block;
249
+ color: #155724;
250
+ font-weight: bold;
251
+ margin-bottom: 10px;
252
+ font-size: 14px;
253
+ text-align: center;
254
+ }
255
+
256
+ /* Estado de erro */
257
+ .recaptcha-container.recaptcha-error {
258
+ border-color: #dc3545;
259
+ background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%);
260
+ }
261
+
262
+ .recaptcha-container.recaptcha-error::before {
263
+ content: '❌ Erro na Verificação';
264
+ display: block;
265
+ color: #721c24;
266
+ font-weight: bold;
267
+ margin-bottom: 10px;
268
+ font-size: 14px;
269
+ text-align: center;
270
+ }
271
+
272
+ /* Animações */
273
+ .recaptcha-status .alert,
274
+ .recaptcha-error .alert {
275
+ margin-bottom: 0;
276
+ animation: slideDown 0.3s ease-out;
277
+ }
278
+
279
+ @keyframes slideDown {
280
+ from {
281
+ opacity: 0;
282
+ transform: translateY(-10px);
283
+ }
284
+ to {
285
+ opacity: 1;
286
+ transform: translateY(0);
287
+ }
288
+ }
289
+
290
+ /* Responsivo */
291
+ @media (max-width: 576px) {
292
+ .recaptcha-container {
293
+ padding: 15px;
294
+ margin: 15px 0;
295
+ }
296
+
297
+ .recaptcha-container .g-recaptcha {
298
+ transform: scale(0.9);
299
+ transform-origin: center;
300
+ }
301
+ }
302
+
303
+ /* Integração com formulários */
304
+ .recaptcha-container + form button[type="submit"].disabled,
305
+ .recaptcha-container + form input[type="submit"].disabled {
306
+ opacity: 0.6;
307
+ cursor: not-allowed;
308
+ }
309
+
310
+ /* Classes CSS personalizadas */
311
+ .recaptcha-container.text-center {
312
+ text-align: center;
313
+ }
314
+
315
+ .recaptcha-container.my-4 {
316
+ margin: 1.5rem 0;
317
+ }
318
+
319
+ .recaptcha-container.mx-auto {
320
+ margin-left: auto;
321
+ margin-right: auto;
322
+ }
323
+
324
+ /* Tema escuro */
325
+ .recaptcha-container[data-theme="dark"] {
326
+ background: #343a40;
327
+ border-color: #495057;
328
+ color: #fff;
329
+ }
330
+
331
+ .recaptcha-container[data-theme="dark"].recaptcha-verified {
332
+ background: linear-gradient(135deg, #1e5c3a 0%, #28a745 100%);
333
+ }
334
+
335
+ .recaptcha-container[data-theme="dark"].recaptcha-error {
336
+ background: linear-gradient(135deg, #5c1e1e 0%, #dc3545 100%);
337
+ }
338
+ </style>
@@ -968,6 +968,11 @@
968
968
  {% endif %}
969
969
  </div>
970
970
  </div>
971
+ <!-- Em vez do StreamField, use include direto -->
972
+ <div class="recaptcha-votacao-container">
973
+ {% include 'enap_designsystem/blocks/recaptcha.html' with value=recaptcha_config %}
974
+ </div>
975
+
971
976
  <div class="sistema-votacao-container">
972
977
  <!-- Header -->
973
978
 
@@ -1170,6 +1175,7 @@
1170
1175
  <p class="sistema-progresso-text" id="progresso-text-modal">0% concluído</p>
1171
1176
  </div>
1172
1177
  {% endif %}
1178
+
1173
1179
 
1174
1180
  <div class="sistema-modal-actions">
1175
1181
  <button class="sistema-btn-continuar" onclick="SistemaVotacao.continuarVotacao()">
@@ -119,7 +119,8 @@ def callback_sso(request):
119
119
  }
120
120
 
121
121
  # ⚠️ Desativa verificação SSL apenas em DEV
122
- verify_ssl = not settings.DEBUG
122
+ #verify_ssl = not settings.DEBUG
123
+ verify_ssl = False
123
124
 
124
125
  # 🔐 Solicita o token
125
126
  print("📥 Enviando para /token:", data)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wagtail-enap-designsystem
3
- Version: 1.2.1.125
3
+ Version: 1.2.1.127
4
4
  Summary: Módulo de componentes utilizado nos portais ENAP, desenvolvido com Wagtail + CodeRedCMS
5
5
  Author: Renan Campos
6
6
  Author-email: renan.oliveira@enap.gov.br