Coverage for apps/ptf/views.py: 50%
1564 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-05-19 19:20 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-05-19 19:20 +0000
1import csv
2import html
3import json
4import os
5import re
6import string
7import urllib.parse
8from itertools import chain
9from operator import itemgetter
11from bs4 import BeautifulSoup
12from django_sendfile import sendfile
13from munch import Munch
14from requests.exceptions import RequestException
15from requests.exceptions import Timeout
17from django.conf import settings
18from django.contrib.syndication.views import Feed
19from django.core.cache import cache
20from django.core.exceptions import SuspiciousOperation
21from django.core.files.temp import NamedTemporaryFile
22from django.core.paginator import EmptyPage
23from django.core.paginator import Paginator
24from django.db.models import F
25from django.db.models import Q
26from django.db.models import Value
27from django.db.models.functions import Greatest
28from django.http import Http404
29from django.http import HttpResponse
30from django.http import HttpResponseBadRequest
31from django.http import HttpResponseRedirect
32from django.http import HttpResponseServerError
33from django.http import JsonResponse
34from django.shortcuts import get_object_or_404
35from django.shortcuts import render
36from django.urls import reverse
37from django.utils import timezone
38from django.utils.decorators import method_decorator
39from django.utils.translation import get_language
40from django.utils.translation import gettext_lazy as _
41from django.views.decorators.csrf import csrf_exempt
42from django.views.decorators.http import require_http_methods
43from django.views.generic import TemplateView
44from django.views.generic import View
46from comments_api.constants import PARAM_PREVIEW
47from matching import crossref
48from matching import matching
49from ptf import citedby
50from ptf import exceptions
51from ptf import model_data
52from ptf import model_data_converter
53from ptf import model_helpers
54from ptf import models
55from ptf.cmds import ptf_cmds
56from ptf.cmds import solr_cmds
57from ptf.cmds import xml_cmds
58from ptf.cmds.xml import xml_utils
59from ptf.cmds.xml.ckeditor.ckeditor_parser import CkeditorParser
60from ptf.cmds.xml.jats.jats_parser import get_tex_from_xml
61from ptf.cmds.xml.jats.jats_parser import get_title_xml
62from ptf.display import resolver
63from ptf.display import utils
64from ptf.models import Article
65from ptf.solr.search_helpers import CleanSearchURL
66from ptf.utils import highlight_diff
68from .url_utils import format_url_with_params
70# from django.utils.crypto import md5
71# from django.utils import cache
72#
73# def ptf_generate_cache_key(request, method, headerlist, key_prefix):
74# """Return a cache key from the headers given in the header list."""
75# url = md5(request.build_absolute_uri().encode("ascii"), usedforsecurity=False)
76# cache_key = "views.decorators.cache.cache_page.%s.%s.%s" % (
77# key_prefix,
78# method,
79# url.hexdigest(),
80# )
81# return cache_key
82#
83#
84# def ptf_generate_cache_header_key(key_prefix, request):
85# """Return a cache key for the header cache."""
86# url = md5(request.build_absolute_uri().encode("ascii"), usedforsecurity=False)
87# cache_key = "views.decorators.cache.cache_header.%s.%s" % (
88# key_prefix,
89# url.hexdigest(),
90# )
91# return cache_key
92#
93#
94# cache._generate_cache_key = ptf_generate_cache_key
95# cache._generate_cache_header_key = ptf_generate_cache_header_key
98def update_context_with_desc(context):
99 online_first_cr = context.get("online_first_cr", False)
100 context[
101 "online_first_desc_fr"
102 ] = """<p>Les articles en « Première publication » ont été évalués par les pairs, acceptés et édités. Ils ont été mis en page et finalisés avec les corrections des auteurs et des relecteurs.</p>"""
103 context[
104 "online_first_desc_fr"
105 ] += """<p>Ces articles sont donc publiés dans leur forme finale et ont reçu un DOI (digital object identifier). Par conséquent, ils ne peuvent plus être modifiés après leur publication électronique. Toute correction qui pourrait être nécessaire doit être effectuée ultérieurement dans un erratum.</p>"""
106 if not online_first_cr:
107 context[
108 "online_first_desc_fr"
109 ] += """<p>Dès que les articles sont affectés à un numéro de la revue, ils sont retirés de cette page.</p>"""
111 context[
112 "online_first_desc_en"
113 ] = """<p>Online First articles have been peer-reviewed, accepted and edited. They have been formatted and finalized with corrections from authors and proofreaders.</p>"""
114 context[
115 "online_first_desc_en"
116 ] += """<p>These articles appear in their final form and have been assigned a digital object identifier (DOI). Therefore, papers cannot be changed after electronic publication. Any corrections that might be necessary have to be made in an erratum.</p>"""
117 if not online_first_cr:
118 context[
119 "online_first_desc_en"
120 ] += """<p>When the articles are assigned to an issue, they will be removed from this page.</p>"""
123@require_http_methods(["POST"])
124def set_formula_display(request):
125 next = request.headers.get("referer")
126 # if formula-display is not in POST, then mathml is choosen
127 formula_display = request.POST.get("formula-display", "mathml")
128 request.session["formula_display"] = formula_display
129 # redis session needs to save item in state
130 request.session.save()
132 # to read session with redis-django-sessions
133 # rdata = request.session.load()
134 return HttpResponseRedirect(next)
137# def views_get_collection(request, collection, api=False, *args, **kwargs):
138# if not collection:
139# raise Http404
140#
141# if collection.coltype in ['journal', 'acta']:
142# return IssuesView.as_view()(request, jid=collection.pid)
143# elif collection.coltype == 'these':
144# return thesis(request)
145# else:
146# # en theorie: coltype=book-series TODO: check lectures
147# url = ('series/"%s"/' % collection.title_html) + "p"
148# path, queryDict = CleanSearchURL.decode(url)
149# request.GET = queryDict
150# request.path = path
151# request.path_info = path
152# return sorted_books(request)
155# @require_http_methods(["GET"])
156# def container(request, pid, api=False):
157# cont = model_helpers.get_container(pid)
158# return views_get_container(request, cont, api)
161def views_update_container_context(request, container, context, display_lang):
162 if container is None: 162 ↛ 163line 162 didn't jump to line 163, because the condition on line 162 was never true
163 raise Http404
165 articles_to_appear = is_cr = False
166 if hasattr(settings, "ISSUE_TO_APPEAR_PID") and settings.ISSUE_TO_APPEAR_PID == container.pid: 166 ↛ 167line 166 didn't jump to line 167, because the condition on line 166 was never true
167 context["articles_to_appear"] = articles_to_appear = True
169 if (
170 hasattr(settings, "SITE_NAME")
171 and len(settings.SITE_NAME) == 6
172 and settings.SITE_NAME[0:2] == "cr"
173 ):
174 context["is_cr"] = is_cr = True
176 context["online_first_cr"] = False
177 if container.with_online_first and is_cr:
178 context["articles_to_appear"] = articles_to_appear = True
179 context["online_first_cr"] = True
181 collection = container.my_collection
182 context["citing_articles"] = container.citations()
183 context["source"] = request.GET.get("source", None)
185 context["breadcrumb"] = model_helpers.get_breadcrumb(None, container, collection)
187 if container.ctype.startswith("book") or container.ctype == "lecture-notes":
188 book_parts = container.article_set.filter(sites__id=settings.SITE_ID).all().order_by("seq")
190 references = False
191 if container.ctype == "book-monograph":
192 # on regarde si il y a au moins une bibliographie
193 for art in container.article_set.all(): 193 ↛ 194line 193 didn't jump to line 194, because the loop on line 193 never started
194 if art.bibitem_set.count() > 0:
195 references = True
196 context["references"] = references
198 context["book"] = container
199 context["book_parts"] = list(book_parts)
200 context["template"] = "blocks/book.html"
202 else:
203 if articles_to_appear and is_cr:
204 articles = container.article_set.all().order_by(
205 F("date_online_first").desc(nulls_last=True), "-seq"
206 )
208 else:
209 articles = container.article_set.all().order_by("seq")
210 # TODO next_issue, previous_issue
212 context["issue"] = container
213 context["articles"] = articles
214 context["template"] = "blocks/issue-items.html"
216 # commun article ?
217 context["coltype"] = collection.coltype
218 context["supplementary_materials"] = models.SupplementaryMaterial.objects.filter(
219 relatedobject_ptr__resource__pk=container.pk,
220 ).filter(rel="supplementary-material")
221 context["reviews"] = models.SupplementaryMaterial.objects.filter(
222 relatedobject_ptr__resource__pk=container.pk,
223 ).filter(rel="review")
224 context["bibtex"] = container.get_bibtex(request)
225 context["ris"] = container.get_ris(request)
226 context["enw"] = container.get_endnote(request)
227 context["howtocite"] = container.get_citation(request)
228 # context['bibtex'] = article.get_bibtex(request.get_host()) pas la meme signature que pour article ?
229 # context['howtocite'] = article.get_citation(request)
231 if collection.pid == "ART": 231 ↛ 232line 231 didn't jump to line 232, because the condition on line 231 was never true
232 qs = collection.content.filter(sites__id=settings.SITE_ID).order_by(
233 "-vseries_int", "-year", "-volume_int", "number_int"
234 )
235 if qs.exists():
236 last_issue = qs.first()
237 if container.pid == last_issue.pid:
238 context["last_issue"] = True
240 context["bs_version"] = (
241 settings.BOOTSTRAP_VERSION if hasattr(settings, "BOOTSTRAP_VERSION") else 3
242 )
244 update_context_with_desc(context)
247# def views_get_container(request, container, api=False, *args, **kwargs):
248# context = {}
249# views_update_container_context(request, container, context)
250# template = context['template']
251# return render(request, template, context)
254# class ContainerView(TemplateView):
255# template_name = ''
256#
257# def get_context_data(self, **kwargs):
258# context = super(ContainerView, self).get_context_data(**kwargs)
259#
260# if 'pid' in kwargs:
261# pid = kwargs['pid']
262# else:
263# pid = self.kwargs.get('pid')
264# container = model_helpers.get_container(pid)
265# if pid == getattr(settings, 'ISSUE_TO_APPEAR_PID', 'XXX') and not container:
266# self.template_name = 'blocks/issue_detail_empty.html'
267# else:
268# views_update_container_context(self.request, container, context)
269# self.template_name = context['template']
270#
271# return context
274# def views_get_article(
275# request: HttpRequest,
276# article: Optional[Article],
277# api=False,
278# lang=None,
279# *args,
280# **kwargs
281# ):
282#
283#
284# return render(request, template, context)
287# @require_http_methods(["GET"])
288# def article(request: HttpRequest, aid: str, api=False):
289# return ItemView.as_view()(request)
290#
291#
292# @require_http_methods(["GET"])
293# def article_lang(request, lang, aid, api=False):
294# return ItemView.as_view()(request)
297class ItemView(TemplateView):
298 def __init__(self, *args, **kwargs):
299 super().__init__(*args, **kwargs)
300 self.template_name = ""
301 self.obj = None
302 self.pid = None
303 self.display_lang = None
305 def get_obj(self, **kwargs):
306 self.pid = self.kwargs.get("aid", None)
307 if self.pid is None:
308 self.pid = self.kwargs.get("pid", None)
309 if self.pid is None: 309 ↛ 310line 309 didn't jump to line 310, because the condition on line 309 was never true
310 self.pid = self.request.GET.get("id", "")
311 if "\x00" in self.pid: 311 ↛ 312line 311 didn't jump to line 312, because the condition on line 311 was never true
312 raise Http404
314 if "/" in self.pid:
315 # If someone types /articles/10.5802/crgeos.150-fr, we set the display lang and get 10.5802/crgeos.150
316 if not self.display_lang and self.pid[-3] == "-" and not self.pid[-2:].isdigit(): 316 ↛ 317line 316 didn't jump to line 317, because the condition on line 316 was never true
317 self.display_lang = self.pid[-2:]
318 self.pid = self.pid[:-3]
320 obj = model_helpers.get_article_by_doi(self.pid, prefetch=True)
321 else:
322 obj = model_helpers.get_resource(self.pid, prefetch=True)
323 if obj is not None:
324 obj = obj.cast()
325 fct = getattr(model_helpers, "get_" + obj.classname.lower())
326 if fct and callable(fct): 326 ↛ 329line 326 didn't jump to line 329, because the condition on line 326 was never false
327 obj = fct(self.pid, prefetch=True)
329 if obj is None and self.pid != getattr(settings, "ISSUE_TO_APPEAR_PID", "XXX"):
330 raise Http404
332 self.obj = obj
334 def get_context_data(self, **kwargs):
335 context = super().get_context_data(**kwargs)
336 context["obj"] = self.obj
337 return context
339 def get(self, request, *args, **kwargs):
340 if "obj" in kwargs:
341 self.obj = kwargs["obj"]
342 if "lang" in kwargs: 342 ↛ 343line 342 didn't jump to line 343, because the condition on line 342 was never true
343 self.display_lang = kwargs["lang"]
345 if (
346 self.obj is not None
347 ): # ItemView may call ArticleView. Avoid infinite loop is self.obj is set
348 return super().get(request, *args, **kwargs)
350 self.get_obj(**kwargs)
351 if not self.display_lang: 351 ↛ 354line 351 didn't jump to line 354, because the condition on line 351 was never false
352 self.display_lang = self.kwargs.get("lang", None)
354 view_cls = ItemViewClassFactory().get_resource_view(self.obj.classname)
355 if view_cls is not None: 355 ↛ 361line 355 didn't jump to line 361, because the condition on line 355 was never false
356 kwargs["obj"] = self.obj
357 if self.display_lang is not None: 357 ↛ 358line 357 didn't jump to line 358, because the condition on line 357 was never true
358 kwargs["lang"] = self.display_lang
359 return view_cls.as_view()(request, *args, **kwargs)
361 return super().get(request, *args, **kwargs)
364class ArticleView(ItemView):
365 def get_context_data(self, **kwargs):
366 context = super().get_context_data(**kwargs)
367 request = self.request
368 display_lang = self.display_lang
369 article = self.obj
371 article.title_tex = article.title_tex.strip()
372 source = request.GET.get("source", None)
373 container = article.my_container
374 collection = container.my_collection
375 citing_articles = article.citations()
377 with_tex = (
378 "formula_display" in request.session and request.session["formula_display"] == "tex"
379 )
381 if display_lang is not None and len(display_lang) == 2: 381 ↛ 382line 381 didn't jump to line 382, because the condition on line 381 was never true
382 context["display_lang"] = display_lang
383 else:
384 display_lang = context["display_lang"] = article.lang
386 context["collection"] = collection
387 context["is_translation"] = False
388 context["full_translation"] = False
389 context["needs_translation"] = True
390 context["start_translation_url"] = os.path.join(
391 settings.TRANSLATION_URL, f"translation/new?doi={article.doi}"
392 )
393 langs = [article.lang] if (article.lang and article.lang != "und") else []
394 # if article.trans_lang and article.trans_lang != 'und':
395 # langs.append(article.trans_lang)
396 for translated_article in article.translations.all(): 396 ↛ 397line 396 didn't jump to line 397, because the loop on line 396 never started
397 if translated_article.lang not in langs:
398 langs.append(translated_article.lang)
399 if display_lang == translated_article.lang:
400 context["is_translation"] = True
401 context["needs_translation"] = False
402 context["translated_article"] = translated_article
403 context["body_html"] = translated_article.body_html
405 context["languages"] = langs
407 if display_lang == article.lang or ( 407 ↛ 417line 407 didn't jump to line 417, because the condition on line 407 was never false
408 not context["is_translation"] and display_lang != article.trans_lang
409 ):
410 context["article_title"] = article.title_tex if with_tex else article.title_html
411 context["body_html"] = article.body_html
412 if display_lang == article.lang: 412 ↛ 414line 412 didn't jump to line 414, because the condition on line 412 was never false
413 context["needs_translation"] = False
414 for abstract in article.get_abstracts(): 414 ↛ 415line 414 didn't jump to line 415, because the loop on line 414 never started
415 if abstract.lang == display_lang:
416 context["abstract"] = abstract
417 elif display_lang == article.trans_lang or not context["is_translation"]:
418 context["article_title"] = (
419 article.trans_title_tex if with_tex else article.trans_title_html
420 )
421 context["article_original_title"] = (
422 article.title_tex if with_tex else article.title_html
423 )
424 if "body_html" not in context or not context["body_html"]:
425 # The full text is not available in the display_lang. Use the original version.
426 context["body_html"] = article.body_html
427 for abstract in article.get_abstracts():
428 if abstract.lang == display_lang:
429 context["abstract"] = abstract
430 else:
431 context["full_translation"] = True
432 context["article_original_title"] = (
433 article.title_tex if with_tex else article.title_html
434 )
435 for translated_article in article.translations.all():
436 if display_lang == translated_article.lang:
437 context["article_title"] = (
438 translated_article.title_tex if with_tex else translated_article.title_html
439 )
440 context["abstract"] = translated_article.get_abstracts().first()
442 if context["is_translation"]: 442 ↛ 443line 442 didn't jump to line 443, because the condition on line 442 was never true
443 context["howtocite"] = translated_article.get_citation(request)
444 else:
445 context["howtocite"] = article.get_citation(request)
447 if ( 447 ↛ 451line 447 didn't jump to line 451
448 hasattr(settings, "ISSUE_TO_APPEAR_PID")
449 and settings.ISSUE_TO_APPEAR_PID == container.pid
450 ):
451 context["articles_to_appear"] = True
453 # Format comment URL if comments are activated
454 if getattr(settings, "COMMENTS_VIEWS_ARTICLE_COMMENTS", False) is True: 454 ↛ 455line 454 didn't jump to line 455, because the condition on line 454 was never true
455 current_url = f"{request.build_absolute_uri()}#article-comments-section"
456 query_params = {"redirect_url": current_url}
457 # Forward some query parameters if present
458 preview_id = request.GET.get(PARAM_PREVIEW)
459 if preview_id:
460 query_params[PARAM_PREVIEW] = preview_id
461 comment_reply_to = request.GET.get("commentReplyTo")
462 if comment_reply_to:
463 query_params["commentReplyTo"] = comment_reply_to
465 context["comments_url"] = format_url_with_params(
466 reverse("article_comments", kwargs={"doi": article.doi}), query_params
467 )
469 stop_at_volume = is_cr = False
470 if ( 470 ↛ 488line 470 didn't jump to line 488
471 hasattr(settings, "SITE_NAME")
472 and len(settings.SITE_NAME) == 6
473 and settings.SITE_NAME[0:2] == "cr"
474 ):
475 context["is_cr"] = is_cr = True
476 year = int(container.year)
477 if (settings.SITE_NAME != "crbiol" and year > 2020) or (
478 settings.SITE_NAME == "crbiol" and year > 2022
479 ):
480 qs = models.Container.objects.filter(
481 my_collection__pid=collection.pid, year=year
482 ).exclude(title_html="")
483 if qs.count() == 0: 483 ↛ 486line 483 didn't jump to line 486, because the condition on line 483 was never false
484 # The volume has thematic issues: we need to display the issue level
485 stop_at_volume = True
486 context["related_articles"] = get_suggested_articles(article)
488 context["online_first_cr"] = False
489 if container.with_online_first and is_cr:
490 context["articles_to_appear"] = True
491 context["online_first_cr"] = True
493 if hasattr(settings, "SHOW_BODY") and settings.SHOW_BODY: 493 ↛ 494line 493 didn't jump to line 494, because the condition on line 493 was never true
494 context["article_show_body"] = True
496 context["breadcrumb"] = model_helpers.get_breadcrumb(
497 article, container, collection, stop_at_volume
498 )
500 # pour les livres du centre Mersenne, on remplace la collection principale par collectionmembership
501 if collection.pid == "MBK": 501 ↛ 502line 501 didn't jump to line 502, because the condition on line 501 was never true
502 for collection_membership in container.collectionmembership_set.all():
503 collection = collection_membership.collection
504 container.my_collection = collection
506 if container.ctype == "issue": 506 ↛ 516line 506 didn't jump to line 516, because the condition on line 506 was never false
507 context.update(
508 {
509 "article": article,
510 "citing_articles": citing_articles,
511 "source": source,
512 }
513 )
514 context["template"] = "blocks/article.html"
515 else:
516 context.update(
517 {"book_part": article, "citing_articles": citing_articles, "source": source}
518 )
519 context["template"] = "blocks/book-part.html"
521 site_entry = settings.SITE_REGISTER[settings.SITE_NAME]
523 licence = None
524 try:
525 year = int(article.my_container.year)
526 except ValueError:
527 year = 0
529 if "licences" in site_entry: 529 ↛ 536line 529 didn't jump to line 536, because the condition on line 529 was never false
530 licences = site_entry["licences"]
531 i = len(licences) - 1
532 while not licence and i >= 0:
533 if year >= licences[i][0]:
534 licence = licences[i][1]
535 i -= 1
536 context["licence"] = licence
538 context["coltype"] = collection.coltype
539 context["supplementary_materials"] = models.SupplementaryMaterial.objects.filter(
540 relatedobject_ptr__resource__pk=article.pk,
541 ).filter(rel="supplementary-material")
542 context["reviews"] = models.SupplementaryMaterial.objects.filter(
543 relatedobject_ptr__resource__pk=article.pk,
544 ).filter(rel="review")
545 context["bibtex"] = article.get_bibtex(request)
546 context["ris"] = article.get_ris(request)
547 context["enw"] = article.get_endnote(request)
548 link = model_helpers.get_extlink(resource=collection, rel="test_website")
549 context["test_website"] = link.location if link else None
550 link = model_helpers.get_extlink(resource=collection, rel="website")
551 context["prod_website"] = link.location if link else None
552 context["bs_version"] = (
553 settings.BOOTSTRAP_VERSION if hasattr(settings, "BOOTSTRAP_VERSION") else 3
554 )
555 context["recommendations"] = article.extid_set.filter(id_type="rdoi")
557 self.template_name = context["template"]
559 return context
562class ContainerView(ItemView):
563 def get_context_data(self, **kwargs):
564 context = super().get_context_data(**kwargs)
566 if self.pid == getattr(settings, "ISSUE_TO_APPEAR_PID", "XXX") and not self.obj: 566 ↛ 567line 566 didn't jump to line 567, because the condition on line 566 was never true
567 self.template_name = "blocks/issue_detail_empty.html"
568 else:
569 container = self.obj
570 if container is None: 570 ↛ 571line 570 didn't jump to line 571, because the condition on line 570 was never true
571 raise Http404
573 views_update_container_context(self.request, container, context, self.display_lang)
575 self.template_name = context["template"]
577 return context
580class CollectionView(ItemView):
581 def get(self, request, *args, **kwargs):
582 if "obj" in kwargs: 582 ↛ 585line 582 didn't jump to line 585, because the condition on line 582 was never false
583 self.obj = kwargs["obj"]
585 collection = self.obj
586 if collection.coltype in ["journal", "acta"]:
587 return IssuesView.as_view()(request, jid=collection.pid)
588 elif collection.coltype in ["these", "thesis"]:
589 return thesis(request, collection=collection)
590 else:
591 # en theorie: coltype=book-series TODO: check lectures
592 url = ('series/"%s"/' % collection.title_html) + "p"
593 path, queryDict = CleanSearchURL.decode(url)
594 request.GET = queryDict
595 request.path = path
596 request.path_info = path
597 return sorted_books(request)
600class VolumeDetailView(TemplateView):
601 template_name = "blocks/volume-items.html"
603 def get_context_data(self, **kwargs):
604 # TODO next_volume, previous_volume
605 context = super().get_context_data(**kwargs)
607 context["group_issues"] = False
608 is_cr = (
609 hasattr(settings, "SITE_NAME")
610 and len(settings.SITE_NAME) == 6
611 and settings.SITE_NAME[0:2] == "cr"
612 )
613 context["is_cr"] = is_cr
615 issues_articles, collection = model_helpers.get_issues_in_volume(kwargs.get("vid"), is_cr)
616 year = int(issues_articles[0]["issue"].year)
617 context["year"] = year
619 if is_cr:
620 context["group_issues"] = (settings.SITE_NAME != "crbiol" and year > 2020) or (
621 settings.SITE_NAME == "crbiol" and year > 2022
622 )
624 with_thematic = (
625 len(issues_articles) > 1 and len(issues_articles[1]["issue"].title_html) > 0
626 )
627 context["with_thematic"] = with_thematic
629 # Olivier 7/30/2020. Le code suivant crash lorsque fpage n'est pas un entier mais un chiffre romain
630 # Il faut rediscuter des specs. Ordonner par 'seq' semble plus simple.
631 # Il faut peut-être juste s'assurer que 'seq' soit bien mis à l'import ?
633 # Ex: /volume/WBLN_2018__5/
634 # issues_articles = []
635 # for issue in issues:
636 # articles = issue.article_set.all().annotate(
637 # fpage_int=Cast(
638 # Case(When(~Q(fpage=''), then='fpage'), default=Value('0')),
639 # IntegerField()
640 # )
641 # ).order_by('seq', 'fpage_int')
642 #
643 # issues_articles.append({'issue': issue, 'articles': articles})
645 context["issues_articles"] = issues_articles
646 context["collection"] = collection
647 context["coltype"] = collection.coltype
648 context["breadcrumb"] = model_helpers.get_breadcrumb(
649 None, issues_articles[-1].get("issue"), collection, True
650 )
652 if collection.pid == "ART": 652 ↛ 653line 652 didn't jump to line 653, because the condition on line 652 was never true
653 now = timezone.now()
654 curyear = now.year
655 context["volume_in_progress"] = year == curyear
657 return context
660class VolumeGeneralDetailView(TemplateView):
661 template_name = "blocks/volume-general-items.html"
663 def get_context_data(self, **kwargs):
664 # TODO next_volume, previous_volume
665 context = super().get_context_data(**kwargs)
667 context["group_issues"] = False
668 is_cr = (
669 hasattr(settings, "SITE_NAME")
670 and len(settings.SITE_NAME) == 6
671 and settings.SITE_NAME[0:2] == "cr"
672 )
673 context["is_cr"] = is_cr
675 issues_articles, collection = model_helpers.get_issues_in_volume(
676 kwargs.get("vid"), is_cr, general_articles=True
677 )
679 if is_cr:
680 year = int(issues_articles[0]["issue"].year)
681 context["group_issues"] = (settings.SITE_NAME != "crbiol" and year > 2020) or (
682 settings.SITE_NAME == "crbiol" and year > 2022
683 )
685 with_thematic = (
686 len(issues_articles) > 1 and len(issues_articles[1]["issue"].title_html) > 0
687 )
688 context["with_thematic"] = with_thematic
690 context["issues_articles"] = issues_articles
691 context["collection"] = collection
692 context["coltype"] = collection.coltype
693 context["breadcrumb"] = model_helpers.get_breadcrumb(
694 None, issues_articles[-1].get("issue"), collection
695 )
696 context["vid"] = kwargs.get("vid")
697 return context
700class ItemViewClassFactory:
701 views = {
702 "article": ArticleView,
703 "container": ContainerView,
704 "collection": CollectionView,
705 "volume": VolumeDetailView,
706 }
708 def get_resource_view(self, classname):
709 classname = classname.lower()
710 if classname in self.views: 710 ↛ 712line 710 didn't jump to line 712, because the condition on line 710 was never false
711 return self.views[classname]
712 return None
715@require_http_methods(["GET"])
716def journals(request, api=False):
717 journals = model_helpers.get_journals().filter(parent=None).order_by("title_tex")
719 context = {"journals": journals, "coltype": "journal"}
721 return render(request, "blocks/journal-list.html", context)
724@require_http_methods(["GET"])
725def actas(request, api=False):
726 journals = model_helpers.get_actas().filter(parent=None)
727 context = {"journals": journals, "coltype": "acta"}
729 return render(request, "blocks/journal-list.html", context)
732@require_http_methods(["GET"])
733def books(request, api=False):
734 books = model_helpers.get_collection_of_books().order_by("title_tex")
736 context = {"journals": books, "coltype": "book"}
738 return render(request, "blocks/journal-list.html", context)
741@require_http_methods(["GET"])
742def proceedings(request):
743 proceedings = model_helpers.get_proceedings().order_by("title_tex")
744 context = {"journals": proceedings, "coltype": "proceeding"}
746 return render(request, "blocks/journal-list.html", context)
749class IssuesView(TemplateView):
750 template_name = "blocks/issue-list.html"
752 def get(self, request, *args, **kwargs):
753 journal = model_helpers.get_collection(self.kwargs.get("jid"))
755 if journal and journal.coltype == "lecture-notes" and journal.pid != "CIF": 755 ↛ 756line 755 didn't jump to line 756, because the condition on line 755 was never true
756 url = reverse("pretty-lectures", args=[f'"{journal.title_html}"-p'])
757 return HttpResponseRedirect(url)
759 return super().get(request, *args, **kwargs)
761 def get_context_data(self, **kwargs):
762 context = super().get_context_data(**kwargs)
764 journal = model_helpers.get_collection(self.kwargs.get("jid"))
766 if journal is None: 766 ↛ 767line 766 didn't jump to line 767, because the condition on line 766 was never true
767 raise Http404
768 try:
769 current_edition = journal.extlink_set.get(rel="website").location
770 if "numdam.org" in current_edition:
771 current_edition = None
772 except models.ExtLink.DoesNotExist:
773 current_edition = None
775 result = model_helpers.get_volumes_in_collection(journal)
776 volume_count = result["volume_count"]
777 collections = []
778 for ancestor in journal.ancestors.all(): 778 ↛ 779line 778 didn't jump to line 779, because the loop on line 778 never started
779 item = model_helpers.get_volumes_in_collection(ancestor)
780 volume_count = max(0, volume_count)
781 item.update({"journal": ancestor})
782 collections.append(item)
784 # add the parent collection to its children list and sort it by date
785 result.update({"journal": journal})
786 collections.append(result)
787 if len(collections) > 1: 787 ↛ 788line 787 didn't jump to line 788, because the condition on line 787 was never true
788 collections.sort(
789 key=lambda ancestor: ancestor["sorted_issues"][0]["volumes"][0]["lyear"],
790 reverse=True,
791 )
793 is_cr = False
794 if ( 794 ↛ 799line 794 didn't jump to line 799
795 hasattr(settings, "SITE_NAME")
796 and len(settings.SITE_NAME) == 6
797 and settings.SITE_NAME[0:2] == "cr"
798 ):
799 is_cr = True
801 display_with_titles = True
802 i = 0
803 while display_with_titles and i < len(collections):
804 col = collections[i]
805 if len(col["sorted_issues"]) != 1: 805 ↛ 806line 805 didn't jump to line 806, because the condition on line 805 was never true
806 display_with_titles = False
807 else:
808 j = 0
809 volumes = col["sorted_issues"][0]["volumes"]
810 while display_with_titles and j < len(volumes):
811 vol = volumes[j]
812 display_with_titles = len(vol["issues"]) == 1
813 if display_with_titles: 813 ↛ 816line 813 didn't jump to line 816, because the condition on line 813 was never false
814 issue = vol["issues"][0]
815 display_with_titles = issue.title_tex != ""
816 j += 1
817 i += 1
819 context.update(
820 {
821 "obj": journal,
822 "journal": journal,
823 "sorted_issues": result["sorted_issues"],
824 "coltype": journal.coltype,
825 "volume_count": volume_count,
826 "max_width": result["max_width"],
827 "to_appear": models.Article.objects.filter(pid__startswith=f"{journal.pid}_0"),
828 "show_issues": True,
829 "is_cr": is_cr,
830 "current_edition": current_edition,
831 "collections": collections,
832 "display_with_titles": display_with_titles,
833 }
834 )
835 update_context_with_desc(context)
837 return context
840@require_http_methods(["GET"])
841def thesis(request, sorted_by="fau", order_by="asc", query="", api=False, collection=None):
842 # request.path like : search/monauteur/a
843 if collection is not None: 843 ↛ 846line 843 didn't jump to line 846, because the condition on line 843 was never false
844 query = f'"{collection.title_html}"-p'
846 path, queryDict = CleanSearchURL.decode(query, "/thesis")
848 request.GET = queryDict
849 request.path = path
850 request.path_info = path
852 filters = []
853 filters = request.GET.getlist("f")
855 path = request.path
856 search_path = path + "?"
858 try:
859 page = int(request.GET.get("page", 1))
860 except ValueError:
861 page = 1
863 params = {
864 "q": '(classname:"Thèse")',
865 "sorted_by": sorted_by,
866 "order_by": order_by,
867 "page": page,
868 "filters": filters,
869 "path": path,
870 "search_path": search_path,
871 "facet_fields": ["firstLetter", "year_facet", "collection_title_facet"],
872 "query": query,
873 }
875 return generic_books(request, "these", params)
878def generic_books(request, coltype, params):
879 rows = 20
880 start = (params["page"] - 1) * rows
882 params["rows"] = 20
883 params["start"] = start
884 if " " in params["sorted_by"]:
885 sorts = params["sorted_by"].split()
886 params["sort"] = ",".join([f"{item} {params['order_by']}" for item in sorts])
887 else:
888 params["sort"] = params["sorted_by"] + " " + params["order_by"]
890 cmd = solr_cmds.solrInternalSearchCmd(params)
891 results = cmd.do()
892 # si len(results.facets['collection_title_facets']) == 1,
893 # on est dans le cas où une collection est choisie
895 context = {"results": results, "coltype": coltype}
897 if results is not None: 897 ↛ 924line 897 didn't jump to line 924, because the condition on line 897 was never false
898 if results.facets["collection_title_facets"]: 898 ↛ 899line 898 didn't jump to line 899, because the condition on line 898 was never true
899 has_one_collection_filter = False
900 collection_title_filter = ""
901 if "filters" in params:
902 for filter_ in params["filters"]:
903 if filter_.find("collection_title_facet:") > -1:
904 has_one_collection_filter = (
905 True if not has_one_collection_filter else False
906 )
907 collection_title_filter = filter_.split('"')[1]
909 if has_one_collection_filter:
910 obj = model_helpers.get_resource(results.docs[0]["pid"])
911 if obj:
912 book = obj.cast()
913 container = book.get_container()
914 collection = container.get_collection()
915 if collection.title_tex == collection_title_filter:
916 context["collection"] = collection
917 else:
918 for other_collection in container.my_other_collections.all():
919 if other_collection.title_tex == collection_title_filter:
920 context["collection"] = other_collection
922 context.update(utils.paginate_from_request(request, params, results.hits))
924 context["query"] = params["query"]
925 if settings.SITE_NAME == "numdam": 925 ↛ 926line 925 didn't jump to line 926, because the condition on line 925 was never true
926 context["numdam"] = True
927 return render(request, "blocks/sorted-books.html", context)
930@require_http_methods(["GET"])
931def sorted_books(request, sorted_by="fau title_sort_key", order_by="asc", query="", api=False):
932 path, queryDict = CleanSearchURL.decode(query, "/series")
934 request.GET = queryDict
935 request.path = path
936 request.path_info = path
938 filters = []
939 filters = request.GET.getlist("f")
941 path = request.path
942 search_path = path + "?"
944 try:
945 page = int(request.GET.get("page", 1))
946 except ValueError:
947 page = 1
949 params = {
950 "q": '(classname:"Livre")',
951 "sorted_by": sorted_by,
952 "order_by": order_by,
953 "page": page,
954 "filters": filters,
955 "path": path,
956 "search_path": search_path,
957 "facet_fields": ["firstLetter", "year_facet", "collection_title_facet"],
958 "query": query,
959 }
960 return generic_books(request, "book-series", params)
963@require_http_methods(["GET"])
964def sorted_lectures(request, sorted_by="fau title_sort_key", order_by="asc", query="", api=False):
965 path, queryDict = CleanSearchURL.decode(query, "/lectures")
967 request.GET = queryDict
968 request.path = path
969 request.path_info = path
971 filters = []
972 filters = request.GET.getlist("f")
974 collection = model_helpers.get_collection("CJPS")
975 if collection is not None and f'collection_title_facet:"{collection.title_tex}"' in filters:
976 sorted_by = "number"
977 order_by = "desc"
978 else:
979 found = False
980 lectures = ["CCIRM", "WBLN"]
981 while not found and len(lectures) > 0:
982 pid = lectures.pop()
983 collection = model_helpers.get_collection(pid)
984 if (
985 collection is not None
986 and f'collection_title_facet:"{collection.title_tex}"' in filters
987 ):
988 found = True
989 sorted_by = "pid"
990 order_by = "desc"
992 path = request.path
993 search_path = path + "?"
995 try:
996 page = int(request.GET.get("page", 1))
997 except ValueError:
998 page = 1
1000 params = {
1001 "q": '(classname:"Notes de cours")',
1002 "sorted_by": sorted_by,
1003 "order_by": order_by,
1004 "page": page,
1005 "filters": filters,
1006 "path": path,
1007 "search_path": search_path,
1008 "facet_fields": ["firstLetter", "year_facet", "collection_title_facet"],
1009 "query": query,
1010 }
1011 return generic_books(request, "lectures", params)
1014@method_decorator(csrf_exempt, name="dispatch")
1015class SearchView(TemplateView):
1016 template_name = "blocks/search-results.html"
1017 GET = None
1018 path = None
1020 def get(self, request, *args, **kwargs):
1021 query = kwargs.get("query", "")
1022 if query == "":
1023 return HttpResponseRedirect(reverse("help"))
1024 path = self.kwargs.get("path")
1025 # query like test-"aitre, -ert"-qa
1026 path, queryDict = CleanSearchURL.decode(query, path)
1027 self.GET = queryDict
1028 self.path = path
1029 try:
1030 result = super().get(request, *args, **kwargs)
1031 except SuspiciousOperation:
1032 result = HttpResponseBadRequest("Bad request")
1034 return result
1036 def post(self, request, *args, **kwargs):
1037 if request.POST.get("q0", "") == "":
1038 return HttpResponseRedirect(reverse("help"))
1040 show_eprint = request.POST.get("show_eprint", False)
1041 show_eprint = True if show_eprint == "on" else show_eprint
1042 path = CleanSearchURL.encode(request.POST, request.path)
1043 path += "&" + urllib.parse.urlencode({"eprint": show_eprint})
1045 return HttpResponseRedirect(path)
1047 def get_context_data(self, **kwargs):
1048 context = super().get_context_data(**kwargs)
1050 context["query"] = kwargs.get("query", "")
1051 eprint = False
1053 try:
1054 parse = urllib.parse.parse_qs(context["query"])
1055 if "eprint" in parse:
1056 eprint = eval(parse["eprint"][0])
1057 cache.set("eprint", eprint, 60 * 60 * 24 * 2)
1058 except:
1059 value = cache.get("eprint")
1060 eprint = False if not value else value
1061 finally:
1062 cache.close()
1064 filters = self.GET.getlist("f")
1066 if eprint is False: 1066 ↛ 1069line 1066 didn't jump to line 1069, because the condition on line 1066 was never false
1067 filters.append('!dt:"e-print"')
1069 search_path = self.path + "?"
1070 qs = []
1072 keep_qs_in_display = True
1073 i = 0
1074 qti = self.GET.get("qt" + str(i), None)
1076 while qti:
1077 if i > 0:
1078 search_path += "&"
1079 search_path += "qt" + str(i) + "=" + qti
1081 if qti == "date":
1082 qfi = self.GET.get("q-f-" + str(i), None)
1083 qli = self.GET.get("q-l-" + str(i), None)
1085 if qfi or qli: 1085 ↛ 1110line 1085 didn't jump to line 1110, because the condition on line 1085 was never false
1086 qs.append({"name": qti, "first": qfi, "last": qli, "value": "", "not": False})
1088 search_path += "&q-f-" + str(i) + "=" + qfi
1089 search_path += "&q-l-" + str(i) + "=" + qli
1090 else:
1091 if qti == "author_ref":
1092 keep_qs_in_display = False
1094 qi = self.GET.get("q" + str(i), None)
1096 if qi or i == 0: 1096 ↛ 1110line 1096 didn't jump to line 1110, because the condition on line 1096 was never false
1097 noti = self.GET.get("not" + str(i), None)
1098 if noti == "on":
1099 noti = True
1100 else:
1101 noti = False
1103 qs.append({"name": qti, "value": qi, "not": noti, "first": "", "last": ""})
1105 search_path += "&q" + str(i) + "=" + qi
1107 if noti:
1108 search_path += "¬" + str(i) + "=on"
1110 i += 1
1111 qti = self.GET.get("qt" + str(i), None)
1113 try:
1114 page = int(self.GET.get("page", 1))
1115 except ValueError:
1116 page = 1
1118 if len(qs) < 1:
1119 # 400.html is used only if DEBUG is False
1120 # (see get_response in django.core.handlers.base)
1121 raise SuspiciousOperation("Bad request")
1123 rows = 20
1124 start = (page - 1) * rows
1126 params = {
1127 "filters": filters,
1128 "qs": qs,
1129 "page": page,
1130 "start": start,
1131 "rows": rows,
1132 "search_path": search_path,
1133 "path": self.path,
1134 }
1136 sort = self.GET.get("order_by")
1137 if sort: 1137 ↛ 1138line 1137 didn't jump to line 1138, because the condition on line 1137 was never true
1138 params["sort"] = sort
1140 cmd = solr_cmds.solrSearchCmd(params)
1141 results = cmd.do()
1143 if not keep_qs_in_display:
1144 qs = []
1146 context.update({"results": results, "qs": qs, "eprint": eprint})
1148 if results is not None: 1148 ↛ 1151line 1148 didn't jump to line 1151, because the condition on line 1148 was never false
1149 context.update(utils.paginate_from_request(self, params, results.hits))
1151 return context
1154@require_http_methods(["GET"])
1155def authors(request, query="", api=False):
1156 """
1157 @param request: like /authors?q=M+r
1158 @param api:
1159 @return:
1160 """
1162 path, queryDict = CleanSearchURL.decode(query, "/authors")
1164 request.GET = queryDict
1165 request.path = path
1166 request.path_info = path
1168 # to set default value on letter
1169 letter = request.GET.get("letter", "A")[0]
1170 filters = request.GET.getlist("f")
1171 for filter_ in filters: 1171 ↛ 1172line 1171 didn't jump to line 1172, because the loop on line 1171 never started
1172 if filter_.startswith("{!tag=firstletter}firstNameFacetLetter:"):
1173 letter = filter_[39]
1175 try:
1176 page = int(request.GET.get("page", 1))
1177 except ValueError:
1178 page = 1
1180 rows = 60
1182 all_authors = model_helpers.get_authors_by_letter(letter)
1183 paginator = Paginator(all_authors, rows)
1184 try:
1185 authors = paginator.page(page)
1186 except EmptyPage:
1187 raise Http404
1189 context = {
1190 "authors": authors,
1191 "letters": string.ascii_uppercase,
1192 "letter_active": letter,
1193 "query": query,
1194 }
1196 params = {"rows": rows, "page": page, "path": path}
1197 context.update(utils.paginate_from_request(request, params, paginator.count))
1199 return render(request, "blocks/authors.html", context)
1202@require_http_methods(["GET"])
1203def citations(request, aid, api=False):
1204 resource = model_helpers.get_resource(aid)
1205 if resource is None or (resource.classname != "Article" and resource.classname != "Container"):
1206 raise Http404
1207 # a priori les citations ne sont que sur les articles/book
1208 # resource.classname != 'Article'-part : et ben non !
1209 item = resource.cast()
1210 citing = item.citations()
1211 context = {"resource": item}
1212 context["citing"] = citing
1213 return render(request, "blocks/resource-citations.html", context)
1216@require_http_methods(["GET"])
1217def get_pdf(request, pid, binary_file_type, extension, relative_path):
1218 """
1219 return PDF file of an Resource or of a RelatedObject for Container if embargo not present
1220 si l'item n'existe pas : 404
1221 si il est protege par barriere mobile : alors on renvoie vers
1222 une page qui redirige apres un delai vers la notice de l'objet
1223 """
1225 return get_binary_file(request, pid, binary_file_type, extension, relative_path)
1228@require_http_methods(["GET"])
1229def get_binary_file(request, pid, binary_file_type, extension, relative_path):
1230 """
1231 return tex file of a Resource if embargo not present
1232 return 404 if the file or the resource does not exist
1233 If there is an embargo redirect to the resource page
1234 """
1236 if len(relative_path) > 0: 1236 ↛ 1237line 1236 didn't jump to line 1237, because the condition on line 1236 was never true
1237 extension = relative_path.split(".")[-1]
1239 type_extension = {
1240 "pdf": "application/pdf",
1241 "djvu": "image/x.djvu",
1242 "tex": "application/x-tex",
1243 "png": "image/png",
1244 "jpg": "image/jpeg",
1245 "html": "text/html",
1246 }
1247 mimetype = type_extension.get(extension, "")
1249 # binary_file_type: 'toc', 'frontmatter', 'backmatter', or 'self' for
1250 # the article/issue binary file
1251 if not binary_file_type:
1252 binary_file_type = "self"
1254 if "/" in pid: 1254 ↛ 1255line 1254 didn't jump to line 1255, because the condition on line 1254 was never true
1255 resource = model_helpers.get_resource_by_doi(pid)
1256 else:
1257 resource = model_helpers.get_resource(pid)
1259 if resource is not None: 1259 ↛ 1262line 1259 didn't jump to line 1262, because the condition on line 1259 was never false
1260 resource = resource.cast()
1262 filename, status = get_binary_filename(resource, relative_path, binary_file_type, mimetype)
1263 return render_binary_file(request, resource, status, filename)
1266def get_binary_filename(resource, relative_path, binary_file_type, mimetype):
1267 """
1268 get the filename from the database
1269 returns the filename and the status 200, 404 (not found) or 403 (embargo)
1270 """
1271 if resource is None: 1271 ↛ 1272line 1271 didn't jump to line 1272, because the condition on line 1271 was never true
1272 return None, 404
1274 allow_local_pdf = not hasattr(settings, "ALLOW_LOCAL_PDF") or settings.ALLOW_LOCAL_PDF
1275 if not allow_local_pdf: 1275 ↛ 1276line 1275 didn't jump to line 1276, because the condition on line 1275 was never true
1276 return None, 404
1278 filename = None
1279 status = 200
1281 try:
1282 # May return None if there is an embargo
1283 filename = resource.get_binary_disk_location(binary_file_type, mimetype, relative_path)
1284 except exceptions.ResourceDoesNotExist:
1285 status = 404
1287 # File is protected
1288 if filename is None:
1289 status = 403
1291 return filename, status
1294def render_binary_file(request, resource, status, filename):
1295 if status == 404: 1295 ↛ 1296line 1295 didn't jump to line 1296, because the condition on line 1295 was never true
1296 raise Http404
1297 elif status == 403:
1298 template_name = "403withRedirect.html"
1299 response = render(request, template_name, {"pid": resource.pid}, status=403)
1300 else:
1301 full_filename = os.path.join(settings.RESOURCES_ROOT, filename)
1303 try:
1304 file_ = open(full_filename, "rb")
1305 except OSError:
1306 print("cannot open ", full_filename)
1307 raise Http404
1308 else:
1309 response = sendfile(request, full_filename)
1310 response["Accept-Ranges"] = "bytes"
1311 file_.close()
1312 return response
1315##########################################################################
1316#
1317# Views that update model objects (besides import/upload)
1318#
1319##########################################################################
1320class APILookupView(View):
1321 def get(self, request):
1322 title = request.GET["title"]
1323 year = request.GET.get("year", False)
1324 authors = request.GET.get("authors", False)
1325 params = {
1326 "qs": [
1327 {"name": "title", "value": title, "not": False, "first": "", "last": ""},
1328 ],
1329 }
1330 if year:
1331 params["qs"].append(
1332 {"name": "date", "value": None, "not": False, "first": year, "last": year},
1333 )
1334 if authors:
1335 params["qs"].append(
1336 {"name": "author", "value": authors, "not": False, "first": "", "last": ""},
1337 )
1338 cmd = solr_cmds.solrSearchCmd(params)
1339 results = cmd.do()
1340 if len(results.docs):
1341 data = {"pid": results.docs[0]["pid"]}
1342 else:
1343 data = {}
1344 return JsonResponse(data)
1347class APIFetchView(View):
1348 def get(self, request):
1349 data = ""
1350 pid = request.GET["pid"]
1351 if pid:
1352 article = get_object_or_404(models.Article, pid=pid)
1353 data = article.get_citation(with_formatting=True)
1354 return HttpResponse(data)
1357class ArticlesAPIView(View):
1358 def get(self, request):
1359 # values_list returns queryset in format [('id',) ('id',)]
1360 nested_article_pids = models.Article.objects.values_list("pid")
1361 # We flatten it to a normal list ['id', 'id']
1362 article_pids = list(chain.from_iterable(nested_article_pids))
1363 return JsonResponse(
1364 {"articles": {"ids": article_pids, "total": nested_article_pids.count()}}
1365 )
1368class ArticleDumpAPIView(View):
1369 def get(self, request, *args, **kwargs):
1370 pid = kwargs.get("pid", None)
1372 if "/" in pid: 1372 ↛ 1373line 1372 didn't jump to line 1373, because the condition on line 1372 was never true
1373 article = model_helpers.get_article_by_doi(pid, prefetch=True)
1374 else:
1375 article = model_helpers.get_article(pid, prefetch=True)
1376 if not article: 1376 ↛ 1377line 1376 didn't jump to line 1377, because the condition on line 1376 was never true
1377 raise Http404
1379 date_accepted = date_published = date_online_first = None
1380 if article.date_accepted: 1380 ↛ 1382line 1380 didn't jump to line 1382, because the condition on line 1380 was never false
1381 date_accepted = article.date_accepted.strftime("%Y-%m-%d")
1382 if article.date_online_first: 1382 ↛ 1384line 1382 didn't jump to line 1384, because the condition on line 1382 was never false
1383 date_online_first = article.date_online_first.strftime("%Y-%m-%d")
1384 if article.date_published: 1384 ↛ 1386line 1384 didn't jump to line 1386
1385 date_published = article.date_published.strftime("%Y-%m-%d")
1386 date_received = (
1387 article.date_received.strftime("%Y-%m-%d") if article.date_received else None
1388 )
1389 date_revised = article.date_revised.strftime("%Y-%m-%d") if article.date_revised else None
1391 author_names = models.get_names(article, "author")
1392 if author_names: 1392 ↛ 1395line 1392 didn't jump to line 1395, because the condition on line 1392 was never false
1393 authors = "; ".join(author_names)
1394 else:
1395 author_names = models.get_names(article, "editor")
1396 if author_names:
1397 authors = "; ".join(author_names) + " (" + str(_("éd.")) + ")"
1398 else:
1399 authors = None
1401 page_count = article.get_article_page_count()
1403 result = {
1404 "title_tex": article.title_tex,
1405 "title_html": article.title_html,
1406 "trans_title_tex": article.trans_title_tex,
1407 "trans_title_html": article.trans_title_html,
1408 "lang": article.lang,
1409 "doi": article.doi or None,
1410 "pid": article.pid,
1411 "authors": authors,
1412 "issue_pid": article.my_container.pid,
1413 "colid": article.my_container.my_collection.pid,
1414 "volume": article.my_container.volume,
1415 "number": article.my_container.number,
1416 "year": article.my_container.year,
1417 "issue_title": article.my_container.title_tex,
1418 "citation": article.get_citation(request),
1419 "date_accepted": date_accepted,
1420 "date_online_first": date_online_first,
1421 "date_published": date_published,
1422 "date_received": date_received,
1423 "date_revised": date_revised,
1424 "page_count": page_count,
1425 "body_html": article.body_html,
1426 "pci_section": article.get_pci_section(),
1427 }
1429 extids = []
1430 for extid in article.extid_set.all():
1431 extids.append([extid.id_type, extid.id_value])
1432 result["extids"] = extids
1434 result["kwds"] = [
1435 {"type": kwd.type, "lang": kwd.lang, "value": kwd.value}
1436 for kwd in article.kwd_set.all()
1437 ]
1439 awards = []
1440 for award in article.award_set.all():
1441 awards.append([award.abbrev, award.award_id])
1442 result["awards"] = awards
1444 abstracts = []
1445 for abstract in article.abstract_set.all():
1446 abstracts.append(
1447 {
1448 "lang": abstract.lang,
1449 "value_tex": abstract.value_tex,
1450 "value_html": abstract.value_html,
1451 }
1452 )
1453 result["abstracts"] = abstracts
1455 bibitems = []
1456 for bib in article.bibitem_set.all():
1457 bibitems.append(bib.citation_html)
1458 result["bibitems"] = bibitems
1460 return JsonResponse(result)
1463class BookDumpAPIView(View):
1464 def get(self, request, *args, **kwargs):
1465 pid = kwargs.get("pid", None)
1467 book = model_helpers.get_container(pid)
1468 if not book: 1468 ↛ 1469line 1468 didn't jump to line 1469, because the condition on line 1468 was never true
1469 raise Http404
1471 result = {
1472 "title_tex": book.title_tex,
1473 "title_html": book.title_html,
1474 "doi": book.doi or "",
1475 "citation": book.get_citation(request),
1476 }
1478 extids = []
1479 for extid in book.extid_set.all():
1480 extids.append([extid.id_type, extid.id_value])
1481 result["extids"] = extids
1483 result["kwds"] = [
1484 {"type": kwd.type, "lang": kwd.lang, "value": kwd.value} for kwd in book.kwd_set.all()
1485 ]
1487 abstracts = []
1488 for abstract in book.abstract_set.all():
1489 abstracts.append(
1490 {
1491 "lang": abstract.lang,
1492 "value_tex": abstract.value_tex,
1493 "value_html": abstract.value_html,
1494 }
1495 )
1496 result["abstracts"] = abstracts
1498 bibitems = []
1499 for bib in book.bibitem_set.all():
1500 bibitems.append(bib.citation_html)
1501 result["bibitems"] = bibitems
1503 return JsonResponse(result)
1506class AllIssuesAPIView(View):
1507 def get(self, request):
1508 issues_pids = list(models.Container.objects.values_list("pid", flat=True).order_by("pid"))
1509 return JsonResponse({"issues": issues_pids})
1512class CollectionIssnAPIView(View):
1513 def get(self, request, *args, **kwargs):
1514 collection = get_object_or_404(
1515 models.Collection,
1516 resourceid__id_value=kwargs.get("issn"),
1517 )
1518 if collection.parent:
1519 url = (
1520 ""
1521 if collection.parent.pid == collection.pid
1522 else collection.parent.get_absolute_url()
1523 )
1524 url += f"#{collection.pid}"
1525 return JsonResponse({"url": url})
1526 return JsonResponse({"url": collection.get_absolute_url()})
1529class CollectionsAPIView(View):
1530 def get(self, request):
1531 collections_pids = list(
1532 models.Collection.objects.filter(parent__isnull=True).values_list("pid", flat=True)
1533 )
1534 return JsonResponse({"collections": collections_pids})
1537class CollectionExportCSV(View):
1538 def get(self, request, *args, **kwargs):
1539 colid = kwargs.get("colid")
1540 collection = get_object_or_404(models.Collection, pid=colid)
1542 response = HttpResponse(content_type="text/csv")
1543 response["Content-Disposition"] = f'attachment; filename="{collection.pid}.csv"'
1545 csv_header = [
1546 "doi",
1547 "title",
1548 "date_accepted",
1549 "date_first_publication",
1550 ]
1552 writer = csv.writer(response, delimiter="\t")
1553 writer.writerow(csv_header)
1555 for article in models.Article.objects.filter(
1556 my_container__my_collection__pid=colid
1557 ).order_by("-date_accepted"):
1558 if article.date_published is not None or article.date_online_first is not None: 1558 ↛ 1555line 1558 didn't jump to line 1555, because the condition on line 1558 was never false
1559 first_online = (
1560 article.date_online_first
1561 if (article.date_online_first is not None and colid.lower()[0:2] == "cr")
1562 else article.date_published
1563 )
1564 writer.writerow(
1565 [
1566 f'=LIEN.HYPERTEXTE("https://doi.org/{article.doi}"; "{article.doi}")',
1567 xml_utils.normalise_span(article.title_tex),
1568 article.date_accepted.strftime("%Y-%m-%d")
1569 if article.date_accepted is not None
1570 else "",
1571 first_online.strftime("%Y-%m-%d"),
1572 ]
1573 )
1575 return response
1578class IssuesAPIView(View):
1579 def get(self, request, *args, **kwargs):
1580 colid = kwargs.get("colid", None)
1581 issues_pids = list(
1582 models.Container.objects.filter(
1583 Q(my_collection__pid=colid) | Q(my_collection__parent__pid=colid)
1584 )
1585 .values_list("pid", flat=True)
1586 .order_by("pid")
1587 )
1588 return JsonResponse({"issues": issues_pids})
1591class IssueListAPIView(View):
1592 def get(self, request, *args, **kwargs):
1593 pid = kwargs.get("pid", None)
1594 articles_pids = list(
1595 models.Article.objects.filter(my_container__pid=pid)
1596 .values_list("pid", flat=True)
1597 .order_by("pid")
1598 )
1599 return JsonResponse({"articles": articles_pids})
1602class ItemXMLView(View):
1603 def get(self, request, *args, **kwargs):
1604 pid = kwargs.get("pid", None)
1605 full_xml = self.request.GET.get("full_xml", "1")
1607 full_xml = False if full_xml == "0" or full_xml == "false" else True
1608 if pid.find(".") > 0:
1609 # The id given is a DOI
1610 article = model_helpers.get_article_by_doi(pid)
1611 if article: 1611 ↛ 1614line 1611 didn't jump to line 1614, because the condition on line 1611 was never false
1612 pid = article.pid
1613 else:
1614 raise Http404
1616 xml_body = ptf_cmds.exportPtfCmd(
1617 {
1618 "pid": pid,
1619 "with_internal_data": False,
1620 "with_binary_files": False,
1621 "for_archive": True,
1622 "full_xml": full_xml,
1623 }
1624 ).do()
1626 return HttpResponse(xml_body, content_type="application/xml")
1629class ItemFileListAPIView(View):
1630 def get(self, request, *args, **kwargs):
1631 pid = kwargs.get("pid", None)
1633 resource = model_helpers.get_resource(pid)
1634 if not resource: 1634 ↛ 1635line 1634 didn't jump to line 1635, because the condition on line 1634 was never true
1635 raise Http404
1637 obj = resource.cast()
1638 binary_files = obj.get_binary_files_location()
1640 result = {"files": binary_files}
1642 if obj.classname == "Container": 1642 ↛ 1643line 1642 didn't jump to line 1643, because the condition on line 1642 was never true
1643 articles = []
1644 for article in obj.article_set.all():
1645 article_files = article.get_binary_files_location()
1646 articles.append({"pid": article.pid, "files": article_files})
1647 result["articles"] = articles
1649 return JsonResponse(result)
1652class APIMatchOneView(View):
1653 def get(self, request, *args, **kwargs):
1654 pid = kwargs.get("pid", None)
1655 seq = kwargs.get("seq", 0)
1656 what = kwargs.get("what", "")
1658 resource = model_helpers.get_resource(pid)
1659 if not resource:
1660 raise Http404
1662 if what not in ["zbl-item-id", "mr-item-id", "doi", "numdam-id", "pmid"]:
1663 return HttpResponseBadRequest(
1664 "Bad request: 'what' has to be one"
1665 "of {zbl-item-id, mr-item-id, doi, numdam-id, pmid}"
1666 )
1667 result = ""
1669 status = 200
1670 message = ""
1672 try:
1673 obj = resource.cast()
1674 seq = int(seq)
1675 if seq == 0:
1676 # Article, Book or Book part match
1677 if obj.classname.lower() == "article":
1678 result = matching.match_article(obj, what)
1679 else:
1680 # TODO match book
1681 pass
1682 else:
1683 bibitem = model_helpers.get_bibitem_by_seq(obj, seq)
1684 if not bibitem:
1685 raise Http404
1686 result = matching.match_bibitem(bibitem, what)
1688 message = pid + " " + str(seq) + " " + what + " : " + result
1689 except Timeout as exception:
1690 return HttpResponse(exception, status=408)
1691 except Exception as exception:
1692 return HttpResponseServerError(exception)
1694 data = {"message": message, "status": status}
1695 return JsonResponse(data)
1698class APIMatchAllView(View):
1699 @staticmethod
1700 def get_existing_ids(pid, what, force):
1701 query_bibitemids = (
1702 models.BibItemId.objects.filter(
1703 bibitem__resource__pid__contains=pid,
1704 id_type__in=what,
1705 )
1706 .select_related(
1707 "bibitem",
1708 "bibitem__resource",
1709 )
1710 .only("bibitem__resource__pid", "bibitem__sequence", "id_type")
1711 )
1712 query_extids = models.ExtId.objects.filter(
1713 resource__pid__contains=pid,
1714 id_type__in=what,
1715 ).select_related("resource")
1716 if force == "1":
1717 query_bibitemids = query_bibitemids.exclude(checked=False, false_positive=False)
1718 query_extids = query_extids.exclude(checked=False, false_positive=False)
1719 if force == "2":
1720 query_bibitemids = models.BibItemId.objects.none()
1721 query_extids = models.ExtId.objects.none()
1722 bibitemids = {
1723 (bibitemid.bibitem.resource.pid, bibitemid.bibitem.sequence, bibitemid.id_type)
1724 for bibitemid in query_bibitemids
1725 }
1726 extids = {(extid.resource.pid, 0, extid.id_type) for extid in query_extids}
1727 return bibitemids.union(extids)
1729 @staticmethod
1730 def get_possible_ids(pid, what):
1731 bibitems = models.BibItem.objects.filter(resource__pid__contains=pid).select_related(
1732 "resource"
1733 )
1734 articles = models.Article.objects.filter(pid__contains=pid).exclude(
1735 classname="TranslatedArticle"
1736 )
1737 bibitemids = {
1738 (item.resource.pid, item.sequence, type_)
1739 for type_ in what
1740 for item in bibitems
1741 if type_ != "pmid"
1742 }
1743 # we remove doi from possible extids
1744 if "doi" in what:
1745 what.remove("doi")
1746 extids = {(article.pid, 0, type_) for type_ in what for article in articles}
1747 return bibitemids.union(extids)
1749 @staticmethod
1750 def delete_ids_if_necessary(pid, what, force):
1751 if force == "1":
1752 models.BibItemId.objects.filter(
1753 bibitem__resource__pid__contains=pid,
1754 id_type__in=what,
1755 checked=False,
1756 false_positive=False,
1757 ).delete()
1758 models.ExtId.objects.filter(
1759 resource__pid__contains=pid,
1760 id_type__in=what,
1761 checked=False,
1762 false_positive=False,
1763 ).delete()
1764 if force == "2":
1765 models.BibItemId.objects.filter(
1766 bibitem__resource__pid__contains=pid, id_type__in=what
1767 ).delete()
1768 models.ExtId.objects.filter(resource__pid__contains=pid, id_type__in=what).delete()
1770 def get(self, request, *args, **kwargs):
1771 pid = kwargs.get("pid", None)
1772 what = kwargs.get("what", "all")
1773 force = kwargs.get("force", "0")
1774 # if what contains several targets, they are separated with an underscore
1775 what = what.split("_")
1776 if force != "0":
1777 self.delete_ids_if_necessary(pid, what, force)
1778 existing = self.get_existing_ids(pid, what, force)
1779 possible = self.get_possible_ids(pid, what)
1780 ids = list(possible - existing)
1781 ids.sort(key=itemgetter(0, 1, 2))
1782 final = [f"{pid}/{number}/{what}" for pid, number, what in ids]
1783 return JsonResponse({"ids": final})
1786class UpdateMatchingView(View):
1787 resource = None
1789 def post_update(self):
1790 model_helpers.post_resource_updated(self.resource)
1792 def update_obj(self, action):
1793 if action == "delete":
1794 models.ExtId.objects.filter(resource=self.resource).delete()
1795 models.BibItemId.objects.filter(bibitem__resource=self.resource).delete()
1796 elif action == "mark-checked":
1797 models.ExtId.objects.filter(resource=self.resource).update(checked=True)
1798 models.BibItemId.objects.filter(bibitem__resource=self.resource).update(checked=True)
1799 elif action == "mark-unchecked":
1800 models.ExtId.objects.filter(resource=self.resource).update(checked=False)
1801 models.BibItemId.objects.filter(bibitem__resource=self.resource).update(checked=False)
1803 for bibitem in models.BibItem.objects.filter(resource=self.resource):
1804 cmd = xml_cmds.updateBibitemCitationXmlCmd()
1805 cmd.set_bibitem(bibitem)
1806 cmd.do()
1808 self.post_update()
1810 def get(self, request, *args, **kwargs):
1811 pid = kwargs.get("pid", None)
1812 action = kwargs.get("action", None)
1814 self.resource = model_helpers.get_resource(pid)
1815 if not self.resource:
1816 raise Http404
1818 self.update_obj(action)
1820 url = reverse("article", kwargs={"aid": self.resource.pid})
1821 return HttpResponseRedirect(url)
1824class UpdateExtIdView(View):
1825 obj = None
1826 resource = None
1827 parent = None
1829 def get_obj(self, pk):
1830 try:
1831 extid = models.ExtId.objects.get(pk=pk)
1832 except models.ExtId.DoesNotExist:
1833 raise Http404
1835 self.obj = extid
1836 self.resource = extid.resource
1838 def post_update(self):
1839 model_helpers.post_resource_updated(self.resource)
1841 def update_obj(self, action):
1842 if not self.obj:
1843 raise Http404
1845 if action == "delete":
1846 self.obj.delete()
1847 elif action == "toggle-checked":
1848 self.obj.checked = False if self.obj.checked else True
1849 self.obj.save()
1850 elif action == "toggle-false-positive":
1851 self.obj.false_positive = False if self.obj.false_positive else True
1852 self.obj.save()
1854 self.post_update()
1856 def get(self, request, *args, **kwargs):
1857 pk = kwargs.get("pk", None)
1858 action = kwargs.get("action", None)
1860 self.get_obj(pk)
1861 self.update_obj(action)
1863 if action == "delete":
1864 url = reverse("article", kwargs={"aid": self.resource.pid})
1865 return HttpResponseRedirect(url)
1866 return JsonResponse({})
1869class UpdateBibItemIdView(UpdateExtIdView):
1870 def get_obj(self, pk):
1871 try:
1872 bibitemid = models.BibItemId.objects.get(pk=pk)
1873 except models.BibItemId.DoesNotExist:
1874 raise Http404
1876 self.obj = bibitemid
1877 self.parent = bibitemid.bibitem
1878 self.resource = bibitemid.bibitem.resource
1880 def post_update(self):
1881 cmd = xml_cmds.updateBibitemCitationXmlCmd()
1882 cmd.set_bibitem(self.parent)
1883 cmd.do()
1885 model_helpers.post_resource_updated(self.resource)
1888class APIFetchId(View):
1889 def get(self, request, *args, **kwargs):
1890 id_ = kwargs.get("id", None)
1891 what = kwargs.get("what", None)
1892 pk = kwargs.get("pk")
1893 resource = kwargs["resource"]
1895 if what == "pmid":
1896 data = {"result": "", "status": 200}
1897 return JsonResponse(data)
1899 if not id_:
1900 return HttpResponseBadRequest("Bad request: 'id' is none")
1902 if what not in ["zbl-item-id", "mr-item-id", "doi", "numdam-id"]:
1903 return HttpResponseBadRequest(
1904 "Bad request: 'what' has to be one" "of {zbl-item-id, mr-item-id, doi, numdam-id}"
1905 )
1907 try:
1908 result = matching.fetch_id(id_, what)
1909 except (IndexError, RequestException) as exception:
1910 return HttpResponseServerError(exception)
1912 if resource == "bibitemid":
1913 bibitem = models.BibItem.objects.get(pk=pk)
1914 raw = bibitem.citation_html
1915 else:
1916 article = models.Article.objects.get(pk=pk)
1917 raw = article.get_citation(request)
1918 result = highlight_diff(raw, result)
1920 data = {"result": result, "status": 200}
1921 return JsonResponse(data)
1924class APIFetchAllView(View):
1925 def get(self, request, *args, **kwargs):
1926 pid = kwargs.get("pid", None)
1928 article = model_helpers.get_article(pid)
1929 if not article:
1930 raise Http404
1932 ids = matching.get_all_fetch_ids(article)
1934 data = {"ids": ids}
1935 return JsonResponse(data)
1938@require_http_methods(["GET"])
1939def malsm_books(request, pid="MALSM", api=False):
1940 template = "malsm.html"
1941 context = {
1942 "journal": model_helpers.get_collection(pid),
1943 "coltype": "book",
1944 "template": template,
1945 }
1947 return render(request, template, context)
1950class LatestArticlesFeed(Feed):
1951 link = ""
1952 ttl = 120
1954 def get_feed(self, obj, request):
1955 feed = super().get_feed(obj, request)
1956 feed.feed["language"] = get_language()
1957 return feed
1959 def get_object(self, request, name, jid=""):
1960 """
1961 Select the site whose RSS feed is requested.
1962 It is annotated with an optional `requested_col_id` for sites with multiple collections (ex: proceedings).
1963 """
1964 self.request = request
1965 return models.Site.objects.annotate(requested_col_id=Value(jid)).get(name=name)
1967 def title(self, obj):
1968 if obj.requested_col_id:
1969 collection = get_object_or_404(
1970 models.Collection, sites__name=obj.name, pid=obj.requested_col_id
1971 )
1972 else:
1973 collection = get_object_or_404(models.Collection, sites__name=obj.name)
1974 return collection.title_sort
1976 def description(self):
1977 return _("Flux RSS des derniers articles parus")
1979 def items(self, obj):
1980 qs = models.Article.objects.filter(sites=obj.pk)
1981 if obj.requested_col_id:
1982 qs = qs.filter(my_container__my_collection__pid=obj.requested_col_id)
1983 return qs.exclude(classname="TranslatedArticle").order_by(
1984 Greatest("date_online_first", "date_published").desc(nulls_last=True), "-seq"
1985 )[:20]
1987 def item_title(self, item):
1988 return f"{item.get_authors_short()} - {item.title_html}"
1990 def item_description(self, item):
1991 language = get_language()
1992 abstracts = item.get_abstracts()
1993 if not abstracts:
1994 return self.item_title(item)
1995 try:
1996 return abstracts.get(lang=language).value_html
1997 except models.Abstract.DoesNotExist:
1998 return abstracts.first().value_html
2000 def item_pubdate(self, item):
2001 if item.date_published:
2002 return item.date_published
2003 return item.date_online_first
2005 def item_link(self, item):
2006 if item.doi:
2007 return f"{settings.DOI_BASE_URL}{item.doi}"
2008 return item.get_absolute_url()
2011class LatestIssue(ItemView):
2012 def get(self, request, *args, **kwargs):
2013 pid = kwargs.get("pid")
2014 if pid is None: 2014 ↛ 2015line 2014 didn't jump to line 2015, because the condition on line 2014 was never true
2015 return HttpResponse(status=404)
2017 container_issues = (
2018 models.Container.objects.filter(my_collection__pid=pid)
2019 .all()
2020 .order_by("-vseries_int", "-year", "-volume_int", "-number_int")
2021 )
2022 if container_issues is None: 2022 ↛ 2023line 2022 didn't jump to line 2023, because the condition on line 2022 was never true
2023 return HttpResponse(status=404)
2025 container = container_issues.first()
2026 url = container.get_absolute_url()
2027 return HttpResponseRedirect(url)
2030class RSSTemplate(TemplateView):
2031 template_name = "blocks/syndication_feed.html"
2033 def dispatch(self, request, *args, **kwargs):
2034 # Numdam does not have an rss feed for now
2035 if settings.SITE_ID == 3:
2036 raise Http404
2037 return super().dispatch(request, *args, **kwargs)
2040class ArticleEditAPIView(View):
2041 def __init__(self, *args, **kwargs):
2042 super().__init__(*args, **kwargs)
2043 self.doi = None
2044 self.colid = None
2045 self.edit_all_fields = False
2046 self.fields_to_update = (
2047 []
2048 ) # Must be used to specify the fields to update (title, authors, references...)
2050 @method_decorator(csrf_exempt)
2051 def dispatch(self, request, *args, **kwargs):
2052 return super().dispatch(request, *args, **kwargs)
2054 def save_data(self, data_article):
2055 pass
2057 def restore_data(self, article):
2058 pass
2060 def convert_data_for_editor(self, data_article):
2061 data_article.trans_title_formulas = []
2062 data_article.title_formulas = []
2063 data_article.abstract_formulas = []
2064 data_article.trans_abstract_formulas = []
2066 data_article.title_tex, data_article.trans_title_tex = get_tex_from_xml(
2067 data_article.title_xml, "title", add_span_around_tex_formula=True
2068 )
2070 for contrib in data_article.contributors:
2071 contrib["address_text"] = "\n".join([address for address in contrib["addresses"]])
2073 if len(data_article.abstracts) == 0:
2074 data_article.abstracts = [
2075 {
2076 "tag": "abstract",
2077 "lang": data_article.lang,
2078 "value_html": "",
2079 "value_tex": "",
2080 "value_xml": "",
2081 }
2082 ]
2084 for abstract in data_article.abstracts:
2085 if abstract["value_xml"]:
2086 value_tex = get_tex_from_xml(
2087 abstract["value_xml"], "abstract", add_span_around_tex_formula=True
2088 )
2089 else:
2090 value_tex = ""
2091 abstract["value_tex"] = value_tex
2093 data_article.conference = ", ".join(
2094 [subj["value"] for subj in data_article.subjs if subj["type"] == "conference"]
2095 )
2096 data_article.topics = [
2097 subj["value"] for subj in data_article.subjs if subj["type"] == "topic"
2098 ]
2100 data_article.pci_section = "".join(
2101 [subj["value"] for subj in data_article.subjs if subj["type"] == "pci"]
2102 )
2104 data_article.subjs = [
2105 subj
2106 for subj in data_article.subjs
2107 if subj["type"] not in ["conference", "topic", "pci"]
2108 ]
2110 with_ordered_label = True
2111 for i, ref in enumerate(data_article.bibitems, start=1):
2112 model_data_converter.convert_refdata_for_editor(ref)
2113 if ref.label != f"[{str(i)}]":
2114 with_ordered_label = False
2115 data_article.bibitems_with_ordered_label = with_ordered_label
2117 ext_link = model_data.get_extlink(data_article, "icon")
2118 data_article.icon_url = None
2119 if ext_link:
2120 data_article.icon_url = resolver.get_icon_url(None, ext_link["location"])
2122 def convert_data_from_editor(self, data_article):
2123 xtitle = CkeditorParser(
2124 html_value=data_article.title_tex,
2125 mml_formulas=data_article.title_formulas,
2126 ignore_p=True,
2127 )
2128 data_article.title_html = xtitle.value_html
2129 data_article.title_tex = xtitle.value_tex
2131 trans_title_xml = ""
2132 if data_article.trans_title_tex:
2133 xtranstitle = CkeditorParser(
2134 html_value=data_article.trans_title_tex,
2135 mml_formulas=data_article.trans_title_formulas,
2136 ignore_p=True,
2137 )
2138 data_article.trans_title_html = xtranstitle.value_html
2139 data_article.trans_title_tex = xtranstitle.value_tex
2140 trans_title_xml = xtranstitle.value_xml
2142 data_article.title_xml = get_title_xml(
2143 xtitle.value_xml, trans_title_xml, data_article.trans_lang, with_tex_values=False
2144 )
2146 for contrib in data_article.contributors:
2147 contrib["addresses"] = []
2148 if "address_text" in contrib:
2149 contrib["addresses"] = contrib["address_text"].split("\n")
2151 for i, abstract in enumerate(data_article.abstracts):
2152 if i > 0:
2153 xabstract = CkeditorParser(
2154 html_value=abstract["value_tex"],
2155 mml_formulas=data_article.trans_abstract_formulas,
2156 )
2157 else:
2158 xabstract = CkeditorParser(
2159 html_value=abstract["value_tex"], mml_formulas=data_article.abstract_formulas
2160 )
2161 abstract["value_html"] = xabstract.value_html
2162 abstract["value_tex"] = xabstract.value_tex
2163 if abstract["lang"] == data_article.lang:
2164 abstract[
2165 "value_xml"
2166 ] = f'<abstract xml:lang="{data_article.lang}">{xabstract.value_xml}</abstract>'
2167 else:
2168 lang = abstract["lang"]
2169 abstract[
2170 "value_xml"
2171 ] = f'<trans-abstract xml:lang="{lang}">{xabstract.value_xml}</trans-abstract>'
2173 data_article.subjs = [
2174 subj for subj in data_article.subjs if subj["type"] not in ["conference", "topic"]
2175 ]
2176 if data_article.topics:
2177 data_article.subjs.extend(
2178 [{"type": "topic", "lang": "en", "value": topic} for topic in data_article.topics]
2179 )
2180 if data_article.conference:
2181 data_article.subjs.append(
2182 {"type": "conference", "lang": "en", "value": data_article.conference}
2183 )
2184 if data_article.pci_section:
2185 data_article.subjs.append(
2186 {"type": "pci", "lang": "en", "value": data_article.pci_section}
2187 )
2189 if self.edit_all_fields or "bibitems" in self.fields_to_update:
2190 for i, ref in enumerate(data_article.bibitems, start=1):
2191 if data_article.bibitems_with_ordered_label and ref["type"] != "unknown":
2192 ref["label"] = f"[{str(i)}]"
2193 if "doi" in ref and len(ref["doi"]) > 0:
2194 doi = xml_utils.clean_doi(ref["doi"])
2195 ref["doi"] = doi
2196 ref["extids"] = [["doi", doi]]
2197 else:
2198 ref["extids"] = []
2199 # URLs are in <comment>
2200 # if 'url' in ref and len(ref['url']) > 0:
2201 # ref['ext_links'] = [{'rel': '',
2202 # 'mimetype': '',
2203 # 'location': ref['url'],
2204 # 'base': '',
2205 # 'metadata': ''}]
2206 # else:
2207 # ref['ext_links'] = []
2209 author_array = ref["contribs_text"].split("\n")
2210 contribs = []
2211 for author_txt in author_array:
2212 if author_txt:
2213 lastname = firstname = ""
2214 pos = author_txt.find(", ")
2215 if pos > 0:
2216 lastname = author_txt[0:pos]
2217 firstname = author_txt[pos + 2 :]
2218 else:
2219 lastname = author_txt
2221 contrib = model_data.create_contributor()
2222 contrib["first_name"] = firstname
2223 contrib["last_name"] = lastname
2224 contrib["role"] = "author"
2225 contrib["contrib_xml"] = xml_utils.get_contrib_xml(contrib)
2226 contribs.append(contrib)
2227 ref["contributors"] = contribs
2229 def replace_p(self, obj, key):
2230 text = obj[key].replace("</p>", "")
2231 pos = text.find("<p>")
2232 if pos > -1:
2233 text = text[0:pos] + text[pos + 3 :]
2234 text = text.replace("<p>", "\n")
2235 obj[key] = text
2237 def replace_return(self, obj, key):
2238 text_array = obj[key].split("\n")
2239 text = "<br>".join([s for s in text_array])
2240 obj[key] = text
2242 def get(self, request, *args, **kwargs):
2243 doi = kwargs.get("doi", None)
2244 # colid = kwargs.get("colid", None)
2246 article = model_helpers.get_article_by_doi(doi, prefetch=True)
2247 if not article:
2248 raise Http404
2250 data_article = model_data_converter.db_to_article_data(article)
2251 self.convert_data_for_editor(data_article)
2253 def obj_to_dict(obj):
2254 return obj.__dict__
2256 dump = json.dumps(data_article, default=obj_to_dict)
2258 return HttpResponse(dump, content_type="application/json")
2260 def update_article(self, article_data, icon_file):
2261 self.save_data(article_data)
2263 collection = model_helpers.get_collection(self.colid)
2265 model_data_converter.update_data_for_jats(article_data)
2267 issue = None
2268 existing_article = model_helpers.get_article_by_doi(article_data.doi)
2269 new_data_article = None
2271 if existing_article is not None:
2272 issue = existing_article.my_container
2273 new_data_article = model_data_converter.db_to_article_data(existing_article)
2274 else:
2275 new_data_article = model_data.create_articledata()
2276 new_data_article.pid = article_data.pid
2277 new_data_article.doi = article_data.doi
2279 if self.edit_all_fields:
2280 new_data_article = article_data
2281 else:
2282 for field in self.fields_to_update:
2283 value = getattr(article_data, field)
2284 setattr(new_data_article, field, value)
2286 if self.edit_all_fields or "ext_links" in self.fields_to_update:
2287 # New icon
2288 if icon_file and issue:
2289 relative_file_name = resolver.copy_file_obj_to_article_folder(
2290 icon_file, self.colid, issue.pid, article_data.pid
2291 )
2292 ext_link = model_data.get_extlink(new_data_article, "icon")
2293 if ext_link is None:
2294 ext_link = model_data.create_extlink()
2295 ext_link["rel"] = "icon"
2296 ext_link["location"] = relative_file_name
2297 new_data_article.ext_links.append(ext_link)
2299 # No or removed icon
2300 if not icon_file and hasattr(article_data, "icon_url") and not article_data.icon_url:
2301 new_data_article.ext_links = [
2302 e for e in new_data_article.ext_links if e["rel"] != "icon"
2303 ]
2305 cmd = xml_cmds.addArticleXmlCmd(
2306 {"xarticle": new_data_article, "use_body": False, "issue": issue, "standalone": True}
2307 )
2308 cmd.set_collection(collection)
2309 article = cmd.do()
2311 self.restore_data(article)
2313 def post(self, request, *args, **kwargs):
2314 self.colid = kwargs.get("colid", None)
2315 self.doi = kwargs.get("doi", None)
2317 body_unicode = request.POST.get("data", "")
2318 body = json.loads(body_unicode)
2320 article_data = Munch(body)
2321 new_bibitems = []
2322 for bib in article_data.bibitems:
2323 new_bibitems.append(Munch(bib))
2324 article_data.bibitems = new_bibitems
2326 new_relations = []
2327 for relation in article_data.relations:
2328 new_relations.append(Munch(relation))
2329 article_data.relations = new_relations
2331 new_translations = []
2332 for translation in article_data.translations:
2333 new_translations.append(Munch(translation))
2334 article_data.translations = new_translations
2336 self.convert_data_from_editor(article_data)
2338 icon_file = None
2339 if "icon" in request.FILES:
2340 icon_file = request.FILES["icon"]
2342 self.update_article(article_data, icon_file)
2344 return JsonResponse({"message": "OK"})
2347class CollectionEditAPIView(View):
2348 def __init__(self, *args, **kwargs):
2349 super().__init__(*args, **kwargs)
2350 self.colid = None
2351 self.collection = None
2352 self.edit_all_fields = True
2353 self.fields_to_update = (
2354 []
2355 ) # Must be used to specify the fields to update (title, wall, periods...)
2357 @method_decorator(csrf_exempt)
2358 def dispatch(self, request, *args, **kwargs):
2359 return super().dispatch(request, *args, **kwargs)
2361 def save_data(self, data):
2362 pass
2364 def restore_data(self, collection):
2365 pass
2367 def convert_data_for_editor(self, data):
2368 data.title, data.trans_title = get_tex_from_xml(
2369 data.title_xml, "title", add_span_around_tex_formula=True
2370 )
2371 # TODO: move periods inside GDML
2372 data.periods = []
2374 def convert_data_from_editor(self, data):
2375 xtitle = CkeditorParser(html_value=data.title_tex, mml_formulas=[], ignore_p=True)
2376 data.title_html = xtitle.value_html
2377 data.title_tex = xtitle.value_tex
2379 # TODO: get_title_xml for Collections
2380 data.title_xml = get_title_xml(xtitle.value_xml, with_tex_values=False)
2382 ids = []
2383 if data.issn:
2384 ids.append(["issn", data.issn])
2385 if data.e_issn:
2386 ids.append(["e-issn", data.e_issn])
2387 data.ids = ids
2389 def replace_p(self, obj, key):
2390 text = obj[key].replace("</p>", "")
2391 pos = text.find("<p>")
2392 if pos > -1:
2393 text = text[0:pos] + text[pos + 3 :]
2394 text = text.replace("<p>", "\n")
2395 obj[key] = text
2397 def replace_return(self, obj, key):
2398 text_array = obj[key].split("\n")
2399 text = "<br>".join([s for s in text_array])
2400 obj[key] = text
2402 def get(self, request, *args, **kwargs):
2403 colid = kwargs.get("colid", None)
2405 self.collection = collection = model_helpers.get_collection(colid, sites=False)
2406 if not collection:
2407 raise Http404
2409 data = model_data_converter.db_to_publication_data(collection)
2410 self.convert_data_for_editor(data)
2412 def obj_to_dict(obj):
2413 return obj.__dict__
2415 dump = json.dumps(data, default=obj_to_dict)
2417 return HttpResponse(dump, content_type="application/json")
2419 def update_collection(self, data):
2420 self.save_data(data)
2422 existing_collection = model_helpers.get_collection(self.colid, sites=False)
2423 if existing_collection is None:
2424 cls = ptf_cmds.addCollectionPtfCmd
2425 new_data = data
2426 else:
2427 cls = ptf_cmds.updateCollectionPtfCmd
2429 if self.edit_all_fields:
2430 new_data = data
2431 else:
2432 new_data = model_data_converter.db_to_publication_data(existing_collection)
2434 for field in self.fields_to_update:
2435 value = getattr(data, field)
2436 setattr(new_data, field, value)
2438 params = {"xobj": new_data, "solr_commit": False}
2439 cmd = cls(params)
2440 if new_data.provider:
2441 provider = model_helpers.get_provider_by_name(new_data.provider)
2442 else:
2443 provider = model_helpers.get_provider("mathdoc-id")
2444 cmd.set_provider(provider)
2445 collection = cmd.do()
2447 # TODO: Move xml_cmds add_objects_with_location inside ptf_cmds
2448 # self.add_objects_with_location(xcol.ext_links, collection, "ExtLink")
2450 self.restore_data(collection)
2452 return collection
2454 def post(self, request, *args, **kwargs):
2455 self.colid = kwargs.get("colid", None)
2457 body_unicode = request.body.decode("utf-8")
2458 # POST.get('data') has to be used with a VueJS FormData
2459 # body_unicode = request.POST.get('data', '')
2460 body = json.loads(body_unicode)
2462 data = Munch(body)
2463 self.convert_data_from_editor(data)
2465 self.update_collection(data)
2467 return JsonResponse({"message": "OK"})
2470class ArticleCitedByView(View):
2471 def get(self, request, *args, **kwargs):
2472 doi = self.kwargs.get("doi", "").strip("/")
2474 if "/" in doi: 2474 ↛ 2477line 2474 didn't jump to line 2477, because the condition on line 2474 was never false
2475 resource = model_helpers.get_resource_by_doi(doi)
2476 else:
2477 resource = model_helpers.get_resource(doi)
2479 if not resource: 2479 ↛ 2480line 2479 didn't jump to line 2480, because the condition on line 2479 was never true
2480 raise Http404
2482 citations, sources, for_stats = citedby.get_citations(resource)
2483 if citations: 2483 ↛ 2484line 2483 didn't jump to line 2484, because the condition on line 2483 was never true
2484 status_code = 200
2485 else:
2486 status_code = 204
2488 data = {
2489 "result": {"citations": citations, "sources": sources, "doi": doi, "json": for_stats},
2490 "status": status_code,
2491 }
2492 return JsonResponse(data)
2495def export_citation(request, **kwargs):
2496 data = ""
2497 pid = kwargs.get("pid", "")
2498 ext = kwargs.get("ext", "")
2500 if "/" in pid: 2500 ↛ 2501line 2500 didn't jump to line 2501, because the condition on line 2500 was never true
2501 resource = model_helpers.get_resource_by_doi(pid)
2502 else:
2503 resource = model_helpers.get_resource(pid)
2505 if not resource: 2505 ↛ 2506line 2505 didn't jump to line 2506, because the condition on line 2505 was never true
2506 return HttpResponse(status=404)
2508 if hasattr(resource, "article"):
2509 document = resource.article
2510 else:
2511 document = model_helpers.get_container(resource.pid)
2513 filename = "citation." + ext
2514 if ext == "bib":
2515 data = document.get_bibtex(request)
2516 elif ext == "ris":
2517 data = document.get_ris(request)
2518 elif ext == "enw": 2518 ↛ 2521line 2518 didn't jump to line 2521, because the condition on line 2518 was never false
2519 data = document.get_endnote(request)
2521 if data: 2521 ↛ 2525line 2521 didn't jump to line 2525, because the condition on line 2521 was never false
2522 response = HttpResponse(data, content_type="text/html; charset=UTF-8")
2523 response["Content-Disposition"] = "attachment; filename=%s" % filename
2524 else:
2525 response = HttpResponse(status=204)
2526 return response
2529def get_tokenized(body):
2530 soup = BeautifulSoup(body, "html.parser")
2531 math_symbols = {}
2532 i = 0
2534 for tag in soup.select("span.mathjax-formula"):
2535 if tag.name == "span":
2536 math_symbols[i] = tag.decode_contents()
2537 tag.string = "[math_symbol_" + str(i) + "]"
2538 i += 1
2539 # else:
2540 # links[j] = tag.decode_contents()
2541 # tag.string = '$link_' + str(j) + '$'
2542 # j += 1
2544 body = str(soup)
2546 return {"body": body, "tokens": {"math": math_symbols}}
2549def detokenize(body, tokens):
2550 soup = BeautifulSoup(body, "html.parser")
2552 for tag in soup.select("span.mathjax-formula"):
2553 token_name = re.sub(r"\[|\]", "", tag.string)
2554 id_list = re.findall(r"\d+", token_name)
2555 id = int(id_list[0])
2556 if id in tokens["math"]:
2557 tag.string = tokens["math"][id]
2559 body = html.unescape(str(soup))
2561 return body
2564class ExportArticleHtml(View):
2565 def get(self, request, **kwargs):
2566 doi = kwargs.get("doi", None)
2567 if doi[-1] == "/": 2567 ↛ 2568line 2567 didn't jump to line 2568, because the condition on line 2567 was never true
2568 doi = doi[:-1]
2569 tokenize = kwargs.get("tokenize", None)
2570 a = Article.objects.get(doi=doi)
2571 body = a.body_html
2572 pid = a.__str__()
2574 if tokenize is not None: 2574 ↛ 2577line 2574 didn't jump to line 2577, because the condition on line 2574 was never true
2575 # tokenize = int(tokenize)
2577 tokens_infos = get_tokenized(body)
2578 body = tokens_infos["body"]
2579 # tokens = tokens_infos["tokens"]
2581 with NamedTemporaryFile("a+", encoding="utf-8") as html_article:
2582 html_article.write(body)
2583 html_article.seek(0)
2584 response = HttpResponse(html_article, content_type="text/html; charset=utf-8")
2585 response["Content-Disposition"] = "attachment; filename=" + pid + ".html"
2587 return response
2590class RecentArticlesPublished(View):
2591 # La méthode permet de retourner 3 articles récents au format JSON avec pour informations :
2592 # - titre de l'article
2593 # - le(s) auteur(s) de l'article
2594 # - collection de l'article
2595 # - doi
2596 # - Infos sur l'article
2597 # - url
2599 def get(self, request):
2600 articles = models.Article.objects.all().order_by(
2601 Greatest("date_online_first", "date_published").desc(nulls_last=True)
2602 )[:100]
2603 articles_infos = []
2604 container_pids = []
2605 nb_articles = articles.count()
2606 i = 0
2607 while i < nb_articles and len(articles_infos) < 3:
2608 article = articles[i]
2609 if article.my_container is not None and article.my_container.pid not in container_pids: 2609 ↛ 2621line 2609 didn't jump to line 2621, because the condition on line 2609 was never false
2610 container_pids.append(article.my_container.pid)
2611 if article.date_published: 2611 ↛ 2621line 2611 didn't jump to line 2621, because the condition on line 2611 was never false
2612 item = {
2613 "title": article.title_html,
2614 "authors": article.get_authors_short(),
2615 "collection": article.my_container.my_collection.title_tex,
2616 "doi": article.doi,
2617 "citation_source": article.get_citation_base(),
2618 "url": resolver.get_doi_url(article.doi),
2619 }
2620 articles_infos.append(item)
2621 i += 1
2622 return JsonResponse({"articles": articles_infos})
2625def get_first_n_authors(authors, n=3):
2626 if len(authors) > n:
2627 authors = authors[0:n]
2628 authors.append("...")
2629 return "; ".join(authors)
2632def get_suggested_articles(article):
2633 documents = []
2634 obj, created = models.RelatedArticles.objects.get_or_create(resource=article)
2635 solr_cmds.auto_suggest_doi(obj, article)
2637 if obj.doi_list: 2637 ↛ 2638line 2637 didn't jump to line 2638, because the condition on line 2637 was never true
2638 exclusion = obj.exclusion_list.split() if obj.exclusion_list else []
2639 # Filter the articles in the exclusion list and that have a DOI that includes the article DOI (translations)
2640 dois = [
2641 doi
2642 for doi in obj.doi_list.split()
2643 if doi not in exclusion and doi.find(article.doi) != 0
2644 ]
2645 for doi in dois:
2646 suggest = Article.objects.filter(doi=doi).first()
2647 doc = {}
2648 base_url = ""
2649 if suggest:
2650 doc = vars(suggest)
2651 doc["authors"] = models.get_names(suggest, "author")
2652 if suggest.my_container:
2653 collection = suggest.my_container.my_collection
2654 base_url = collection.website() or ""
2655 doc["year"] = suggest.my_container.year
2656 doc["journal_abbrev"] = collection.abbrev
2657 else:
2658 try:
2659 doc = crossref.crossref_request(doi)
2660 except:
2661 continue
2662 doc["title_html"] = doc.get("title", "")
2663 doc["journal_abbrev"] = doc.get("journal", "")
2664 authors = doc.get("authors", ";").split(";")
2665 doc["authors"] = [" ".join(au.split(",")).title() for au in authors]
2667 if doc:
2668 doc["authors"] = get_first_n_authors(doc.get("authors", []))
2669 if base_url and suggest:
2670 doc["url"] = base_url + "/articles/" + doi
2671 else:
2672 doc["url"] = resolver.get_doi_url(doi)
2673 documents.append(doc)
2674 return documents
2677@csrf_exempt
2678def update_suggest(request, doi):
2679 if request.method == "POST": 2679 ↛ exitline 2679 didn't return from function 'update_suggest', because the condition on line 2679 was never false
2680 article = Article.objects.filter(doi=doi).first()
2681 if not article:
2682 return JsonResponse({"message": "No article found"})
2683 obj, created = models.RelatedArticles.objects.get_or_create(resource=article)
2684 obj.resource_doi = article.doi
2685 obj.doi_list = request.POST.get("doi_list")
2686 obj.date_modified = request.POST.get("date_modified")
2687 obj.exclusion_list = request.POST.get("exclusion_list")
2688 obj.automatic_list = request.POST.get("automatic_list", True)
2689 obj.save()
2690 return JsonResponse({"message": "OK"})
2693@csrf_exempt
2694def graphical_abstract(request, doi):
2695 article = get_object_or_404(models.Article, doi=doi)
2696 if request.method == "POST": 2696 ↛ 2704line 2696 didn't jump to line 2704, because the condition on line 2696 was never false
2697 obj, created = models.GraphicalAbstract.objects.get_or_create(resource=article)
2698 obj.resource_doi = article.doi
2699 obj.date_modified = request.POST.get("date_modified")
2700 obj.graphical_abstract = request.FILES.get("graphical_abstract")
2701 obj.illustration = request.FILES.get("illustration")
2702 obj.save()
2703 return JsonResponse({"message": "OK"})
2704 elif request.method == "DELETE":
2705 obj = get_object_or_404(models.GraphicalAbstract, resource=article)
2706 if obj:
2707 obj.delete()
2708 return JsonResponse({"message": "OK"})
2711class MoreLikeThisView(View):
2712 def get(self, request, *args, **kwargs):
2713 doi = self.kwargs.get("doi", "")
2714 article = model_helpers.get_resource_by_doi(doi)
2715 if not article:
2716 raise Http404
2718 results = solr_cmds.research_more_like_this(article)
2719 return JsonResponse(results)