fix(journal): Corrige falha de atualização de journal_history, causando status "inprogress" e impedindo a publicação - #1052
Conversation
…lectronic trocado
- Adiciona fallback para busca com issn_electronic/issn_print invertidos
- Adiciona fallback final via Q(OR) sobre o conjunto de ISSNs informados
- MultipleObjectsReturned agora usa AND (issn_electronic + issn_print) em vez de OR, ordenando por -updated
- Adiciona property is_complete (missing_fields + core_synchronized)
- missing_fields passa a considerar journal_acron, ausência de Official Journal, histórico por collection e ausência de collection
- JournalCollection: painel usa InlinePanel('journal_history') em vez de AutocompletePanel('journal'/'collection')
- JournalCollection.create_or_update: não sobrescreve updated_by ao encontrar registro existente
- JournalHistory.create_or_update: retorna o objeto atualizado
Registra novo ViewSet para JournalCollection (journal, collection, creator, updated, created, updated_by) no grupo JournalViewSetGroup, com busca por título do journal, nome e acrônimo da collection.
Cobre match exato, match trocado (issn_electronic/issn_print invertidos), fallback final via Q(OR) e o comportamento atual quando falta ISSN no ramo trocado.
Filtra JournalHistory pela collection e journal do próprio JournalProc, centralizando lógica antes duplicada em publication/api/journal.py.
…ecker - Introduz método abstrato is_updated(obj); get_or_fetch passa a reconsultar o core quando o registro local existe mas está desatualizado - ensure_proc_exists deixa de ser staticmethod e passa a delegar para get_or_fetch - JournalDataChecker.is_updated verifica is_complete + existência de JournalProc - IssueDataChecker.is_updated verifica existência de IssueProc - Remove funções standalone create_or_update_journal/create_or_update_issue, substituídas pelos DataCheckers - process_journal_result: usa Journal.get_registered antes de criar novo, limpa M2M (subject/publisher/owner/sponsor) antes de reatribuir, ignora nomes de instituição vazios, levanta ValueError quando não há scielo_journal e consolida journal_acron/collection ao final do loop
…pdated ensure_journal_proc_exists/ensure_issue_proc_exists passam a instanciar JournalDataChecker/IssueDataChecker e chamar ensure_proc_exists(force_update), alinhado com a nova API de proc/source_core_api.py.
Cobre BaseDataChecker.get_or_fetch/refresh, JournalDataChecker e IssueDataChecker (is_updated/ensure_proc_exists/fetch_from_core), paginação de busca de journals e process_journal_result/process_issue_result.
- translate_status(event_type, interruption_reason) centraliza o mapeamento de reason/event_type para status (REASON_MAP/EVENT_MAP), com fallback 'inprogress' - publish_journal: usa journal.is_complete (em vez de core_synchronized) para decidir se sincroniza com o core; usa proc.journal_history; propaga force_update - JournalPayload.add_event_to_timeline passa a chamar translate_status internamente - Adiciona add_current_status() e add_forced_current_status(force_update), que força status 'current' com data de fallback quando não há status_history - Inicializa institution_responsible_for em reset_lists
…load build_journal deixa de calcular event_type/current_status inline e passa a delegar para builder.add_event_to_timeline (com translate_status), builder.add_current_status() e builder.add_forced_current_status(force_update). Aceita novo parâmetro force_update (default False) e corrige duplicidade em add_publisher (agora usa union de owner+publisher via set 'names').
…oad e publish_journal Cobre mapeamento de reason/event_type, setters de JournalPayload, clean_br_tags, add_current_status/add_forced_current_status e o fluxo de publish_journal (sincronização condicional via is_complete, tratamento silencioso de exceção no fetch e montagem do payload).
Cobre campos básicos, mission/timeline, current_status/force_update, ISSNs e títulos com fallback para official_journal, fallback de logo_url, related journals, sponsors/owners/publishers (união sem duplicar) e escopos temáticos/visibilidade.
JournalCollection.create_or_update não retornava objeto existente, causando status "inprogress" na publicação| search_fields = ( | ||
| "journal__title", # ajuste para o campo textual real de Journal | ||
| "collection__name", # ajuste para o campo textual real de Collection | ||
| "collection__acron3", # se existir, ajuda muito na busca por sigla |
There was a problem hiding this comment.
O correto é collection__acron e não collection__acron3. Ver modelo Collection
|
|
||
| @property | ||
| def is_complete(self): | ||
| if self.missing_fields: |
There was a problem hiding this comment.
Num teste local com a RAE eletrônica, carregada no Core a partir do ArticleMeta, is_complete continuou False mesmo após uma sincronização bem-sucedida. Na fonte, o periódico está identificado como eletrônico (v35=ONLIN), não possui ISSN impresso nem URL de submissão. A localização está registrada no Core e é retornada pela API, mas ainda não é processada por process_journal_result. Consequentemente, o Upload mantém Print ISSN, Submission Online URL e Contact Location como ausentes e consulta novamente o Core em toda publicação com force_update=True, embora uma nova sincronização não resolva esses campos. Podemos restringir a completude aos dados realmente obrigatórios e que esse fluxo consegue preencher?
| for item in result.get("scielo_journal") or []: | ||
| journal_acron = journal.journal_acron | ||
|
|
||
| scielo_journals = result.get("scielo_journal") or [] |
There was a problem hiding this comment.
Neste ponto, o journal já foi salvo e seus relacionamentos de e-mail, assunto, publisher, owner e sponsor já foram removidos/recriados. Se scielo_journal estiver ausente, nenhuma coleção local for encontrada ou ocorrer uma exceção posterior, fetch_and_create_journal apenas registra o erro e essas alterações parciais permanecem no banco. Podemos validar scielo_journal/coleções antes das mutações e executar process_journal_result em uma transação atômica para evitar deixar o periódico parcialmente sincronizado?
| raise IssueProc.DoesNotExist(f"IssueProc does not exist: {issue}") | ||
|
|
||
|
|
||
| def create_or_update_issue( |
There was a problem hiding this comment.
Esta função ainda é importada por upload/models.py:42 e utilizada em PidV2Generator.get_issue_pid (upload/models.py:2409). Confirmei que python manage.py shell -c 'import upload.models' falha com ImportError: cannot import name 'create_or_update_issue'.
| title=result.get("title"), | ||
| short_title=result.get("short_title"), | ||
| ) | ||
| journal.core_synchronized = False |
There was a problem hiding this comment.
Existe a possibilidade de o periódico permanecer associado ao registro com os ISSNs trocados. Quando get_registered encontra o journal pelo fallback de ISSNs invertidos, o official_journal correto criado ou atualizado anteriormente pode ser diferente daquele atualmente associado ao journal. Não encontrei a reatribuição dessa relação antes do save(). Nesse caso, o processamento pode terminar com core_synchronized=True, mas mantendo os ISSNs invertidos e deixando o novo OfficialJournal sem uso. Uma solução cabível seria atribuir explicitamente journal.official_journal = official_journal antes de salvar.
O que este PR faz
Descreva de forma objetiva o que foi alterado e por quê.
Corrige o bug raiz que causava
current_status = "inprogress"em periódicos já publicáveis:JournalCollection.create_or_updatenão tinhareturn objno ramo em que o registro já existia, retornandoNoneimplicitamente e quebrando a associação deJournalHistorydurante o reprocessamento de dados docore.scielo.org. Corrige o mesmo padrão de bug emJournalHistory.create_or_update. Em conjunto, remove redundâncias identificadas durante a investigação: imports locais repetidos deCollectionemjournal/models.py; funções standalone (create_or_update_journal,create_or_update_issue) que duplicavam a lógica hoje centralizada emBaseDataChecker.get_or_fetch; e o cálculo deevent_type/current_status, antes duplicado inline embuild_journal, agora centralizado emJournalPayload(translate_status,add_current_status,add_forced_current_status). Também unifica o acesso ao histórico do periódico viaJournalProc.journal_history, eliminando um filtro repetido empublish_journal.Por que essa mudança é necessária
Explique o problema/motivação que originou este PR.
Periódicos com
JournalCollectionjá cadastrada perdiam o vínculo comJournalHistorydurante o reprocessamento (retornoNonedecreate_or_update), resultando emstatus_historyvazio ecurrent_status = "inprogress", o que bloqueava a publicação do periódico e, em cascata, de todos os seus artigos. As redundâncias de código identificadas na investigação também aumentavam o risco de a mesma classe de bug reaparecer em pontos similares.Como foi testado
Descreva os testes realizados (unitários, manuais, etc.).
unittest/mock:journal/tests/test_models_get_registered.py: cobreget_registered(match exato, trocado, fallback por ISSNs).proc/tests/test_source_core_api.py: cobreprocess_journal_resultde ponta a ponta comTestCase(banco real), validando queJournalHistoryé criado e associado corretamente mesmo quandoJournalCollectionjá existe previamente.publication/tests/test_api_journal.pyetest_utils_journal.py: cobremtranslate_status,add_current_status,add_forced_current_statusebuild_journal, garantindo que o histórico processado corretamente resulta emcurrent_statuscorreto.JournalCollectionpré-existente + reprocessamento com novo item dejournal_history→ antes do fix,JournalHistory.create_or_updaterecebiajournal_collection=None; após o fix, recebe o objeto correto ecurrent_statuspassa a refletir o histórico real.python manage.py test journal.tests proc.tests publication.tests.Closes: Fixes #1051
Checklist NSI.04 – Segurança (baseado no diff)
Esta alteração manipula dados sensíveis de usuários (dados pessoais, credenciais, tokens)?
Esta alteração expõe novos endpoints, views ou campos de API?
JournalCollectionViewSetao Wagtail admin (journal/wagtail_hooks.py), expondo uma nova view de snippet (journal,collection,creator,updated,created,updated_by) restrita a usuários autenticados do admin. Não é um endpoint público de API REST.Esta alteração modifica regras de autenticação ou autorização?
Esta alteração introduz ou modifica chamadas a serviços externos (ex.: core.scielo.org)?
fetch_and_create_journal/fetch_journal_data_with_paginationtiveram a assinatura e o fluxo de chamada alterados (remoção do bloco de retry comcollection_acron=None);JournalDataChecker.get_or_fetch/ensure_proc_existsagora decidem chamar o Core com base emis_updated, podendo gerar mais chamadas ao Core do que antes em cenários de dado local incompleto.Esta alteração manipula entrada de dados externos (payload do core.scielo.org) sem validação/sanitização?
process_journal_result(block_unregistered_collection, que descartava o registro caso aCollectiondo payload não existisse localmente) foi removida. Agora, seCollection.objects.get(acron=...)falhar e houver apenas um item emscielo_journal, a exceçãoCollection.DoesNotExisté propagada (raise) em vez de ser silenciosamente ignorada — mudança de comportamento de validação que precisa ser avaliada quanto a impacto em dados de collections ainda não sincronizadas localmente.Esta alteração pode causar exposição de logs com dados sensíveis?
logging.info(f"publish_journal {journal_proc}")elogging.info(f"journal_history.event_type: ...")foram removidos (não adicionados); nenhum log novo com dados sensíveis foi introduzido.Esta alteração foi revisada quanto a riscos de injeção (SQL/NoSQL/comando)?
Journal.objects.get/filter,Q()objects), sem SQL raw,extra()ou concatenação de strings em queries. Nenhum uso deeval,execou chamadas de shell.Esta alteração requer nova configuração de permissões, variáveis de ambiente ou segredos?
JournalCollectionViewSetreutiliza a infraestrutura existente deSnippetViewSet/get_menu_order.