wagtail-enap-designsystem 1.2.1.129__py3-none-any.whl → 1.2.1.131__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.

Files changed (35) hide show
  1. enap_designsystem/blocks/__init__.py +6 -0
  2. enap_designsystem/blocks/form.py +243 -2
  3. enap_designsystem/blocks/html_blocks.py +316 -6
  4. enap_designsystem/blocks/semana_inovacao.py +1 -0
  5. enap_designsystem/context_processors.py +30 -6
  6. enap_designsystem/migrations/0395_formulariopage_enable_scoring_and_more.py +106 -0
  7. enap_designsystem/migrations/0396_alter_areaaluno_body_alter_enapcomponentes_body_and_more.py +51263 -0
  8. enap_designsystem/migrations/0397_alter_areaaluno_body_alter_enapcomponentes_body_and_more.py +51718 -0
  9. enap_designsystem/migrations/0398_alter_areaaluno_body_alter_enapcomponentes_body_and_more.py +51718 -0
  10. enap_designsystem/migrations/0399_alter_areaaluno_body_alter_enapcomponentes_body_and_more.py +51718 -0
  11. enap_designsystem/migrations/0400_alter_areaaluno_body_alter_enapcomponentes_body_and_more.py +51718 -0
  12. enap_designsystem/migrations/0401_alter_areaaluno_body_alter_cursoeadpage_curso_and_more.py +52692 -0
  13. enap_designsystem/migrations/0402_alter_areaaluno_body_alter_enapcomponentes_body_and_more.py +52082 -0
  14. enap_designsystem/migrations/0403_alter_areaaluno_body_alter_enapcomponentes_body_and_more.py +52802 -0
  15. enap_designsystem/migrations/0404_sistemavotacaopage_conteudo_pagina.py +253 -0
  16. enap_designsystem/migrations/0405_sistemavotacaopage_exigir_recaptcha_and_more.py +41 -0
  17. enap_designsystem/models.py +94 -46
  18. enap_designsystem/settings.py +4 -0
  19. enap_designsystem/templates/enap_designsystem/blocks/apresentacao_simple_block.html +1 -1
  20. enap_designsystem/templates/enap_designsystem/blocks/cards_titles.html +309 -0
  21. enap_designsystem/templates/enap_designsystem/blocks/clientes_block.html +1 -1
  22. enap_designsystem/templates/enap_designsystem/blocks/cpnu_dashboard_block.html +5 -0
  23. enap_designsystem/templates/enap_designsystem/blocks/logos_simple_block.html +329 -0
  24. enap_designsystem/templates/enap_designsystem/blocks/numeros_block.html +195 -0
  25. enap_designsystem/templates/enap_designsystem/blocks/page/pagenoticias_block.html +6 -0
  26. enap_designsystem/templates/enap_designsystem/blocks/recaptcha.html +146 -126
  27. enap_designsystem/templates/enap_designsystem/form_templates/formulario_page.html +1 -0
  28. enap_designsystem/templates/enap_designsystem/sistema_votacao_page.html +583 -543
  29. enap_designsystem/views.py +1 -1
  30. enap_designsystem/wagtail_hooks.py +390 -9
  31. {wagtail_enap_designsystem-1.2.1.129.dist-info → wagtail_enap_designsystem-1.2.1.131.dist-info}/METADATA +1 -1
  32. {wagtail_enap_designsystem-1.2.1.129.dist-info → wagtail_enap_designsystem-1.2.1.131.dist-info}/RECORD +35 -21
  33. {wagtail_enap_designsystem-1.2.1.129.dist-info → wagtail_enap_designsystem-1.2.1.131.dist-info}/WHEEL +0 -0
  34. {wagtail_enap_designsystem-1.2.1.129.dist-info → wagtail_enap_designsystem-1.2.1.131.dist-info}/licenses/LICENSE +0 -0
  35. {wagtail_enap_designsystem-1.2.1.129.dist-info → wagtail_enap_designsystem-1.2.1.131.dist-info}/top_level.txt +0 -0
@@ -29,6 +29,9 @@ from .html_blocks import (
29
29
  ApresentacaoBlock,
30
30
  ApresentacaoSimpleBlock,
31
31
  RecaptchaBlock,
32
+ SecaoApresentacaoCardsBlock,
33
+ LogosSimpleBlock,
34
+ NumerosBlock
32
35
  )
33
36
 
34
37
  from .semana_blocks import (
@@ -358,6 +361,9 @@ class ContentStreamBlock(StreamBlock):
358
361
  html = HTMLCustomBlock(label="🔧 HTML Customizado")
359
362
  apresentcao = ApresentacaoBlock(label="📰 Componente simples com título, quadrado de conteúdo e botão")
360
363
  ApresentacaoBlock = ApresentacaoSimpleBlock(label="Componente com título, texto e grid flexível de cards")
364
+ enap_cards_apresentacao = SecaoApresentacaoCardsBlock(label="🎴 Seção com título & cards")
365
+ enap_cards_logs = LogosSimpleBlock(label="🎴 Seção com logos")
366
+ enap_cards_numebrs = NumerosBlock(label="🎴 Seção com numeros")
361
367
 
362
368
  class Meta:
363
369
  label = "📰 Conteúdo e Texto"
@@ -1001,6 +1001,46 @@ class FormStepBlock(StructBlock):
1001
1001
 
1002
1002
 
1003
1003
 
1004
+ class FormFieldScoring(models.Model):
1005
+ """Armazena pontuação configurada para cada campo"""
1006
+ formulario_page = models.ForeignKey('FormularioPage', on_delete=models.CASCADE, related_name='field_scorings')
1007
+ field_id = models.CharField(max_length=255, verbose_name="ID do Campo")
1008
+ field_label = models.CharField(max_length=500, verbose_name="Pergunta")
1009
+ field_type = models.CharField(max_length=100, verbose_name="Tipo de Campo")
1010
+ scoring_data = models.JSONField(verbose_name="Dados de Pontuação", default=dict)
1011
+
1012
+ class Meta:
1013
+ verbose_name = "Pontuação do Campo"
1014
+ verbose_name_plural = "Pontuações dos Campos"
1015
+ unique_together = ['formulario_page', 'field_id']
1016
+
1017
+ def __str__(self):
1018
+ return f"{self.field_label} ({self.field_type})"
1019
+
1020
+
1021
+ # MODELO para submissões com pontuação
1022
+ class FormularioSubmissionScored(models.Model):
1023
+ """Submissões com pontuação calculada"""
1024
+ original_submission = models.OneToOneField(
1025
+ 'FormularioSubmission',
1026
+ on_delete=models.CASCADE,
1027
+ related_name='scoring'
1028
+ )
1029
+ total_score = models.FloatField(verbose_name="Pontuação Total", default=0)
1030
+ score_details = models.JSONField(verbose_name="Detalhes da Pontuação", default=list)
1031
+ calculated_at = models.DateTimeField(auto_now_add=True)
1032
+
1033
+ class Meta:
1034
+ verbose_name = "Pontuação da Submissão"
1035
+ verbose_name_plural = "Pontuações das Submissões"
1036
+
1037
+ def __str__(self):
1038
+ return f"Pontuação: {self.total_score} pts"
1039
+
1040
+
1041
+
1042
+
1043
+
1004
1044
  class FormularioPage(Page):
1005
1045
  """Página de formulário com steps dinâmicos - Wagtail 7.0"""
1006
1046
 
@@ -1132,6 +1172,12 @@ class FormularioPage(Page):
1132
1172
  help_text="Email para receber notificações (ex: admin@enap.gov.br)"
1133
1173
  )
1134
1174
 
1175
+ enable_scoring = models.BooleanField(
1176
+ verbose_name="Ativar Sistema de Pontuação",
1177
+ default=False,
1178
+ help_text="Ativar para poder configurar pontos (invisível para usuários)"
1179
+ )
1180
+
1135
1181
  content_panels = Page.content_panels + [
1136
1182
  MultiFieldPanel([
1137
1183
  FieldPanel('form_title'),
@@ -1140,8 +1186,11 @@ class FormularioPage(Page):
1140
1186
  ], "🎬 Tela de Boas-vindas"),
1141
1187
 
1142
1188
  FieldPanel('intro'),
1189
+
1190
+ MultiFieldPanel([
1191
+ FieldPanel('enable_scoring'),
1192
+ ], "Sistema de Pontuação (Oculto)"),
1143
1193
 
1144
- # 🚀 NOVO: StreamField para steps dinâmicos
1145
1194
  FieldPanel('form_steps'),
1146
1195
 
1147
1196
  MultiFieldPanel([
@@ -1653,11 +1702,203 @@ class FormularioPage(Page):
1653
1702
  }
1654
1703
 
1655
1704
  return conditional_data
1705
+
1706
+ def save_field_scoring(self, field_id, scoring_data):
1707
+ '''Salva pontuação para um campo'''
1708
+ field_info = None
1709
+
1710
+ for field in self.extract_scorable_fields():
1711
+ if field['field_id'] == field_id:
1712
+ field_info = field
1713
+ break
1714
+
1715
+ if not field_info:
1716
+ return False
1717
+
1718
+ scoring, created = FormFieldScoring.objects.get_or_create(
1719
+ formulario_page=self,
1720
+ field_id=field_id,
1721
+ defaults={
1722
+ 'field_label': field_info['field_label'],
1723
+ 'field_type': field_info['field_type'],
1724
+ 'scoring_data': scoring_data
1725
+ }
1726
+ )
1727
+
1728
+ if not created:
1729
+ scoring.scoring_data = scoring_data
1730
+ scoring.save()
1731
+
1732
+ return True
1733
+
1734
+ def calculate_submission_score(self, submission):
1735
+ '''Calcula pontuação para uma submissão'''
1736
+ if not self.enable_scoring:
1737
+ return 0, []
1738
+
1739
+ total_score = 0
1740
+ score_details = []
1741
+ form_data = submission.form_data
1742
+
1743
+ field_scorings = FormFieldScoring.objects.filter(formulario_page=self)
1744
+
1745
+ for scoring in field_scorings:
1746
+ field_id = scoring.field_id
1747
+ user_response = form_data.get(field_id)
1748
+
1749
+ if not user_response:
1750
+ continue
1751
+
1752
+ field_score = 0
1753
+
1754
+ if scoring.field_type in ['dropdown_field', 'radio_field']:
1755
+ option_scores = scoring.scoring_data.get('option_scores', {})
1756
+ field_score = option_scores.get(user_response, 0)
1757
+
1758
+ elif scoring.field_type == 'checkbox_multiple_field':
1759
+ if isinstance(user_response, list):
1760
+ option_scores = scoring.scoring_data.get('option_scores', {})
1761
+ calculation_method = scoring.scoring_data.get('calculation_method', 'sum')
1762
+
1763
+ scores = [option_scores.get(option, 0) for option in user_response]
1764
+
1765
+ if calculation_method == 'sum':
1766
+ field_score = sum(scores)
1767
+ elif calculation_method == 'max':
1768
+ field_score = max(scores) if scores else 0
1769
+ elif calculation_method == 'average':
1770
+ field_score = sum(scores) / len(scores) if scores else 0
1771
+
1772
+ elif scoring.field_type == 'rating_field':
1773
+ try:
1774
+ rating_value = float(user_response)
1775
+ multiplier = scoring.scoring_data.get('multiplier', 1.0)
1776
+ field_score = rating_value * multiplier
1777
+ except (ValueError, TypeError):
1778
+ field_score = 0
1779
+
1780
+ total_score += field_score
1781
+
1782
+ score_details.append({
1783
+ 'field_label': scoring.field_label,
1784
+ 'user_response': user_response,
1785
+ 'field_score': field_score
1786
+ })
1787
+
1788
+ return total_score, score_details
1789
+
1790
+ def get_scoring_url(self):
1791
+ '''URL para configurar pontuação'''
1792
+ return reverse('formulario_scoring', args=[self.pk])
1793
+
1794
+ def get_results_url(self):
1795
+ '''URL para ver resultados'''
1796
+ return reverse('formulario_results', args=[self.pk])
1656
1797
 
1657
-
1798
+ def extract_scorable_fields(self):
1799
+ scorable_fields = []
1658
1800
 
1801
+ for step_index, step_block in enumerate(self.form_steps):
1802
+ step_number = step_index + 1
1803
+
1804
+ for block in step_block.value['fields']:
1805
+ field_id = f"{block.block_type}_{block.id}"
1806
+
1807
+ # Apenas campos que fazem sentido pontuar
1808
+ if block.block_type in ['dropdown_field', 'radio_field', 'checkbox_multiple_field', 'rating_field']:
1809
+ field_data = {
1810
+ 'field_id': field_id,
1811
+ 'field_label': block.value.get('label', 'Campo sem título'),
1812
+ 'field_type': block.block_type,
1813
+ 'step_number': step_number,
1814
+ 'options': block.value.get('options', []),
1815
+ 'block_data': block.value
1816
+ }
1817
+ scorable_fields.append(field_data)
1818
+
1819
+ return scorable_fields
1659
1820
 
1821
+ def save_field_scoring(self, field_id, scoring_data):
1822
+ field_info = None
1823
+
1824
+ for field in self.extract_scorable_fields():
1825
+ if field['field_id'] == field_id:
1826
+ field_info = field
1827
+ break
1828
+
1829
+ if not field_info:
1830
+ return False
1831
+
1832
+ scoring, created = FormFieldScoring.objects.get_or_create(
1833
+ formulario_page=self,
1834
+ field_id=field_id,
1835
+ defaults={
1836
+ 'field_label': field_info['field_label'],
1837
+ 'field_type': field_info['field_type'],
1838
+ 'scoring_data': scoring_data
1839
+ }
1840
+ )
1841
+
1842
+ if not created:
1843
+ scoring.scoring_data = scoring_data
1844
+ scoring.save()
1845
+
1846
+ return True
1660
1847
 
1848
+ def calculate_submission_score(self, submission):
1849
+ if not self.enable_scoring:
1850
+ return 0, []
1851
+
1852
+ total_score = 0
1853
+ score_details = []
1854
+ form_data = submission.form_data
1855
+
1856
+ field_scorings = FormFieldScoring.objects.filter(formulario_page=self)
1857
+
1858
+ for scoring in field_scorings:
1859
+ field_id = scoring.field_id
1860
+ user_response = form_data.get(field_id)
1861
+
1862
+ if not user_response:
1863
+ continue
1864
+
1865
+ field_score = 0
1866
+
1867
+ if scoring.field_type in ['dropdown_field', 'radio_field']:
1868
+ option_scores = scoring.scoring_data.get('option_scores', {})
1869
+ field_score = option_scores.get(user_response, 0)
1870
+
1871
+ elif scoring.field_type == 'checkbox_multiple_field':
1872
+ if isinstance(user_response, list):
1873
+ option_scores = scoring.scoring_data.get('option_scores', {})
1874
+ calculation_method = scoring.scoring_data.get('calculation_method', 'sum')
1875
+
1876
+ scores = [option_scores.get(option, 0) for option in user_response]
1877
+
1878
+ if calculation_method == 'sum':
1879
+ field_score = sum(scores)
1880
+ elif calculation_method == 'max':
1881
+ field_score = max(scores) if scores else 0
1882
+ elif calculation_method == 'average':
1883
+ field_score = sum(scores) / len(scores) if scores else 0
1884
+
1885
+ elif scoring.field_type == 'rating_field':
1886
+ try:
1887
+ rating_value = float(user_response)
1888
+ multiplier = scoring.scoring_data.get('multiplier', 1.0)
1889
+ field_score = rating_value * multiplier
1890
+ except (ValueError, TypeError):
1891
+ field_score = 0
1892
+
1893
+ total_score += field_score
1894
+
1895
+ score_details.append({
1896
+ 'field_label': scoring.field_label,
1897
+ 'user_response': user_response,
1898
+ 'field_score': field_score
1899
+ })
1900
+
1901
+ return total_score, score_details
1661
1902
  class Meta:
1662
1903
  verbose_name = "Formulário Dinâmico"
1663
1904
  verbose_name_plural = "Formulários Dinâmicos"
@@ -7582,25 +7582,86 @@ class ApresentacaoSimpleBlock(blocks.StructBlock):
7582
7582
  help_text="Quantos cards por linha",
7583
7583
  label="Cards por linha"
7584
7584
  )
7585
-
7586
- # Stream de cards
7585
+
7586
+ # Lista de cards
7587
7587
  cards = blocks.StreamBlock([
7588
- ('card', ApresentacaoCardBlock()),
7588
+ ('card_apresentacao', ApresentacaoCardBlock()),
7589
7589
  ],
7590
7590
  required=False,
7591
- help_text="Adicione quantos cards precisar"
7591
+ help_text="Cards da seção de apresentação",
7592
+ label="Cards"
7592
7593
  )
7593
7594
 
7594
7595
  class Meta:
7595
7596
  template = 'enap_designsystem/blocks/apresentacao_simple_block.html'
7596
7597
  icon = 'doc-full'
7597
- label = 'Apresentação com Cards'
7598
+ label = 'Componente com título, texto e grid flexível de cards'
7598
7599
  help_text = 'Componente com título, texto e grid flexível de cards'
7599
7600
 
7600
7601
 
7601
7602
 
7602
7603
 
7603
7604
 
7605
+ class SecaoApresentacaoCardsBlock(blocks.StructBlock):
7606
+ """
7607
+ Seção de apresentação com título, conteúdo e grid de cards
7608
+ Componente do Design System ENAP
7609
+ """
7610
+
7611
+ background_image_fundo_bg = StreamField(
7612
+ [('background_image_stream', ImageChooserBlock(
7613
+ label="Imagem de Fundo",
7614
+ help_text="Selecione uma imagem de fundo para o formulário"
7615
+ ))],
7616
+ verbose_name="Imagem de Fundo",
7617
+ use_json_field=True,
7618
+ max_num=1,
7619
+ blank=True,
7620
+ help_text="Adicione uma imagem de fundo para o formulário"
7621
+ )
7622
+
7623
+ # Título
7624
+ titulo = blocks.CharBlock(
7625
+ required=True,
7626
+ max_length=200,
7627
+ help_text="Título da seção"
7628
+ )
7629
+
7630
+ cor_titulo = blocks.ChoiceBlock(
7631
+ choices=ENAP_GREEN_COLORS + [('#FFFFFF', 'Branco (#FFFFFF)')],
7632
+ required=False,
7633
+ default='#FFFFFF',
7634
+ help_text="Cor do título"
7635
+ )
7636
+
7637
+ # Layout dos cards
7638
+ layout_cards = blocks.ChoiceBlock(
7639
+ choices=[
7640
+ ('cards-1-coluna', '1 card por linha'),
7641
+ ('cards-2-colunas', 'Até 2 cards por linha'),
7642
+ ('cards-3-colunas', 'Até 3 cards por linha'),
7643
+ ('cards-4-colunas', 'Até 4 cards por linha'),
7644
+ ('cards-5-colunas', 'Até 5 cards por linha')
7645
+ ],
7646
+ default='cards-5-colunas',
7647
+ help_text="Layout da grade de cards",
7648
+ label="Layout dos cards"
7649
+ )
7650
+
7651
+ # Stream de cards
7652
+ cards = blocks.StreamBlock([
7653
+ ('card', CardBlock()),
7654
+ ],
7655
+ required=False,
7656
+ help_text="Adicione quantos cards precisar"
7657
+ )
7658
+
7659
+ class Meta:
7660
+ template = 'enap_designsystem/blocks/cards_titles.html'
7661
+ icon = 'doc-full'
7662
+ label = 'Seção com título & cards'
7663
+ help_text = 'Seção com título & cards'
7664
+
7604
7665
 
7605
7666
  class RecaptchaBlock(StructBlock):
7606
7667
  """Componente reCAPTCHA isolado para usar em qualquer formulário"""
@@ -7656,4 +7717,253 @@ class RecaptchaBlock(StructBlock):
7656
7717
  template = 'enap_designsystem/blocks/recaptcha.html'
7657
7718
  icon = 'lock'
7658
7719
  label = 'reCAPTCHA'
7659
- help_text = 'Componente de verificação reCAPTCHA'
7720
+ help_text = 'Componente de verificação reCAPTCHA'
7721
+
7722
+
7723
+
7724
+
7725
+
7726
+
7727
+
7728
+
7729
+ class LogoCardBlock(blocks.StructBlock):
7730
+ """
7731
+ Bloco individual para cada logo/card
7732
+ """
7733
+ logo = ImageChooserBlock(
7734
+ required=True,
7735
+ help_text="Logo ou imagem do card"
7736
+ )
7737
+
7738
+ class Meta:
7739
+ icon = 'image'
7740
+ label = 'Logo Card'
7741
+
7742
+
7743
+ class LogosSimpleBlock(blocks.StructBlock):
7744
+ """
7745
+ Componente de apresentação com título, texto e grid de logos/cards
7746
+ """
7747
+
7748
+ # Background
7749
+ cor_fundo = blocks.ChoiceBlock(
7750
+ choices=BACKGROUND_COLOR_CHOICES,
7751
+ required=False,
7752
+ default='#6A1B9A', # Roxo como na imagem
7753
+ help_text="Cor de fundo do componente"
7754
+ )
7755
+
7756
+ # Título
7757
+ titulo = blocks.CharBlock(
7758
+ required=True,
7759
+ max_length=200,
7760
+ help_text="Título principal da seção"
7761
+ )
7762
+
7763
+ cor_titulo = blocks.ChoiceBlock(
7764
+ choices=ENAP_GREEN_COLORS + [('#FFFFFF', 'Branco (#FFFFFF)')],
7765
+ required=False,
7766
+ default='#FFFFFF',
7767
+ help_text="Cor do título"
7768
+ )
7769
+
7770
+ # Quadrado de conteúdo
7771
+ cor_fundo_conteudo = blocks.ChoiceBlock(
7772
+ choices=BACKGROUND_COLOR_CHOICES,
7773
+ required=False,
7774
+ default='#FFFFFF',
7775
+ help_text="Cor de fundo do quadrado de conteúdo"
7776
+ )
7777
+
7778
+ # Grid de logos/cards
7779
+ tipo_grid_logos = blocks.ChoiceBlock(
7780
+ choices=[
7781
+ ('logos-grid-1', '1 logo por linha'),
7782
+ ('logos-grid-2', 'Até 2 logos por linha'),
7783
+ ('logos-grid-3', 'Até 3 logos por linha'),
7784
+ ('logos-grid-4', 'Até 4 logos por linha'),
7785
+ ('logos-grid-5', 'Até 5 logos por linha'),
7786
+ ('logos-grid-6', 'Até 6 logos por linha')
7787
+ ],
7788
+ default='logos-grid-5',
7789
+ help_text="Quantas logos por linha no desktop",
7790
+ label="Layout do grid de logos"
7791
+ )
7792
+
7793
+ # Lista de logos/cards
7794
+ lista_logos = blocks.ListBlock(
7795
+ LogoCardBlock(),
7796
+ min_num=1,
7797
+ max_num=30,
7798
+ help_text="Adicione as logos/cards que serão exibidas no grid"
7799
+ )
7800
+
7801
+ # Configurações adicionais do grid
7802
+ espacamento_logos = blocks.ChoiceBlock(
7803
+ choices=[
7804
+ ('spacing-sm', 'Espaçamento pequeno'),
7805
+ ('spacing-md', 'Espaçamento médio'),
7806
+ ('spacing-lg', 'Espaçamento grande'),
7807
+ ],
7808
+ default='spacing-md',
7809
+ help_text="Espaçamento entre as logos"
7810
+ )
7811
+
7812
+ tamanho_logos = blocks.ChoiceBlock(
7813
+ choices=[
7814
+ ('logo-sm', 'Logos pequenas'),
7815
+ ('logo-md', 'Logos médias'),
7816
+ ('logo-lg', 'Logos grandes'),
7817
+ ],
7818
+ default='logo-md',
7819
+ help_text="Tamanho das logos no grid"
7820
+ )
7821
+
7822
+ centralizar_logos = blocks.BooleanBlock(
7823
+ required=False,
7824
+ default=True,
7825
+ help_text="Centralizar logos dentro dos cards"
7826
+ )
7827
+
7828
+ class Meta:
7829
+ template = 'enap_designsystem/blocks/logos_simple_block.html'
7830
+ icon = 'doc-full'
7831
+ label = 'Apresentação com Grid de Logos'
7832
+ help_text = 'Componente com título & logos'
7833
+
7834
+
7835
+
7836
+
7837
+
7838
+
7839
+ class NumeroCardBlock(blocks.StructBlock):
7840
+ """
7841
+ Bloco individual para cada card de número/estatística
7842
+ """
7843
+ numero = blocks.CharBlock(
7844
+ required=True,
7845
+ max_length=50,
7846
+ help_text="Número ou estatística (ex: 29, +4960, 10)"
7847
+ )
7848
+
7849
+ descricao = blocks.CharBlock(
7850
+ required=True,
7851
+ max_length=200,
7852
+ help_text="Descrição do número (ex: Parceiros Impactados)"
7853
+ )
7854
+
7855
+ cor_card = blocks.ChoiceBlock(
7856
+ choices=BACKGROUND_COLOR_CHOICES,
7857
+ required=False,
7858
+ default='#6A1B9A', # Roxo padrão
7859
+ help_text="Cor de fundo do card"
7860
+ )
7861
+
7862
+ cor_numero = blocks.ChoiceBlock(
7863
+ choices=ENAP_GREEN_COLORS + [('#FFFFFF', 'Branco (#FFFFFF)')],
7864
+ required=False,
7865
+ default='#FFFFFF',
7866
+ help_text="Cor do número"
7867
+ )
7868
+
7869
+ cor_descricao = blocks.ChoiceBlock(
7870
+ choices=ENAP_GREEN_COLORS + [('#FFFFFF', 'Branco (#FFFFFF)')],
7871
+ required=False,
7872
+ default='#FFFFFF',
7873
+ help_text="Cor da descrição"
7874
+ )
7875
+
7876
+ class Meta:
7877
+ icon = 'snippet'
7878
+ label = 'Card de Número'
7879
+
7880
+
7881
+ class NumerosBlock(blocks.StructBlock):
7882
+ """
7883
+ Componente de apresentação de números/estatísticas
7884
+ """
7885
+
7886
+ # Background
7887
+ cor_fundo = blocks.ChoiceBlock(
7888
+ choices=BACKGROUND_COLOR_CHOICES,
7889
+ required=False,
7890
+ default='#F8F9FA', # Cinza claro ENAP
7891
+ help_text="Cor de fundo do componente"
7892
+ )
7893
+
7894
+ # Título
7895
+ titulo = blocks.CharBlock(
7896
+ required=True,
7897
+ max_length=200,
7898
+ help_text="Título principal da seção (ex: Nossos números)"
7899
+ )
7900
+
7901
+ cor_titulo = blocks.ChoiceBlock(
7902
+ choices=ENAP_GREEN_COLORS,
7903
+ required=False,
7904
+ default='#024248',
7905
+ help_text="Cor do título"
7906
+ )
7907
+
7908
+ # Quadrado de conteúdo
7909
+ cor_fundo_conteudo = blocks.ChoiceBlock(
7910
+ choices=BACKGROUND_COLOR_CHOICES,
7911
+ required=False,
7912
+ default='#FFFFFF',
7913
+ help_text="Cor de fundo do quadrado de conteúdo"
7914
+ )
7915
+
7916
+ # Grid de cards
7917
+ tipo_grid_numeros = blocks.ChoiceBlock(
7918
+ choices=[
7919
+ ('numeros-grid-1', '1 card por linha'),
7920
+ ('numeros-grid-2', 'Até 2 cards por linha'),
7921
+ ('numeros-grid-3', 'Até 3 cards por linha'),
7922
+ ('numeros-grid-4', 'Até 4 cards por linha'),
7923
+ ('numeros-grid-5', 'Até 5 cards por linha'),
7924
+ ],
7925
+ default='numeros-grid-4',
7926
+ help_text="Quantos cards por linha no desktop",
7927
+ label="Layout do grid de números"
7928
+ )
7929
+
7930
+ # Lista de cards de números
7931
+ lista_numeros = blocks.ListBlock(
7932
+ NumeroCardBlock(),
7933
+ min_num=1,
7934
+ max_num=20,
7935
+ help_text="Adicione os cards de números/estatísticas"
7936
+ )
7937
+
7938
+ # Configurações adicionais do grid
7939
+ espacamento_cards = blocks.ChoiceBlock(
7940
+ choices=[
7941
+ ('spacing-sm', 'Espaçamento pequeno'),
7942
+ ('spacing-md', 'Espaçamento médio'),
7943
+ ('spacing-lg', 'Espaçamento grande'),
7944
+ ],
7945
+ default='spacing-md',
7946
+ help_text="Espaçamento entre os cards"
7947
+ )
7948
+
7949
+ tamanho_cards = blocks.ChoiceBlock(
7950
+ choices=[
7951
+ ('card-sm', 'Cards pequenos'),
7952
+ ('card-md', 'Cards médios'),
7953
+ ('card-lg', 'Cards grandes'),
7954
+ ],
7955
+ default='card-md',
7956
+ help_text="Tamanho dos cards"
7957
+ )
7958
+
7959
+ bordas_arredondadas = blocks.BooleanBlock(
7960
+ required=False,
7961
+ default=True,
7962
+ help_text="Cards com bordas arredondadas"
7963
+ )
7964
+
7965
+ class Meta:
7966
+ template = 'enap_designsystem/blocks/numeros_block.html'
7967
+ icon = 'snippet'
7968
+ label = 'Grid de Números/Estatísticas'
7969
+ help_text = 'Componente para exibir números e estatísticas importantes'
@@ -1066,6 +1066,7 @@ class EditalConcursoInovacao(Page):
1066
1066
  'enap_designsystem.SejaAvaliador',
1067
1067
  'enap_designsystem.ENAPComponentes',
1068
1068
  'enap_designsystem.ENAPSemana',
1069
+ 'enap_designsystem.SistemaVotacaoPage',
1069
1070
  ]
1070
1071
 
1071
1072
  def get_context(self, request, *args, **kwargs):
@@ -5,11 +5,9 @@ from .models import EnapNavbarSnippet
5
5
 
6
6
 
7
7
  def global_template_context(request):
8
- return {
9
- 'debug': settings.DEBUG
10
- }
11
-
12
-
8
+ return {
9
+ 'debug': settings.DEBUG
10
+ }
13
11
 
14
12
 
15
13
  def navbar_context(request):
@@ -20,4 +18,30 @@ def navbar_context(request):
20
18
  navbar = EnapNavbarSnippet.objects.first()
21
19
  return {'enap_navbar': navbar}
22
20
  except EnapNavbarSnippet.DoesNotExist:
23
- return {'enap_navbar': None}
21
+ return {'enap_navbar': None}
22
+
23
+
24
+ def recaptcha_context(request):
25
+ """Adiciona configurações do reCAPTCHA ao contexto global"""
26
+ try:
27
+ from django.conf import settings
28
+
29
+ # Obter chaves do settings
30
+ public_key = getattr(settings, 'RECAPTCHA_PUBLIC_KEY', '')
31
+ private_key = getattr(settings, 'RECAPTCHA_PRIVATE_KEY', '')
32
+
33
+ print(f"DEBUG reCAPTCHA Context - EXECUTANDO:")
34
+ print(f"- Public Key: {public_key[:20]}..." if public_key else "- Public Key: VAZIA")
35
+ print(f"- Private Key: {private_key[:20]}..." if private_key else "- Private Key: VAZIA")
36
+ print(f"- Has Keys: {bool(public_key and private_key)}")
37
+
38
+ return {
39
+ 'recaptcha_public_key': public_key,
40
+ 'has_recaptcha_keys': bool(public_key and private_key),
41
+ }
42
+ except Exception as e:
43
+ print(f"ERRO no context processor: {e}")
44
+ return {
45
+ 'recaptcha_public_key': '',
46
+ 'has_recaptcha_keys': False,
47
+ }