wagtail-enap-designsystem 1.2.1.119__py3-none-any.whl → 1.2.1.121__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.
- enap_designsystem/migrations/0379_categoriavotacao_sistemavotacaopage_and_more.py +51471 -0
- enap_designsystem/migrations/0380_sistemavotacaopage_imagem_fundo.py +33 -0
- enap_designsystem/migrations/0381_sistemavotacaopage_footer_sistemavotacaopage_navbar.py +36 -0
- enap_designsystem/models.py +484 -3
- enap_designsystem/templates/enap_designsystem/blocks/card_flex_block.html +0 -0
- enap_designsystem/templates/enap_designsystem/blocks/content_box.html +271 -0
- enap_designsystem/templates/enap_designsystem/blocks/content_flex_block.html +0 -0
- enap_designsystem/templates/enap_designsystem/blocks/video_hero_banner.html +263 -0
- enap_designsystem/templates/enap_designsystem/sistema_votacao_page.html +1707 -0
- enap_designsystem/urls.py +6 -1
- enap_designsystem/views.py +229 -0
- enap_designsystem/wagtail_hooks.py +103 -0
- {wagtail_enap_designsystem-1.2.1.119.dist-info → wagtail_enap_designsystem-1.2.1.121.dist-info}/METADATA +1 -1
- {wagtail_enap_designsystem-1.2.1.119.dist-info → wagtail_enap_designsystem-1.2.1.121.dist-info}/RECORD +17 -9
- {wagtail_enap_designsystem-1.2.1.119.dist-info → wagtail_enap_designsystem-1.2.1.121.dist-info}/WHEEL +0 -0
- {wagtail_enap_designsystem-1.2.1.119.dist-info → wagtail_enap_designsystem-1.2.1.121.dist-info}/licenses/LICENSE +0 -0
- {wagtail_enap_designsystem-1.2.1.119.dist-info → wagtail_enap_designsystem-1.2.1.121.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Generated by Django 5.1.6 on 2025-09-02 19:00
|
|
2
|
+
|
|
3
|
+
import wagtail.fields
|
|
4
|
+
from django.db import migrations
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
("enap_designsystem", "0379_categoriavotacao_sistemavotacaopage_and_more"),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AddField(
|
|
15
|
+
model_name="sistemavotacaopage",
|
|
16
|
+
name="imagem_fundo",
|
|
17
|
+
field=wagtail.fields.StreamField(
|
|
18
|
+
[("image", 0)],
|
|
19
|
+
blank=True,
|
|
20
|
+
block_lookup={
|
|
21
|
+
0: (
|
|
22
|
+
"wagtail.images.blocks.ImageChooserBlock",
|
|
23
|
+
(),
|
|
24
|
+
{
|
|
25
|
+
"help_text": "Selecione uma imagem para usar como fundo da página",
|
|
26
|
+
"required": False,
|
|
27
|
+
},
|
|
28
|
+
)
|
|
29
|
+
},
|
|
30
|
+
verbose_name="Imagem de Fundo",
|
|
31
|
+
),
|
|
32
|
+
),
|
|
33
|
+
]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Generated by Django 5.1.6 on 2025-09-02 20:50
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
("enap_designsystem", "0380_sistemavotacaopage_imagem_fundo"),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AddField(
|
|
15
|
+
model_name="sistemavotacaopage",
|
|
16
|
+
name="footer",
|
|
17
|
+
field=models.ForeignKey(
|
|
18
|
+
blank=True,
|
|
19
|
+
null=True,
|
|
20
|
+
on_delete=django.db.models.deletion.SET_NULL,
|
|
21
|
+
related_name="+",
|
|
22
|
+
to="enap_designsystem.enapfootersnippet",
|
|
23
|
+
),
|
|
24
|
+
),
|
|
25
|
+
migrations.AddField(
|
|
26
|
+
model_name="sistemavotacaopage",
|
|
27
|
+
name="navbar",
|
|
28
|
+
field=models.ForeignKey(
|
|
29
|
+
blank=True,
|
|
30
|
+
null=True,
|
|
31
|
+
on_delete=django.db.models.deletion.SET_NULL,
|
|
32
|
+
related_name="+",
|
|
33
|
+
to="enap_designsystem.enapnavbarsnippet",
|
|
34
|
+
),
|
|
35
|
+
),
|
|
36
|
+
]
|
enap_designsystem/models.py
CHANGED
|
@@ -36,11 +36,13 @@ from datetime import datetime
|
|
|
36
36
|
from .utils.sso import get_valid_access_token
|
|
37
37
|
from wagtail.blocks import PageChooserBlock, StructBlock, CharBlock, BooleanBlock, ListBlock, IntegerBlock
|
|
38
38
|
from django.conf import settings
|
|
39
|
-
|
|
39
|
+
from django.utils import timezone
|
|
40
40
|
from .blocks.layout_blocks import EnapAccordionBlock
|
|
41
41
|
|
|
42
42
|
from wagtail.blocks import StreamBlock, StructBlock, CharBlock, ChoiceBlock, RichTextBlock, ChooserBlock, ListBlock
|
|
43
43
|
|
|
44
|
+
import uuid
|
|
45
|
+
from django.utils import timezone
|
|
44
46
|
|
|
45
47
|
from django.db import models
|
|
46
48
|
from django.contrib.auth.models import Group
|
|
@@ -148,8 +150,6 @@ from enap_designsystem.blocks import CARD_CARDS_STREAMBLOCKS
|
|
|
148
150
|
|
|
149
151
|
class ENAPComponentes(Page):
|
|
150
152
|
"""Página personalizada independente do CoderedWebPage."""
|
|
151
|
-
|
|
152
|
-
parent_page_types = ['MbaEspecializacao']
|
|
153
153
|
|
|
154
154
|
admin_notes = models.TextField(
|
|
155
155
|
verbose_name="Anotações Internas",
|
|
@@ -4494,3 +4494,484 @@ class GroupPageTypePermission(models.Model):
|
|
|
4494
4494
|
valid_content_types = self.get_allowed_content_types()
|
|
4495
4495
|
self.content_types.set([ct for ct in self.content_types.all() if ct in valid_content_types])
|
|
4496
4496
|
|
|
4497
|
+
|
|
4498
|
+
|
|
4499
|
+
|
|
4500
|
+
|
|
4501
|
+
|
|
4502
|
+
|
|
4503
|
+
|
|
4504
|
+
|
|
4505
|
+
|
|
4506
|
+
|
|
4507
|
+
@register_snippet
|
|
4508
|
+
class CategoriaVotacao(models.Model):
|
|
4509
|
+
"""
|
|
4510
|
+
Categorias/Tabs do sistema de votação
|
|
4511
|
+
Gerenciadas dinamicamente pelo admin
|
|
4512
|
+
"""
|
|
4513
|
+
nome = models.CharField(
|
|
4514
|
+
max_length=100,
|
|
4515
|
+
verbose_name="Nome da Categoria",
|
|
4516
|
+
help_text="Ex: Inovação Tecnológica, Sustentabilidade, etc."
|
|
4517
|
+
)
|
|
4518
|
+
|
|
4519
|
+
descricao = models.TextField(
|
|
4520
|
+
blank=True,
|
|
4521
|
+
verbose_name="Descrição",
|
|
4522
|
+
help_text="Descrição opcional da categoria"
|
|
4523
|
+
)
|
|
4524
|
+
|
|
4525
|
+
ordem = models.PositiveIntegerField(
|
|
4526
|
+
default=0,
|
|
4527
|
+
verbose_name="Ordem de Exibição",
|
|
4528
|
+
help_text="Ordem das tabs (menor número = primeiro)"
|
|
4529
|
+
)
|
|
4530
|
+
|
|
4531
|
+
ativo = models.BooleanField(
|
|
4532
|
+
default=True,
|
|
4533
|
+
verbose_name="Categoria Ativa",
|
|
4534
|
+
help_text="Desmarque para ocultar esta categoria"
|
|
4535
|
+
)
|
|
4536
|
+
|
|
4537
|
+
icone = models.CharField(
|
|
4538
|
+
max_length=50,
|
|
4539
|
+
blank=True,
|
|
4540
|
+
verbose_name="Ícone (classe CSS)",
|
|
4541
|
+
help_text="Ex: fa-microchip, fa-leaf, fa-users"
|
|
4542
|
+
)
|
|
4543
|
+
|
|
4544
|
+
cor_destaque = models.CharField(
|
|
4545
|
+
max_length=7,
|
|
4546
|
+
default="#00E5CC",
|
|
4547
|
+
verbose_name="Cor de Destaque",
|
|
4548
|
+
help_text="Cor hexadecimal para esta categoria"
|
|
4549
|
+
)
|
|
4550
|
+
|
|
4551
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
4552
|
+
updated_at = models.DateTimeField(auto_now=True)
|
|
4553
|
+
|
|
4554
|
+
class Meta:
|
|
4555
|
+
verbose_name = "Categoria de Votação"
|
|
4556
|
+
verbose_name_plural = "Categorias de Votação"
|
|
4557
|
+
ordering = ['ordem', 'nome']
|
|
4558
|
+
|
|
4559
|
+
def __str__(self):
|
|
4560
|
+
return self.nome
|
|
4561
|
+
|
|
4562
|
+
@property
|
|
4563
|
+
def total_projetos(self):
|
|
4564
|
+
"""Retorna total de projetos ativos nesta categoria"""
|
|
4565
|
+
return self.projetos.filter(ativo=True).count()
|
|
4566
|
+
|
|
4567
|
+
@property
|
|
4568
|
+
def total_votos(self):
|
|
4569
|
+
"""Retorna total de votos recebidos nesta categoria"""
|
|
4570
|
+
return VotoRegistrado.objects.filter(
|
|
4571
|
+
projeto__categoria=self,
|
|
4572
|
+
projeto__ativo=True
|
|
4573
|
+
).count()
|
|
4574
|
+
|
|
4575
|
+
|
|
4576
|
+
@register_snippet
|
|
4577
|
+
class ProjetoVotacao(ClusterableModel):
|
|
4578
|
+
"""
|
|
4579
|
+
Projetos participantes da votação
|
|
4580
|
+
Cards dinâmicos configuráveis pelo admin
|
|
4581
|
+
"""
|
|
4582
|
+
titulo = models.CharField(
|
|
4583
|
+
max_length=200,
|
|
4584
|
+
verbose_name="Título do Projeto"
|
|
4585
|
+
)
|
|
4586
|
+
|
|
4587
|
+
descricao = RichTextField(
|
|
4588
|
+
verbose_name="Descrição do Projeto",
|
|
4589
|
+
help_text="Descrição completa do projeto"
|
|
4590
|
+
)
|
|
4591
|
+
|
|
4592
|
+
categoria = models.ForeignKey(
|
|
4593
|
+
CategoriaVotacao,
|
|
4594
|
+
on_delete=models.CASCADE,
|
|
4595
|
+
related_name='projetos',
|
|
4596
|
+
verbose_name="Categoria"
|
|
4597
|
+
)
|
|
4598
|
+
|
|
4599
|
+
# Equipe
|
|
4600
|
+
nome_equipe = models.CharField(
|
|
4601
|
+
max_length=150,
|
|
4602
|
+
verbose_name="Nome da Equipe/Organização"
|
|
4603
|
+
)
|
|
4604
|
+
|
|
4605
|
+
icone_equipe = models.ImageField(
|
|
4606
|
+
upload_to='votacao/equipes/',
|
|
4607
|
+
blank=True,
|
|
4608
|
+
null=True,
|
|
4609
|
+
verbose_name="Logo/Ícone da Equipe"
|
|
4610
|
+
)
|
|
4611
|
+
|
|
4612
|
+
# Vídeo
|
|
4613
|
+
video_youtube = models.URLField(
|
|
4614
|
+
blank=True,
|
|
4615
|
+
verbose_name="URL do Vídeo YouTube",
|
|
4616
|
+
help_text="Cole a URL completa do YouTube"
|
|
4617
|
+
)
|
|
4618
|
+
|
|
4619
|
+
video_arquivo = models.FileField(
|
|
4620
|
+
upload_to='votacao/videos/',
|
|
4621
|
+
blank=True,
|
|
4622
|
+
null=True,
|
|
4623
|
+
verbose_name="Arquivo de Vídeo",
|
|
4624
|
+
help_text="Alternativamente, faça upload de um vídeo"
|
|
4625
|
+
)
|
|
4626
|
+
|
|
4627
|
+
# Contato
|
|
4628
|
+
email_contato = models.EmailField(
|
|
4629
|
+
blank=True,
|
|
4630
|
+
verbose_name="Email de Contato",
|
|
4631
|
+
help_text="Email da equipe (opcional)"
|
|
4632
|
+
)
|
|
4633
|
+
|
|
4634
|
+
# Configurações
|
|
4635
|
+
ordem = models.PositiveIntegerField(
|
|
4636
|
+
default=0,
|
|
4637
|
+
verbose_name="Ordem na Categoria",
|
|
4638
|
+
help_text="Ordem do projeto dentro da categoria"
|
|
4639
|
+
)
|
|
4640
|
+
|
|
4641
|
+
ativo = models.BooleanField(
|
|
4642
|
+
default=True,
|
|
4643
|
+
verbose_name="Projeto Ativo",
|
|
4644
|
+
help_text="Desmarque para ocultar este projeto"
|
|
4645
|
+
)
|
|
4646
|
+
|
|
4647
|
+
destacado = models.BooleanField(
|
|
4648
|
+
default=False,
|
|
4649
|
+
verbose_name="Projeto em Destaque",
|
|
4650
|
+
help_text="Marque para destacar este projeto"
|
|
4651
|
+
)
|
|
4652
|
+
|
|
4653
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
4654
|
+
updated_at = models.DateTimeField(auto_now=True)
|
|
4655
|
+
|
|
4656
|
+
panels = [
|
|
4657
|
+
MultiFieldPanel([
|
|
4658
|
+
FieldPanel('titulo'),
|
|
4659
|
+
FieldPanel('categoria'),
|
|
4660
|
+
FieldPanel('descricao'),
|
|
4661
|
+
], heading="Informações Básicas"),
|
|
4662
|
+
|
|
4663
|
+
MultiFieldPanel([
|
|
4664
|
+
FieldPanel('nome_equipe'),
|
|
4665
|
+
FieldPanel('icone_equipe'),
|
|
4666
|
+
FieldPanel('email_contato'),
|
|
4667
|
+
InlinePanel('apresentadores', label="Apresentadores"),
|
|
4668
|
+
], heading="Equipe"),
|
|
4669
|
+
|
|
4670
|
+
MultiFieldPanel([
|
|
4671
|
+
FieldPanel('video_youtube'),
|
|
4672
|
+
FieldPanel('video_arquivo'),
|
|
4673
|
+
], heading="Vídeo do Projeto"),
|
|
4674
|
+
|
|
4675
|
+
MultiFieldPanel([
|
|
4676
|
+
FieldPanel('ordem'),
|
|
4677
|
+
FieldPanel('ativo'),
|
|
4678
|
+
FieldPanel('destacado'),
|
|
4679
|
+
], heading="Configurações"),
|
|
4680
|
+
]
|
|
4681
|
+
|
|
4682
|
+
class Meta:
|
|
4683
|
+
verbose_name = "Projeto de Votação"
|
|
4684
|
+
verbose_name_plural = "Projetos de Votação"
|
|
4685
|
+
ordering = ['categoria__ordem', 'ordem', 'titulo']
|
|
4686
|
+
|
|
4687
|
+
def __str__(self):
|
|
4688
|
+
return f"{self.titulo} ({self.categoria.nome})"
|
|
4689
|
+
|
|
4690
|
+
@property
|
|
4691
|
+
def total_votos(self):
|
|
4692
|
+
"""Retorna total de votos recebidos por este projeto"""
|
|
4693
|
+
return self.votos.count()
|
|
4694
|
+
|
|
4695
|
+
@property
|
|
4696
|
+
def video_embed_url(self):
|
|
4697
|
+
"""Converte URL do YouTube para embed"""
|
|
4698
|
+
if self.video_youtube:
|
|
4699
|
+
if "youtube.com/watch?v=" in self.video_youtube:
|
|
4700
|
+
video_id = self.video_youtube.split("watch?v=")[1].split("&")[0]
|
|
4701
|
+
return f"https://www.youtube.com/embed/{video_id}"
|
|
4702
|
+
elif "youtu.be/" in self.video_youtube:
|
|
4703
|
+
video_id = self.video_youtube.split("youtu.be/")[1].split("?")[0]
|
|
4704
|
+
return f"https://www.youtube.com/embed/{video_id}"
|
|
4705
|
+
return None
|
|
4706
|
+
|
|
4707
|
+
def get_apresentadores_list(self):
|
|
4708
|
+
"""Retorna lista de apresentadores como badges"""
|
|
4709
|
+
return [ap.nome for ap in self.apresentadores.all()]
|
|
4710
|
+
|
|
4711
|
+
|
|
4712
|
+
class ApresentadorProjeto(Orderable):
|
|
4713
|
+
"""
|
|
4714
|
+
Apresentadores de cada projeto
|
|
4715
|
+
Inline para criar badges dinâmicas
|
|
4716
|
+
"""
|
|
4717
|
+
projeto = ParentalKey(
|
|
4718
|
+
ProjetoVotacao,
|
|
4719
|
+
related_name='apresentadores',
|
|
4720
|
+
on_delete=models.CASCADE
|
|
4721
|
+
)
|
|
4722
|
+
|
|
4723
|
+
nome = models.CharField(
|
|
4724
|
+
max_length=100,
|
|
4725
|
+
verbose_name="Nome do Apresentador"
|
|
4726
|
+
)
|
|
4727
|
+
|
|
4728
|
+
cargo = models.CharField(
|
|
4729
|
+
max_length=100,
|
|
4730
|
+
blank=True,
|
|
4731
|
+
verbose_name="Cargo/Função",
|
|
4732
|
+
help_text="Ex: Desenvolvedor, Designer, etc."
|
|
4733
|
+
)
|
|
4734
|
+
|
|
4735
|
+
panels = [
|
|
4736
|
+
FieldPanel('nome'),
|
|
4737
|
+
FieldPanel('cargo'),
|
|
4738
|
+
]
|
|
4739
|
+
|
|
4740
|
+
def __str__(self):
|
|
4741
|
+
return self.nome
|
|
4742
|
+
|
|
4743
|
+
|
|
4744
|
+
class VotoRegistrado(models.Model):
|
|
4745
|
+
"""
|
|
4746
|
+
Registro de cada voto realizado
|
|
4747
|
+
Para auditoria e controle básico anti-fraude
|
|
4748
|
+
"""
|
|
4749
|
+
id = models.UUIDField(
|
|
4750
|
+
primary_key=True,
|
|
4751
|
+
default=uuid.uuid4,
|
|
4752
|
+
editable=False
|
|
4753
|
+
)
|
|
4754
|
+
|
|
4755
|
+
projeto = models.ForeignKey(
|
|
4756
|
+
ProjetoVotacao,
|
|
4757
|
+
on_delete=models.CASCADE,
|
|
4758
|
+
related_name='votos',
|
|
4759
|
+
verbose_name="Projeto Votado"
|
|
4760
|
+
)
|
|
4761
|
+
|
|
4762
|
+
ip_address = models.GenericIPAddressField(
|
|
4763
|
+
verbose_name="Endereço IP"
|
|
4764
|
+
)
|
|
4765
|
+
|
|
4766
|
+
user_agent = models.TextField(
|
|
4767
|
+
blank=True,
|
|
4768
|
+
verbose_name="User Agent",
|
|
4769
|
+
help_text="Navegador utilizado"
|
|
4770
|
+
)
|
|
4771
|
+
|
|
4772
|
+
timestamp = models.DateTimeField(
|
|
4773
|
+
default=timezone.now,
|
|
4774
|
+
verbose_name="Data/Hora do Voto"
|
|
4775
|
+
)
|
|
4776
|
+
|
|
4777
|
+
# Campos para relatórios
|
|
4778
|
+
categoria_nome = models.CharField(
|
|
4779
|
+
max_length=100,
|
|
4780
|
+
verbose_name="Nome da Categoria",
|
|
4781
|
+
help_text="Cache do nome da categoria no momento do voto"
|
|
4782
|
+
)
|
|
4783
|
+
|
|
4784
|
+
class Meta:
|
|
4785
|
+
verbose_name = "Voto Registrado"
|
|
4786
|
+
verbose_name_plural = "Votos Registrados"
|
|
4787
|
+
ordering = ['-timestamp']
|
|
4788
|
+
indexes = [
|
|
4789
|
+
models.Index(fields=['projeto', 'timestamp']),
|
|
4790
|
+
models.Index(fields=['ip_address', 'timestamp']),
|
|
4791
|
+
models.Index(fields=['categoria_nome', 'timestamp']),
|
|
4792
|
+
]
|
|
4793
|
+
|
|
4794
|
+
def __str__(self):
|
|
4795
|
+
return f"Voto em {self.projeto.titulo} - {self.timestamp}"
|
|
4796
|
+
|
|
4797
|
+
def save(self, *args, **kwargs):
|
|
4798
|
+
# Cache do nome da categoria
|
|
4799
|
+
if not self.categoria_nome:
|
|
4800
|
+
self.categoria_nome = self.projeto.categoria.nome
|
|
4801
|
+
super().save(*args, **kwargs)
|
|
4802
|
+
|
|
4803
|
+
|
|
4804
|
+
class SistemaVotacaoPage(Page):
|
|
4805
|
+
"""
|
|
4806
|
+
Página principal do sistema de votação
|
|
4807
|
+
Configurações gerais editáveis pelo admin
|
|
4808
|
+
"""
|
|
4809
|
+
|
|
4810
|
+
navbar = models.ForeignKey(
|
|
4811
|
+
"EnapNavbarSnippet",
|
|
4812
|
+
null=True,
|
|
4813
|
+
blank=True,
|
|
4814
|
+
on_delete=models.SET_NULL,
|
|
4815
|
+
related_name="+",
|
|
4816
|
+
)
|
|
4817
|
+
|
|
4818
|
+
subtitulo = models.CharField(
|
|
4819
|
+
max_length=255,
|
|
4820
|
+
default="Escolha os melhores projetos em cada categoria",
|
|
4821
|
+
verbose_name="Subtítulo",
|
|
4822
|
+
help_text="Texto que aparece abaixo do título"
|
|
4823
|
+
)
|
|
4824
|
+
|
|
4825
|
+
descricao_header = RichTextField(
|
|
4826
|
+
blank=True,
|
|
4827
|
+
verbose_name="Descrição do Header",
|
|
4828
|
+
help_text="Texto adicional no topo da página (opcional)"
|
|
4829
|
+
)
|
|
4830
|
+
|
|
4831
|
+
imagem_fundo = StreamField([
|
|
4832
|
+
("image", ImageChooserBlock(
|
|
4833
|
+
required=False,
|
|
4834
|
+
help_text="Selecione uma imagem para usar como fundo da página"
|
|
4835
|
+
))
|
|
4836
|
+
],
|
|
4837
|
+
blank=True,
|
|
4838
|
+
use_json_field=True,
|
|
4839
|
+
verbose_name="Imagem de Fundo",
|
|
4840
|
+
)
|
|
4841
|
+
|
|
4842
|
+
mostrar_progresso = models.BooleanField(
|
|
4843
|
+
default=True,
|
|
4844
|
+
verbose_name="Mostrar Barra de Progresso",
|
|
4845
|
+
help_text="Exibe progresso de quantas categorias o usuário votou"
|
|
4846
|
+
)
|
|
4847
|
+
|
|
4848
|
+
permitir_multiplos_votos = models.BooleanField(
|
|
4849
|
+
default=True,
|
|
4850
|
+
verbose_name="Permitir Múltiplos Votos",
|
|
4851
|
+
help_text="Usuário pode votar em diferentes projetos de diferentes categorias"
|
|
4852
|
+
)
|
|
4853
|
+
|
|
4854
|
+
ordenacao_projetos = models.CharField(
|
|
4855
|
+
max_length=20,
|
|
4856
|
+
choices=[
|
|
4857
|
+
('ordem', 'Ordem Manual'),
|
|
4858
|
+
('votos_desc', 'Mais Votados Primeiro'),
|
|
4859
|
+
('votos_asc', 'Menos Votados Primeiro'),
|
|
4860
|
+
('alfabetica', 'Ordem Alfabética'),
|
|
4861
|
+
('recentes', 'Mais Recentes Primeiro'),
|
|
4862
|
+
],
|
|
4863
|
+
default='ordem',
|
|
4864
|
+
verbose_name="Ordenação dos Projetos"
|
|
4865
|
+
)
|
|
4866
|
+
|
|
4867
|
+
# Meta configurações
|
|
4868
|
+
votacao_ativa = models.BooleanField(
|
|
4869
|
+
default=True,
|
|
4870
|
+
verbose_name="Votação Ativa",
|
|
4871
|
+
help_text="Desmarque para pausar a votação"
|
|
4872
|
+
)
|
|
4873
|
+
|
|
4874
|
+
data_inicio = models.DateTimeField(
|
|
4875
|
+
blank=True,
|
|
4876
|
+
null=True,
|
|
4877
|
+
verbose_name="Data de Início",
|
|
4878
|
+
help_text="Data/hora de início da votação (opcional)"
|
|
4879
|
+
)
|
|
4880
|
+
|
|
4881
|
+
data_fim = models.DateTimeField(
|
|
4882
|
+
blank=True,
|
|
4883
|
+
null=True,
|
|
4884
|
+
verbose_name="Data de Encerramento",
|
|
4885
|
+
help_text="Data/hora de encerramento da votação (opcional)"
|
|
4886
|
+
)
|
|
4887
|
+
|
|
4888
|
+
footer = models.ForeignKey(
|
|
4889
|
+
"EnapFooterSnippet",
|
|
4890
|
+
null=True,
|
|
4891
|
+
blank=True,
|
|
4892
|
+
on_delete=models.SET_NULL,
|
|
4893
|
+
related_name="+",
|
|
4894
|
+
)
|
|
4895
|
+
|
|
4896
|
+
|
|
4897
|
+
content_panels = Page.content_panels + [
|
|
4898
|
+
|
|
4899
|
+
MultiFieldPanel([
|
|
4900
|
+
FieldPanel('subtitulo'),
|
|
4901
|
+
FieldPanel('descricao_header'),
|
|
4902
|
+
FieldPanel('imagem_fundo'),
|
|
4903
|
+
FieldPanel('navbar'),
|
|
4904
|
+
FieldPanel('footer'),
|
|
4905
|
+
], heading="Conteúdo do Header"),
|
|
4906
|
+
|
|
4907
|
+
MultiFieldPanel([
|
|
4908
|
+
FieldPanel('mostrar_progresso'),
|
|
4909
|
+
FieldPanel('permitir_multiplos_votos'),
|
|
4910
|
+
FieldPanel('ordenacao_projetos'),
|
|
4911
|
+
], heading="Configurações de Exibição"),
|
|
4912
|
+
|
|
4913
|
+
MultiFieldPanel([
|
|
4914
|
+
FieldPanel('votacao_ativa'),
|
|
4915
|
+
FieldPanel('data_inicio'),
|
|
4916
|
+
FieldPanel('data_fim'),
|
|
4917
|
+
], heading="Controle da Votação"),
|
|
4918
|
+
]
|
|
4919
|
+
|
|
4920
|
+
class Meta:
|
|
4921
|
+
verbose_name = "Sistema de Votação"
|
|
4922
|
+
|
|
4923
|
+
def get_context(self, request):
|
|
4924
|
+
context = super().get_context(request)
|
|
4925
|
+
|
|
4926
|
+
# Buscar categorias ativas
|
|
4927
|
+
categorias = CategoriaVotacao.objects.filter(ativo=True).order_by('ordem')
|
|
4928
|
+
|
|
4929
|
+
# Buscar projetos por categoria
|
|
4930
|
+
projetos_por_categoria = {}
|
|
4931
|
+
for categoria in categorias:
|
|
4932
|
+
projetos = categoria.projetos.filter(ativo=True)
|
|
4933
|
+
|
|
4934
|
+
# Aplicar ordenação
|
|
4935
|
+
if self.ordenacao_projetos == 'votos_desc':
|
|
4936
|
+
projetos = sorted(projetos, key=lambda p: p.total_votos, reverse=True)
|
|
4937
|
+
elif self.ordenacao_projetos == 'votos_asc':
|
|
4938
|
+
projetos = sorted(projetos, key=lambda p: p.total_votos)
|
|
4939
|
+
elif self.ordenacao_projetos == 'alfabetica':
|
|
4940
|
+
projetos = projetos.order_by('titulo')
|
|
4941
|
+
elif self.ordenacao_projetos == 'recentes':
|
|
4942
|
+
projetos = projetos.order_by('-created_at')
|
|
4943
|
+
else: # 'ordem'
|
|
4944
|
+
projetos = projetos.order_by('ordem', 'titulo')
|
|
4945
|
+
|
|
4946
|
+
projetos_por_categoria[categoria] = projetos
|
|
4947
|
+
|
|
4948
|
+
# Estatísticas gerais
|
|
4949
|
+
total_categorias = categorias.count()
|
|
4950
|
+
total_projetos = ProjetoVotacao.objects.filter(ativo=True).count()
|
|
4951
|
+
total_votos = VotoRegistrado.objects.count()
|
|
4952
|
+
|
|
4953
|
+
context.update({
|
|
4954
|
+
'categorias': categorias,
|
|
4955
|
+
'projetos_por_categoria': projetos_por_categoria,
|
|
4956
|
+
'total_categorias': total_categorias,
|
|
4957
|
+
'total_projetos': total_projetos,
|
|
4958
|
+
'total_votos': total_votos,
|
|
4959
|
+
'votacao_ativa': self.is_votacao_ativa(),
|
|
4960
|
+
})
|
|
4961
|
+
|
|
4962
|
+
return context
|
|
4963
|
+
|
|
4964
|
+
def is_votacao_ativa(self):
|
|
4965
|
+
"""Verifica se a votação está ativa baseado nas configurações"""
|
|
4966
|
+
if not self.votacao_ativa:
|
|
4967
|
+
return False
|
|
4968
|
+
|
|
4969
|
+
now = timezone.now()
|
|
4970
|
+
|
|
4971
|
+
if self.data_inicio and now < self.data_inicio:
|
|
4972
|
+
return False
|
|
4973
|
+
|
|
4974
|
+
if self.data_fim and now > self.data_fim:
|
|
4975
|
+
return False
|
|
4976
|
+
|
|
4977
|
+
return True
|
|
File without changes
|