Adiciona a capacidade de cadastrar organizações manualmente nos formulários de histórico de periódicos #1271
Conversation
- 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
There was a problem hiding this comment.
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
OrganizationMixincom campos/validação para cadastro manual de organização e tentativa de criação/associação deOrganization. - Introduz ModelForms para histórico (Owner/Publisher/Sponsor/CopyrightHolder) visando aplicar o mixin.
- Ajusta a atribuição de
issn_scielopara quebrar linha e melhorar legibilidade.
Comments suppressed due to low confidence (1)
journal/forms.py:318
- Há duas definições de
SciELOJournalModelFormneste 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.
| 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 | ||
|
|
There was a problem hiding this comment.
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).
| 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 | ||
|
|
||
|
|
There was a problem hiding this comment.
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.
| 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 |
| # Cria nova organization | ||
| try: | ||
| organization = Organization.objects.create( | ||
| name=manual_name, | ||
| location=location, | ||
| source="user", # Marca como fonte user | ||
| ) |
There was a problem hiding this comment.
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).
| # 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 |
| manual_city = self.cleaned_data.get("manual_city") | ||
| manual_state = self.cleaned_data.get("manual_state") |
There was a problem hiding this comment.
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.
| manual_city = self.cleaned_data.get("manual_city") | |
| manual_state = self.cleaned_data.get("manual_state") |
| existing_org = Organization.objects.filter(name=manual_name).first() | ||
| if existing_org: |
There was a problem hiding this comment.
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.
| self._meta.model = OwnerHistory | ||
|
|
||
|
|
||
| class PublisherHistoryForm(OrganizationMixin, CoreAdminModelForm): |
There was a problem hiding this comment.
This assignment to 'PublisherHistoryForm' is unnecessary as it is redefined before this value is used.
| class PublisherHistoryForm(OrganizationMixin, CoreAdminModelForm): | |
| class _PublisherHistoryFormBase(OrganizationMixin, CoreAdminModelForm): |
| 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 | ||
|
|
||
|
|
There was a problem hiding this comment.
This assignment to 'SponsorHistoryForm' is unnecessary as it is redefined before this value is used.
| 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 | ||
|
|
||
|
|
There was a problem hiding this comment.
This assignment to 'CopyrightHolderHistoryForm' is unnecessary as it is redefined before this value is used.
| 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 |
|
|
||
|
|
||
| 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): |
There was a problem hiding this comment.
This assignment to 'SciELOJournalModelForm' is unnecessary as it is redefined before this value is used.
| 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): |
There was a problem hiding this comment.
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
OwnerHistoryFormwon't be shown in the Wagtail editor unless they are included inpanels. Currently onlyorganizationis displayed viaAutocompletePanel("organization"). AddFieldPanelentries 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:
SponsorHistoryFormadds manual org fields butSponsorHistory.panelsdoesn't include them, so users can't fill them out in Wagtail. Include the manual fields inpanelsso 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:
CopyrightHolderHistoryFormadds manual org fields, butCopyrightHolderHistory.panelsdoesn't include them, so the manual entry UI won't appear. AddFieldPanelentries 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 byPublisherHistoryFormare not included inpanels, so they won't render in the Wagtail UI. AddFieldPanelentries 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 |
There was a problem hiding this comment.
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).
| source="user", # Marca como fonte user |
| if not manual_name: | ||
| return None | ||
|
|
||
| # Busca organization existente primeiro | ||
| existing_org = Organization.objects.filter(name=manual_name).first() |
There was a problem hiding this comment.
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.
| 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() |
| # Busca ou cria location (simplificado) | ||
| location_data = {"country": manual_country} |
There was a problem hiding this comment.
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.
| # 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 |
| OwnerHistoryForm, | ||
| PublisherHistoryForm, | ||
| SponsorHistoryForm, | ||
| CopyrightHolderHistoryForm |
There was a problem hiding this comment.
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).
| OwnerHistoryForm, | |
| PublisherHistoryForm, | |
| SponsorHistoryForm, | |
| CopyrightHolderHistoryForm | |
| PublisherHistoryForm, | |
| SponsorHistoryForm, | |
| CopyrightHolderHistoryForm, | |
| SciELOJournalModelForm, |
| 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 | ||
|
|
||
|
|
There was a problem hiding this comment.
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.
| 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 |
| organization = Organization.objects.create( | ||
| name=manual_name, | ||
| location=location, | ||
| source="user", # Marca como fonte user |
There was a problem hiding this comment.
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.
| source="user", # Marca como fonte user |
| 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 |
There was a problem hiding this comment.
_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.
| 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 | ||
|
|
||
|
|
There was a problem hiding this comment.
This assignment to 'SponsorHistoryForm' is unnecessary as it is redefined before this value is used.
| 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. |
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 deOrganizatione da respectivaLocationassociada.Alterações Técnicas
journal/forms.pyOrganizationMixin**: Centraliza a lógica de campos manuais (manual_org_name,manual_city,manual_state,manual_country)._create_or_get_organizationque:Location(se o país for fornecido).Organizationmarcando a origem (source) como "user".Novas Classes de Formulário:
OwnerHistoryFormPublisherHistoryFormSponsorHistoryFormCopyrightHolderHistoryFormLazy Imports: Utiliza importações dentro do
__init__para evitar problemas de importação circular com os modelos do appjournal.journal/models.pybase_form_classnos modelos de histórico para utilizarem seus respectivos novos formulários em vez do genéricoSciELOJournalModelForm.Comportamento Esperado