wagtail-enap-designsystem 1.2.1.103__py3-none-any.whl → 1.2.1.105__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 (27) hide show
  1. enap_designsystem/blocks/html_blocks.py +582 -19
  2. enap_designsystem/migrations/0367_alter_areaaluno_body_alter_cursoeadpage_curso_and_more.py +52658 -0
  3. enap_designsystem/migrations/0368_enaprevista.py +616 -0
  4. enap_designsystem/migrations/0369_alter_enaprevista_body.py +577 -0
  5. enap_designsystem/migrations/0370_alter_articlepage_body_alter_enapnoticia_body_and_more.py +1781 -0
  6. enap_designsystem/migrations/0371_alter_enaprevista_body.py +639 -0
  7. enap_designsystem/migrations/0372_enaprevista_background_color_and_more.py +98 -0
  8. enap_designsystem/migrations/0373_alter_jobvacancypage_options_and_more.py +415 -0
  9. enap_designsystem/migrations/0374_pagetypepermission.py +36 -0
  10. enap_designsystem/migrations/0375_grouppagepermission_delete_pagetypepermission.py +55 -0
  11. enap_designsystem/migrations/0376_grouppagetypepermission_delete_grouppagepermission.py +55 -0
  12. enap_designsystem/migrations/0377_alter_grouppagetypepermission_options_and_more.py +54 -0
  13. enap_designsystem/models.py +136 -2
  14. enap_designsystem/static/enap_designsystem/blocks/article_grid_conditional.js +108 -0
  15. enap_designsystem/static/enap_designsystem/pages/article/joomla/enap_noticia_importada.css +3 -3
  16. enap_designsystem/templates/enap_designsystem/blocks/article_grid_block.html +202 -0
  17. enap_designsystem/templates/enap_designsystem/blocks/image_text_block.html +260 -0
  18. enap_designsystem/templates/enap_designsystem/blocks/suap/apisuap_courses_block.html +8 -8
  19. enap_designsystem/templates/enap_designsystem/pages/article/enap_revista.html +264 -0
  20. enap_designsystem/templates/enap_designsystem/pages/template_mba.html +10 -0
  21. enap_designsystem/views.py +6 -0
  22. enap_designsystem/wagtail_hooks.py +89 -1
  23. {wagtail_enap_designsystem-1.2.1.103.dist-info → wagtail_enap_designsystem-1.2.1.105.dist-info}/METADATA +1 -1
  24. {wagtail_enap_designsystem-1.2.1.103.dist-info → wagtail_enap_designsystem-1.2.1.105.dist-info}/RECORD +27 -12
  25. {wagtail_enap_designsystem-1.2.1.103.dist-info → wagtail_enap_designsystem-1.2.1.105.dist-info}/WHEEL +0 -0
  26. {wagtail_enap_designsystem-1.2.1.103.dist-info → wagtail_enap_designsystem-1.2.1.105.dist-info}/licenses/LICENSE +0 -0
  27. {wagtail_enap_designsystem-1.2.1.103.dist-info → wagtail_enap_designsystem-1.2.1.105.dist-info}/top_level.txt +0 -0
@@ -736,13 +736,13 @@ class APISuapCourseBlock(StructBlock):
736
736
  processed_data = []
737
737
  for item in data:
738
738
  # Converter data_inicio de string para datetime se existir
739
- if item.get('data_inicio'):
740
- try:
741
- # Assumindo formato YYYY-MM-DD
742
- item['data_inicio'] = datetime.strptime(item['data_inicio'], '%Y-%m-%d').date()
743
- except (ValueError, TypeError):
744
- # Se não conseguir converter, mantém como string
745
- pass
739
+ try:
740
+ # Assumindo formato YYYY-MM-DD
741
+ item['data_inicio'] = datetime.strptime(item['data_inicio'], '%Y-%m-%d').date()
742
+ item['data_inicio_aula'] = datetime.strptime(item['data_inicio_aula'], '%Y-%m-%d').date()
743
+ except (ValueError, TypeError):
744
+ # Se não conseguir converter, mantém como string
745
+ pass
746
746
 
747
747
  processed_data.append(SimpleNamespace(**item))
748
748
 
@@ -1371,6 +1371,152 @@ class VideoBlock(blocks.StructBlock):
1371
1371
  icon = 'media'
1372
1372
  label = 'Embed Vídeo'
1373
1373
 
1374
+
1375
+
1376
+ class ImageTextBlockChoices(blocks.StructBlock):
1377
+ """
1378
+ Componente flexível de imagem com texto
1379
+ Permite escolher diferentes proporções de layout
1380
+ """
1381
+
1382
+ # Opções de proporção
1383
+ LAYOUT_CHOICES = [
1384
+ ('50-50', 'Imagem 50% - Texto 50%'),
1385
+ ('30-70', 'Imagem 30% - Texto 70%'),
1386
+ ('70-30', 'Imagem 70% - Texto 30%'),
1387
+ ('40-60', 'Imagem 40% - Texto 60%'),
1388
+ ('60-40', 'Imagem 60% - Texto 40%'),
1389
+ ]
1390
+
1391
+ # Posicionamento da imagem
1392
+ POSITION_CHOICES = [
1393
+ ('left', 'Imagem à esquerda'),
1394
+ ('right', 'Imagem à direita'),
1395
+ ]
1396
+
1397
+ # Configuração do layout
1398
+ layout_proportion = blocks.ChoiceBlock(
1399
+ choices=LAYOUT_CHOICES,
1400
+ default='50-50',
1401
+ label='Proporção do Layout',
1402
+ help_text='Escolha a proporção entre imagem e texto'
1403
+ )
1404
+
1405
+ image_position = blocks.ChoiceBlock(
1406
+ choices=POSITION_CHOICES,
1407
+ default='left',
1408
+ label='Posição da Imagem',
1409
+ help_text='Escolha se a imagem fica à esquerda ou direita do texto'
1410
+ )
1411
+
1412
+ # Conteúdo
1413
+ image = ImageChooserBlock(
1414
+ label='Imagem',
1415
+ help_text='Selecione a imagem a ser exibida'
1416
+ )
1417
+
1418
+ image_alt = blocks.CharBlock(
1419
+ label='Texto alternativo da imagem',
1420
+ help_text='Descrição da imagem para acessibilidade',
1421
+ required=False
1422
+ )
1423
+
1424
+ title = blocks.CharBlock(
1425
+ label='Título',
1426
+ required=False,
1427
+ help_text='Título opcional acima do texto'
1428
+ )
1429
+
1430
+ text = blocks.RichTextBlock(
1431
+ label='Texto',
1432
+ help_text='Conteúdo de texto ao lado da imagem',
1433
+ features=['bold', 'italic', 'link', 'ol', 'ul', 'h2', 'h3', 'h4']
1434
+ )
1435
+
1436
+ # Configurações visuais opcionais
1437
+ background_color = blocks.ChoiceBlock(
1438
+ choices=[
1439
+ ('', 'Padrão (sem cor)'),
1440
+ ('bg-light', 'Fundo claro'),
1441
+ ('bg-secondary', 'Fundo secundário'),
1442
+ ('bg-primary-light', 'Fundo primário claro'),
1443
+ ],
1444
+ default='',
1445
+ required=False,
1446
+ label='Cor de fundo',
1447
+ help_text='Cor de fundo opcional para o componente'
1448
+ )
1449
+
1450
+ add_container = blocks.BooleanBlock(
1451
+ default=True,
1452
+ required=False,
1453
+ label='Adicionar container',
1454
+ help_text='Adiciona margens laterais responsivas'
1455
+ )
1456
+
1457
+ vertical_spacing = blocks.ChoiceBlock(
1458
+ choices=[
1459
+ ('py-3', 'Pequeno'),
1460
+ ('py-4', 'Médio'),
1461
+ ('py-5', 'Grande'),
1462
+ ('py-6', 'Extra Grande'),
1463
+ ],
1464
+ default='py-4',
1465
+ label='Espaçamento vertical',
1466
+ help_text='Espaçamento superior e inferior do componente'
1467
+ )
1468
+
1469
+ def get_image_width_class(self, value):
1470
+ """Retorna a classe CSS para a largura da imagem"""
1471
+ proportion = value.get('layout_proportion', '50-50')
1472
+ width_map = {
1473
+ '30-70': 'col-md-4 col-lg-3',
1474
+ '40-60': 'col-md-5 col-lg-4',
1475
+ '50-50': 'col-md-6',
1476
+ '60-40': 'col-md-7 col-lg-8',
1477
+ '70-30': 'col-md-8 col-lg-9',
1478
+ }
1479
+ return width_map.get(proportion, 'col-md-6')
1480
+
1481
+ def get_text_width_class(self, value):
1482
+ """Retorna a classe CSS para a largura do texto"""
1483
+ proportion = value.get('layout_proportion', '50-50')
1484
+ width_map = {
1485
+ '30-70': 'col-md-8 col-lg-9',
1486
+ '40-60': 'col-md-7 col-lg-8',
1487
+ '50-50': 'col-md-6',
1488
+ '60-40': 'col-md-5 col-lg-4',
1489
+ '70-30': 'col-md-4 col-lg-3',
1490
+ }
1491
+ return width_map.get(proportion, 'col-md-6')
1492
+
1493
+ def get_column_order(self, value):
1494
+ """Retorna as classes de ordem para posicionamento responsivo"""
1495
+ position = value.get('image_position', 'left')
1496
+ if position == 'right':
1497
+ return {
1498
+ 'image_order': 'order-md-2',
1499
+ 'text_order': 'order-md-1'
1500
+ }
1501
+ return {
1502
+ 'image_order': '',
1503
+ 'text_order': ''
1504
+ }
1505
+
1506
+ class Meta:
1507
+ template = 'enap_designsystem/blocks/image_text_block.html'
1508
+ icon = 'image'
1509
+ label = 'Imagem com Texto'
1510
+ help_text = 'Componente flexível de imagem com texto em diferentes proporções'
1511
+
1512
+
1513
+
1514
+
1515
+
1516
+
1517
+
1518
+
1519
+
1374
1520
  ARTICLE_STREAMBLOCKS = [
1375
1521
  ('richtext', RichTextBlock()),
1376
1522
  ("button", ButtonBlock()),
@@ -1381,8 +1527,60 @@ ARTICLE_STREAMBLOCKS = [
1381
1527
  ("embed_video", VideoBlock()),
1382
1528
  ("noticias_carousel", NewsCarouselBlock()),
1383
1529
  ("eventos_carousel", EventsCarouselBlock()),
1530
+ ("Texto_image_choices", ImageTextBlockChoices()),
1384
1531
  ]
1385
1532
 
1533
+
1534
+
1535
+ class ArticleGridBlock(blocks.StructBlock):
1536
+ """
1537
+ Wrapper para criar layouts de notícia normal ou revista
1538
+ """
1539
+
1540
+ LAYOUT_CHOICES = [
1541
+ ('noticia', 'Notícia Normal (uma coluna)'),
1542
+ ('revista', 'Notícia Revista (duas colunas)'),
1543
+ ]
1544
+
1545
+ layout_type = blocks.ChoiceBlock(
1546
+ choices=LAYOUT_CHOICES,
1547
+ default='noticia',
1548
+ label='Tipo de Layout',
1549
+ help_text='Escolha entre notícia normal ou formato revista'
1550
+ )
1551
+
1552
+ # Conteúdo para notícia normal (uma coluna)
1553
+ conteudo = blocks.StreamBlock(
1554
+ ARTICLE_STREAMBLOCKS,
1555
+ label='Conteúdo',
1556
+ help_text='Conteúdo da notícia (aparece apenas no formato notícia normal)',
1557
+ required=False
1558
+ )
1559
+
1560
+ # Conteúdos para formato revista (duas colunas)
1561
+ coluna_esquerda = blocks.StreamBlock(
1562
+ ARTICLE_STREAMBLOCKS,
1563
+ label='Coluna Esquerda',
1564
+ help_text='Conteúdo da primeira coluna (aparece apenas no formato revista)',
1565
+ required=False
1566
+ )
1567
+
1568
+ coluna_direita = blocks.StreamBlock(
1569
+ ARTICLE_STREAMBLOCKS,
1570
+ label='Coluna Direita',
1571
+ help_text='Conteúdo da segunda coluna (aparece apenas no formato revista)',
1572
+ required=False
1573
+ )
1574
+
1575
+ class Meta:
1576
+ js = ('enap_designsystem/blocks/article_grid_conditional.js',)
1577
+ template = 'enap_designsystem/blocks/article_grid_block.html'
1578
+ icon = 'doc-full'
1579
+ label = 'Layout de Artigo'
1580
+ help_text = 'Escolha entre notícia normal ou formato revista'
1581
+
1582
+
1583
+
1386
1584
  class ArticlePage(Page):
1387
1585
  """
1388
1586
  Página de artigo, adequada para notícias ou conteúdo de blog.
@@ -1643,7 +1841,7 @@ class ArticleIndexPage(Page):
1643
1841
 
1644
1842
 
1645
1843
  class ENAPNoticiasIndexPage(Page):
1646
- subpage_types = ['enap_designsystem.ENAPNoticiaImportada', 'enap_designsystem.ENAPNoticia']
1844
+ subpage_types = ['enap_designsystem.ENAPNoticiaImportada', 'enap_designsystem.ENAPNoticia', 'enap_designsystem.ENAPRevista']
1647
1845
 
1648
1846
  class Meta:
1649
1847
  verbose_name = _("ENAP Índice de Notícias")
@@ -2071,6 +2269,361 @@ class ENAPNoticiaImportada(Page):
2071
2269
  verbose_name = _("ENAP Noticias Joomla")
2072
2270
  verbose_name_plural = _("ENAP Noticias Joomla")
2073
2271
 
2272
+
2273
+
2274
+
2275
+
2276
+
2277
+
2278
+
2279
+ class ENAPRevista(Page):
2280
+ """Modelo para artigos em formato revista com layout em duas colunas."""
2281
+
2282
+ body = StreamField(
2283
+ [
2284
+ ("article_grid", ArticleGridBlock()),
2285
+ ],
2286
+ null=True,
2287
+ blank=True,
2288
+ verbose_name=_("Conteúdo"),
2289
+ help_text=_("Configure o layout: escolha 'Notícia Normal' ou 'Notícia Revista' e adicione o conteúdo."),
2290
+ use_json_field=True,
2291
+ )
2292
+
2293
+ subtitulo = models.CharField(
2294
+ max_length=255,
2295
+ blank=True,
2296
+ verbose_name=_("Subtítulo"),
2297
+ help_text=_("Texto complementar ao título da revista."),
2298
+ )
2299
+
2300
+ legenda_home = models.TextField(
2301
+ max_length=500,
2302
+ blank=True,
2303
+ verbose_name=_("Legenda para Home"),
2304
+ help_text=_("Texto que aparece nas listagens da home (máx. 500 caracteres). Se vazio, usará o subtítulo."),
2305
+ )
2306
+
2307
+ imagem_externa = models.ForeignKey(
2308
+ get_image_model_string(),
2309
+ null=True,
2310
+ blank=True,
2311
+ on_delete=models.SET_NULL,
2312
+ related_name='+',
2313
+ verbose_name=_("Imagem Externa"),
2314
+ help_text=_("Imagem principal da revista para exibição externa (listagens, cards, etc)."),
2315
+ )
2316
+
2317
+ # Campos de background da página
2318
+ background_type = models.CharField(
2319
+ max_length=20,
2320
+ choices=[
2321
+ ('color', _('Cor de Fundo')),
2322
+ ('image', _('Imagem de Fundo')),
2323
+ ],
2324
+ default='color',
2325
+ verbose_name=_("Tipo de Fundo"),
2326
+ help_text=_("Escolha entre cor sólida ou imagem de fundo."),
2327
+ )
2328
+
2329
+ background_color = models.CharField(
2330
+ max_length=50,
2331
+ choices=[('', _('Padrão (sem cor)'))] + BACKGROUND_COLOR_CHOICES,
2332
+ default='',
2333
+ blank=True,
2334
+ verbose_name=_("Cor de Fundo"),
2335
+ help_text=_("Cor de fundo da página (usado quando 'Tipo de Fundo' é 'Cor')."),
2336
+ )
2337
+
2338
+ background_image = models.ForeignKey(
2339
+ get_image_model_string(),
2340
+ null=True,
2341
+ blank=True,
2342
+ on_delete=models.SET_NULL,
2343
+ related_name='+',
2344
+ verbose_name=_("Imagem de Fundo"),
2345
+ help_text=_("Imagem de fundo da página (usado quando 'Tipo de Fundo' é 'Imagem')."),
2346
+ )
2347
+
2348
+ background_image_position = models.CharField(
2349
+ max_length=30,
2350
+ choices=[
2351
+ ('center', _('Centro')),
2352
+ ('top', _('Topo')),
2353
+ ('bottom', _('Baixo')),
2354
+ ('left', _('Esquerda')),
2355
+ ('right', _('Direita')),
2356
+ ('top-left', _('Topo Esquerda')),
2357
+ ('top-right', _('Topo Direita')),
2358
+ ('bottom-left', _('Baixo Esquerda')),
2359
+ ('bottom-right', _('Baixo Direita')),
2360
+ ],
2361
+ default='center',
2362
+ verbose_name=_("Posição da Imagem"),
2363
+ help_text=_("Posição da imagem de fundo."),
2364
+ )
2365
+
2366
+ background_image_size = models.CharField(
2367
+ max_length=20,
2368
+ choices=[
2369
+ ('cover', _('Cobrir (Cover)')),
2370
+ ('contain', _('Conter (Contain)')),
2371
+ ('auto', _('Automático')),
2372
+ ],
2373
+ default='cover',
2374
+ verbose_name=_("Tamanho da Imagem"),
2375
+ help_text=_("Como a imagem de fundo deve ser redimensionada."),
2376
+ )
2377
+
2378
+ author = models.ForeignKey(
2379
+ settings.AUTH_USER_MODEL,
2380
+ null=True,
2381
+ blank=True,
2382
+ editable=True,
2383
+ on_delete=models.SET_NULL,
2384
+ verbose_name=_("Autor"),
2385
+ related_name='enap_revista_set',
2386
+ )
2387
+
2388
+ author_display = models.CharField(
2389
+ max_length=255,
2390
+ blank=True,
2391
+ verbose_name=_("Exibir autor como"),
2392
+ help_text=_("Substitui como o nome do autor é exibido neste artigo."),
2393
+ )
2394
+
2395
+ date_display = models.DateField(
2396
+ null=True,
2397
+ blank=False,
2398
+ verbose_name=_("Data de publicação para exibição"),
2399
+ )
2400
+
2401
+ featured_image = StreamField(
2402
+ [
2403
+ ("image", ImageBlock()),
2404
+ ],
2405
+ null=True,
2406
+ blank=True,
2407
+ verbose_name=_("Imagem Destacada"),
2408
+ help_text=_("Imagem de destaque que aparece no topo do artigo."),
2409
+ use_json_field=True,
2410
+ )
2411
+
2412
+ # Campos específicos para revista
2413
+ edicao_numero = models.CharField(
2414
+ max_length=50,
2415
+ blank=True,
2416
+ verbose_name=_("Número da Edição"),
2417
+ help_text=_("Ex: Edição 15, Vol. 3, etc."),
2418
+ )
2419
+
2420
+ categoria_revista = models.CharField(
2421
+ max_length=100,
2422
+ blank=True,
2423
+ choices=[
2424
+ ('entrevista', _('Entrevista')),
2425
+ ('artigo', _('Artigo')),
2426
+ ('especial', _('Especial')),
2427
+ ('inovacao', _('Inovação')),
2428
+ ('capacitacao', _('Capacitação')),
2429
+ ('pesquisa', _('Pesquisa')),
2430
+ ('internacional', _('Internacional')),
2431
+ ],
2432
+ verbose_name=_("Categoria da Revista"),
2433
+ help_text=_("Categoria específica para organização da revista."),
2434
+ )
2435
+
2436
+ navbar = models.ForeignKey(
2437
+ "EnapNavbarSnippet",
2438
+ null=True,
2439
+ blank=True,
2440
+ on_delete=models.SET_NULL,
2441
+ related_name="+",
2442
+ )
2443
+
2444
+ footer = models.ForeignKey(
2445
+ "EnapFooterSnippet",
2446
+ null=True,
2447
+ blank=True,
2448
+ on_delete=models.SET_NULL,
2449
+ related_name="+",
2450
+ )
2451
+
2452
+ # Propriedade para obter o estilo CSS do background
2453
+ @property
2454
+ def background_style(self):
2455
+ """Gera o CSS para o background da página"""
2456
+ if self.background_type == 'image' and self.background_image:
2457
+ return (
2458
+ f"background-image: url('{self.background_image.file.url}'); "
2459
+ f"background-position: {self.background_image_position}; "
2460
+ f"background-size: {self.background_image_size}; "
2461
+ f"background-repeat: no-repeat;"
2462
+ )
2463
+ elif self.background_type == 'color' and self.background_color:
2464
+ return f"background-color: {self.background_color};"
2465
+ return ""
2466
+
2467
+ content_panels = Page.content_panels + [
2468
+ FieldPanel("navbar"),
2469
+ FieldPanel("footer"),
2470
+ MultiFieldPanel(
2471
+ [
2472
+ FieldPanel('subtitulo'),
2473
+ FieldPanel('legenda_home'),
2474
+ FieldPanel('imagem_externa'),
2475
+ FieldPanel('edicao_numero'),
2476
+ FieldPanel('categoria_revista'),
2477
+ ],
2478
+ heading=_("Informações da Revista"),
2479
+ ),
2480
+ MultiFieldPanel(
2481
+ [
2482
+ FieldPanel('background_type'),
2483
+ FieldPanel('background_color'),
2484
+ FieldPanel('background_image'),
2485
+ FieldPanel('background_image_position'),
2486
+ FieldPanel('background_image_size'),
2487
+ ],
2488
+ heading=_("Configurações de Fundo"),
2489
+ help_text=_("Configure o fundo da página - escolha entre cor sólida ou imagem."),
2490
+ ),
2491
+ FieldPanel('featured_image'),
2492
+ MultiFieldPanel(
2493
+ [
2494
+ FieldPanel('body'),
2495
+ ],
2496
+ heading=_("Conteúdo da Revista"),
2497
+ help_text=_("DICA: Use o bloco 'Layout de Artigo' → 'Notícia Revista' para criar o layout em duas colunas como na imagem exemplo."),
2498
+ ),
2499
+ MultiFieldPanel(
2500
+ [
2501
+ FieldPanel('author'),
2502
+ FieldPanel('author_display'),
2503
+ FieldPanel('date_display'),
2504
+ ],
2505
+ heading=_("Informações de Publicação"),
2506
+ ),
2507
+ ]
2508
+
2509
+ # Mesmas propriedades da ENAPNoticia para compatibilidade
2510
+ @property
2511
+ def titulo_filter(self):
2512
+ return strip_tags(self.title or "").strip()
2513
+
2514
+ @property
2515
+ def descricao_filter(self):
2516
+ return strip_tags(self.subtitulo or "").strip()
2517
+
2518
+ @property
2519
+ def legenda_home_filter(self):
2520
+ """Retorna legenda_home ou fallback para subtítulo"""
2521
+ if self.legenda_home:
2522
+ return strip_tags(self.legenda_home).strip()
2523
+ return strip_tags(self.subtitulo or "").strip()
2524
+
2525
+ @property
2526
+ def categoria(self):
2527
+ return "Notícias"
2528
+
2529
+ @property
2530
+ def data_atualizacao_filter(self):
2531
+ return self.date_display or self.last_published_at or self.latest_revision_created_at or self.first_published_at
2532
+
2533
+ @property
2534
+ def url_filter(self):
2535
+ if hasattr(self, 'full_url') and self.full_url:
2536
+ return self.full_url
2537
+ return self.get_url_parts()[2] if self.get_url_parts() else ""
2538
+
2539
+ @property
2540
+ def imagem_filter(self):
2541
+ """Retorna URL da imagem externa ou da featured_image como fallback."""
2542
+ # Prioridade: imagem_externa
2543
+ try:
2544
+ if self.imagem_externa:
2545
+ return self.imagem_externa.file.url
2546
+ except Exception:
2547
+ pass
2548
+
2549
+ # Fallback: featured_image
2550
+ if self.featured_image and len(self.featured_image.stream_data) > 0:
2551
+ primeiro_bloco = self.featured_image.stream_data[0]
2552
+ if primeiro_bloco["type"] == "image":
2553
+ valor = primeiro_bloco["value"]
2554
+ if isinstance(valor, dict) and valor.get("id"):
2555
+ try:
2556
+ from wagtail.images import get_image_model
2557
+ Image = get_image_model()
2558
+ imagem = Image.objects.get(id=valor["id"])
2559
+ return imagem.file.url
2560
+ except Image.DoesNotExist:
2561
+ pass
2562
+ return ""
2563
+
2564
+ @property
2565
+ def texto_unificado(self):
2566
+ def extract_text_from_block(block_value):
2567
+ result = []
2568
+
2569
+ if isinstance(block_value, list):
2570
+ for subblock in block_value:
2571
+ result.extend(extract_text_from_block(subblock))
2572
+ elif hasattr(block_value, "get"):
2573
+ for key, val in block_value.items():
2574
+ result.extend(extract_text_from_block(val))
2575
+ elif isinstance(block_value, str):
2576
+ cleaned = strip_tags(block_value).strip()
2577
+ if cleaned and cleaned.lower() not in {
2578
+ "default", "tipo terciário", "tipo secundário", "tipo bg image",
2579
+ "bg-gray", "bg-blue", "bg-white", "fundo cinza", "fundo branco"
2580
+ }:
2581
+ result.append(cleaned)
2582
+ elif hasattr(block_value, "source"):
2583
+ cleaned = strip_tags(block_value.source).strip()
2584
+ if cleaned:
2585
+ result.append(cleaned)
2586
+
2587
+ return result
2588
+
2589
+ textos = []
2590
+ if self.body:
2591
+ for block in self.body:
2592
+ textos.extend(extract_text_from_block(block.value))
2593
+
2594
+ if self.subtitulo:
2595
+ textos.append(strip_tags(self.subtitulo).strip())
2596
+
2597
+ if self.legenda_home:
2598
+ textos.append(strip_tags(self.legenda_home).strip())
2599
+
2600
+ if self.edicao_numero:
2601
+ textos.append(strip_tags(self.edicao_numero).strip())
2602
+
2603
+ return re.sub(r"\s+", " ", " ".join([t for t in textos if t])).strip()
2604
+
2605
+ search_fields = Page.search_fields + [
2606
+ index.SearchField("title", boost=3),
2607
+ index.SearchField("titulo_filter", name="titulo"),
2608
+ index.SearchField("descricao_filter", name="descricao"),
2609
+ index.SearchField("legenda_home_filter", name="legenda_home"),
2610
+ index.FilterField("categoria", name="categoria_filter"),
2611
+ index.SearchField("url_filter", name="url"),
2612
+ index.SearchField("data_atualizacao_filter", name="data_atualizacao"),
2613
+ index.SearchField("imagem_filter", name="imagem"),
2614
+ index.SearchField("texto_unificado", name="body"),
2615
+ index.FilterField("categoria_revista"),
2616
+ index.SearchField("edicao_numero"),
2617
+ ]
2618
+
2619
+ class Meta:
2620
+ verbose_name = _("ENAP Revista")
2621
+ verbose_name_plural = _("ENAP Revistas")
2622
+
2623
+ template = "enap_designsystem/pages/article/enap_revista.html"
2624
+
2625
+
2626
+
2074
2627
  class ENAPDummyPage(Page):
2075
2628
  """Página usada apenas como container na árvore, sem conteúdo visível."""
2076
2629
 
@@ -4141,15 +4694,15 @@ class JobVacancyPage(Page):
4141
4694
  max_length=20,
4142
4695
  choices=STATUS_CHOICES,
4143
4696
  default='aberta',
4144
- verbose_name=_('Status da Vaga')
4697
+ verbose_name=_('Status (Aberto, em andamento ou encerrada)'),
4145
4698
  )
4146
4699
 
4147
4700
  area = models.CharField(
4148
4701
  max_length=30,
4149
4702
  choices=AREA_CHOICES,
4150
4703
  default='outros',
4151
- verbose_name=_('Área da Vaga'),
4152
- help_text=_('Categoria da vaga para melhor organização')
4704
+ verbose_name=_('Área ou categoria'),
4705
+ help_text=_('Categoria para melhor organização')
4153
4706
  )
4154
4707
 
4155
4708
  image = StreamField([
@@ -4168,7 +4721,7 @@ class JobVacancyPage(Page):
4168
4721
 
4169
4722
  link_text = models.CharField(
4170
4723
  max_length=100,
4171
- verbose_name=_('Texto do botão'),
4724
+ verbose_name=_('Texto do botão de destaque'),
4172
4725
  default='Ver detalhes',
4173
4726
  blank=True
4174
4727
  )
@@ -4180,20 +4733,20 @@ class JobVacancyPage(Page):
4180
4733
  )
4181
4734
 
4182
4735
  registration_start = models.DateField(
4183
- verbose_name=_('Início das Inscrições')
4736
+ verbose_name=_('Data de Inicio')
4184
4737
  )
4185
4738
 
4186
4739
  registration_end = models.DateField(
4187
- verbose_name=_('Fim das Inscrições')
4740
+ verbose_name=_('Data Final')
4188
4741
  )
4189
4742
 
4190
4743
  inscription_link = models.URLField(
4191
- verbose_name=_('Link para Inscrição'),
4744
+ verbose_name=_('Link de destaque'),
4192
4745
  blank=True
4193
4746
  )
4194
4747
 
4195
4748
  full_description = RichTextField(
4196
- verbose_name=_('Descrição completa da Vaga')
4749
+ verbose_name=_('Descrição completa')
4197
4750
  )
4198
4751
 
4199
4752
  download_files = RichTextField(
@@ -4363,8 +4916,8 @@ class JobVacancyPage(Page):
4363
4916
  ]
4364
4917
 
4365
4918
  class Meta:
4366
- verbose_name = _('Vaga de Emprego')
4367
- verbose_name_plural = _('Vagas de Emprego')
4919
+ verbose_name = _('Template (Vagas & Licitações)')
4920
+ verbose_name_plural = _('Vagas e Licitações')
4368
4921
 
4369
4922
 
4370
4923
 
@@ -6881,4 +7434,14 @@ class FormularioDinamicoBlock(blocks.StructBlock):
6881
7434
  if parent_context:
6882
7435
  context['page'] = parent_context.get('page')
6883
7436
 
6884
- return context
7437
+ return context
7438
+
7439
+
7440
+
7441
+
7442
+
7443
+
7444
+
7445
+
7446
+
7447
+