Files
Diarios-Oficiais-ALEMS/diarios/models.py

168 lines
5.7 KiB
Python
Raw Normal View History

import os
from urllib.parse import urlparse
2025-03-15 16:52:23 +01:00
import requests
from babel.dates import format_date
2025-03-15 16:52:23 +01:00
from django.core.files.base import ContentFile
2025-06-16 13:11:57 -04:00
from django.db import models, transaction
from django.core.exceptions import ValidationError
2025-06-16 13:11:57 -04:00
import fitz # PyMuPDF
from asgiref.sync import async_to_sync
class TipoDiarioOficial(models.Model):
nome = models.CharField(max_length=100, unique=True)
def __str__(self):
return self.nome
class Meta:
verbose_name_plural = "Tipos de Diários Oficiais"
class DiarioOficial(models.Model):
data = models.DateField()
arquivo = models.FileField(upload_to="diarios_oficiais/", blank=True, null=True)
tipo = models.ForeignKey(
TipoDiarioOficial,
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name="diarios",
)
numero = models.CharField(max_length=20, unique=True)
link = models.URLField(blank=True, null=True, unique=True)
def save(self, *args, **kwargs):
2025-06-16 13:11:57 -04:00
updated = False
super().save(*args, **kwargs)
if self.link and not self.arquivo:
self._download_pdf_from_link()
2025-06-16 13:11:57 -04:00
updated = True
2025-06-16 13:11:57 -04:00
if self.arquivo and not self.paginas.exists():
self._extract_pdf_pages()
2025-06-16 13:11:57 -04:00
updated = True
2025-06-16 13:11:57 -04:00
if updated:
super().save(*args, **kwargs)
def clean(self):
super().clean()
if not self.arquivo and not self.link:
raise ValidationError("Informe um arquivo ou um link para o Diário.")
def _validar_link(self):
if not self.link.lower().endswith(".pdf"):
raise ValidationError("O link deve apontar para um arquivo PDF.")
def _download_pdf_from_link(self):
try:
response = requests.get(self.link)
response.raise_for_status()
parsed_url = urlparse(self.link)
file_name = os.path.basename(parsed_url.path) or f"diario_{self.numero}.pdf"
self.arquivo.save(file_name, ContentFile(response.content), save=True)
except requests.RequestException as e:
raise ValidationError(f"Não foi possível baixar o PDF: {e}")
def _extract_pdf_pages(self):
try:
2025-06-16 13:11:57 -04:00
# Salvar temporariamente o PDF para abrir com o PyMuPDF
with self.arquivo.open("rb") as pdf_file:
2025-06-16 13:11:57 -04:00
temp_pdf_path = f"/tmp/diario_{self.id}.pdf"
with open(temp_pdf_path, "wb") as temp_file:
temp_file.write(pdf_file.read())
2025-06-16 13:11:57 -04:00
# Abrir e processar com fitz
doc = fitz.open(temp_pdf_path)
self._process_pdf_pages(doc)
doc.close()
2025-06-16 13:11:57 -04:00
# Remover arquivo temporário
os.remove(temp_pdf_path)
2025-06-16 13:11:57 -04:00
except Exception as pdf_error:
raise ValidationError(f"Não foi possível processar o PDF: {pdf_error}")
def _process_pdf_pages(self, doc):
with transaction.atomic():
self.paginas.all().delete()
for i, page in enumerate(doc):
try:
blocks = page.get_text("blocks")
# Ordenar os blocos por coordenadas (y, x) para manter a ordem de leitura
blocks.sort(key=lambda b: (b[1], b[0]))
page_text = ""
for block in blocks:
text = block[4].strip()
if text:
page_text += text + "\n"
# Crucial: Remove NULL bytes from the extracted text
# PostgreSQL text fields cannot contain NUL (0x00) bytes
cleaned_text = page_text.strip().replace('\x00', '')
if cleaned_text:
PageDiarioOficial.objects.create(
diario=self,
numero=i + 1,
conteudo=cleaned_text,
)
else:
PageDiarioOficial.objects.create(
diario=self,
numero=i + 1,
conteudo="[Conteúdo não extraído ou vazio]",
)
except Exception as page_error:
PageDiarioOficial.objects.create(
diario=self,
numero=i + 1,
2025-06-16 13:11:57 -04:00
conteudo=f"[Erro na extração do texto: {str(page_error)}]",
)
2025-06-16 13:11:57 -04:00
print(f"Erro ao processar a página {i+1} no Diario ID {self.id}: {page_error}")
@property
def data_formatada(self):
return format_date(self.data, format="long", locale="pt_BR")
@property
def is_online(self):
return bool(self.link)
def __str__(self):
tipo_nome = self.tipo.nome if self.tipo else "Sem Tipo"
return f"Diário {tipo_nome}{self.numero}, {self.data_formatada}"
class Meta:
constraints = [models.UniqueConstraint(fields=["numero"], name="unique_numero")]
verbose_name_plural = "Diários Oficiais"
class PageDiarioOficial(models.Model):
diario = models.ForeignKey(
DiarioOficial, on_delete=models.CASCADE, related_name="paginas"
)
layout_duas_colunas = models.BooleanField(default=False)
numero = models.PositiveIntegerField()
conteudo = models.TextField()
class Meta:
unique_together = ("diario", "numero")
verbose_name = "Página de Diário Oficial"
verbose_name_plural = "Páginas de Diários Oficiais"
2025-06-16 13:11:57 -04:00
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if self.diario:
self.diario.save()
def __str__(self):
return f"Página {self.numero} do Diário {self.diario.numero}"