Skip to content

Adiciona a capacidade de cadastrar organizações manualmente nos formulários de histórico de periódicos #1271

Open
robertatakenaka wants to merge 5 commits intoscieloorg:mainfrom
robertatakenaka:remodela_organization_parte_2d
Open

Adiciona a capacidade de cadastrar organizações manualmente nos formulários de histórico de periódicos #1271
robertatakenaka wants to merge 5 commits intoscieloorg:mainfrom
robertatakenaka:remodela_organization_parte_2d

Conversation

@robertatakenaka
Copy link
Member

@robertatakenaka robertatakenaka commented Feb 3, 2026

Descrição

Este PR introduz a capacidade de cadastrar organizações manualmente nos formulários de histórico de periódicos (Owner, Publisher, Sponsor e Copyright Holder).

Anteriormente, o sistema limitava a seleção a organizações já existentes no banco de dados. Agora, através do OrganizationMixin, os usuários podem inserir dados de uma nova organização caso ela não seja encontrada na busca padrão. O sistema cuidará da criação automática do registro de Organization e da respectiva Location associada.

Alterações Técnicas

journal/forms.py

  • **Criação do OrganizationMixin**: Centraliza a lógica de campos manuais (manual_org_name, manual_city, manual_state, manual_country).
  • Lógica de Persistência: Implementa o método _create_or_get_organization que:
  1. Verifica se a organização já existe pelo nome.
  2. Se não existir, cria uma nova Location (se o país for fornecido).
  3. Cria a Organization marcando a origem (source) como "user".
  • Novas Classes de Formulário:

  • OwnerHistoryForm

  • PublisherHistoryForm

  • SponsorHistoryForm

  • CopyrightHolderHistoryForm

  • Lazy Imports: Utiliza importações dentro do __init__ para evitar problemas de importação circular com os modelos do app journal.

journal/models.py

  • Atualização da propriedade base_form_class nos modelos de histórico para utilizarem seus respectivos novos formulários em vez do genérico SciELOJournalModelForm.

Comportamento Esperado

  1. O usuário tenta buscar uma organização.
  2. Se não encontrar, preenche o campo "Organization Name (Manual)" e seleciona obrigatoriamente um país.
  3. Ao salvar, o Django cria a organização em background e a vincula ao registro de histórico.
  4. Se uma organização com o mesmo nome exato já existir, ela será reutilizada em vez de duplicada.

- Adicionado OrganizationMixin para permitir cadastro manual (nome, cidade, país)
- Criados formulários de histórico para Proprietário, Editora, Patrocinador e Detentor de Direitos
- Implementada carga dinâmica de modelos para evitar importação circular
Copilot AI review requested due to automatic review settings February 3, 2026 14:31
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Este PR tenta adicionar suporte a entrada manual de dados de organização (quando não encontrada na lista padrão) em formulários do app journal, e inclui um pequeno ajuste de formatação na atribuição de issn_scielo.

Changes:

  • Adiciona OrganizationMixin com campos/validação para cadastro manual de organização e tentativa de criação/associação de Organization.
  • Introduz ModelForms para histórico (Owner/Publisher/Sponsor/CopyrightHolder) visando aplicar o mixin.
  • Ajusta a atribuição de issn_scielo para quebrar linha e melhorar legibilidade.
Comments suppressed due to low confidence (1)

journal/forms.py:318

  • Há duas definições de SciELOJournalModelForm neste arquivo (uma começa na linha 163 e outra na 317). Em Python, a segunda sobrescreve a primeira, então toda a lógica/campos adicionados na primeira classe será descartada em runtime. Remover a duplicação e consolidar em uma única classe (ou extrair a lógica para um mixin reutilizado).
class SciELOJournalModelForm(CoreAdminModelForm):
    def save_all(self, user):

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +123 to +131
class OwnerHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import OwnerHistory

self._meta.model = OwnerHistory

Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Esses ModelForms (OwnerHistoryForm, PublisherHistoryForm, etc.) não são referenciados em nenhum outro ponto do repositório, e os modelos correspondentes em journal/models.py não definem base_form_class para usá-los. Do jeito que está, a lógica de entrada manual provavelmente não será aplicada na UI/admin. Para que tenha efeito, definir base_form_class nos models/Orderables corretos (ou registrar esses forms no local apropriado do admin).

Copilot uses AI. Check for mistakes.
Comment on lines +277 to 316
class OwnerHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import OwnerHistory

self._meta.model = OwnerHistory


class PublisherHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import PublisherHistory

self._meta.model = PublisherHistory


class SponsorHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import SponsorHistory

self._meta.model = SponsorHistory


class CopyrightHolderHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import CopyrightHolderHistory

self._meta.model = CopyrightHolderHistory


Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Os formulários OwnerHistoryForm/PublisherHistoryForm/SponsorHistoryForm/CopyrightHolderHistoryForm estão declarados duas vezes no mesmo módulo (ex.: OwnerHistoryForm na linha 123 e novamente na 277). A segunda declaração sobrescreve a primeira e é um forte indício de código duplicado acidental; manter apenas uma definição para evitar confusão e erros de manutenção.

Suggested change
class OwnerHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import OwnerHistory
self._meta.model = OwnerHistory
class PublisherHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import PublisherHistory
self._meta.model = PublisherHistory
class SponsorHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import SponsorHistory
self._meta.model = SponsorHistory
class CopyrightHolderHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import CopyrightHolderHistory
self._meta.model = CopyrightHolderHistory

Copilot uses AI. Check for mistakes.
Comment on lines +110 to +116
# Cria nova organization
try:
organization = Organization.objects.create(
name=manual_name,
location=location,
source="user", # Marca como fonte user
)
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Organization.objects.create(..., source="user") vai falhar com TypeError porque o modelo organization.Organization não possui um campo source (ver organization/models.py). Além disso, a exceção está sendo engolida e a função retorna None, o que impede a criação da organização mesmo quando o usuário preenche os dados manuais. Remover/ajustar esse atributo e usar o método existente Organization.create_or_update(...) (ou adicionar o campo no modelo, se realmente necessário).

Suggested change
# Cria nova organization
try:
organization = Organization.objects.create(
name=manual_name,
location=location,
source="user", # Marca como fonte user
)
# Cria nova organization usando o método auxiliar do model
try:
org_result = Organization.create_or_update(
name=manual_name,
location=location,
)
# Suporta tanto retorno direto da instância quanto tupla (instância, created)
if isinstance(org_result, tuple):
organization, _created = org_result
else:
organization = org_result

Copilot uses AI. Check for mistakes.
Comment on lines +88 to +89
manual_city = self.cleaned_data.get("manual_city")
manual_state = self.cleaned_data.get("manual_state")
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Em _create_or_get_organization, manual_city e manual_state são lidos do cleaned_data mas nunca são utilizados para compor o Location (hoje só usa country). Isso adiciona campos/complexidade sem efeito prático e provavelmente não atende ao objetivo de capturar a localização manual. Ou usar esses valores na criação/lookup do Location (via Location.create_or_update) ou remover os campos/variáveis se não forem necessários.

Suggested change
manual_city = self.cleaned_data.get("manual_city")
manual_state = self.cleaned_data.get("manual_state")

Copilot uses AI. Check for mistakes.
Comment on lines +96 to +97
existing_org = Organization.objects.filter(name=manual_name).first()
if existing_org:
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O lookup de organização existente usa Organization.objects.filter(name=manual_name).first(), que é case-sensitive e não normaliza espaços; isso pode criar duplicatas que o próprio modelo tenta evitar com name__iexact/remove_extra_spaces. Prefira normalizar o texto e consultar com name__iexact (e idealmente incluir location na chave) para reduzir registros duplicados.

Copilot uses AI. Check for mistakes.
return None


class OwnerHistoryForm(OrganizationMixin, CoreAdminModelForm):
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to 'OwnerHistoryForm' is unnecessary as it is redefined before this value is used.

Copilot uses AI. Check for mistakes.
self._meta.model = OwnerHistory


class PublisherHistoryForm(OrganizationMixin, CoreAdminModelForm):
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to 'PublisherHistoryForm' is unnecessary as it is redefined before this value is used.

Suggested change
class PublisherHistoryForm(OrganizationMixin, CoreAdminModelForm):
class _PublisherHistoryFormBase(OrganizationMixin, CoreAdminModelForm):

Copilot uses AI. Check for mistakes.
Comment on lines +143 to +152
class SponsorHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import SponsorHistory

self._meta.model = SponsorHistory


Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to 'SponsorHistoryForm' is unnecessary as it is redefined before this value is used.

Suggested change
class SponsorHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import SponsorHistory
self._meta.model = SponsorHistory

Copilot uses AI. Check for mistakes.
Comment on lines +153 to +162
class CopyrightHolderHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import CopyrightHolderHistory

self._meta.model = CopyrightHolderHistory


Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to 'CopyrightHolderHistoryForm' is unnecessary as it is redefined before this value is used.

Suggested change
class CopyrightHolderHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import CopyrightHolderHistory
self._meta.model = CopyrightHolderHistory

Copilot uses AI. Check for mistakes.
Comment on lines +161 to +205


class SciELOJournalModelForm(CoreAdminModelForm):
"""
Mixin para adicionar campos de entrada manual de organização
que não estão presentes na lista padrão de Organization.
"""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

# Adiciona campos para entrada manual de organização
self.fields["manual_org_name"] = forms.CharField(
label=_("Organization Name (Manual)"),
max_length=255,
required=False,
help_text=_("Enter organization name if not found in the list above"),
widget=forms.TextInput(
attrs={"placeholder": _("Enter standardized organization name")}
),
)

self.fields["manual_city"] = forms.CharField(
label=_("City"),
max_length=100,
required=False,
help_text=_("City where organization is located"),
)

self.fields["manual_state"] = forms.CharField(
label=_("State/Province"),
max_length=100,
required=False,
help_text=_("State or province where organization is located"),
)

self.fields["manual_country"] = forms.ModelChoiceField(
label=_("Country"),
queryset=Country.objects.all(),
required=False,
help_text=_("Country where organization is located"),
empty_label=_("Select country..."),
)

def clean(self):
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to 'SciELOJournalModelForm' is unnecessary as it is redefined before this value is used.

Suggested change
class SciELOJournalModelForm(CoreAdminModelForm):
"""
Mixin para adicionar campos de entrada manual de organização
que não estão presentes na lista padrão de Organization.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Adiciona campos para entrada manual de organização
self.fields["manual_org_name"] = forms.CharField(
label=_("Organization Name (Manual)"),
max_length=255,
required=False,
help_text=_("Enter organization name if not found in the list above"),
widget=forms.TextInput(
attrs={"placeholder": _("Enter standardized organization name")}
),
)
self.fields["manual_city"] = forms.CharField(
label=_("City"),
max_length=100,
required=False,
help_text=_("City where organization is located"),
)
self.fields["manual_state"] = forms.CharField(
label=_("State/Province"),
max_length=100,
required=False,
help_text=_("State or province where organization is located"),
)
self.fields["manual_country"] = forms.ModelChoiceField(
label=_("Country"),
queryset=Country.objects.all(),
required=False,
help_text=_("Country where organization is located"),
empty_label=_("Select country..."),
)
def clean(self):

Copilot uses AI. Check for mistakes.
@robertatakenaka robertatakenaka changed the title Remodela organization parte 2d Adiciona a capacidade de cadastrar organizações manualmente nos formulários de histórico de periódicos Feb 3, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.

Comments suppressed due to low confidence (4)

journal/models.py:1318

  • The new manual-organization fields added by OwnerHistoryForm won't be shown in the Wagtail editor unless they are included in panels. Currently only organization is displayed via AutocompletePanel("organization"). Add FieldPanel entries for the manual fields (and consider grouping/collapsing them) so users can actually enter the manual org data.
    base_form_class = OwnerHistoryForm

    panels = BaseHistoryItem.panels + [
        AutocompletePanel("institution", read_only=True),
        AutocompletePanel("organization"),
        InlinePanel(

journal/models.py:1386

  • Same issue here: SponsorHistoryForm adds manual org fields but SponsorHistory.panels doesn't include them, so users can't fill them out in Wagtail. Include the manual fields in panels so the feature is usable.
    base_form_class = SponsorHistoryForm

    panels = BaseHistoryItem.panels + [
        AutocompletePanel("institution", read_only=True),
        AutocompletePanel("organization"),
        InlinePanel(

journal/models.py:1423

  • Same issue as above: CopyrightHolderHistoryForm adds manual org fields, but CopyrightHolderHistory.panels doesn't include them, so the manual entry UI won't appear. Add FieldPanel entries for the manual fields to the panels list.
    base_form_class = CopyrightHolderHistoryForm

    panels = BaseHistoryItem.panels + [
        AutocompletePanel("institution", read_only=True),
        AutocompletePanel("organization"),
        InlinePanel(

journal/models.py:1352

  • Same as OwnerHistory: the manual-organization fields provided by PublisherHistoryForm are not included in panels, so they won't render in the Wagtail UI. Add FieldPanel entries for the manual fields (and optionally make them conditional/inside a collapsed panel).
    base_form_class = PublisherHistoryForm

    panels = BaseHistoryItem.panels + [
        AutocompletePanel("institution", read_only=True),
        AutocompletePanel("organization"),
        InlinePanel(

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

organization = Organization.objects.create(
name=manual_name,
location=location,
source="user", # Marca como fonte user
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Organization.objects.create(..., source="user") will fail because Organization does not have a source field/constructor argument in organization/models.py. Remove this argument or implement the intended tracking field in the model/migration (and update admin/panels accordingly).

Suggested change
source="user", # Marca como fonte user

Copilot uses AI. Check for mistakes.
Comment on lines +92 to +96
if not manual_name:
return None

# Busca organization existente primeiro
existing_org = Organization.objects.filter(name=manual_name).first()
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing-organization lookup uses an exact, case-sensitive match on name and ignores Location. Since Organization is unique by (name, acronym, location) and multiple orgs can share the same name, this can attach the wrong organization. Consider normalizing the input (e.g., trim/extra spaces), using name__iexact, and including location criteria (at least country, and state/city if provided) when reusing an existing org.

Suggested change
if not manual_name:
return None
# Busca organization existente primeiro
existing_org = Organization.objects.filter(name=manual_name).first()
# Normaliza nome (remove espaços extras nas bordas)
if manual_name is not None:
manual_name = manual_name.strip()
if not manual_name:
return None
# Busca organization existente primeiro, usando comparação case-insensitive
# e filtrando por país quando disponível, para respeitar a unicidade (name, location).
org_filters = {"name__iexact": manual_name}
if manual_country:
org_filters["location__country"] = manual_country
existing_org = Organization.objects.filter(**org_filters).first()

Copilot uses AI. Check for mistakes.
Comment on lines +104 to +105
# Busca ou cria location (simplificado)
location_data = {"country": manual_country}
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

manual_city and manual_state are collected but never used when creating the Location (only country is used). This drops user-provided data and also collapses all orgs created for the same country onto the same (country, state=None, city=None) location record. Use the provided city/state values to resolve/create City/State and pass them into Location creation, or remove these fields if they are not intended to be persisted.

Suggested change
# Busca ou cria location (simplificado)
location_data = {"country": manual_country}
# Busca ou cria location (incluindo estado/cidade quando fornecidos)
location_data = {"country": manual_country}
if manual_state:
location_data["state"] = manual_state
if manual_city:
location_data["city"] = manual_city

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +65
OwnerHistoryForm,
PublisherHistoryForm,
SponsorHistoryForm,
CopyrightHolderHistoryForm
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SciELOJournalModelForm is still referenced later in this module (e.g., SciELOJournal.base_form_class = SciELOJournalModelForm), but it is no longer imported here. This will raise NameError on import. Also, OwnerHistoryForm is imported twice in this block—please remove the duplicate and add back SciELOJournalModelForm (and any other required forms).

Suggested change
OwnerHistoryForm,
PublisherHistoryForm,
SponsorHistoryForm,
CopyrightHolderHistoryForm
PublisherHistoryForm,
SponsorHistoryForm,
CopyrightHolderHistoryForm,
SciELOJournalModelForm,

Copilot uses AI. Check for mistakes.
Comment on lines +123 to +162
class OwnerHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import OwnerHistory

self._meta.model = OwnerHistory


class PublisherHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import PublisherHistory

self._meta.model = PublisherHistory


class SponsorHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import SponsorHistory

self._meta.model = SponsorHistory


class CopyrightHolderHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import CopyrightHolderHistory

self._meta.model = CopyrightHolderHistory


Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These form classes are defined twice in this file (OwnerHistoryForm/PublisherHistoryForm/SponsorHistoryForm/CopyrightHolderHistoryForm, and SciELOJournalModelForm). The later definitions silently overwrite the earlier ones, which is error-prone and makes future edits confusing. Please remove the duplicated block and keep a single authoritative definition for each class.

Suggested change
class OwnerHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import OwnerHistory
self._meta.model = OwnerHistory
class PublisherHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import PublisherHistory
self._meta.model = PublisherHistory
class SponsorHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import SponsorHistory
self._meta.model = SponsorHistory
class CopyrightHolderHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import CopyrightHolderHistory
self._meta.model = CopyrightHolderHistory

Copilot uses AI. Check for mistakes.
organization = Organization.objects.create(
name=manual_name,
location=location,
source="user", # Marca como fonte user
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as above: Organization.objects.create(..., source="user") will raise because Organization has no source field/kwarg. Once the duplicate block is removed, ensure the remaining creation call does not pass unsupported kwargs.

Suggested change
source="user", # Marca como fonte user

Copilot uses AI. Check for mistakes.
Comment on lines +72 to +120
def save(self, commit=True):
instance = super().save(commit=False)

# Se não tem organization mas tem dados manuais, cria/busca organization
if not instance.organization and self.cleaned_data.get("manual_org_name"):
organization = self._create_or_get_organization()
if organization:
instance.organization = organization

if commit:
instance.save()
return instance

def _create_or_get_organization(self):
"""Cria ou busca Organization baseado nos dados manuais."""
manual_name = self.cleaned_data.get("manual_org_name")
manual_city = self.cleaned_data.get("manual_city")
manual_state = self.cleaned_data.get("manual_state")
manual_country = self.cleaned_data.get("manual_country")

if not manual_name:
return None

# Busca organization existente primeiro
existing_org = Organization.objects.filter(name=manual_name).first()
if existing_org:
return existing_org

# Cria location se tiver dados geográficos
location = None
if manual_country:
try:
# Busca ou cria location (simplificado)
location_data = {"country": manual_country}
location, created = Location.objects.get_or_create(**location_data)
except Exception:
location = None

# Cria nova organization
try:
organization = Organization.objects.create(
name=manual_name,
location=location,
source="user", # Marca como fonte user
)
return organization
except Exception:
# Se não conseguir criar, retorna None - usuário terá que usar lista padrão
return None
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_create_or_get_organization swallows all exceptions and returns None. In save(), that means the history item can be saved with organization=None even when the user provided manual_org_name (and passed clean()), silently losing the intended link. Instead of broad except Exception, catch expected errors and add a form error / raise ValidationError when organization/location creation fails so the user can fix the input.

Copilot uses AI. Check for mistakes.
Comment on lines +123 to +162
class OwnerHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import OwnerHistory

self._meta.model = OwnerHistory


class PublisherHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import PublisherHistory

self._meta.model = PublisherHistory


class SponsorHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import SponsorHistory

self._meta.model = SponsorHistory


class CopyrightHolderHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import CopyrightHolderHistory

self._meta.model = CopyrightHolderHistory


Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to 'SponsorHistoryForm' is unnecessary as it is redefined before this value is used.

Suggested change
class OwnerHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import OwnerHistory
self._meta.model = OwnerHistory
class PublisherHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import PublisherHistory
self._meta.model = PublisherHistory
class SponsorHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import SponsorHistory
self._meta.model = SponsorHistory
class CopyrightHolderHistoryForm(OrganizationMixin, CoreAdminModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Define o model dinamicamente para evitar importação circular
if not hasattr(self._meta, "model") or self._meta.model is None:
from journal.models import CopyrightHolderHistory
self._meta.model = CopyrightHolderHistory
# NOTE: Earlier duplicate history form definitions were removed here
# to avoid redefining OwnerHistoryForm, PublisherHistoryForm,
# SponsorHistoryForm and CopyrightHolderHistoryForm.
# The canonical definitions of these forms are declared later
# in this module and are the ones that should be used.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant