feat: Cria endpoint para criar Article a partir de PidProviderXML#1445
feat: Cria endpoint para criar Article a partir de PidProviderXML#1445samuelveigarangel wants to merge 9 commits into
Conversation
| router.register("pid_provider", PidProviderViewSet, basename="pid_provider") | ||
| router.register("fix_pid_v2", FixPidV2ViewSet, basename="fix_pid_v2") | ||
| router.register( | ||
| "published_article", |
There was a problem hiding this comment.
@samuelveigarangel troque published_article para publish_article, pois está fazendo uma ação e não uma obtenção de dado
| from tempfile import NamedTemporaryFile, TemporaryDirectory | ||
| from config.settings.base import TASK_EXPIRES, TASK_TIMEOUT, RUN_ASYNC | ||
|
|
||
| from article.models import Article |
| from pid_provider.models import PidProviderXML | ||
| from pid_provider.provider import PidProvider | ||
| from pid_provider.tasks import ( | ||
| task_delete_provide_pid_tmp_zip, | ||
| task_provide_pid_for_xml_zip, | ||
| ) |
There was a problem hiding this comment.
@samuelveigarangel fora do estilo recomendado de 3 blocos de import: nativas, externas, internas.
| class PublishedArticleRegistrationSerializer(serializers.Serializer): | ||
| pid_v3 = serializers.CharField( | ||
| required=True, allow_blank=False, max_length=23, min_length=23 | ||
| ) | ||
| sps_pkg_name = serializers.CharField( | ||
| required=True, allow_blank=False, max_length=100 | ||
| ) | ||
|
|
||
|
|
There was a problem hiding this comment.
@samuelveigarangel manter o padrão, isso fica no arquiv serializers.py
| ) | ||
|
|
||
|
|
||
| class PublishedArticleRegistrationViewSet(GenericViewSet): |
There was a problem hiding this comment.
@samuelveigarangel Era esperado que o código do endpoint fosse feito em article/api/v1/views.
Além disso, não seria necessário criar uma nova classe. Pode usar a ArticleViewSet. Veja o exemplo hipotético. Por outro lado, se realmente ficar grande no
@action(
detail=False,
methods=["post"],
permission_classes=[IsAuthenticated],
url_path="publish"
)
def publish_article(self, request):
"""
Busca o XML no pid_provider e realiza a publicação do Article.
URL: POST /api/v1/article/publish/
"""
serializer = PublishedArticleRegistrationSerializer(data=request.data)
if not serializer.is_valid():
return Response(serializer.errors, status=rest_framework_status.HTTP_400_BAD_REQUEST)
identifiers = serializer.validated_data
# 1. Busca o XML no pid_provider
try:
pp_xml = PidProviderXML.objects.select_related("current_version").get(
v3=identifiers["pid_v3"],
pkg_name=identifiers["sps_pkg_name"],
)
except PidProviderXML.DoesNotExist:
return Response({
"error": "PidProviderXML not found",
"pid_v3": identifiers["pid_v3"],
"sps_pkg_name": identifiers["sps_pkg_name"],
}, status=rest_framework_status.HTTP_404_NOT_FOUND)
# 2. Executa as regras de negócio de publicação do Article
try:
operation = (
"updated"
if models.Article.get_by_pid_v3_or_by_sps_pkg_name(
pid_v3=pp_xml.v3, sps_pkg_name=pp_xml.pkg_name
).exists()
else "created"
)
# Carrega, salva e valida a disponibilidade do artigo
article = load_article(request.user, pp_xml=pp_xml)
pp_xml.collections.set(article.collections)
article.check_availability(request.user)
except Exception as e:
logging.error(f"Erro ao publicar artigo: {e}", exc_info=True)
return Response(
{"error_type": str(type(e)), "error_message": str(e)},
status=rest_framework_status.HTTP_400_BAD_REQUEST
)
# 3. Log e Retorno
timestamp = timezone.now().isoformat()
logging.info(f"Article published: id={article.id} operation={operation} user={request.user.username}")
response_status = (
rest_framework_status.HTTP_201_CREATED
if operation == "created"
else rest_framework_status.HTTP_200_OK
)
return Response({
"article_id": article.id,
"pid_v3": article.pid_v3,
"sps_pkg_name": article.sps_pkg_name,
"operation": operation,
"data_status": article.data_status,
"is_public": article.is_public,
"timestamp": timestamp
}, status=response_status)
robertatakenaka
left a comment
There was a problem hiding this comment.
A ação é sobre Article então o endpoint tem que ser em article
robertatakenaka
left a comment
There was a problem hiding this comment.
@samuelveigarangel corrigir a documentação e o texto do PR
O que esse PR faz?
Onde a revisão poderia começar?
pelo commit 62edbcb
Como este poderia ser testado manualmente?
Criar a task
task_dispatch_articlesinformando uma coleção, um periódico e uma data.Ao ser executado a task, pegar um pid_v3 e sps_pkg_name.
realizar um curl para obter autenticacao:
curl -X POST http://localhost:8009/api/v2/auth/token/ -H 'Content-Type: application/x-www-form-urlencoded' -d 'username=your_username&password=your_password'Realizar outro curl para informar o pid_v3, sps_pkg_name e o token:
Executar os testes:
python manage.py test article.test_publish_article_api -v 3Algum cenário de contexto que queira dar?
Endpoints como published_article e pid_provider (modo síncrono) executam fluxos grandes:
load_article() cria/atualiza Article, autores, afiliações, DOI, issue, journal, etc.
check_availability() verifica URLs externas via HTTP (check_url) enquanto ainda está dentro da view
Com ATOMIC_REQUESTS, tudo isso corre dentro de uma única transação. Segurar locks no PostgreSQL durante parsing de XML + dezenas de writes + chamadas HTTP externas é um anti-padrão clássico: piora concorrência, aumenta risco de deadlock e timeout.
Quais são tickets relevantes?
CLOSED #1444