wagtail-enap-designsystem 1.2.1.195__py3-none-any.whl → 1.2.1.196__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 wagtail-enap-designsystem might be problematic. Click here for more details.

@@ -1312,7 +1312,7 @@ class FormularioPage(Page):
1312
1312
  return super().serve(request, *args, **kwargs)
1313
1313
 
1314
1314
  def process_form_submission(self, request):
1315
- """Processa os dados do formulário"""
1315
+ """Processa os dados do formulário com suporte para múltiplos arquivos"""
1316
1316
  form_data = {}
1317
1317
  files_data = {} # ← Separar arquivos dos dados
1318
1318
 
@@ -1327,21 +1327,49 @@ class FormularioPage(Page):
1327
1327
 
1328
1328
  # PROCESSAR UPLOAD DE ARQUIVOS SEPARADAMENTE
1329
1329
  if block.block_type == 'file_upload_field':
1330
- if field_id in request.FILES:
1331
- uploaded_file = request.FILES[field_id]
1332
-
1333
- # Salvar informações do arquivo (não o arquivo em si) no form_data
1334
- form_data[field_id] = {
1335
- 'filename': uploaded_file.name,
1336
- 'size': uploaded_file.size,
1337
- 'content_type': uploaded_file.content_type,
1338
- }
1330
+ # Verificar se é um campo que aceita múltiplos arquivos
1331
+ is_multiple = block.value.get('multiple_files', False)
1332
+
1333
+ if is_multiple:
1334
+ # O nome do campo no HTML tem [] anexado
1335
+ field_id_arr = f"{field_id}[]"
1336
+ # Usar getlist para pegar todos os arquivos
1337
+ uploaded_files = request.FILES.getlist(field_id_arr)
1339
1338
 
1340
- # Salvar arquivo real em files_data para processamento
1341
- files_data[field_id] = uploaded_file
1339
+ if uploaded_files:
1340
+ # Lista para metadados de múltiplos arquivos
1341
+ files_info = []
1342
+
1343
+ # Processar cada arquivo
1344
+ for i, uploaded_file in enumerate(uploaded_files):
1345
+ # Metadados do arquivo
1346
+ file_info = {
1347
+ 'filename': uploaded_file.name,
1348
+ 'size': uploaded_file.size,
1349
+ 'content_type': uploaded_file.content_type,
1350
+ }
1351
+ files_info.append(file_info)
1352
+
1353
+ # Cada arquivo tem sua própria chave
1354
+ files_data[f"{field_id}_{i}"] = uploaded_file
1355
+
1356
+ # Armazenar a lista com todos os metadados
1357
+ form_data[field_id] = files_info
1358
+ else:
1359
+ # Código original para campo com um único arquivo
1360
+ if field_id in request.FILES:
1361
+ uploaded_file = request.FILES[field_id]
1362
+
1363
+ form_data[field_id] = {
1364
+ 'filename': uploaded_file.name,
1365
+ 'size': uploaded_file.size,
1366
+ 'content_type': uploaded_file.content_type,
1367
+ }
1368
+
1369
+ files_data[field_id] = uploaded_file
1342
1370
  continue
1343
1371
 
1344
- # Processar outros tipos de campo
1372
+ # O restante do código permanece igual
1345
1373
  if block.block_type == 'checkbox_multiple_field':
1346
1374
  values = request.POST.getlist(field_id)
1347
1375
  if values:
@@ -1351,7 +1379,7 @@ class FormularioPage(Page):
1351
1379
  if value:
1352
1380
  form_data[field_id] = value
1353
1381
 
1354
- # Validar dados obrigatórios
1382
+ # O restante da função permanece igual
1355
1383
  errors = self.validate_form_data(form_data, request)
1356
1384
  if errors:
1357
1385
  context = self.get_context(request)
@@ -1359,23 +1387,12 @@ class FormularioPage(Page):
1359
1387
  context['form_data'] = form_data
1360
1388
  return render(request, self.get_template(request), context)
1361
1389
 
1362
- # Salvar submissão
1363
1390
  submission = self.save_form_submission(form_data, files_data, request)
1364
-
1365
- # 📧 ENVIAR EMAILS COM SIMPLEEEMAILSERVICE
1366
1391
  email_results = self.send_emails_with_service(form_data, submission)
1367
1392
 
1368
- # Log dos resultados
1369
- logger.info(f"Submissão {submission.id} processada. Emails: {email_results}")
1370
-
1371
- # Redirecionar com status dos emails
1372
- success_url = f'{request.path}?success=1'
1373
- if email_results.get('user_sent'):
1374
- success_url += '&email_sent=1'
1375
- if email_results.get('admin_sent'):
1376
- success_url += '&admin_notified=1'
1377
-
1378
- return redirect(success_url)
1393
+ logger.info(f"Submissão {submission.id} processada.")
1394
+ # Redirecionar para página de sucesso
1395
+ return redirect(self.url + '?success=1')
1379
1396
 
1380
1397
  def send_emails_with_service(self, form_data, submission):
1381
1398
  """Envia emails usando SimpleEmailService"""
@@ -2444,14 +2444,14 @@ class ENAPNoticia(Page):
2444
2444
  ),
2445
2445
  ]
2446
2446
 
2447
- def save(self, *args, **kwargs):
2448
- if self.destaque_fixo:
2449
- self.desmarcar_destaques()
2450
- super().save(*args, **kwargs)
2451
-
2452
- def desmarcar_destaques(self):
2453
- ENAPNoticia.objects.all().update(destaque_fixo=False)
2454
- self.destaque_fixo=True
2447
+ # def save(self, *args, **kwargs):
2448
+ # if self.destaque_fixo:
2449
+ # self.desmarcar_destaques()
2450
+ # super().save(*args, **kwargs)
2451
+
2452
+ # def desmarcar_destaques(self):
2453
+ # ENAPNoticia.objects.all().update(destaque_fixo=False)
2454
+ # self.destaque_fixo=True
2455
2455
 
2456
2456
  @property
2457
2457
  def titulo_filter(self):
@@ -2555,14 +2555,13 @@ class ENAPNoticia(Page):
2555
2555
  return cls.objects.filter(
2556
2556
  live=True,
2557
2557
  destaque_fixo=True
2558
- ).first()
2558
+ ).order_by('-date_display', '-first_published_at').first()
2559
2559
 
2560
2560
  @classmethod
2561
2561
  def get_noticias_normais(cls, limit=5):
2562
2562
  """Retorna outras notícias (sem a de destaque) ordenadas por data"""
2563
2563
  return cls.objects.filter(
2564
- live=True,
2565
- destaque_fixo=False
2564
+ live=True
2566
2565
  ).order_by('-date_display', '-first_published_at')[:limit]
2567
2566
 
2568
2567
  @classmethod
@@ -2572,13 +2571,12 @@ class ENAPNoticia(Page):
2572
2571
  depois as outras por ordem cronológica
2573
2572
  """
2574
2573
  destaque = cls.get_noticia_destaque()
2575
- normais = cls.get_noticias_normais(limit - 1 if destaque else limit)
2574
+ normais = list(cls.get_noticias_normais(limit))
2576
2575
 
2577
- if destaque:
2578
- # Destaque primeiro, depois as normais
2579
- return [destaque] + list(normais)
2580
- else:
2581
- return list(normais)
2576
+ if destaque and destaque in normais:
2577
+ normais.remove(destaque)
2578
+
2579
+ return [destaque] + normais if destaque else normais
2582
2580
 
2583
2581
  search_fields = Page.search_fields + [
2584
2582
  index.SearchField("title", boost=3),
@@ -1843,20 +1843,14 @@ document.addEventListener('DOMContentLoaded', function() {
1843
1843
  }
1844
1844
 
1845
1845
  if (e.target.classList.contains('phone-field')) {
1846
- // Remove todos os caracteres não numéricos
1847
1846
  let value = e.target.value.replace(/[^\d]/g, '');
1848
-
1849
- // Formato com apenas espaços
1850
1847
  if (value.length <= 10) {
1851
- // Para telefones fixos (até 10 dígitos): XX XXXX XXXX
1852
- if (value.length > 2) {
1853
- value = value.replace(/(\d{2})(\d{0,4})(\d{0,4})/, '$1 $2 $3').trim();
1854
- }
1848
+ value = value.replace(/(\d{2})(\d)/, '$1 $2');
1849
+ value = value.replace(/(\d{4})(\d)/, '$1 $2');
1855
1850
  } else {
1856
- // Para celulares (11 dígitos): XX XXXXX XXXX
1857
- value = value.replace(/(\d{2})(\d{0,5})(\d{0,4})/, '$1 $2 $3').trim();
1851
+ value = value.replace(/(\d{2})(\d)/, '$1 $2');
1852
+ value = value.replace(/(\d{5})(\d)/, '$1 $2');
1858
1853
  }
1859
-
1860
1854
  e.target.value = value;
1861
1855
  }
1862
1856
  });
@@ -1893,37 +1887,169 @@ document.addEventListener('DOMContentLoaded', function() {
1893
1887
  });
1894
1888
 
1895
1889
  // FUNÇÕES DE UPLOAD (mantém as existentes)
1890
+ // Variável global para armazenar arquivos
1891
+ const selectedFilesMap = new Map();
1892
+
1893
+ // Função para atualizar o nome do arquivo
1896
1894
  function updateFileName(input) {
1897
1895
  const fieldId = input.id;
1898
1896
  const displayDiv = document.getElementById(`file-display-${fieldId}`);
1899
1897
  const errorDiv = document.getElementById(`file-error-${fieldId}`);
1900
1898
  const dropzone = document.getElementById(`dropzone-${fieldId}`);
1901
1899
 
1900
+ // Inicializar o armazenamento para este campo se não existir
1901
+ if (!selectedFilesMap.has(fieldId)) {
1902
+ selectedFilesMap.set(fieldId, new DataTransfer());
1903
+ }
1904
+
1905
+ // Obter o armazenamento atual
1906
+ let dataTransfer = selectedFilesMap.get(fieldId);
1907
+
1908
+ // Limpar mensagens de erro
1902
1909
  errorDiv.style.display = 'none';
1903
1910
  errorDiv.textContent = '';
1904
1911
 
1912
+ // Verificar novos arquivos
1905
1913
  if (input.files && input.files.length > 0) {
1906
1914
  const maxSize = parseInt(input.dataset.maxSize) * 1024 * 1024;
1907
- const file = input.files[0];
1915
+ const maxFiles = parseInt(input.dataset.maxFiles || 5);
1908
1916
 
1909
- if (file.size > maxSize) {
1910
- errorDiv.textContent = `Arquivo excede ${input.dataset.maxSize}MB`;
1917
+ // Verificar se excederia o limite de arquivos
1918
+ if (dataTransfer.files.length + input.files.length > maxFiles) {
1919
+ errorDiv.textContent = `Máximo de ${maxFiles} arquivo(s) permitido(s)`;
1911
1920
  errorDiv.style.display = 'block';
1912
- input.value = '';
1913
1921
  return;
1914
1922
  }
1915
1923
 
1924
+ // Validar e adicionar cada novo arquivo
1925
+ for (let i = 0; i < input.files.length; i++) {
1926
+ const file = input.files[i];
1927
+
1928
+ // Verificar tamanho
1929
+ if (file.size > maxSize) {
1930
+ errorDiv.textContent = `Arquivo "${file.name}" excede ${input.dataset.maxSize}MB`;
1931
+ errorDiv.style.display = 'block';
1932
+ continue;
1933
+ }
1934
+
1935
+ // Verificar se já existe um arquivo com esse nome
1936
+ let fileExists = false;
1937
+ for (let j = 0; j < dataTransfer.files.length; j++) {
1938
+ if (dataTransfer.files[j].name === file.name) {
1939
+ fileExists = true;
1940
+ break;
1941
+ }
1942
+ }
1943
+
1944
+ if (!fileExists) {
1945
+ // Adicionar ao DataTransfer
1946
+ dataTransfer.items.add(file);
1947
+ }
1948
+ }
1949
+
1950
+ // Atualizar o campo de input com os arquivos acumulados
1951
+ input.files = dataTransfer.files;
1952
+ }
1953
+
1954
+ // Atualizar a exibição dos arquivos
1955
+ updateFilesDisplay(fieldId);
1956
+ }
1957
+
1958
+ // Função para atualizar a exibição dos arquivos
1959
+ function updateFilesDisplay(fieldId) {
1960
+ const displayDiv = document.getElementById(`file-display-${fieldId}`);
1961
+ const input = document.getElementById(fieldId);
1962
+ const dataTransfer = selectedFilesMap.get(fieldId);
1963
+
1964
+ if (!dataTransfer || dataTransfer.files.length === 0) {
1965
+ displayDiv.innerHTML = '';
1966
+ displayDiv.style.display = 'none';
1967
+ return;
1968
+ }
1969
+
1970
+ let html = '';
1971
+ let totalSize = 0;
1972
+
1973
+ // Criar HTML para cada arquivo
1974
+ for (let i = 0; i < dataTransfer.files.length; i++) {
1975
+ const file = dataTransfer.files[i];
1976
+ totalSize += file.size;
1977
+
1916
1978
  const fileSize = (file.size / (1024 * 1024)).toFixed(2);
1917
- displayDiv.innerHTML = `
1918
- <div style="display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem; background: #f8f9fa; border-radius: 4px;">
1919
- <span>📎</span>
1920
- <span style="flex: 1;">${file.name}</span>
1921
- <span style="color: #666; font-size: 0.8rem;">${fileSize}MB</span>
1922
- <span style="color: #28a745;">✓</span>
1979
+ const fileIcon = getFileIcon(file.name);
1980
+
1981
+ html += `
1982
+ <div class="file-item" style="display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem; background: #f8f9fa; border-radius: 4px; margin-bottom: 0.5rem;">
1983
+ <span style="font-size: 1.2rem;">${fileIcon}</span>
1984
+ <span style="flex: 1; overflow: hidden; text-overflow: ellipsis;">${file.name}</span>
1985
+ <span style="color: #666; font-size: 0.8rem; white-space: nowrap;">${fileSize}MB</span>
1986
+ <button type="button" class="remove-file-btn"
1987
+ style="background: #dc3545; color: white; border: none; width: 24px; height: 24px; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; font-weight: bold;"
1988
+ onclick="removeFile('${fieldId}', ${i})">×</button>
1989
+ </div>
1990
+ `;
1991
+ }
1992
+
1993
+ // Adicionar resumo para múltiplos arquivos
1994
+ if (dataTransfer.files.length > 1) {
1995
+ const totalSizeMB = (totalSize / (1024 * 1024)).toFixed(2);
1996
+ html += `
1997
+ <div style="text-align: right; margin-top: 5px; padding: 3px 8px; background: #f0f0f0; border-radius: 4px;">
1998
+ <span style="font-size: 0.9rem; font-weight: 500;">${dataTransfer.files.length} arquivos (${totalSizeMB}MB total)</span>
1923
1999
  </div>
1924
2000
  `;
1925
- displayDiv.style.display = 'block';
1926
2001
  }
2002
+
2003
+ // Atualizar o display
2004
+ displayDiv.innerHTML = html;
2005
+ displayDiv.style.display = 'block';
2006
+ }
2007
+
2008
+ // Função para remover um arquivo específico
2009
+ function removeFile(fieldId, index) {
2010
+ if (!selectedFilesMap.has(fieldId)) return;
2011
+
2012
+ const dataTransfer = selectedFilesMap.get(fieldId);
2013
+ const newDataTransfer = new DataTransfer();
2014
+
2015
+ // Copiar todos os arquivos exceto o que deve ser removido
2016
+ for (let i = 0; i < dataTransfer.files.length; i++) {
2017
+ if (i !== index) {
2018
+ newDataTransfer.items.add(dataTransfer.files[i]);
2019
+ }
2020
+ }
2021
+
2022
+ // Atualizar a lista de arquivos
2023
+ selectedFilesMap.set(fieldId, newDataTransfer);
2024
+
2025
+ // Atualizar o campo input
2026
+ const input = document.getElementById(fieldId);
2027
+ input.files = newDataTransfer.files;
2028
+
2029
+ // Atualizar a exibição
2030
+ updateFilesDisplay(fieldId);
2031
+ }
2032
+
2033
+ // Função para tratar o drop de arquivos
2034
+ function handleFileDrop(e, inputId) {
2035
+ e.preventDefault();
2036
+ e.stopPropagation();
2037
+
2038
+ const input = document.getElementById(inputId);
2039
+ const files = e.dataTransfer.files;
2040
+
2041
+ // Criar uma cópia dos arquivos para o input
2042
+ const tempTransfer = new DataTransfer();
2043
+ for (let i = 0; i < files.length; i++) {
2044
+ tempTransfer.items.add(files[i]);
2045
+ }
2046
+
2047
+ input.files = tempTransfer.files;
2048
+ updateFileName(input);
2049
+
2050
+ // Resetar visual
2051
+ e.currentTarget.style.borderColor = '#C8D1E0';
2052
+ e.currentTarget.style.background = '#f8f9fa';
1927
2053
  }
1928
2054
 
1929
2055
  function getFileIcon(filename) {
@@ -1196,14 +1196,14 @@
1196
1196
 
1197
1197
  <input
1198
1198
  type="file"
1199
- id="{{ field_block.block_type }}_{{ field_block.id }}"
1200
- name="{{ field_block.block_type }}_{{ field_block.id }}"
1199
+ id="{{ block.block_type }}_{{ block.id }}"
1200
+ name="{{ block.block_type }}_{{ block.id }}"
1201
1201
  style="display: none;"
1202
- {% if field_block.value.required %}required{% endif %}
1203
- {% if field_block.value.multiple_files|default:False %}multiple{% endif %}
1204
- accept="{% for file_type in field_block.value.allowed_types %}{% if file_type == 'pdf' %}.pdf{% elif file_type == 'doc' %}.doc,.docx{% elif file_type == 'image' %}.jpg,.jpeg,.png,.gif{% elif file_type == 'excel' %}.xls,.xlsx{% elif file_type == 'text' %}.txt{% elif file_type == 'csv' %}.csv{% endif %}{% if not forloop.last %},{% endif %}{% endfor %}"
1205
- data-max-size="{{ field_block.value.max_size_mb }}"
1206
- {% if field_block.value.multiple_files|default:False %}data-max-files="{{ field_block.value.max_files|default:3 }}"{% endif %}
1202
+ {% if block.value.required %}required{% endif %}
1203
+ {% if block.value.multiple_files|default:False %}multiple{% endif %}
1204
+ accept="..."
1205
+ data-max-size="{{ block.value.max_size_mb }}"
1206
+ {% if block.value.multiple_files|default:False %}data-max-files="{{ block.value.max_files|default:3 }}"{% endif %}
1207
1207
  onchange="updateFileName(this)"
1208
1208
  >
1209
1209
 
@@ -1971,7 +1971,7 @@
1971
1971
  <input
1972
1972
  type="file"
1973
1973
  id="{{ block.block_type }}_{{ block.id }}"
1974
- name="{{ block.block_type }}_{{ block.id }}"
1974
+ name="{{ block.block_type }}_{{ block.id }}{% if block.value.multiple_files|default:False %}[]{% endif %}"
1975
1975
  style="display: none;"
1976
1976
  {% if block.value.required %}required{% endif %}
1977
1977
  {% if block.value.multiple_files|default:False %}multiple{% endif %}
@@ -2353,129 +2353,104 @@
2353
2353
  {% endif %}
2354
2354
 
2355
2355
  <script>
2356
- function updateFileName(input) {
2357
- const fieldId = input.id;
2358
- const displayDiv = document.getElementById(`file-display-${fieldId}`);
2359
- const errorDiv = document.getElementById(`file-error-${fieldId}`);
2360
- const dropzone = document.getElementById(`dropzone-${fieldId}`);
2356
+ // FUNÇÃO DE UPLOAD CORRIGIDA
2357
+ function updateFileName(input) {
2358
+ console.log("Função updateFileName chamada"); // Log para debug
2359
+
2360
+ const fieldId = input.id;
2361
+ const displayDiv = document.getElementById(`file-display-${fieldId}`);
2362
+ const errorDiv = document.getElementById(`file-error-${fieldId}`);
2363
+ const dropzone = document.getElementById(`dropzone-${fieldId}`);
2364
+
2365
+ console.log("Files selecionados:", input.files.length); // Log para debug
2366
+ console.log("Multiple?", input.multiple); // Log para debug
2367
+
2368
+ // Limpar erros anteriores
2369
+ errorDiv.style.display = 'none';
2370
+ errorDiv.textContent = '';
2371
+ displayDiv.innerHTML = ''; // Limpar a área de exibição
2372
+
2373
+ if (input.files && input.files.length > 0) {
2374
+ const maxSize = parseInt(input.dataset.maxSize) * 1024 * 1024;
2375
+ const maxFiles = input.multiple ? (parseInt(input.dataset.maxFiles) || 5) : 1;
2361
2376
 
2362
- // Limpar erros anteriores
2363
- errorDiv.style.display = 'none';
2364
- errorDiv.textContent = '';
2377
+ // Verificar se excedeu o número máximo de arquivos
2378
+ if (input.files.length > maxFiles) {
2379
+ errorDiv.textContent = `Máximo de ${maxFiles} arquivo(s) permitido(s)`;
2380
+ errorDiv.style.display = 'block';
2381
+ input.value = '';
2382
+ return;
2383
+ }
2365
2384
 
2366
- if (input.files && input.files.length > 0) {
2367
- const maxSize = parseInt(input.dataset.maxSize) * 1024 * 1024; // Converter MB para bytes
2368
- const isMultiple = input.hasAttribute('multiple');
2369
- const maxFiles = parseInt(input.dataset.maxFiles) || 1;
2370
- let hasError = false;
2371
- let errorMessages = [];
2372
-
2373
- // Verificar número de arquivos
2374
- if (isMultiple && input.files.length > maxFiles) {
2375
- errorMessages.push(`Máximo ${maxFiles} arquivo(s) permitido(s)`);
2376
- hasError = true;
2377
- }
2385
+ // Verificar tamanho de cada arquivo
2386
+ let totalSize = 0;
2387
+ let fileHtml = '';
2388
+ let errors = [];
2389
+
2390
+ // Processar cada arquivo
2391
+ for (let i = 0; i < input.files.length; i++) {
2392
+ const file = input.files[i];
2393
+ totalSize += file.size;
2378
2394
 
2379
- // Verificar cada arquivo
2380
- let validFiles = [];
2381
- for (let i = 0; i < input.files.length; i++) {
2382
- const file = input.files[i];
2383
-
2384
- // Verificar tamanho
2385
- if (file.size > maxSize) {
2386
- errorMessages.push(`"${file.name}" excede ${input.dataset.maxSize}MB`);
2387
- hasError = true;
2388
- continue;
2389
- }
2390
-
2391
- validFiles.push(file);
2395
+ // Verificar tamanho individual
2396
+ if (file.size > maxSize) {
2397
+ errors.push(`Arquivo "${file.name}" excede ${input.dataset.maxSize}MB`);
2398
+ continue;
2392
2399
  }
2393
2400
 
2394
- if (hasError) {
2395
- errorDiv.textContent = errorMessages.join('; ');
2396
- errorDiv.style.display = 'block';
2397
- dropzone.style.borderColor = '#dc3545';
2398
- dropzone.style.background = '#fff5f5';
2399
- input.value = ''; // Limpar seleção
2400
- displayDiv.style.display = 'none';
2401
- return;
2402
- }
2401
+ // Adicionar à lista de exibição
2402
+ const fileSize = (file.size / (1024 * 1024)).toFixed(2);
2403
+ const fileIcon = getFileIcon(file.name);
2403
2404
 
2404
- // Exibir arquivos válidos
2405
- let fileHtml = '';
2406
- validFiles.forEach(file => {
2407
- const fileSize = (file.size / (1024 * 1024)).toFixed(2);
2408
- const fileIcon = getFileIcon(file.name);
2409
- fileHtml += `
2410
- <div class="selected-file" style="display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem; background: #f8f9fa; border-radius: 4px; margin-bottom: 0.25rem;">
2411
- <span style="font-size: 1.2rem;">${fileIcon}</span>
2412
- <span style="flex: 1; font-weight: 500;">${file.name}</span>
2413
- <span style="font-size: 0.8rem; color: #666;">${fileSize}MB</span>
2414
- <span style="color: #28a745; font-weight: bold;">✓</span>
2415
- </div>
2416
- `;
2417
- });
2418
-
2419
- displayDiv.innerHTML = fileHtml;
2420
- displayDiv.style.display = 'block';
2421
-
2422
- // Atualizar visual do dropzone
2423
- dropzone.style.borderColor = '#28a745';
2424
- dropzone.style.background = '#f8fff8';
2425
-
2426
- } else {
2427
- displayDiv.style.display = 'none';
2428
- dropzone.style.borderColor = '#C8D1E0';
2429
- dropzone.style.background = '#f8f9fa';
2405
+ fileHtml += `
2406
+ <div style="display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem; background: #f8f9fa; border-radius: 4px; margin-bottom: 0.5rem;">
2407
+ <span style="font-size: 1.2rem;">${fileIcon}</span>
2408
+ <span style="flex: 1; overflow: hidden; text-overflow: ellipsis;">${file.name}</span>
2409
+ <span style="color: #666; font-size: 0.8rem; white-space: nowrap;">${fileSize}MB</span>
2410
+ <span style="color: #28a745; font-weight: bold;">✓</span>
2411
+ </div>
2412
+ `;
2413
+ }
2414
+
2415
+ // Se houver erros, mostrar e abortar
2416
+ if (errors.length > 0) {
2417
+ errorDiv.textContent = errors.join('; ');
2418
+ errorDiv.style.display = 'block';
2419
+ input.value = '';
2420
+ return;
2430
2421
  }
2431
- }
2432
-
2433
- // Função para obter ícone baseado na extensão
2434
- function getFileIcon(filename) {
2435
- const ext = filename.split('.').pop().toLowerCase();
2436
- const icons = {
2437
- 'pdf': '📄',
2438
- 'doc': '📝', 'docx': '📝',
2439
- 'xls': '📊', 'xlsx': '📊',
2440
- 'jpg': '🖼️', 'jpeg': '🖼️', 'png': '🖼️', 'gif': '🖼️',
2441
- 'txt': '📄',
2442
- 'csv': '📋'
2443
- };
2444
- return icons[ext] || '📎';
2445
- }
2446
-
2447
- // Drag and Drop
2448
- function handleDragOver(e) {
2449
- e.preventDefault();
2450
- e.stopPropagation();
2451
- e.currentTarget.style.borderColor = '#007bff';
2452
- e.currentTarget.style.background = '#f0f8ff';
2453
- }
2454
-
2455
- function handleDragLeave(e) {
2456
- e.preventDefault();
2457
- e.stopPropagation();
2458
- e.currentTarget.style.borderColor = '#C8D1E0';
2459
- e.currentTarget.style.background = '#f8f9fa';
2460
- }
2461
-
2462
- function handleFileDrop(e, inputId) {
2463
- e.preventDefault();
2464
- e.stopPropagation();
2465
2422
 
2466
- const input = document.getElementById(inputId);
2467
- const files = e.dataTransfer.files;
2423
+ // Se tudo estiver ok, exibir os arquivos
2424
+ displayDiv.innerHTML = fileHtml;
2468
2425
 
2469
- if (files.length > 0) {
2470
- // Simular seleção de arquivo
2471
- input.files = files;
2472
- updateFileName(input);
2426
+ // Adicionar resumo para múltiplos arquivos
2427
+ if (input.files.length > 1) {
2428
+ const totalSizeMB = (totalSize / (1024 * 1024)).toFixed(2);
2429
+ const summaryDiv = document.createElement('div');
2430
+ summaryDiv.style.textAlign = 'right';
2431
+ summaryDiv.style.padding = '0.3rem 0.5rem';
2432
+ summaryDiv.style.fontSize = '0.9rem';
2433
+ summaryDiv.style.fontWeight = 'bold';
2434
+ summaryDiv.style.color = '#2A5E2C';
2435
+ summaryDiv.style.background = '#f0f8f0';
2436
+ summaryDiv.style.borderRadius = '4px';
2437
+ summaryDiv.style.marginTop = '0.5rem';
2438
+ summaryDiv.textContent = `${input.files.length} arquivos (${totalSizeMB}MB total)`;
2439
+ displayDiv.appendChild(summaryDiv);
2473
2440
  }
2474
2441
 
2475
- // Resetar visual
2476
- e.currentTarget.style.borderColor = '#C8D1E0';
2477
- e.currentTarget.style.background = '#f8f9fa';
2442
+ displayDiv.style.display = 'block';
2443
+
2444
+ // Atualizar visual do dropzone
2445
+ dropzone.style.borderColor = '#28a745';
2446
+ dropzone.style.background = '#f8fff8';
2447
+ } else {
2448
+ // Não há arquivos selecionados
2449
+ displayDiv.style.display = 'none';
2450
+ dropzone.style.borderColor = '#C8D1E0';
2451
+ dropzone.style.background = '#f8f9fa';
2478
2452
  }
2453
+ }
2479
2454
 
2480
2455
  // Atualizar texto de rating
2481
2456
  document.addEventListener('click', function(e) {
@@ -17,7 +17,7 @@ from django.conf import settings
17
17
  import os
18
18
  from wagtail import hooks
19
19
  from django.contrib.contenttypes.models import ContentType
20
-
20
+ import re
21
21
 
22
22
 
23
23
 
@@ -63,7 +63,7 @@ def add_export_button():
63
63
  # VIEWS DE DOWNLOAD DE ARQUIVOS - CORRIGIDAS
64
64
  # ===============================================
65
65
  def download_form_file(request, page_id, submission_id, field_name):
66
- """Download de arquivo do FormularioSubmission tradicional - CORRIGIDA"""
66
+ """Download de arquivo do FormularioSubmission tradicional - CORRIGIDA PARA MÚLTIPLOS ARQUIVOS"""
67
67
  try:
68
68
  print(f"🚀 INICIANDO DOWNLOAD:")
69
69
  print(f" submission_id: {submission_id}")
@@ -78,6 +78,33 @@ def download_form_file(request, page_id, submission_id, field_name):
78
78
  print("❌ ERRO: Usuário sem permissão")
79
79
  raise Http404("Sem permissão")
80
80
 
81
+ # Verificar se é parte de um campo de múltiplos arquivos
82
+ is_multiple_item = '_' in field_name and field_name.split('_')[-1].isdigit()
83
+ original_filename = None
84
+
85
+ if is_multiple_item:
86
+ # Extrair o nome base do campo e índice
87
+ parts = field_name.split('_')
88
+ index = int(parts[-1])
89
+ base_field_name = '_'.join(parts[:-1])
90
+
91
+ print(f"🔍 Campo múltiplo detectado: base={base_field_name}, índice={index}")
92
+
93
+ # Buscar no campo base que contém a lista de arquivos
94
+ if base_field_name in submission.form_data:
95
+ files_list = submission.form_data[base_field_name]
96
+ if isinstance(files_list, list) and len(files_list) > index:
97
+ file_info = files_list[index]
98
+ if isinstance(file_info, dict) and 'filename' in file_info:
99
+ original_filename = file_info['filename']
100
+ print(f"✅ Nome encontrado na lista: {original_filename}")
101
+
102
+ # Se não conseguiu determinar o nome, usar lógica padrão
103
+ if not original_filename:
104
+ form_data = submission.form_data or {}
105
+ field_data = form_data.get(field_name, {})
106
+ original_filename = field_data.get('filename', os.path.basename(field_name)) if isinstance(field_data, dict) else os.path.basename(field_name)
107
+
81
108
  print("🔍 Chamando find_file_path_traditional...")
82
109
  file_path = find_file_path_traditional(submission, field_name, page_id)
83
110
  print(f"📂 Resultado: {file_path}")
@@ -85,7 +112,7 @@ def download_form_file(request, page_id, submission_id, field_name):
85
112
  if not file_path:
86
113
  print("❌ ERRO: Arquivo não encontrado")
87
114
 
88
- folder_path = os.path.join(settings.MEDIA_ROOT, 'form_submissions', str(submission_id))
115
+ folder_path = os.path.join(settings.MEDIA_ROOT, 'form_submissions', str(page_id), str(submission_id))
89
116
  print(f"🔍 Verificando pasta: {folder_path}")
90
117
 
91
118
  if os.path.exists(folder_path):
@@ -101,11 +128,6 @@ def download_form_file(request, page_id, submission_id, field_name):
101
128
 
102
129
  raise Http404("Arquivo não encontrado")
103
130
 
104
- # Nome original do arquivo
105
- form_data = submission.form_data or {}
106
- field_data = form_data.get(field_name, {})
107
- original_filename = field_data.get('filename', os.path.basename(file_path)) if isinstance(field_data, dict) else os.path.basename(file_path)
108
-
109
131
  print(f"📎 Nome original: {original_filename}")
110
132
  print(f"🎯 Retornando arquivo: {file_path}")
111
133
 
@@ -120,6 +142,7 @@ def download_form_file(request, page_id, submission_id, field_name):
120
142
  import traceback
121
143
  print(f"🔥 TRACEBACK: {traceback.format_exc()}")
122
144
  raise Http404(f"Erro ao baixar arquivo: {e}")
145
+
123
146
 
124
147
 
125
148
  def verificar_arquivos_tradicionais():
@@ -377,53 +400,142 @@ def find_file_path_dynamic(submission, field_name):
377
400
  def format_field_value_for_csv(field_name, value, page_id, submission=None, request=None):
378
401
  """Formata valores para CSV com links de download quando possível"""
379
402
 
380
- if isinstance(value, list):
381
- return ', '.join(str(v) for v in value if v)
403
+ # DEPURAÇÃO: Imprimir tipo e valor para diagnóstico
404
+ print(f"CSV: Campo {field_name}, tipo: {type(value)}, valor: {value}")
382
405
 
383
- elif isinstance(value, dict) and 'filename' in value:
384
- filename = value.get('filename', '')
385
- size = value.get('size', 0)
406
+ # CASO 1: Lista de dicionários (múltiplos arquivos)
407
+ if isinstance(value, list) and len(value) > 0 and isinstance(value[0], dict) and 'filename' in value[0]:
408
+ print(f"📋 Processando LISTA de arquivos para campo: {field_name}")
386
409
 
387
- # Tentar criar link de download
388
- download_url = None
389
- if submission and request:
390
- try:
391
- # Determinar o tipo de submissão
392
- if hasattr(submission, 'page'):
393
- # FormularioSubmission (tradicional)
410
+ file_entries = []
411
+ for i, file_dict in enumerate(value):
412
+ filename = file_dict.get('filename', '')
413
+ size = file_dict.get('size', 0)
414
+
415
+ # Formatar tamanho
416
+ size_info = ""
417
+ if size:
418
+ size_mb = round(size / (1024 * 1024), 2)
419
+ size_info = f" ({size_mb} MB)"
420
+
421
+ # Inicializar download_url
422
+ download_url = None
423
+
424
+ # Tentar criar URL de download
425
+ if submission and request:
426
+ try:
427
+ # Usar índice específico no nome do campo
394
428
  download_url = request.build_absolute_uri(
395
429
  reverse('download_form_file', kwargs={
430
+ 'page_id': page_id,
396
431
  'submission_id': submission.id,
397
- 'field_name': field_name,
398
- 'page_id': page_id
399
- })
400
- )
401
- else:
402
- # FormularioDinamicoSubmission
403
- download_url = request.build_absolute_uri(
404
- reverse('download_dynamic_file', kwargs={
405
- 'submission_id': submission.id,
406
- 'field_name': field_name,
407
- 'page_id': page_id
432
+ 'field_name': f"{field_name}_{i}"
408
433
  })
409
434
  )
410
- except Exception as e:
411
- print(f"⚠️ Erro ao criar URL de download: {e}")
435
+ except Exception as e:
436
+ print(f"⚠️ Erro ao criar URL de download para {filename}: {e}")
437
+
438
+ # Adicionar entrada formatada
439
+ if download_url:
440
+ file_entries.append(f"{filename}{size_info} - DOWNLOAD: {download_url}")
441
+ else:
442
+ file_entries.append(f"ARQUIVO: {filename}{size_info}")
443
+
444
+ # Juntar todas as entradas com separador
445
+ return " | ".join(file_entries)
446
+
447
+ # CASO 2: Verificar submissão diretamente para arquivos múltiplos
448
+ # Este caso é para quando os arquivos estão armazenados individualmente na submissão
449
+ elif submission and submission.form_data:
450
+ multiple_files = []
451
+ pattern = re.compile(f"^{re.escape(field_name)}_\\d+$")
452
+
453
+ # Procurar por campos de múltiplos arquivos (field_name_0, field_name_1, etc.)
454
+ for key in submission.form_data.keys():
455
+ if pattern.match(key):
456
+ file_data = submission.form_data[key]
457
+ if isinstance(file_data, dict) and 'filename' in file_data:
458
+ try:
459
+ index = int(key.split('_')[-1])
460
+
461
+ filename = file_data.get('filename', '')
462
+ size = file_data.get('size', 0)
463
+
464
+ size_info = ""
465
+ if size:
466
+ size_mb = round(size / (1024 * 1024), 2)
467
+ size_info = f" ({size_mb} MB)"
468
+
469
+ # Tentar criar URL de download
470
+ download_url = None
471
+ if request:
472
+ try:
473
+ download_url = request.build_absolute_uri(
474
+ reverse('download_form_file', kwargs={
475
+ 'page_id': page_id,
476
+ 'submission_id': submission.id,
477
+ 'field_name': key
478
+ })
479
+ )
480
+ except Exception as e:
481
+ print(f"⚠️ Erro ao criar URL para {filename}: {e}")
482
+
483
+ if download_url:
484
+ multiple_files.append((index, f"{filename}{size_info} - DOWNLOAD: {download_url}"))
485
+ else:
486
+ multiple_files.append((index, f"ARQUIVO: {filename}{size_info}"))
487
+ except Exception as e:
488
+ print(f"Erro ao processar arquivo múltiplo {key}: {e}")
412
489
 
413
- # Formatar resposta
490
+ # Se encontrou múltiplos arquivos, formatar juntos
491
+ if multiple_files:
492
+ print(f"📋 Encontrados {len(multiple_files)} arquivos para {field_name}")
493
+ # Ordenar por índice
494
+ multiple_files.sort(key=lambda x: x[0])
495
+ return " | ".join([item[1] for item in multiple_files])
496
+
497
+ # CASO 3: Arquivo único como dicionário
498
+ if isinstance(value, dict) and 'filename' in value:
499
+ print(f"📄 Processando arquivo único: {field_name}")
500
+
501
+ filename = value.get('filename', '')
502
+ size = value.get('size', 0)
503
+
504
+ # Formatar tamanho
505
+ size_info = ""
414
506
  if size:
415
507
  size_mb = round(size / (1024 * 1024), 2)
416
508
  size_info = f" ({size_mb} MB)"
417
- else:
418
- size_info = ""
419
509
 
510
+ # Inicializar download_url
511
+ download_url = None
512
+
513
+ # Tentar criar link de download
514
+ if submission and request:
515
+ try:
516
+ download_url = request.build_absolute_uri(
517
+ reverse('download_form_file', kwargs={
518
+ 'page_id': page_id,
519
+ 'submission_id': submission.id,
520
+ 'field_name': field_name
521
+ })
522
+ )
523
+ except Exception as e:
524
+ print(f"⚠️ Erro ao criar URL de download: {e}")
525
+
526
+ # Formatar resposta
420
527
  if download_url:
421
528
  return f"{filename}{size_info} - DOWNLOAD: {download_url}"
422
529
  else:
423
530
  return f"ARQUIVO: {filename}{size_info}"
424
531
 
425
- else:
426
- return str(value) if value else ''
532
+ # CASO 4: Lista genérica (não de arquivos)
533
+ elif isinstance(value, list):
534
+ return ', '.join(str(v) for v in value if v)
535
+
536
+ # CASO 5: Valor padrão para outros tipos
537
+ return str(value) if value else ''
538
+
427
539
 
428
540
  # ===============================================
429
541
  # VIEWS DE EXPORTAÇÃO CSV
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wagtail-enap-designsystem
3
- Version: 1.2.1.195
3
+ Version: 1.2.1.196
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
@@ -6,13 +6,13 @@ enap_designsystem/settings.py,sha256=gCkWCK4QZlD8i82G8v7aHhKz954KFkY6xdhKNw9CCpw
6
6
  enap_designsystem/signals.py,sha256=oaHpZms4Dkc97ql-55Q0QxU7-45jwg2Y1XoRDOJ45tc,1978
7
7
  enap_designsystem/urls.py,sha256=EJ52w-55BysQU5pTZR4ltMtZM7aSKyjZdnQDGFipDXY,2219
8
8
  enap_designsystem/views.py,sha256=7C-juv7sLLDiF8UfZthzbA0lkH1yIYVqiYZK-76lKCk,78334
9
- enap_designsystem/wagtail_hooks.py,sha256=SpPKCaAoTe9iNE-myKAC_2Ny6Q0JPlkEo1ETplPHAus,71629
9
+ enap_designsystem/wagtail_hooks.py,sha256=0SU5gtFY_9JFRqTKG6QsU8Hzh1qwZvmIydbaw6rlMA4,76939
10
10
  enap_designsystem/blocks/__init__.py,sha256=jtlNAEIsIo_KipwQcUMVQuR4vdt4AjrLjag6CFURD6E,39527
11
11
  enap_designsystem/blocks/base_blocks.py,sha256=ZuqVWn4PEAvD3pKM1ST7wjo4lwv98ooen_rs15rRJbg,10866
12
12
  enap_designsystem/blocks/chatbot_blocks.py,sha256=YeCznrXMbFa9MP9vjdTYl53ZhKsywkGOXvFK2bwcqW0,1133
13
13
  enap_designsystem/blocks/content_blocks.py,sha256=X8Ldf6eMRhjhIYxC2rLssb151r2iFFFQ8XxwPpBbjyI,17282
14
- enap_designsystem/blocks/form.py,sha256=oK0Lswd1nIH0bLTdBL_XF_1LbNPixEYr5FJWj8DAxHY,88782
15
- enap_designsystem/blocks/html_blocks.py,sha256=bQzkwilXfYpXF_hL6QdoYnacBCAXF8_bJp1s2R6dLeg,281968
14
+ enap_designsystem/blocks/form.py,sha256=rQ_KfMgafbA7NSBGneUsregEhphKCxfNh4rG8s6FEWI,90007
15
+ enap_designsystem/blocks/html_blocks.py,sha256=YE8xNA8HQ5iavP_UIlJrhwIUpgtfVMQTP3XVk2dg4J0,281986
16
16
  enap_designsystem/blocks/layout_blocks.py,sha256=qND7aUna3VL3PK7sAKE7PiPfSvahMwHK_lZoKUkudeo,23461
17
17
  enap_designsystem/blocks/security.py,sha256=QA7lmQ_eQ6iopunatl_DrHkEegAwMZJGwXunRulbCjk,2099
18
18
  enap_designsystem/blocks/semana_blocks.py,sha256=AfaxJQmStvFkw6yrPeKyZurC6jzCxWxyzmdny_pret0,70929
@@ -834,7 +834,7 @@ enap_designsystem/templates/enap_designsystem/blocks/suap/apisuap_courses_block.
834
834
  enap_designsystem/templates/enap_designsystem/blocks/suap/suap_courses_block.html,sha256=_7AC4WBH4qCXmwlKqnRLbPeUnAopLGeKUIrd6FYcvps,16036
835
835
  enap_designsystem/templates/enap_designsystem/blocks/suap/suap_events_block.html,sha256=mL2DFQeAuDIx_GyCoEURKmME-Mmd-zQ_NZkO7YW9Z2k,20182
836
836
  enap_designsystem/templates/enap_designsystem/form_templates/form_report.html,sha256=WXf4HgNQY0M6zZ-tERqf01mHbGflqWXT96RaJYjCxFA,16081
837
- enap_designsystem/templates/enap_designsystem/form_templates/formulario_page.html,sha256=KxF8fRklIfsUPYnGfXeoF-QOYNmJ8LvH9REIDJnyOAM,59845
837
+ enap_designsystem/templates/enap_designsystem/form_templates/formulario_page.html,sha256=kDdB_pSFU5_Z4Kh81oSyItDs6xy6U88rJFTfFv91ld0,64367
838
838
  enap_designsystem/templates/enap_designsystem/form_templates/formulario_page_landing.html,sha256=2dVaFwunBrHsq0b3rP1buEFxO6hfplFH3-GoUuyLJPo,7598
839
839
  enap_designsystem/templates/enap_designsystem/form_templates/formulario_page_success.html,sha256=jFE9GYRxy19ha37pVvucEVYDKTeU56Nav2Fd3phqmZ4,9363
840
840
  enap_designsystem/templates/enap_designsystem/form_templates/home_page.html,sha256=BYV5TV6xp0uY3SWtNsAf8p-aDqPiHfM8j4pWbqTUV2M,42329
@@ -843,7 +843,7 @@ enap_designsystem/templates/enap_designsystem/form_templates/welcome_page.html,s
843
843
  enap_designsystem/templates/enap_designsystem/includes/chatbot_global.html,sha256=CO7wXrGx1l_icY8vb9OdrILCUXEFBomJPPxQz_e9hq4,12308
844
844
  enap_designsystem/templates/enap_designsystem/includes/cookie_banner.html,sha256=vyrvj1W_haSA4oKmQiyjFpjoTFqjPSZfz3u5nfr-P2g,10871
845
845
  enap_designsystem/templates/enap_designsystem/includes/footer.html,sha256=qsDmN413A_NNOhRFZz1Uxym1j5UPVhEDuRRDLTgRMPA,4063
846
- enap_designsystem/templates/enap_designsystem/includes/form_field.html,sha256=HcWMIJrI3xNW8tvcYqCmNcx8APU0z7XsdNPqt27Q0rM,135615
846
+ enap_designsystem/templates/enap_designsystem/includes/form_field.html,sha256=4yT3b5GfhZVST9VEBq1vKtzraW2CPVJY5gRVquadJmU,134614
847
847
  enap_designsystem/templates/enap_designsystem/pages/404.html,sha256=v_vUTG27_ohx0_YLcOBrxMG4MYdDT92g3boJE9m8Mrg,2885
848
848
  enap_designsystem/templates/enap_designsystem/pages/area_aluno.html,sha256=aWDmOE3Ti0Ct-ztxh7tcgIDGvdij7Heq5gzMyHLjjR0,15775
849
849
  enap_designsystem/templates/enap_designsystem/pages/article_index_page.html,sha256=6VjJ3_EroEPCidFCW5gBDzvbi81UcFX9lGpbc4j6r9U,11482
@@ -934,8 +934,8 @@ enap_designsystem/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
934
934
  enap_designsystem/utils/decorators.py,sha256=aq6SbLn0LcH2rfE3ZFit8jkD7pSx9fLVBUUwVB747hg,335
935
935
  enap_designsystem/utils/services.py,sha256=6dG5jLSbwH49jpZV9ZNpWlaZqI49gTlwlr1vaerxdiU,5824
936
936
  enap_designsystem/utils/sso.py,sha256=vjAuoYgoLeQAa_dkkyQ6-LmHvKMaVCxizNFpe5y3iUA,1145
937
- wagtail_enap_designsystem-1.2.1.195.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
938
- wagtail_enap_designsystem-1.2.1.195.dist-info/METADATA,sha256=Ju7B0HSlgcMv5sGqSYgOJm84PTXgvFMuHcoP2J0viwE,3651
939
- wagtail_enap_designsystem-1.2.1.195.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
940
- wagtail_enap_designsystem-1.2.1.195.dist-info/top_level.txt,sha256=RSFgMASxoA-hVftm5i4Qd0rArlX4Dq08lLv5G4sYD-g,18
941
- wagtail_enap_designsystem-1.2.1.195.dist-info/RECORD,,
937
+ wagtail_enap_designsystem-1.2.1.196.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
938
+ wagtail_enap_designsystem-1.2.1.196.dist-info/METADATA,sha256=Qd_mzE5YjJ5qBZjg23cPheMlCtDXuaeUTxIvSqwrgUQ,3651
939
+ wagtail_enap_designsystem-1.2.1.196.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
940
+ wagtail_enap_designsystem-1.2.1.196.dist-info/top_level.txt,sha256=RSFgMASxoA-hVftm5i4Qd0rArlX4Dq08lLv5G4sYD-g,18
941
+ wagtail_enap_designsystem-1.2.1.196.dist-info/RECORD,,