Coverage for apps/ptf/models.py: 82%
1924 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 os
2import re
3from urllib.parse import urljoin
4from urllib.parse import urlparse
6from django.conf import settings
7from django.contrib.sites.models import Site
8from django.core.exceptions import MultipleObjectsReturned
9from django.core.files.storage import FileSystemStorage
10from django.db import models
11from django.db.models import Max
12from django.db.models import Prefetch
13from django.db.models import Q
14from django.db.models.signals import pre_delete
15from django.dispatch import receiver
16from django.urls import reverse
17from django.utils import timezone
18from django.utils.translation import get_language
19from django.utils.translation import gettext_lazy as _
21from ptf import exceptions
22from ptf.bibtex import append_in_latex
23from ptf.bibtex import get_bibtex_id
24from ptf.bibtex import get_bibtex_names
25from ptf.cmds.xml import xml_utils
26from ptf.display import resolver
27from ptf.utils import get_display_name
28from ptf.utils import volume_display
30CONTRIB_TYPE_AUTHOR = "author"
31CONTRIB_TYPE_EDITOR = "editor"
32CONTRIB_TYPE_CONTRIBUTOR = "contributor"
33CONTRIB_TYPE_REDAKTOR = "redaktor"
34CONTRIB_TYPE_ORGANIZER = "organizer"
35CONTRIB_TYPE_PRESENTER = "presenter"
36EDITED_BOOK_TYPE = "book-edited-book"
39# http://stackoverflow.com/questions/929029/
40# how-do-i-access-the-child-classes-of-an-object-in-django-without-knowing-the-nam
41# http://djangosnippets.org/snippets/1031/
44class UnknownRelationType(Exception):
45 pass
48class Identifier:
49 """
50 descripteur
51 """
53 def __init__(self, id_type):
54 self.id_type = id_type
56 def __get__(self, obj, objtype):
57 value = ""
58 idobj_qs = obj.resourceid_set.filter(id_type=self.id_type)
59 if idobj_qs.count() > 0:
60 value = idobj_qs.first().id_value
62 return value
64 def __set__(self, obj, value):
65 raise NotImplementedError("Operation not implemented")
68class PtfSite(Site):
69 """Site hébergé"""
71 acro = models.CharField(max_length=32, unique=True)
72 public = models.OneToOneField(
73 "self", null=True, related_name="test_site", on_delete=models.CASCADE
74 )
75 prev_pub_date = models.DateField(null=True)
76 last_pub_date = models.DateField(null=True)
79class ResourceQuerySet(models.QuerySet):
80 def prefetch_contributors(self):
81 return self.prefetch_related("contributions", "contributions__contribaddress_set")
83 def prefetch_references(self):
84 sorted_ids = BibItemId.objects.filter(
85 bibitem__resource__pk__in=self.values("pk")
86 ).order_by("id_type")
87 return self.prefetch_related(
88 Prefetch("bibitem_set__bibitemid_set", queryset=sorted_ids),
89 "bibitem_set__contributions",
90 "bibitem_set__contributions__contribaddress_set",
91 )
93 def prefetch_work(self):
94 return self.prefetch_related(
95 "resourceid_set",
96 "extid_set",
97 "abstract_set",
98 "kwd_set",
99 "subj_set",
100 "datastream_set",
101 "relatedobject_set",
102 "extlink_set",
103 "resourcecount_set",
104 "subject_of",
105 "object_of",
106 "award_set",
107 "frontmatter",
108 )
110 def prefetch_all(self):
111 return self.prefetch_references().prefetch_contributors().prefetch_work()
114class Resource(models.Model):
115 classname = models.CharField(max_length=32, editable=False, db_index=True)
116 ##
117 # dans SiteMembership
118 provider = models.ForeignKey("Provider", null=True, on_delete=models.CASCADE)
119 ##
120 # provider id
121 # pas unique globalement, mais unique par provider
122 # et par site hébergé
123 pid = models.CharField(max_length=80, db_index=True, blank=True, default="")
124 ##
125 # surrogate provider id -- unique par provider
126 sid = models.CharField(max_length=64, db_index=True, blank=True, null=True)
127 doi = models.CharField(max_length=64, unique=True, null=True, blank=True)
128 right_resources_related_to_me = models.ManyToManyField(
129 "self",
130 symmetrical=False,
131 through="Relationship",
132 related_name="left_resources_related_to_me",
133 )
134 sites = models.ManyToManyField(PtfSite, symmetrical=False, through="SiteMembership")
136 title_xml = models.TextField(default="")
138 lang = models.CharField(max_length=3, default="und")
139 title_tex = models.TextField(default="")
140 title_html = models.TextField(default="")
142 trans_lang = models.CharField(max_length=3, default="und")
143 trans_title_tex = models.TextField(default="")
144 trans_title_html = models.TextField(default="")
146 abbrev = models.CharField(max_length=128, blank=True, db_index=True)
147 funding_statement_html = models.TextField(default="")
148 funding_statement_xml = models.TextField(default="")
149 footnotes_html = models.TextField(default="")
150 footnotes_xml = models.TextField(default="")
152 body_html = models.TextField(default="")
153 body_tex = models.TextField(default="")
154 body_xml = models.TextField(default="")
156 objects = ResourceQuerySet.as_manager()
158 class Meta:
159 unique_together = ("provider", "pid")
161 def __str__(self):
162 return self.pid
164 def get_absolute_url(self):
165 """
166 @warning : return an absolute path, not an URL
167 @return: absolute path without scheme or domain name
168 """
170 return reverse("item_id", kwargs={"pid": self.pid})
172 def get_url_absolute(self):
173 if settings.SITE_NAME == "numdam": 173 ↛ 174line 173 didn't jump to line 174, because the condition on line 173 was never true
174 domain = "http://numdam.org/"
175 else:
176 col = self.get_collection()
177 try:
178 website_extlink = col.extlink_set.get(rel="website", metadata="website")
179 # add ending / for urljoin
180 domain = website_extlink.location + "/"
181 except ExtLink.DoesNotExist:
182 domain = "/"
184 # remove beginning / for urljoin
185 resource_path = re.sub(r"^/*", "", self.get_absolute_url())
186 return urljoin(domain, resource_path)
188 def save(self, *args, **kwargs):
189 if not self.id:
190 self.classname = self.__class__.__name__
191 super().save(*args, **kwargs)
193 def cast(self):
194 base_obj = self
195 if not hasattr(self, self.classname.lower()): 195 ↛ 196line 195 didn't jump to line 196, because the condition on line 195 was never true
196 base_obj = self.article
197 return base_obj.__getattribute__(self.classname.lower())
199 def get_collection(self):
200 return None
202 def get_top_collection(self):
203 return None
205 def get_container(self):
206 return None
208 def is_deployed(self, site):
209 try:
210 site = self.sites.get(id=site.id)
211 except Site.DoesNotExist:
212 return False
213 else:
214 return True
216 def deploy(self, site, deployed_date=None):
217 """
218 Warning: As of July 2018, only 1 site id is stored in a SolR document
219 Although the SolR schema is already OK to store multiple sites ("sites" is an array)
220 no Solr commands have been written to add/remove sites
221 We only have add commands.
222 Search only works if the Solr instance is meant for individual or ALL sites
224 :param site:
225 :param deployed_date:
226 :return:
227 """
228 if deployed_date is None:
229 deployed_date = timezone.now()
230 try:
231 membership = SiteMembership.objects.get(resource=self, site=site)
232 membership.deployed = deployed_date
233 membership.save()
234 except SiteMembership.DoesNotExist:
235 membership = SiteMembership(resource=self, site=site, deployed=deployed_date)
236 membership.save()
238 def undeploy(self, site):
239 try:
240 membership = SiteMembership.objects.get(resource=self, site=site)
241 except SiteMembership.DoesNotExist:
242 pass
243 else:
244 membership.delete()
246 def date_time_deployed(self, site):
247 try:
248 membership = SiteMembership.objects.get(resource=self, site=site)
249 except SiteMembership.DoesNotExist:
250 return None
251 return membership.deployed
253 def deployed_date(self, site=None):
254 if site is None and settings.SITE_NAME == "ptf_tools":
255 # on est sur ptf-tools et dans un template on fait appel à deployed_date
256 # si le site lié à la collection est créé on renvoie la date de déploiement sur ce site
257 # sinon None
258 from ptf import model_helpers
260 site = model_helpers.get_site_mersenne(self.get_collection().pid)
261 return self.date_time_deployed(site)
262 if not site:
263 site = Site.objects.get_current()
265 return self.date_time_deployed(site)
267 def get_id_value(self, id_type):
268 try:
269 rid = self.resourceid_set.get(id_type=id_type)
270 except ResourceId.DoesNotExist:
271 return None
272 else:
273 return rid.id_value
275 # TODO : doi is in ResourceId and in Resource ? maybe use only one ...
276 def get_doi_href(self):
277 href = None
278 if self.doi:
279 href = resolver.get_doi_url(self.doi)
280 return href
282 def get_link(self, link_type):
283 href = None
284 for link in self.extlink_set.all():
285 if link.rel == link_type:
286 href = link.get_href()
288 return href
290 def website(self):
291 return self.get_link("website")
293 def test_website(self):
294 return self.get_link("test_website")
296 def icon(self):
297 return self.get_link("icon")
299 def small_icon(self):
300 return self.get_link("small_icon")
302 # def relation_names(self):
303 # names = set()
304 # for rel in self.subject_of.select_related('rel_info').all():
305 # name = rel.rel_info.left
306 # names.add(name)
307 # for rel in self.object_of.select_related('rel_info').all():
308 # name = rel.rel_info.right
309 # names.add(name)
310 # return names
311 #
312 # def get_related(self, rel_type, count_only=True):
313 # is_subject = False
314 # try:
315 # rel = RelationName.objects.get(left=rel_type)
316 # except RelationName.DoesNotExist:
317 # try:
318 # rel = RelationName.objects.get(right=rel_type)
319 # except RelationName.DoesNotExist:
320 # raise UnknownRelationType(rel_type)
321 # else:
322 # pass
323 # else:
324 # is_subject = True
325 # if is_subject:
326 # qs = self.subject_of.filter(rel_info=rel)
327 # if count_only:
328 # return qs.count()
329 # result = [x.related.cast() for x in qs]
330 # else:
331 # qs = self.object_of.filter(rel_info=rel)
332 # if count_only:
333 # return qs.count()
334 # result = [x.resource.cast() for x in qs]
335 # return result
337 def is_edited_book(self):
338 return False
340 # NOTE - 12/08/2017 - Basile
341 # utilisé nul part à delete ?
342 # def has_errata(self):
343 # return self.get_related('corrected-by')
345 # def is_erratum_to(self):
346 # return self.get_related('corrects', count_only=False)
348 # def errata(self):
349 # return self.get_related('corrected-by', count_only=False)
351 # def erratum(self):
352 # errata = self.get_related('corrected-by', count_only=False)
353 # if len(errata) == 1:
354 # return errata[0]
355 # return None
357 # def questions(self):
358 # return self.get_related('resolves', count_only=False)
360 # def solutions(self):
361 # return self.get_related('resolved-by', count_only=False)
363 # def complements(self):
364 # return self.get_related('complements', count_only=False)
366 # def completed(self):
367 # return self.get_related('complemented-by', count_only=False)
369 # def follows(self):
370 # followed = self.get_related('follows', count_only=False)
371 # if len(followed) == 1:
372 # return followed[0]
373 # return 0
375 # def followed(self):
376 # follows = self.get_related('followed-by', count_only=False)
377 # if len(follows) == 1:
378 # return follows[0]
379 # return 0
381 # def citations_count(self):
382 # if not self.pid:
383 # return 0
384 # qs = BibItemId.objects.select_related().filter(
385 # id_type=self.provider.pid_type,
386 # id_value=self.pid,
387 # bibitem__resource__sites__id=settings.SITE_ID
388 # )
389 # return qs.count()
391 def citations(self):
392 if not self.pid or not self.provider:
393 return []
395 qs = BibItemId.objects.select_related().filter(
396 id_type=self.provider.pid_type,
397 id_value=self.pid,
398 bibitem__resource__sites__id=settings.SITE_ID,
399 )
400 # on ne peut pas trier par date sur la requete car
401 # on peut avoir soit des containers soit des articles
402 # comme resource et year est sur le container uniquement
403 result = [bid.bibitem.resource.cast() for bid in qs]
404 result.sort(key=lambda item: item.get_year(), reverse=True)
405 return result
407 def get_contributions(self, role):
408 # prefetch probably has already queried the database for the contributions
409 # Using filter on self.contributions would result in a separate SQL query
410 return [
411 contribution
412 for contribution in self.contributions.all()
413 if contribution.role.find(role) == 0
414 ]
416 def get_author_contributions(self, strict=True):
417 authors = self.get_contributions("author")
418 if not strict and len(authors) == 0:
419 authors = self.get_editors()
420 return authors
422 def get_authors_short(self):
423 authors = self.get_contributions("author")
424 if len(authors) > 2:
425 authors = "; ".join([str(author) for author in authors[:2]])
426 authors += " <i>et al.</i>"
427 return authors
428 return "; ".join([str(author) for author in authors])
430 def get_editors(self):
431 return self.get_contributions("editor")
433 def get_contributors(self):
434 return self.get_contributions("contributor")
436 def get_redaktors(self):
437 return self.get_contributions("redaktor")
439 def get_organizers(self):
440 return self.get_contributions("organizer")
442 def get_presenters(self):
443 return self.get_contributions("presenter")
445 def get_kwds_by_type(self):
446 msc = [kwd for kwd in self.kwd_set.all() if kwd.type == "msc"]
447 kwds = [kwd for kwd in self.kwd_set.all() if kwd.type != "msc" and kwd.lang == self.lang]
448 trans_kwds = [
449 kwd for kwd in self.kwd_set.all() if kwd.type != "msc" and kwd.lang != self.lang
450 ]
451 return msc, kwds, trans_kwds
453 def get_subjs_by_type_and_lang(self):
454 subjs = {}
455 for subj in self.subj_set.all():
456 if subj.type in subjs:
457 if subj.lang in subjs[subj.type]:
458 subjs[subj.type][subj.lang].append(subj)
459 else:
460 subjs[subj.type][subj.lang] = [subj]
461 else:
462 subjs[subj.type] = {subj.lang: [subj]}
464 return subjs
466 def self_uris_no_xml(self):
467 """
468 Returns a list of links to the datastream of the resource (= pdf/djvu of the resource)
469 This function is only used to export an xml (oai)
470 HTML templates use get_binary_files_href (see below)
471 """
472 links = [
473 {
474 "mimetype": link.mimetype,
475 "full_path": self.get_binary_file_href_full_path(
476 "self", link.mimetype, link.location
477 ),
478 "link": link.text,
479 }
480 for link in self.datastream_set.all()
481 ]
482 return links
484 @staticmethod
485 def append_href_to_binary_files(binary_files, key, mimetype, href):
486 if mimetype == "application/pdf":
487 if key in binary_files: 487 ↛ 488line 487 didn't jump to line 488, because the condition on line 487 was never true
488 binary_files[key]["pdf"] = href
489 else:
490 binary_files[key] = {"pdf": href}
491 elif mimetype == "image/x.djvu": 491 ↛ 492line 491 didn't jump to line 492, because the condition on line 491 was never true
492 if key in binary_files:
493 binary_files[key]["djvu"] = href
494 else:
495 binary_files[key] = {"djvu": href}
496 elif mimetype == "application/x-tex":
497 if key in binary_files: 497 ↛ 500line 497 didn't jump to line 500, because the condition on line 497 was never false
498 binary_files[key]["tex"] = href
499 else:
500 binary_files[key] = {"tex": href}
501 elif mimetype == "video": 501 ↛ 502line 501 didn't jump to line 502, because the condition on line 501 was never true
502 if key in binary_files:
503 binary_files[key]["video"] = href
504 else:
505 binary_files[key] = {"video": href}
507 def get_binary_files_location(self):
508 binary_files = []
510 for obj in self.extlink_set.all():
511 if obj.rel == "icon" or obj.rel == "small_icon":
512 binary_files.append(obj.location)
514 for obj in self.relatedobject_set.all():
515 if obj.rel != "video" and obj.rel != "html-image": 515 ↛ 516line 515 didn't jump to line 516, because the condition on line 515 was never true
516 binary_files.append(obj.location)
518 for obj in self.datastream_set.all():
519 binary_files.append(obj.location)
521 if hasattr(self, "translations"):
522 for translated_article in self.translations.all(): 522 ↛ 523line 522 didn't jump to line 523, because the loop on line 522 never started
523 binary_files.extend(translated_article.get_binary_files_location())
525 return binary_files
527 def get_binary_files_href(self):
528 """
529 Get all the HREFs (without http://site) of the binary files (pdf/djvu) related to the resource
530 Result: { 'self': {'pdf':href, 'djvu':href, 'tex': href},
531 'toc':{'pdf':href, 'djvu':href},
532 'frontmatter':{'pdf':href, 'djvu':href},
533 'backmatter':{'pdf':href, 'djvu':href},
534 'translations': {<lang>: {'pdf':href,...}} }
535 result['self'] is the main pdf/djvu of the resource (article pdf,
536 full volume pdf, book part pdf)
537 The information come from the DataStream
538 The other results come from RelatedObject
539 """
540 binary_files = {}
542 for related_object in self.relatedobject_set.all():
543 key = related_object.rel
544 mimetype = related_object.mimetype
545 location = related_object.location
546 href = self.get_binary_file_href_full_path(key, mimetype, location)
548 self.append_href_to_binary_files(binary_files, key, mimetype, href)
550 allow_local_pdf = not hasattr(settings, "ALLOW_LOCAL_PDF") or settings.ALLOW_LOCAL_PDF
552 for stream in self.datastream_set.all():
553 key = "self"
554 mimetype = stream.mimetype
555 location = stream.location
557 if allow_local_pdf or mimetype != "application/pdf": 557 ↛ 552line 557 didn't jump to line 552, because the condition on line 557 was never false
558 if location.find("http") == 0: 558 ↛ 559line 558 didn't jump to line 559, because the condition on line 558 was never true
559 href = location
560 else:
561 href = self.get_binary_file_href_full_path("self", mimetype, location)
563 self.append_href_to_binary_files(binary_files, key, mimetype, href)
565 if not allow_local_pdf: 565 ↛ 566line 565 didn't jump to line 566, because the condition on line 565 was never true
566 qs = self.extlink_set.filter(rel="article-pdf")
567 if qs:
568 extlink = qs.first()
569 href = extlink.location
570 key = "self"
571 mimetype = "application/pdf"
573 self.append_href_to_binary_files(binary_files, key, mimetype, href)
575 translations = {}
576 if hasattr(self, "translations"):
577 for trans_article in self.translations.all(): 577 ↛ 578line 577 didn't jump to line 578, because the loop on line 577 never started
578 result = trans_article.get_binary_files_href()
579 if "self" in result:
580 translations[trans_article.lang] = result["self"]
582 binary_files["translations"] = translations
584 return binary_files
586 def get_binary_file_href_full_path(self, doctype, mimetype, location):
587 """
588 Returns an encoded URL to a pdf/djvu
589 URLs in HTML pages do not return the exact path to a file (for safety reason)
590 An encode URL is used instead
591 Ex: URL to an article pdf: article/pid.pdf
592 URL to an issue full pdf: issue/pid.pdf
593 URL to an issue toc djvu: issue/toc/pid.pdf
594 URL to an issue frontmatter: issue/frontmatter.pid.djvu
595 When you click on such a link, ptf/views.py will decode the URL in get_pdf
596 and use the DataStream/RelatedObject true location to return the file
597 Input: doctype: 'self', 'toc', 'frontmatter', 'backmatter'
598 mimetype: 'application/pdf', 'image/x.djvu'
600 Ex: /article/ALCO_2018__1_1_23_0.pdf
601 /issue/MSMF_1978__55-56__1_0.pdf
602 /issue/toc/CSHM_1980__1_.pdf
603 /article/ALCO_2018__1_1_23_0/tex/src/tex/ALCO_Thiem_31.tex
605 """
606 if self.embargo():
607 return ""
609 pid = self.pid
610 doi = getattr(self, "doi")
611 if doi is not None:
612 pid = doi
614 prefix = doctype if doctype != "self" else ""
616 if "annexe" in location: 616 ↛ 620line 616 didn't jump to line 620, because the condition on line 616 was never true
617 # 04/08/2021. Not used anymore ?
619 # Ex: /article/SMAI-JCM_2015__1__83_0/attach/Allaire-Dapogny-supp.pdf
620 href = reverse(
621 "annexe-pdf", kwargs={"pid": pid, "relative_path": location.split("/")[-1]}
622 )
624 elif mimetype in ["application/pdf", "image/x.djvu"] and doctype not in [
625 "review",
626 "supplementary-material",
627 ]:
628 extension = "pdf" if mimetype == "application/pdf" else "djvu"
629 if len(prefix) > 0:
630 href = reverse(
631 "issue-relatedobject-pdf",
632 kwargs={"pid": pid, "extension": extension, "binary_file_type": prefix},
633 )
634 else:
635 href = reverse("item-pdf", kwargs={"pid": pid, "extension": extension})
637 elif mimetype == "application/x-tex":
638 to_find = "/src/tex/"
639 i = location.find(to_find)
640 if i > 0:
641 location = location[i + 1 :]
643 href = reverse("article-binary-files", kwargs={"pid": pid, "relative_path": location})
645 else:
646 # All other attachments (videos, zip, etc...) :
648 to_find = "/attach/"
649 i = location.find(to_find)
650 if i > 0: 650 ↛ 651line 650 didn't jump to line 651, because the condition on line 650 was never true
651 location = location[i + 1 :]
653 # Ex: /article/ALCO_2018__1_1_23_0/file/review_history.pdf
654 href = reverse("article-binary-files", kwargs={"pid": pid, "relative_path": location})
656 return href
658 def get_binary_disk_location(self, doctype, mimetype, relativepath):
659 """
660 Returns the path of a binary file (pdf/djvu) on the file server
661 This function is called when you click on a link like issue/frontmatter/pid.pdf
663 URLs in HTML pages do not return the exact path to a file (for safety reason)
664 An encoded URL is used instead (see get_binary_file_href_full_path above)
665 Ex: URL to an issue toc djvu: issue/toc/pid.pdf
667 When you click on such a link, ptf/views.py will decode the URL in get_pdf
668 before calling get_binary_disk_location to get the exact pdf/djvu disk location
669 based on the DataStream/RelatedObject location
671 Input: doctype: 'self', 'toc', 'frontmatter', 'backmatter'
672 mimetype: 'application/pdf', 'image/x.djvu'
673 relativepath: Ex: 'src/tex/ALCO_Thiem_31.tex'
675 Returns value: path or None if there is an embargo
676 May raise exceptions.ResourceDoesNotExist if the resource
677 (RelatedObject/DataStream) does not exist
678 """
679 if self.embargo():
680 return None
682 filename = None
684 if ( 684 ↛ 689line 684 didn't jump to line 689
685 doctype not in ["self", "toc", "frontmatter", "backmatter"]
686 and len(doctype) == 2
687 and hasattr(self, "translations")
688 ):
689 for translated_article in self.translations.all():
690 if translated_article.lang == doctype:
691 filename = translated_article.get_binary_disk_location(
692 "self", mimetype, relativepath
693 )
694 elif relativepath: 694 ↛ 696line 694 didn't jump to line 696, because the condition on line 694 was never true
695 # relative path are used with supplementary materials or TeX Source file
696 filename = os.path.join(self.get_relative_folder(), relativepath)
697 else:
698 if doctype != "self":
699 try:
700 obj = self.relatedobject_set.filter(mimetype=mimetype, rel=doctype).get()
702 except RelatedObject.DoesNotExist:
703 # status = 404
704 raise exceptions.ResourceDoesNotExist("The binary file does not exist")
705 else:
706 try:
707 obj = self.datastream_set.get(mimetype=mimetype)
709 except DataStream.DoesNotExist:
710 # status = 404
711 raise exceptions.ResourceDoesNotExist("The binary file does not exist")
712 filename = obj.location
714 return filename
716 def get_abstracts(self):
717 return self.abstract_set.filter(tag__endswith="abstract")
719 def get_avertissements(self):
720 return self.abstract_set.filter(tag="avertissement")
722 def get_descriptions(self):
723 return self.abstract_set.filter(tag="description")
725 def get_editorial_intros(self):
726 return self.abstract_set.filter(tag__endswith="intro")
728 def get_toc(self):
729 return self.abstract_set.filter(tag__endswith="toc")
731 def visit(self, visitor):
732 return visitor.visit(self.cast())
734 def natural_key(self):
735 return (self.pid, self.provider.id)
737 def get_solr_body(self, field):
738 from ptf.cmds import solr_cmds
740 body = None
741 cmd = solr_cmds.solrGetDocumentByPidCmd({"pid": self.pid})
742 result = cmd.do()
743 if result:
744 if field in result: 744 ↛ 747line 744 didn't jump to line 747, because the condition on line 744 was never false
745 body = result[field]
747 return body
749 def get_body(self):
750 return self.get_solr_body("body")
752 def volume_string(self):
753 return ""
755 def update_bibtex_with_commons(self, bibtex, container, hostname, scheme, indent):
756 # TODO chapter, howpublished?
757 # TODO institution
758 # if self.institution:
759 # append_in_latex(bibtex, indent + 'institution = {' + self.institution + '},' )
761 to_appear = container.to_appear()
762 is_cr = container.is_cr()
764 # pages = self.pages()
765 publisher = container.my_publisher
767 if publisher is not None:
768 if publisher.pub_name: 768 ↛ 770line 768 didn't jump to line 770, because the condition on line 768 was never false
769 append_in_latex(bibtex, indent + "publisher = {" + publisher.pub_name + "},")
770 if publisher.pub_loc:
771 append_in_latex(bibtex, indent + "address = {" + publisher.pub_loc + "},")
773 if not to_appear:
774 if container.volume:
775 append_in_latex(bibtex, indent + "volume = {" + self.volume_string() + "},")
776 if container.number and not (is_cr and container.number[0] == "G"):
777 append_in_latex(bibtex, indent + "number = {" + container.number + "},")
779 if container.year != "0": 779 ↛ 781line 779 didn't jump to line 781, because the condition on line 779 was never false
780 append_in_latex(bibtex, indent + "year = {" + container.year + "},")
781 elif hasattr(self, "date_online_first") and self.date_online_first is not None:
782 year = self.date_online_first.strftime("%Y")
783 append_in_latex(bibtex, indent + "year = {" + year + "},")
785 if self.doi:
786 append_in_latex(bibtex, indent + "doi = {" + self.doi + "},")
788 ext_type = {"zbl-item-id": "zbl", "mr-item-id": "mrnumber"}
789 for extid in self.extid_set.filter(id_type__in=["zbl-item-id", "mr-item-id"]):
790 append_in_latex(
791 bibtex, indent + ext_type.get(extid.id_type) + " = {" + extid.id_value + "},"
792 )
794 if self.lang and self.lang != "und":
795 append_in_latex(bibtex, indent + "language = {" + self.lang + "},")
797 if to_appear:
798 append_in_latex(bibtex, indent + "note = {Online first},")
799 elif not is_cr and len(hostname) > 0:
800 ## No latex encoding, so append directly in the array
801 url = f"{scheme}://{hostname}{self.get_absolute_url()}"
802 bibtex.append("{}url = {}{}{}".format(indent, "{", url, "}"))
804 def update_ris_with_commons(self, items, container, hostname, scheme, sep):
805 to_appear = container.to_appear()
806 is_cr = container.is_cr()
808 if container.year != "0": 808 ↛ 810line 808 didn't jump to line 810, because the condition on line 808 was never false
809 items.append("PY" + sep + container.year)
810 elif hasattr(self, "date_online_first") and self.date_online_first is not None:
811 year = self.date_online_first.strftime("%Y")
812 items.append("PY" + sep + year)
814 if not to_appear:
815 if hasattr(self, "pages") and callable(self.pages) and self.pages():
816 pages = self.pages(for_bibtex=False).split("-")
817 items.append("SP" + sep + pages[0])
818 if len(pages) > 1: 818 ↛ 820line 818 didn't jump to line 820, because the condition on line 818 was never false
819 items.append("EP" + sep + pages[1])
820 if container.volume:
821 items.append("VL" + sep + container.volume)
822 if container.number and not (is_cr and container.number[0] == "G"): 822 ↛ 825line 822 didn't jump to line 825, because the condition on line 822 was never false
823 items.append("IS" + sep + container.number)
825 publisher = container.my_publisher
826 if publisher is not None:
827 if publisher.pub_name: 827 ↛ 829line 827 didn't jump to line 829, because the condition on line 827 was never false
828 items.append("PB" + sep + publisher.pub_name)
829 if publisher.pub_loc: 829 ↛ 832line 829 didn't jump to line 832, because the condition on line 829 was never false
830 items.append("PP" + sep + publisher.pub_loc)
832 if to_appear:
833 items.append("N1" + sep + "Online first")
834 elif not is_cr and len(hostname) > 0:
835 url = f"{scheme}://{hostname}{self.get_absolute_url()}"
836 items.append("UR" + sep + url)
838 if self.doi:
839 items.append("DO" + sep + self.doi)
841 if self.lang and self.lang != "und":
842 items.append("LA" + sep + self.lang)
844 if self.pid: 844 ↛ 846line 844 didn't jump to line 846, because the condition on line 844 was never false
845 items.append("ID" + sep + self.pid)
846 items.append("ER" + sep)
848 def update_endnote_with_commons(self, items, container, hostname, scheme, sep):
849 to_appear = container.to_appear()
850 is_cr = container.is_cr()
852 if container.year != "0": 852 ↛ 854line 852 didn't jump to line 854, because the condition on line 852 was never false
853 items.append("%D" + sep + container.year)
854 elif hasattr(self, "date_online_first") and self.date_online_first is not None:
855 year = self.date_online_first.strftime("%Y")
856 items.append("%D" + sep + year)
858 if not to_appear:
859 if hasattr(self, "pages") and callable(self.pages) and self.pages():
860 pages = self.pages(for_bibtex=False)
861 items.append("%P" + sep + pages)
862 if container.volume:
863 items.append("%V" + sep + container.volume)
864 if container.number and not (is_cr and container.number[0] == "G"): 864 ↛ 867line 864 didn't jump to line 867, because the condition on line 864 was never false
865 items.append("%N" + sep + container.number)
867 publisher = container.my_publisher
868 if publisher is not None:
869 if publisher.pub_name: 869 ↛ 871line 869 didn't jump to line 871, because the condition on line 869 was never false
870 items.append("%I" + sep + publisher.pub_name)
871 if publisher.pub_loc: 871 ↛ 874line 871 didn't jump to line 874, because the condition on line 871 was never false
872 items.append("%C" + sep + publisher.pub_loc)
874 if to_appear:
875 items.append("%Z" + sep + "Online first")
876 elif not is_cr and len(hostname) > 0:
877 url = f"{scheme}://{hostname}{self.get_absolute_url()}"
878 items.append("%U" + sep + url)
880 if self.doi:
881 items.append("%R" + sep + self.doi)
883 if self.lang and self.lang != "und":
884 items.append("%G" + sep + self.lang)
886 if self.pid: 886 ↛ exitline 886 didn't return from function 'update_endnote_with_commons', because the condition on line 886 was never false
887 items.append("%F" + sep + self.pid)
890class PublisherQuerySet(models.QuerySet):
891 def get_by_natural_key(self, pub_key, pub_name):
892 return self.get(pub_key=pub_key)
895class Publisher(Resource):
896 """
897 les classes Publisher, EventSeries et Event sont un peu à part:
898 a priori pas de pid ni sid et même pas d'identificateur du tout
899 d'où les slugs
900 On peut les sortir de la hiérarchie resource -- les y laisser
901 permet de leur attache des meta suppléméntaires. Voir Provider
902 pour la possibilité inverse :-)
903 """
905 pub_key = models.CharField(max_length=128, unique=True)
906 pub_name = models.CharField(max_length=256, db_index=True)
907 pub_loc = models.CharField(max_length=128, db_index=True)
909 # 2016-05-18: Publisher is only used by Container: ManyToOne relation
910 # publishes = models.ManyToManyField(Resource, related_name='Publisher')
912 def __str__(self):
913 return self.pub_key
915 @staticmethod
916 def get_collection():
917 return None
919 @staticmethod
920 def get_top_collection():
921 return None
923 @staticmethod
924 def get_container():
925 return None
927 def natural_key(self):
928 return (
929 self.pub_key,
930 self.pub_name,
931 )
933 objects = PublisherQuerySet.as_manager()
936class CollectionQuerySet(models.QuerySet):
937 def order_by_date(self):
938 return self.annotate(year=Max("content__year")).order_by("year")
940 def get_by_natural_key(self, pid, provider):
941 return self.get(pid=pid, provider=provider)
944class Collection(Resource):
945 # journal, acta, book-series, these, lectures
946 coltype = models.CharField(max_length=32, db_index=True)
947 title_sort = models.CharField(max_length=128, db_index=True) # sort key, not displayed
948 issn = Identifier("issn")
949 e_issn = Identifier("e-issn")
950 wall = models.IntegerField(default=5)
951 alive = models.BooleanField(default=True)
952 # First/Last year of a collection. Comes from its containers. Is typically
953 # updated when a container is imported.
954 fyear = models.IntegerField(default=0)
955 lyear = models.IntegerField(default=0)
956 last_doi = models.IntegerField(default=0)
958 # Ancestors means journals that existed before the creation of the Collection: time-based relation
959 # (the name 'ancestor' does not fit with 'parent' as 'parent' refers to a tree structure)
960 # We don't really create a tree.
961 # Ex: AFST is the root journal. AFST-0996-0481 is the original journal that became AFST-0240-2955 that became AFST.
962 # We create a top node (AFST) and 2 ancestors (AFST-0996-0481, AFST-0240-2955)
963 parent = models.ForeignKey(
964 "self", on_delete=models.CASCADE, null=True, blank=True, related_name="ancestors"
965 )
966 objects = CollectionQuerySet.as_manager()
968 class Meta:
969 ordering = ["title_sort"]
971 # This function is used by Django: https://docs.djangoproject.com/fr/3.0/ref/models/instances/
972 # Unfortunately, the name is wrong: get_absolute_url must return a relative path !?!
973 # => hence the get_url_absolute below that returns an absolute URL
974 def get_absolute_url(self):
975 if self.coltype == "thesis":
976 url = reverse("pretty_thesis", args=[f'"{self.title_html}"-p'])
977 elif self.coltype == "book-series":
978 url = reverse("pretty-series", args=[f'"{self.title_html}"-p'])
979 elif self.coltype == "lectures" or self.coltype == "lecture-notes":
980 url = reverse("pretty-lectures", args=[f'"{self.title_html}"-p'])
981 else:
982 url = reverse(self.coltype + "-issues", kwargs={"jid": self.pid})
983 return url
985 def bloodline(self):
986 # returns parent + siblings, ordered by date (of the last volume published)
987 if self.parent or self.ancestors.exists():
988 parent_pid = self.parent.pid if self.parent else self.pid
989 return Collection.objects.filter(
990 Q(pid=parent_pid) | Q(parent__pid=parent_pid)
991 ).order_by_date()
992 return Collection.objects.none()
994 def preceding_journal(self):
995 # returns my ancestor (timed-based) = the Collection that was published just before me
996 bloodline = self.bloodline()
997 for index, collection in enumerate(bloodline):
998 if collection == self and index:
999 return bloodline[index - 1]
1000 return Collection.objects.none()
1002 def get_wall(self):
1003 return self.wall
1005 def get_collection(self):
1006 return self
1008 def get_top_collection(self):
1009 return self.parent if self.parent else self
1011 def deployed_date(self, site=None):
1012 # return the last date of metadata deployed date for all of containers
1013 containers = self.content.all()
1014 date = None
1015 if containers:
1016 site = Site.objects.get_current()
1017 sms = (
1018 SiteMembership.objects.filter(resource__in=containers)
1019 .filter(site=site)
1020 .order_by("-deployed")
1021 )
1022 date = sms.first().deployed
1023 return date
1025 def get_relative_folder(self):
1026 return resolver.get_relative_folder(self.get_top_collection().pid)
1029class ContainerQuerySet(ResourceQuerySet):
1030 def prefetch_all(self):
1031 return super().prefetch_all().select_related("my_collection", "my_publisher")
1033 def prefetch_for_toc(self):
1034 return (
1035 self.prefetch_contributors()
1036 .prefetch_work()
1037 .prefetch_related(
1038 "article_set__datastream_set",
1039 "article_set__subj_set",
1040 "article_set__extlink_set",
1041 "article_set__resourcecount_set",
1042 "article_set__contributions",
1043 )
1044 .select_related("my_collection", "my_publisher")
1045 )
1048class Container(Resource):
1049 """
1050 mappe issue et book (on pourrait faire deux classes) ou une hiérarchie
1051 container <--- issue
1052 <--- book
1053 """
1055 # issue, book-monograph, book-edited-book (multiple authors, with editors), lecture-notes
1056 ctype = models.CharField(max_length=32, db_index=True)
1057 year = models.CharField(max_length=32, db_index=True)
1059 last_modified = models.DateTimeField(null=False, blank=False)
1060 ##
1061 # if ctype == issue
1062 number = models.CharField(max_length=32, db_index=True) # issue number
1063 ##
1064 # data for relation with enclosing serial, if any
1065 my_collection = models.ForeignKey(
1066 Collection, null=True, related_name="content", on_delete=models.CASCADE
1067 )
1068 vseries = models.CharField(max_length=32, db_index=True)
1069 volume = models.CharField(max_length=64, db_index=True)
1070 # la même chose pour le tri
1071 vseries_int = models.IntegerField(default=0, db_index=True)
1072 volume_int = models.IntegerField(default=0, db_index=True)
1073 number_int = models.IntegerField(default=0, db_index=True)
1074 seq = models.IntegerField(db_index=True)
1075 with_online_first = models.BooleanField(default=False) # Used by ptf-tools only
1077 my_publisher = models.ForeignKey(
1078 Publisher, related_name="publishes", null=True, on_delete=models.CASCADE
1079 )
1081 # Initially, a container could only be in 1 collection.
1082 # In 2018, we extended the model so that a container can be
1083 # in multiple collections (Bourbaki and Asterisque)
1084 #
1085 # This is an exception, most containers are in only 1 collection.
1086 # my_collection is kept to store the main collection, the one used in the "how to cite" field.
1087 # my_other_collections stores the additional collections.
1088 #
1089 # vseries/volume/number/seq of the main collection are kept in Container.
1090 # As a result, there is no need to fetch the CollectionMembership to get
1091 # these info for the main collection.
1092 my_other_collections = models.ManyToManyField(
1093 Collection, symmetrical=False, through="CollectionMembership"
1094 )
1096 objects = ContainerQuerySet.as_manager()
1098 class Meta:
1099 ordering = ["seq"]
1100 get_latest_by = ["year", "vseries_int", "volume_int", "number_int"]
1102 def allow_crossref(self):
1103 # we need at least a doi or an issn to allow crossref record
1104 result = self.my_collection.doi or self.my_collection.issn or self.my_collection.e_issn
1106 # if there are unpublished articles in the volume, we block crossref record
1107 if self.article_set.filter( 1107 ↛ 1110line 1107 didn't jump to line 1110, because the condition on line 1107 was never true
1108 date_published__isnull=True, date_online_first__isnull=True
1109 ).exists():
1110 result = False
1112 return result
1114 def all_doi_are_registered(self):
1115 if self.article_set.count() > 0:
1116 # il y a des articles, on vérifie qu'ils sont tous enregistrés
1117 return (
1118 self.article_set.filter(doibatch__status="Enregistré").count()
1119 == self.article_set.count()
1120 )
1121 if self.doi and self.doibatch is not None:
1122 return self.doibatch.status == "Enregistré"
1123 # aucun doi associé aux articles ou au container
1124 return False
1126 def registered_in_doaj(self):
1127 query = Q(date_published__isnull=True, date_online_first__isnull=True) | Q(
1128 do_not_publish__isnull=True
1129 )
1130 unpublished = self.article_set.filter(query).count()
1131 registered = self.article_set.filter(doajbatch__status="registered").count()
1132 all_registered = True if registered == self.article_set.count() - unpublished else False
1133 if not all_registered and hasattr(self, "doajbatch"):
1134 self.doajbatch.status = "unregistered"
1135 self.doajbatch.save()
1137 def are_all_articles_published(self):
1138 from ptf import model_helpers
1140 result = True
1142 for article in self.article_set.all():
1143 year = article.get_year()
1144 fyear, lyear = model_helpers.get_first_last_years(year)
1145 try:
1146 fyear = int(fyear)
1147 except ValueError:
1148 fyear = 0
1150 if fyear > 2017:
1151 if not article.date_published: 1151 ↛ 1152line 1151 didn't jump to line 1152, because the condition on line 1151 was never true
1152 result = False
1153 elif fyear == 0:
1154 result = False
1156 return result
1158 def get_wall(self):
1159 return self.my_collection.get_wall()
1161 def previous(self):
1162 issue = None
1163 qs = self.my_collection.content.filter(seq=self.seq - 1)
1164 if qs.count() > 0:
1165 issue = qs.first()
1166 return issue
1168 def next(self):
1169 issue = None
1170 qs = self.my_collection.content.filter(seq=self.seq + 1)
1171 if qs.count() > 0:
1172 issue = qs.first()
1173 return issue
1175 # TODO container in multiple collections
1176 def get_collection(self):
1177 return self.my_collection
1179 def get_top_collection(self):
1180 return self.my_collection.get_top_collection()
1182 def get_other_collections(self):
1183 return self.my_other_collections.all()
1185 def get_container(self):
1186 return self
1188 def get_volume(self):
1189 return self.volume
1191 def get_number(self):
1192 return self.number
1194 def embargo(self):
1195 return resolver.embargo(self.get_wall(), self.year)
1197 def to_appear(self):
1198 return self.with_online_first or (
1199 hasattr(settings, "ISSUE_TO_APPEAR_PID") and settings.ISSUE_TO_APPEAR_PID == self.pid
1200 )
1202 def is_cr(self):
1203 return (
1204 hasattr(settings, "SITE_NAME")
1205 and len(settings.SITE_NAME) == 6
1206 and settings.SITE_NAME[0:2] == "cr"
1207 )
1209 @staticmethod
1210 def get_base_url():
1211 return resolver.get_issue_base_url()
1213 def get_relative_folder(self):
1214 collection = self.get_top_collection()
1215 return resolver.get_relative_folder(collection.pid, self.pid)
1217 def get_vid(self):
1218 """
1219 08/09/2022: support of Collection ancestors
1220 The collection.pid might no longer be the top_collection.pid
1221 => The volume URL would change if we were to keep the same vid
1222 To keep URLs relatively similar, we use the pid of the first_issue of the volume
1224 VolumeDetailView (ptf/views.py) handles the vid differently and no longer decrypts the vid
1225 """
1226 # vid = f"{self.my_collection.pid}_{self.year}_{self.vseries}_{self.volume}"
1227 vid = self.pid
1228 return vid
1230 def get_year(self):
1231 return self.year
1233 def is_edited_book(self):
1234 return self.ctype == EDITED_BOOK_TYPE
1236 def get_citation(self, request):
1237 citation = ""
1239 author_names = get_names(self, "author")
1240 authors = ""
1241 if author_names:
1242 authors = "; ".join(author_names)
1244 if not author_names or authors == "Collectif":
1245 author_names = get_names(self, "editor")
1246 if author_names:
1247 authors = "; ".join(author_names) + " (" + str(_("éd.")) + ")"
1248 else:
1249 authors = ""
1251 if authors != "Collectif": 1251 ↛ 1254line 1251 didn't jump to line 1254, because the condition on line 1251 was never false
1252 citation += authors
1254 if citation:
1255 if not citation.endswith("."): 1255 ↛ 1257line 1255 didn't jump to line 1257, because the condition on line 1255 was never false
1256 citation += "."
1257 citation += " "
1259 citation += self.title_tex + ". "
1260 citation += self.my_collection.title_tex
1262 if self.vseries:
1263 citation += f", {str(_('Série'))} {self.vseries}"
1265 if self.volume:
1266 citation += ", " + str(volume_display()) + " " + self.volume + " (" + self.year + ") "
1268 if self.number: 1268 ↛ 1275line 1268 didn't jump to line 1275, because the condition on line 1268 was never false
1269 citation += "no. " + self.number + ", "
1270 elif self.number:
1271 citation += ", no. " + self.number + " (" + self.year + "), "
1272 else:
1273 citation += " (" + self.year + "), "
1275 redactors = self.get_redaktors()
1276 if len(redactors) > 0:
1277 redactors_str = "; ".join(get_names(self, "redaktor"))
1278 citation += f"{redactors_str} (red.), "
1280 for pages in self.resourcecount_set.all():
1281 citation += pages.value + " p."
1283 for resourceid in self.resourceid_set.all():
1284 if resourceid.id_type == "doi": 1284 ↛ 1283line 1284 didn't jump to line 1283, because the condition on line 1284 was never false
1285 citation += " doi : " + resourceid.id_value + "."
1287 citation += " " + self.get_url_absolute()
1289 return citation
1291 def has_detailed_info(self):
1292 # Ignore citations here.
1294 result = False
1296 if self.extid_set.exists(): 1296 ↛ 1297line 1296 didn't jump to line 1297, because the condition on line 1296 was never true
1297 result = True
1298 elif self.kwd_set.exists(): 1298 ↛ 1299line 1298 didn't jump to line 1299, because the condition on line 1298 was never true
1299 result = True
1300 else:
1301 for resourceid in self.resourceid_set.all():
1302 if resourceid.id_type != "numdam-prod-id": 1302 ↛ 1301line 1302 didn't jump to line 1301, because the condition on line 1302 was never false
1303 result = True
1305 return result
1307 def get_bibtex(self, request):
1308 """
1310 :param self:
1311 :return: a string encoded in latex (with latexcodec)
1312 """
1313 bibtex = []
1314 indent = " "
1316 collection = self.get_collection()
1318 is_phdthesis = False
1320 # no bibtex for an issue, only for a book (book, these)
1321 if self.ctype == "issue":
1322 return bibtex
1324 if collection is not None and collection.coltype == "thesis": 1324 ↛ 1325line 1324 didn't jump to line 1325, because the condition on line 1324 was never true
1325 is_phdthesis = True
1327 author_names = get_bibtex_names(self, "author")
1328 editor_names = get_bibtex_names(self, "editor")
1330 type_ = "book"
1331 if is_phdthesis: 1331 ↛ 1332line 1331 didn't jump to line 1332, because the condition on line 1331 was never true
1332 type_ = "phdthesis"
1334 # Numdam meeting: Use the resource pid as the bibtex id => No latex encoding to keep the '_'
1335 id_ = self.pid
1336 bibtex.append("@" + type_ + "{" + id_ + ",")
1338 if author_names:
1339 append_in_latex(bibtex, indent + author_names)
1340 if editor_names:
1341 append_in_latex(bibtex, indent + editor_names)
1342 append_in_latex(bibtex, indent + "title = {" + self.title_tex + "},", is_title=True)
1344 if collection is not None: 1344 ↛ 1347line 1344 didn't jump to line 1347, because the condition on line 1344 was never false
1345 append_in_latex(bibtex, indent + "series = {" + collection.title_tex + "},")
1347 self.update_bibtex_with_commons(bibtex, self, request.get_host(), request.scheme, indent)
1349 append_in_latex(bibtex, "}")
1350 return "\n".join(bibtex)
1352 def get_ris(self, request):
1353 """
1355 :param self:
1356 :return: a string
1357 """
1358 items = []
1359 sep = " - "
1361 collection = self.get_collection()
1363 is_phdthesis = False
1365 # no citation for an issue, only for a book (book, these)
1366 if self.ctype == "issue":
1367 return ""
1369 if collection is not None and collection.coltype == "these": 1369 ↛ 1370line 1369 didn't jump to line 1370, because the condition on line 1369 was never true
1370 is_phdthesis = True
1372 if is_phdthesis: 1372 ↛ 1373line 1372 didn't jump to line 1373, because the condition on line 1372 was never true
1373 items.append("TY" + sep + "THES")
1374 else:
1375 items.append("TY" + sep + "BOOK")
1377 author_names = get_names(self, CONTRIB_TYPE_AUTHOR)
1378 for author in author_names: 1378 ↛ 1379line 1378 didn't jump to line 1379, because the loop on line 1378 never started
1379 items.append("AU" + sep + author)
1381 editor_names = get_names(self, CONTRIB_TYPE_EDITOR)
1382 for editor in editor_names:
1383 items.append("ED" + sep + editor)
1385 items.append("TI" + sep + self.title_tex)
1387 if collection is not None: 1387 ↛ 1390line 1387 didn't jump to line 1390, because the condition on line 1387 was never false
1388 items.append("T3" + sep + collection.title_tex)
1390 self.update_ris_with_commons(items, self, request.get_host(), request.scheme, sep)
1391 return "\r\n".join(items)
1393 def get_endnote(self, request):
1394 """
1396 :param self:
1397 :return: a string
1398 """
1399 items = []
1400 sep = " "
1402 collection = self.get_collection()
1404 is_phdthesis = False
1406 # no citation for an issue, only for a book (book, these)
1407 if self.ctype == "issue":
1408 return ""
1410 if collection is not None and collection.coltype == "these": 1410 ↛ 1411line 1410 didn't jump to line 1411, because the condition on line 1410 was never true
1411 is_phdthesis = True
1413 if is_phdthesis: 1413 ↛ 1414line 1413 didn't jump to line 1414, because the condition on line 1413 was never true
1414 items.append("%0" + sep + "Thesis")
1415 else:
1416 items.append("%0" + sep + "Book")
1418 author_names = get_names(self, CONTRIB_TYPE_AUTHOR)
1419 for author in author_names: 1419 ↛ 1420line 1419 didn't jump to line 1420, because the loop on line 1419 never started
1420 items.append("%A" + sep + author)
1422 editor_names = get_names(self, CONTRIB_TYPE_EDITOR)
1423 for editor in editor_names:
1424 items.append("%E" + sep + editor)
1426 items.append("%T" + sep + self.title_tex)
1428 if collection is not None: 1428 ↛ 1431line 1428 didn't jump to line 1431, because the condition on line 1428 was never false
1429 items.append("%S" + sep + collection.title_tex)
1431 self.update_endnote_with_commons(items, self, request.get_host(), request.scheme, sep)
1432 return "\r\n".join(items)
1434 def has_articles_excluded_from_publication(self):
1435 result = self.article_set.filter(do_not_publish=True).count() > 0
1436 return result
1439class EventSeries(Resource):
1440 """to do: clé fabriquée voir _manager.make_key done"""
1442 slug = models.CharField(max_length=128, unique=True)
1443 event_type = models.CharField(max_length=32, db_index=True)
1444 acro = models.CharField(max_length=32, db_index=True)
1445 title_sort = models.CharField(max_length=128, db_index=True)
1446 short_title = models.CharField(max_length=64, db_index=True)
1449class Event(Resource):
1450 """to do: clé fabriquée voir _manager.make_key done"""
1452 slug = models.CharField(max_length=128, unique=True)
1453 event_type = models.CharField(max_length=32, db_index=True)
1454 title_sort = models.CharField(max_length=128, db_index=True)
1455 string_event = models.CharField(max_length=128, db_index=True)
1456 year = models.CharField(max_length=32, db_index=True)
1457 acro = models.CharField(max_length=32, db_index=True)
1458 number = models.CharField(max_length=4, db_index=True)
1459 loc = models.CharField(max_length=64, db_index=True)
1460 theme = models.CharField(max_length=64, db_index=True)
1461 contrib = models.TextField()
1462 proceedings = models.ManyToManyField(Resource, related_name="Events", symmetrical=False)
1463 series = models.ForeignKey(EventSeries, null=True, on_delete=models.CASCADE)
1465 class Meta:
1466 ordering = ["year"] # seq (int)
1469class ArticleQuerySet(ResourceQuerySet):
1470 def order_by_published_date(self):
1471 return self.order_by("-date_published", "-seq")
1473 def order_by_sequence(self):
1474 return self.order_by("seq")
1476 def prefetch_for_toc(self):
1477 return (
1478 self.prefetch_contributors()
1479 .prefetch_work()
1480 .select_related("my_container", "my_container__my_collection")
1481 )
1484class Article(Resource):
1485 """mappe journal article, book-part"""
1487 atype = models.CharField(max_length=32, db_index=True)
1488 fpage = models.CharField(max_length=32, db_index=True)
1489 lpage = models.CharField(max_length=32, db_index=True)
1490 page_range = models.CharField(max_length=32, db_index=True)
1491 page_type = models.CharField(max_length=64)
1492 elocation = models.CharField(max_length=32, db_index=True)
1493 article_number = models.CharField(max_length=32)
1494 talk_number = models.CharField(max_length=32)
1495 date_received = models.DateTimeField(null=True, blank=True)
1496 date_accepted = models.DateTimeField(null=True, blank=True)
1497 date_revised = models.DateTimeField(null=True, blank=True)
1498 date_online_first = models.DateTimeField(null=True, blank=True)
1499 date_published = models.DateTimeField(null=True, blank=True)
1500 date_pre_published = models.DateTimeField(
1501 null=True, blank=True
1502 ) # Used by ptf-tools only to measure delays
1503 coi_statement = models.TextField(null=True, blank=True) # Conflict of interest
1504 show_body = models.BooleanField(
1505 default=True
1506 ) # Used by ptf-tools only (to export or not the body)
1507 do_not_publish = models.BooleanField(
1508 default=False
1509 ) # Used by ptf-tools only (to export or not the article)
1511 ##
1512 # container
1513 my_container = models.ForeignKey(Container, null=True, on_delete=models.CASCADE)
1514 seq = models.IntegerField()
1515 ##
1516 # parent part
1517 parent = models.ForeignKey(
1518 "self", null=True, related_name="children", on_delete=models.CASCADE
1519 )
1520 pseq = models.IntegerField()
1521 objects = ArticleQuerySet.as_manager()
1523 class Meta:
1524 ordering = ["seq", "fpage"]
1526 def __str__(self):
1527 return self.pid
1529 @staticmethod
1530 def get_base_url():
1531 return resolver.get_article_base_url()
1533 def get_absolute_url(self):
1534 if self.doi is not None:
1535 return reverse("article", kwargs={"aid": self.doi})
1536 else:
1537 return reverse("item_id", kwargs={"pid": self.pid})
1539 def get_relative_folder(self):
1540 collection = self.get_top_collection()
1541 return resolver.get_relative_folder(collection.pid, self.my_container.pid, self.pid)
1543 def embargo(self):
1544 if self.my_container is None: 1544 ↛ 1545line 1544 didn't jump to line 1545, because the condition on line 1544 was never true
1545 return False
1547 return self.my_container.embargo()
1549 def get_wall(self):
1550 if self.my_container is None: 1550 ↛ 1551line 1550 didn't jump to line 1551, because the condition on line 1550 was never true
1551 return 0
1552 return self.my_container.get_wall()
1554 def get_collection(self):
1555 return self.my_container.get_collection()
1557 def get_top_collection(self):
1558 return self.my_container.get_top_collection()
1560 def get_container(self):
1561 return self.my_container
1563 def get_volume(self):
1564 return self.my_container.get_volume()
1566 def get_number(self):
1567 return self.my_container.get_number()
1569 def get_page_count(self):
1570 page_count = None
1571 for resourcecount in self.resourcecount_set.all():
1572 if resourcecount.name == "page-count": 1572 ↛ 1571line 1572 didn't jump to line 1571, because the condition on line 1572 was never false
1573 page_count = resourcecount.value
1575 return page_count
1577 def get_article_page_count(self):
1578 try:
1579 page_count = self.get_page_count() or 0
1580 page_count = int(page_count)
1581 except ValueError:
1582 page_count = 0
1584 lpage = fpage = 0
1585 try:
1586 fpage = int(self.fpage)
1587 except ValueError:
1588 pass
1589 try:
1590 lpage = int(self.lpage)
1591 except ValueError:
1592 pass
1593 if lpage > 0 and fpage > 0:
1594 page_count = lpage - fpage + 1
1595 return page_count
1597 def pages(self, for_bibtex=False):
1598 """
1599 Returns a string with the article pages.
1600 It is used for the different exports (BibTeX, EndNote, RIS)
1602 typically "{fpage}-{lpage}"
1603 For BibTex, 2 '-' are used, ie "{fpage}--{lpage}"
1605 Some articles have a page_range. Use this field instead of the fpage/lpage in this case.
1606 """
1607 if self.page_range:
1608 return self.page_range
1609 if self.fpage or self.lpage:
1610 if self.lpage:
1611 return (
1612 f"{self.fpage}--{self.lpage}" if for_bibtex else f"{self.fpage}-{self.lpage}"
1613 )
1614 else:
1615 return self.fpage
1616 return None
1618 def volume_series(self):
1619 # Use only for latex
1620 if not self.my_container.vseries:
1621 return ""
1622 if self.lang == "fr":
1623 return self.my_container.vseries + "e s{\\'e}rie, "
1624 return f"Ser. {self.my_container.vseries}, "
1626 def volume_string(self):
1627 return self.volume_series() + self.my_container.volume
1629 def get_page_text(self, use_pp=False):
1630 if (self.talk_number or self.article_number) and self.get_page_count():
1631 return self.get_page_count() + " p."
1633 if not (self.lpage or self.fpage):
1634 return ""
1636 if self.lpage == self.fpage:
1637 return f"p. {self.lpage}"
1639 text = ""
1640 if not self.page_range:
1641 if self.fpage and self.lpage and use_pp:
1642 text = "pp. "
1643 else:
1644 text = "p. "
1645 text += self.fpage
1646 if self.fpage and self.lpage:
1647 text += "-"
1648 if self.lpage:
1649 text += self.lpage
1650 elif self.page_range[0] != "p": 1650 ↛ 1653line 1650 didn't jump to line 1653, because the condition on line 1650 was never false
1651 text = "p. " + self.page_range
1653 return text
1655 def get_summary_page_text(self):
1656 text = ""
1657 if self.talk_number:
1658 text += str(_("Exposé")) + " no. " + str(self.talk_number) + ", "
1659 if self.article_number:
1660 text += "article no. " + str(self.article_number) + ", "
1662 text += self.get_page_text()
1664 return text
1666 def get_breadcrumb_page_text(self):
1667 text = ""
1668 if self.my_container.with_online_first:
1669 if self.doi is not None:
1670 text = self.doi
1671 else:
1672 text = str(_("Première publication"))
1673 elif self.talk_number:
1674 text += str(_("Exposé")) + " no. " + str(self.talk_number)
1675 elif self.article_number:
1676 text += "article no. " + str(self.article_number)
1677 else:
1678 text += self.get_page_text()
1680 return text
1682 def get_year(self):
1683 return self.my_container.year
1685 def previous(self):
1686 try:
1687 return self.my_container.article_set.get(seq=(self.seq - 1))
1688 except (Article.DoesNotExist, MultipleObjectsReturned):
1689 return None
1691 def next(self):
1692 try:
1693 return self.my_container.article_set.get(seq=(self.seq + 1))
1694 except (Article.DoesNotExist, MultipleObjectsReturned):
1695 return None
1697 def get_citation_base(self, year=None, page_text=None):
1698 citation = ""
1699 if year is None: 1699 ↛ 1702line 1699 didn't jump to line 1702, because the condition on line 1699 was never false
1700 year = self.my_container.year
1702 to_appear = self.my_container.to_appear()
1703 is_cr = self.my_container.is_cr()
1705 if to_appear and year != "0":
1706 citation += f", Online first ({year})"
1707 elif to_appear:
1708 citation += ", Online first"
1709 else:
1710 if self.my_container.vseries:
1711 citation += f", {str(_('Série'))} {self.my_container.vseries}"
1713 if self.my_container.volume:
1714 citation += f", {str(volume_display())} {self.my_container.volume} ({year})"
1716 if self.my_container.number and not (is_cr and self.my_container.number[0] == "G"): 1716 ↛ 1723line 1716 didn't jump to line 1723, because the condition on line 1716 was never false
1717 citation += f" no. {self.my_container.number}"
1718 elif self.my_container.number: 1718 ↛ 1721line 1718 didn't jump to line 1721, because the condition on line 1718 was never false
1719 citation += f", no. {self.my_container.number} ({year})"
1720 else:
1721 citation += f" ({year})"
1723 if self.talk_number: 1723 ↛ 1724line 1723 didn't jump to line 1724, because the condition on line 1723 was never true
1724 citation += f", {str(_('Exposé'))} no. {str(self.talk_number)}"
1725 if self.article_number: 1725 ↛ 1726line 1725 didn't jump to line 1726, because the condition on line 1725 was never true
1726 citation += f", article no. {str(self.article_number)}"
1728 if page_text is None: 1728 ↛ 1730line 1728 didn't jump to line 1730, because the condition on line 1728 was never false
1729 page_text = self.get_page_text(True)
1730 if len(page_text) > 0:
1731 citation += f", {page_text}"
1733 if citation[-1] != ".":
1734 citation += "."
1736 if not to_appear:
1737 if self.page_type == "volant":
1738 citation += f" ({str(_('Pages volantes'))})"
1739 elif self.page_type == "supplement":
1740 citation += f" ({str(_('Pages supplémentaires'))})"
1741 elif self.page_type == "preliminaire":
1742 citation += f" ({str(_('Pages préliminaires'))})"
1743 elif self.page_type == "special":
1744 citation += f" ({str(_('Pages spéciales'))})"
1746 return citation
1748 def get_citation(self, request=None, with_formatting=False):
1749 citation = ""
1751 author_names = get_names(self, "author")
1752 if author_names:
1753 authors = "; ".join(author_names)
1754 else:
1755 author_names = get_names(self, "editor")
1756 if author_names:
1757 authors = "; ".join(author_names) + " (" + str(_("éd.")) + ")"
1758 else:
1759 authors = ""
1761 if authors != "Collectif": 1761 ↛ 1764line 1761 didn't jump to line 1764, because the condition on line 1761 was never false
1762 citation += authors
1764 if citation:
1765 if not citation.endswith("."): 1765 ↛ 1767line 1765 didn't jump to line 1767, because the condition on line 1765 was never false
1766 citation += "."
1767 citation += " "
1769 if with_formatting:
1770 citation += f"<strong>{self.title_tex}</strong>"
1771 else:
1772 citation += self.title_tex
1773 if self.my_container.ctype != "issue":
1774 citation += ", "
1775 citation += str(_("dans")) + " <em>" + self.my_container.title_tex + "</em>, "
1776 else:
1777 citation += ". "
1778 citation += self.my_container.my_collection.title_tex
1780 citation += self.get_citation_base()
1782 to_appear = self.my_container.to_appear()
1783 is_cr = self.my_container.is_cr()
1785 if not to_appear or is_cr: 1785 ↛ 1793line 1785 didn't jump to line 1793, because the condition on line 1785 was never false
1786 if self.doi is not None: 1786 ↛ 1789line 1786 didn't jump to line 1789, because the condition on line 1786 was never false
1787 citation += " doi : " + self.doi + "."
1789 if not to_appear and request is not None:
1790 url = f"{request.scheme}://{request.get_host()}{self.get_absolute_url()}"
1791 citation += " " + url
1793 return citation
1795 def has_detailed_info(self):
1796 # Ignore citations here.
1798 result = False
1800 if (
1801 self.date_received
1802 or self.date_revised
1803 or self.date_accepted
1804 or self.date_published
1805 or self.date_online_first
1806 ):
1807 result = True
1808 elif self.extid_set.exists():
1809 result = True
1810 elif self.kwd_set.exists():
1811 result = True
1812 elif self.doi is not None: 1812 ↛ 1813line 1812 didn't jump to line 1813, because the condition on line 1812 was never true
1813 result = True
1814 else:
1815 for resourceid in self.resourceid_set.all():
1816 if resourceid.id_type != "numdam-prod-id": 1816 ↛ 1815line 1816 didn't jump to line 1815, because the condition on line 1816 was never false
1817 result = True
1819 return result
1821 def get_ojs_id(self):
1822 ojs_id = ""
1823 qs = self.resourceid_set.filter(id_type="ojs-id")
1824 if qs.count() > 0:
1825 resourceid = qs.first()
1826 ojs_id = resourceid.id_value
1827 pos = ojs_id.find("$$")
1828 if pos > 0: 1828 ↛ 1829line 1828 didn't jump to line 1829, because the condition on line 1828 was never true
1829 ojs_id = ojs_id[0:pos]
1830 return ojs_id
1832 def update_bibtex_with_book_contributors(
1833 self, bibtex, collection, container, indent, author_names, editor_names
1834 ):
1835 if container is not None: 1835 ↛ 1847line 1835 didn't jump to line 1847, because the condition on line 1835 was never false
1836 append_in_latex(bibtex, indent + "booktitle = {" + container.title_tex + "},")
1838 # @incollection (edited-books): add the book editors
1839 book_author_names = get_bibtex_names(container, "author")
1840 book_editor_names = get_bibtex_names(container, "editor")
1842 if not author_names and book_author_names: 1842 ↛ 1843line 1842 didn't jump to line 1843, because the condition on line 1842 was never true
1843 append_in_latex(bibtex, indent + book_author_names)
1844 if not editor_names and book_editor_names: 1844 ↛ 1847line 1844 didn't jump to line 1847, because the condition on line 1844 was never false
1845 append_in_latex(bibtex, indent + book_editor_names)
1847 if collection is not None: 1847 ↛ exitline 1847 didn't return from function 'update_bibtex_with_book_contributors', because the condition on line 1847 was never false
1848 append_in_latex(bibtex, indent + "series = {" + collection.title_tex + "},")
1850 def get_bibtex(self, request=None, is_title=True):
1851 """
1853 :param self:
1854 :return: string encoded in latex (with latexcodec)
1855 """
1856 bibtex = []
1857 indent = " "
1859 container = self.my_container
1860 collection = self.get_collection()
1862 is_article = True
1863 is_incollection = False
1864 is_inbook = False
1865 is_phdthesis = False
1867 if container is not None and container.ctype != "issue":
1868 is_article = False
1870 if collection is not None and collection.coltype == "these": 1870 ↛ 1871line 1870 didn't jump to line 1871, because the condition on line 1870 was never true
1871 is_phdthesis = True
1872 elif container.ctype == "book-monograph": 1872 ↛ 1873line 1872 didn't jump to line 1873, because the condition on line 1872 was never true
1873 is_inbook = True
1874 elif container.ctype in ["book-edited-book", "lecture-notes"]: 1874 ↛ 1879line 1874 didn't jump to line 1879, because the condition on line 1874 was never false
1875 is_incollection = True
1876 elif collection.coltype == "proceeding": 1876 ↛ 1877line 1876 didn't jump to line 1877, because the condition on line 1876 was never true
1877 is_incollection = True
1879 to_appear = container.to_appear()
1880 is_cr = container.is_cr()
1882 # No bibtex at the article level for a these
1883 if is_phdthesis: 1883 ↛ 1884line 1883 didn't jump to line 1884, because the condition on line 1883 was never true
1884 return ""
1886 author_names = get_bibtex_names(self, "author")
1887 editor_names = get_bibtex_names(self, "editor")
1889 type_ = "article"
1890 if to_appear and not is_cr: 1890 ↛ 1891line 1890 didn't jump to line 1891, because the condition on line 1890 was never true
1891 type_ = "unpublished"
1892 elif is_inbook: 1892 ↛ 1893line 1892 didn't jump to line 1893, because the condition on line 1892 was never true
1893 type_ = "inbook"
1894 elif is_incollection:
1895 type_ = "incollection"
1896 elif len(author_names) == 0 and len(editor_names) == 0:
1897 type_ = "misc"
1899 # Numdam meeting: Use the resource pid as the bibtex id => No latex encoding to keep the '_'
1900 bibtex.append("@" + type_ + "{" + self.pid + ",")
1902 if author_names:
1903 append_in_latex(bibtex, indent + author_names)
1904 if editor_names:
1905 append_in_latex(bibtex, indent + editor_names)
1907 title = xml_utils.normalise_span(self.title_tex)
1908 append_in_latex(bibtex, indent + "title = {" + title + "},", is_title=is_title)
1910 if is_article and not is_incollection:
1911 title = xml_utils.normalise_span(collection.title_tex)
1912 append_in_latex(bibtex, indent + "journal = {" + title + "},")
1913 elif is_article: 1913 ↛ 1914line 1913 didn't jump to line 1914, because the condition on line 1913 was never true
1914 title = xml_utils.normalise_span(container.title_tex)
1915 append_in_latex(bibtex, indent + "booktitle = {" + title + "},")
1916 title = xml_utils.normalise_span(collection.title_tex)
1917 append_in_latex(bibtex, indent + "series = {" + title + "},")
1918 else:
1919 self.update_bibtex_with_book_contributors(
1920 bibtex, collection, container, indent, author_names, editor_names
1921 )
1923 if not to_appear:
1924 if self.talk_number:
1925 append_in_latex(bibtex, indent + "note = {talk:" + self.talk_number + "},")
1926 if self.article_number: 1926 ↛ 1927line 1926 didn't jump to line 1927, because the condition on line 1926 was never true
1927 append_in_latex(bibtex, indent + "eid = {" + self.article_number + "},")
1928 if self.pages():
1929 append_in_latex(bibtex, indent + "pages = {" + self.pages(for_bibtex=True) + "},")
1931 hostname = request.get_host() if request is not None else ""
1932 scheme = request.scheme if request is not None else "https"
1933 self.update_bibtex_with_commons(bibtex, container, hostname, scheme, indent)
1935 append_in_latex(bibtex, "}")
1937 return "\n".join(bibtex)
1939 def get_ris(self, request=None):
1940 """
1942 :param self:
1943 :return: string
1944 """
1945 items = []
1946 sep = " - "
1948 container = self.my_container
1949 collection = self.get_collection()
1951 is_article = True
1952 is_incollection = False
1953 is_inbook = False
1954 is_phdthesis = False
1956 if container is not None and container.ctype != "issue":
1957 is_article = False
1959 if collection is not None and collection.coltype == "these": 1959 ↛ 1960line 1959 didn't jump to line 1960, because the condition on line 1959 was never true
1960 is_phdthesis = True
1961 elif container.ctype == "book-monograph": 1961 ↛ 1962line 1961 didn't jump to line 1962, because the condition on line 1961 was never true
1962 is_inbook = True
1963 elif container.ctype == "book-edited-book": 1963 ↛ 1965line 1963 didn't jump to line 1965, because the condition on line 1963 was never false
1964 is_incollection = True
1965 elif container.ctype == "lecture-notes":
1966 is_incollection = True
1968 to_appear = container.to_appear()
1969 is_cr = container.is_cr()
1971 # no citation at the article level for a these
1972 if is_phdthesis: 1972 ↛ 1973line 1972 didn't jump to line 1973, because the condition on line 1972 was never true
1973 return ""
1975 type_ = "JOUR" # "article"
1976 if to_appear and not is_cr: 1976 ↛ 1977line 1976 didn't jump to line 1977, because the condition on line 1976 was never true
1977 type_ = "UNPB" # "unpublished"
1978 elif is_inbook: 1978 ↛ 1979line 1978 didn't jump to line 1979, because the condition on line 1978 was never true
1979 type_ = "CHAP"
1980 elif is_incollection:
1981 type_ = "CHAP"
1982 items.append("TY" + sep + type_)
1984 author_names = get_names(self, CONTRIB_TYPE_AUTHOR)
1985 for author in author_names:
1986 items.append("AU" + sep + author)
1988 editor_names = get_names(self, CONTRIB_TYPE_EDITOR)
1989 for editor in editor_names:
1990 items.append("ED" + sep + editor)
1992 title = xml_utils.remove_html(self.title_tex)
1993 items.append("TI" + sep + title)
1995 collection_title = xml_utils.remove_html(collection.title_tex)
1996 if collection is not None and is_article:
1997 items.append("JO" + sep + collection_title)
1998 else:
1999 if container is not None: 1999 ↛ 2008line 1999 didn't jump to line 2008, because the condition on line 1999 was never false
2000 items.append("BT" + sep + container.title_tex)
2001 author_names = get_names(container, CONTRIB_TYPE_AUTHOR)
2002 for author in author_names: 2002 ↛ 2003line 2002 didn't jump to line 2003, because the loop on line 2002 never started
2003 items.append("AU" + sep + author)
2005 editor_names = get_names(container, CONTRIB_TYPE_EDITOR)
2006 for editor in editor_names:
2007 items.append("ED" + sep + editor)
2008 if collection is not None: 2008 ↛ 2011line 2008 didn't jump to line 2011, because the condition on line 2008 was never false
2009 items.append("T3" + sep + collection_title)
2011 if not to_appear:
2012 if self.talk_number: 2012 ↛ 2013line 2012 didn't jump to line 2013, because the condition on line 2012 was never true
2013 items.append("N1" + sep + "talk:" + self.talk_number)
2014 # if self.article_number:
2015 # items.append("M1" + sep + "eid = " + self.article_number)
2017 hostname = request.get_host() if request is not None else ""
2018 scheme = request.scheme if request is not None else "https"
2019 self.update_ris_with_commons(items, container, hostname, scheme, sep)
2020 return "\r\n".join(items)
2022 def get_endnote(self, request=None):
2023 """
2025 :param self:
2026 :return: string
2027 """
2028 items = []
2029 sep = " "
2031 container = self.my_container
2032 collection = self.get_collection()
2034 is_article = True
2035 is_incollection = False
2036 is_inbook = False
2037 is_phdthesis = False
2039 if container is not None and container.ctype != "issue":
2040 is_article = False
2042 if collection is not None and collection.coltype == "these": 2042 ↛ 2043line 2042 didn't jump to line 2043, because the condition on line 2042 was never true
2043 is_phdthesis = True
2044 elif container.ctype == "book-monograph": 2044 ↛ 2045line 2044 didn't jump to line 2045, because the condition on line 2044 was never true
2045 is_inbook = True
2046 elif container.ctype == "book-edited-book": 2046 ↛ 2048line 2046 didn't jump to line 2048, because the condition on line 2046 was never false
2047 is_incollection = True
2048 elif container.ctype == "lecture-notes":
2049 is_incollection = True
2051 to_appear = container.to_appear()
2052 is_cr = container.is_cr()
2054 # no citation at the article level for a these
2055 if is_phdthesis: 2055 ↛ 2056line 2055 didn't jump to line 2056, because the condition on line 2055 was never true
2056 return ""
2058 type_ = "Journal Article" # "article"
2059 if to_appear and not is_cr: 2059 ↛ 2060line 2059 didn't jump to line 2060, because the condition on line 2059 was never true
2060 type_ = "Unpublished Work" # "unpublished"
2061 elif is_inbook: 2061 ↛ 2062line 2061 didn't jump to line 2062, because the condition on line 2061 was never true
2062 type_ = "Book Section"
2063 elif is_incollection:
2064 type_ = "Book Section"
2065 items.append("%0" + sep + type_)
2067 author_names = get_names(self, CONTRIB_TYPE_AUTHOR)
2068 for author in author_names:
2069 items.append("%A" + sep + author)
2071 editor_names = get_names(self, CONTRIB_TYPE_EDITOR)
2072 for editor in editor_names:
2073 items.append("%E" + sep + editor)
2075 title = xml_utils.remove_html(self.title_tex)
2076 items.append("%T" + sep + title)
2078 collection_title = xml_utils.remove_html(collection.title_tex)
2079 if collection is not None and is_article:
2080 items.append("%J" + sep + collection_title)
2081 else:
2082 if container is not None: 2082 ↛ 2091line 2082 didn't jump to line 2091, because the condition on line 2082 was never false
2083 items.append("%B" + sep + container.title_tex)
2084 author_names = get_names(container, CONTRIB_TYPE_AUTHOR)
2085 for author in author_names: 2085 ↛ 2086line 2085 didn't jump to line 2086, because the loop on line 2085 never started
2086 items.append("%A" + sep + author)
2088 editor_names = get_names(container, CONTRIB_TYPE_EDITOR)
2089 for editor in editor_names:
2090 items.append("%E" + sep + editor)
2091 if collection is not None: 2091 ↛ 2094line 2091 didn't jump to line 2094, because the condition on line 2091 was never false
2092 items.append("%S" + sep + collection_title)
2094 if not to_appear:
2095 if self.talk_number: 2095 ↛ 2096line 2095 didn't jump to line 2096, because the condition on line 2095 was never true
2096 items.append("%Z" + sep + "talk:" + self.talk_number)
2097 # if self.article_number:
2098 # items.append("%1" + sep + "eid = " + self.article_number)
2100 hostname = request.get_host() if request is not None else ""
2101 scheme = request.scheme if request is not None else "https"
2102 self.update_endnote_with_commons(items, container, hostname, scheme, sep)
2103 return "\r\n".join(items)
2105 def get_conference(self):
2106 text = ""
2108 subjs = []
2109 for subj in self.subj_set.all():
2110 if subj.type == "conference":
2111 subjs.append(subj.value)
2113 text = ", ".join(subjs)
2115 return text
2117 def get_topics(self):
2118 text = ""
2120 subjs = []
2121 for subj in self.subj_set.all():
2122 if subj.type == "topic":
2123 subjs.append(subj.value)
2125 text = ", ".join(subjs)
2127 return text
2129 def get_subj_text(self):
2130 text = ""
2131 lang = get_language()
2133 subj_types = ["subject", "type", "pci", "heading", "conference"]
2134 if self.my_container.my_collection.pid == "CRMATH":
2135 subj_types = ["subject", "heading"]
2137 subj_groups = self.get_subjs_by_type_and_lang()
2138 for type_ in subj_types:
2139 if type_ in subj_groups:
2140 sg_type_langs = subj_groups[type_]
2141 if type_ == "pci":
2142 subtext = ", ".join(
2143 list(
2144 [
2145 resolver.get_pci(subj.value)
2146 for lang_ in sg_type_langs
2147 for subj in sg_type_langs[lang_]
2148 ]
2149 )
2150 )
2151 else:
2152 if lang in sg_type_langs:
2153 subtext = ", ".join(
2154 list([subj.value for subj in subj_groups[type_][lang]])
2155 )
2156 else:
2157 subtext = ", ".join(
2158 list(
2159 [
2160 subj.value
2161 for lang_ in sg_type_langs
2162 if lang_ != lang
2163 for subj in sg_type_langs[lang_]
2164 ]
2165 )
2166 )
2168 if text:
2169 text += " - "
2170 text += subtext
2172 return text
2174 def get_pci_section(self):
2175 pci = ""
2176 for subj in self.subj_set.all():
2177 if subj.type == "pci":
2178 pci = subj.value
2180 return pci
2182 def get_pci_value(self):
2183 return resolver.get_pci(self.get_pci_section())
2185 def is_uga_pci(self):
2186 return self.get_pci_section() in resolver.PCJ_UGA_SECTION
2188 def allow_crossref(self):
2189 # we need at least a doi or an issn to allow crossref record
2190 doi = self.my_container.my_collection.doi
2191 issn = self.my_container.my_collection.issn
2192 e_issn = self.my_container.my_collection.e_issn
2193 result = bool(doi) or bool(issn) or bool(e_issn)
2195 # and we need a published date (online_first or final)
2196 result = result and (self.date_published is not None or self.date_online_first is not None)
2198 return result
2200 def get_illustrations(self):
2201 return GraphicalAbstract.objects.filter(resource=self).first()
2203 def has_graphical_abstract(self):
2204 collections = ["CRCHIM"]
2205 return True if self.my_container.my_collection.pid in collections else False
2208class ResourceCategory(models.Model):
2209 """non utilisé"""
2211 category = models.CharField(max_length=32, db_index=True)
2214class Provider(models.Model):
2215 """
2216 en faire une resource permettrait d'attacher des metadonnées
2217 supplémentaires -- à voir
2218 """
2220 name = models.CharField(max_length=32, unique=True)
2221 pid_type = models.CharField(max_length=32, unique=True)
2222 sid_type = models.CharField(max_length=64, unique=True, null=True, blank=True)
2224 def __str__(self):
2225 return self.name
2228class SiteMembership(models.Model):
2229 """
2230 Warning: As of July 3018, only 1 site id is stored in a SolR document
2231 Although the SolR schema is already OK to store multiple sites ("sites" is an array)
2232 no Solr commands have been written to add/remove sites
2233 We only have add commands.
2234 Search only works if the Solr instance is meant for individual or ALL sites
2235 """
2237 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2238 deployed = models.DateTimeField(null=True)
2239 online = models.DateTimeField(db_index=True, null=True) # pour cedram
2240 site = models.ForeignKey(PtfSite, on_delete=models.CASCADE)
2242 class Meta:
2243 unique_together = (
2244 "resource",
2245 "site",
2246 )
2249class CollectionMembership(models.Model):
2250 collection = models.ForeignKey(Collection, on_delete=models.CASCADE)
2251 container = models.ForeignKey(Container, on_delete=models.CASCADE)
2253 vseries = models.CharField(max_length=32, db_index=True)
2254 volume = models.CharField(max_length=64, db_index=True)
2255 number = models.CharField(max_length=32, db_index=True) # issue number
2256 # la même chose pour le tri
2257 vseries_int = models.IntegerField(default=0, db_index=True)
2258 volume_int = models.IntegerField(default=0, db_index=True)
2259 number_int = models.IntegerField(default=0, db_index=True)
2261 seq = models.IntegerField(db_index=True)
2263 class Meta:
2264 unique_together = (
2265 "collection",
2266 "container",
2267 )
2269 def __str__(self):
2270 return f"{self.collection} - {self.container}"
2273class RelationName(models.Model):
2274 """
2275 Triple store ;-)
2276 """
2278 left = models.CharField(max_length=32, unique=True)
2279 right = models.CharField(max_length=32, unique=True)
2280 gauche = models.CharField(max_length=64, unique=True)
2281 droite = models.CharField(max_length=64, unique=True)
2284class Relationship(models.Model):
2285 resource = models.ForeignKey(
2286 Resource, null=True, related_name="subject_of", on_delete=models.CASCADE
2287 )
2288 related = models.ForeignKey(
2289 Resource, null=True, related_name="object_of", on_delete=models.CASCADE
2290 )
2291 subject_pid = models.CharField(max_length=64, db_index=True)
2292 object_pid = models.CharField(max_length=64, db_index=True)
2293 rel_info = models.ForeignKey(RelationName, null=True, on_delete=models.CASCADE)
2296class ExtRelationship(models.Model):
2297 """
2298 Triple store (resources externes)
2299 """
2301 resource = models.ForeignKey(Resource, related_name="subject", on_delete=models.CASCADE)
2302 ext_object = models.CharField(max_length=256) # uri
2303 predicate = models.CharField(max_length=256, db_index=True) # predicate uri
2306class XmlBase(models.Model):
2307 base = models.CharField(max_length=200, unique=True)
2309 def __str__(self):
2310 return self.base
2313class DataStream(models.Model):
2314 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2315 rel = models.CharField(max_length=32)
2316 mimetype = models.CharField(max_length=32)
2317 base = models.ForeignKey(XmlBase, blank=True, null=True, on_delete=models.CASCADE)
2318 location = models.URLField()
2319 text = models.CharField(max_length=32, default="Link")
2320 seq = models.IntegerField(db_index=True)
2322 class Meta:
2323 ordering = ["seq"]
2325 def __str__(self):
2326 return " - ".join([self.resource.pid, self.mimetype])
2329class ExtLink(models.Model):
2330 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2331 rel = models.CharField(max_length=50)
2332 mimetype = models.CharField(max_length=32)
2333 base = models.ForeignKey(XmlBase, blank=True, null=True, on_delete=models.CASCADE)
2334 location = models.CharField(max_length=200)
2335 metadata = models.TextField()
2336 seq = models.IntegerField(db_index=True)
2338 class Meta:
2339 ordering = ["seq"]
2341 def __str__(self):
2342 return f"{self.rel}: {self.get_href()}"
2344 def save(self, *args, **kwargs):
2345 if "website" in self.rel:
2346 self.metadata = "website"
2347 if not self.seq: 2347 ↛ 2348line 2347 didn't jump to line 2348, because the condition on line 2347 was never true
2348 self.seq = self.generate_seq()
2349 super().save(*args, **kwargs)
2351 def generate_seq(self):
2352 max_seq = ExtLink.objects.filter(resource=self.resource).aggregate(Max("seq"))["seq__max"]
2353 if max_seq:
2354 return max_seq + 1
2355 else:
2356 return 1
2358 def get_href(self):
2359 if self.rel in ("small_icon", "icon"):
2360 # construction du chemin vers l'icone
2361 # filename = os.path.basename(self.location)
2362 resource_pid = self.resource.pid
2363 href = resolver.get_icon_url(resource_pid, self.location)
2364 return href
2365 return self.location
2368class RelatedObject(models.Model):
2369 """
2370 Related Objects are used to store pdf/djvu related to an issue (tdm, preliminary pages)
2371 """
2373 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2374 rel = models.CharField(max_length=32)
2375 mimetype = models.CharField(max_length=32)
2376 base = models.ForeignKey(XmlBase, blank=True, null=True, on_delete=models.CASCADE)
2377 location = models.CharField(max_length=200)
2378 metadata = models.TextField()
2379 seq = models.IntegerField(db_index=True)
2381 class Meta:
2382 ordering = ["seq"]
2384 def __str__(self):
2385 return " - ".join([self.resource.pid, self.mimetype])
2387 def get_href(self):
2388 the_dynamic_object = self.resource.cast()
2389 href = the_dynamic_object.get_binary_file_href_full_path(
2390 self.rel, self.mimetype, self.location
2391 )
2392 return href
2395class SupplementaryMaterial(RelatedObject):
2396 caption = models.TextField()
2398 def __str__(self):
2399 return self.location.split("/")[-1]
2401 def embeded_link(self):
2402 if "youtube" in self.location:
2403 video_id = urlparse(self.location).query.split("=")[1]
2404 return f"https://www.youtube-nocookie.com/embed/{video_id}"
2405 return self.get_href()
2408class MetaDataPart(models.Model):
2409 """
2410 stockage des metadonnées
2411 qui ne sont pas aiilleurs (!)
2412 non utilisé - à voir
2413 3 classes qui font sans doute doublon:
2414 MetadataPart, ResourceAttribute, CustomMeta -- à voir
2415 """
2417 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2418 name = models.CharField(max_length=32, db_index=True)
2419 seq = models.IntegerField(db_index=True)
2420 data = models.TextField()
2422 class Meta:
2423 ordering = ["seq"]
2426class ResourceAttribute(models.Model):
2427 """
2428 not used
2429 """
2431 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2432 name = models.CharField(max_length=32, db_index=True)
2433 value = models.TextField()
2436class CustomMeta(models.Model):
2437 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2438 name = models.CharField(max_length=32, db_index=True)
2439 value = models.CharField(max_length=128, db_index=True)
2442class ResourceId(models.Model):
2443 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2444 id_type = models.CharField(max_length=32, db_index=True)
2445 id_value = models.CharField(max_length=64, db_index=True)
2447 class Meta:
2448 unique_together = ("id_type", "id_value")
2450 def __str__(self):
2451 return f"{self.id_type} {self.id_value}"
2453 def get_href(self):
2454 href = ""
2455 if self.id_type == "doi": 2455 ↛ 2457line 2455 didn't jump to line 2457, because the condition on line 2455 was never false
2456 href = resolver.get_doi_url(self.id_value)
2457 return href
2460class ExtId(models.Model):
2461 """
2462 zbl, mr, jfm, etc..
2463 mis à part car non uniques
2464 """
2466 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2467 id_type = models.CharField(max_length=32, db_index=True)
2468 id_value = models.CharField(max_length=64, db_index=True)
2469 checked = models.BooleanField(default=True)
2470 false_positive = models.BooleanField(default=False)
2472 class Meta:
2473 unique_together = ["resource", "id_type"]
2475 def __str__(self):
2476 return f"{self.resource} - {self.id_type}:{self.id_value}"
2478 def get_href(self):
2479 return resolver.resolve_id(self.id_type, self.id_value)
2482class Abstract(models.Model):
2483 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2484 lang = models.CharField(max_length=3, default="und")
2485 tag = models.CharField(max_length=32, db_index=True)
2486 # specific use, content_type, label ?
2487 seq = models.IntegerField(db_index=True)
2488 value_xml = models.TextField(default="")
2489 value_tex = models.TextField(default="")
2490 value_html = models.TextField(default="")
2492 class Meta:
2493 ordering = ["seq"]
2495 def __str__(self):
2496 return f"{self.resource} - {self.tag}"
2499class Kwd(models.Model):
2500 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2501 lang = models.CharField(max_length=3, default="und")
2502 type = models.CharField(max_length=32, db_index=True)
2503 value = models.TextField(default="")
2504 seq = models.IntegerField(db_index=True)
2506 class Meta:
2507 ordering = ["seq"]
2509 def __str__(self):
2510 return f"{self.type} - {self.lang} - {self.value}"
2513class Subj(models.Model):
2514 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2515 lang = models.CharField(max_length=3, default="und")
2516 type = models.CharField(max_length=32, db_index=True)
2517 value = models.TextField(default="")
2518 seq = models.IntegerField(db_index=True)
2520 class Meta:
2521 ordering = ["seq"]
2523 def __str__(self):
2524 return f"{self.type} - {self.lang} - {self.value}"
2527class Award(models.Model):
2528 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2529 abbrev = models.CharField(max_length=600, db_index=True)
2530 award_id = models.CharField(max_length=600, db_index=True)
2531 seq = models.IntegerField(db_index=True)
2533 class Meta:
2534 ordering = ["seq"]
2537class BibItem(models.Model):
2538 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2539 sequence = models.PositiveIntegerField(db_index=True)
2540 label = models.CharField(max_length=128, default="")
2541 citation_xml = models.TextField(default="")
2542 citation_tex = models.TextField(default="")
2543 citation_html = models.TextField(default="")
2545 type = models.CharField(max_length=32, default="", db_index=True)
2546 user_id = models.TextField(default="")
2548 # article_title, chapter_title and source can contain formulas. Save the tex version.
2549 # artile_title, chapter_title and source were created from title, booktitle and chapter.
2550 # We need to do reverse engineering when exporting the bibtex (see
2551 # get_bibtex)
2552 article_title_tex = models.TextField(default="")
2553 chapter_title_tex = models.TextField(default="")
2554 source_tex = models.TextField(default="")
2556 publisher_name = models.TextField(default="")
2557 publisher_loc = models.TextField(default="")
2558 institution = models.TextField(default="")
2559 series = models.TextField(default="")
2560 volume = models.TextField(default="")
2561 issue = models.TextField(default="")
2562 month = models.CharField(max_length=32, default="")
2563 year = models.TextField(default="")
2564 comment = models.TextField(default="")
2565 annotation = models.CharField(max_length=255, default="")
2566 fpage = models.TextField(default="")
2567 lpage = models.TextField(default="")
2568 page_range = models.TextField(default="")
2569 size = models.TextField(default="")
2571 class Meta:
2572 ordering = ["sequence"]
2574 def __str__(self):
2575 return f"{self.resource} - {self.label}"
2577 def get_ref_id(self):
2578 ref_id = ""
2579 # self.resource.body_html is for PCJ case when SHOW_BODY is at false but we have a body html to display
2580 if (hasattr(settings, "SHOW_BODY") and settings.SHOW_BODY) or self.resource.body_html: 2580 ↛ 2581line 2580 didn't jump to line 2581, because the condition on line 2580 was never true
2581 ref_id = "r" + str(self.sequence)
2582 else:
2583 ref_id = self.user_id
2584 return ref_id
2586 def get_doi(self):
2587 bibitemId_doi = BibItemId.objects.filter(bibitem=self, id_type="doi")
2588 if bibitemId_doi: 2588 ↛ 2589line 2588 didn't jump to line 2589, because the condition on line 2588 was never true
2589 return bibitemId_doi.get().id_value
2590 return None
2592 def get_bibtex(self):
2593 """
2595 :param self:
2596 :return: an array of strings encoded in latex (with latexcodec)
2597 """
2598 bibtex = []
2599 indent = " "
2601 author_names = get_bibtex_names(self, "author")
2602 editor_names = get_bibtex_names(self, "editor")
2604 if self.user_id: 2604 ↛ 2607line 2604 didn't jump to line 2607, because the condition on line 2604 was never false
2605 id_ = self.user_id
2606 else:
2607 id_ = get_bibtex_id(self, self.year)
2609 append_in_latex(bibtex, "@" + self.type + "{" + id_ + ",")
2611 if author_names: 2611 ↛ 2613line 2611 didn't jump to line 2613, because the condition on line 2611 was never false
2612 append_in_latex(bibtex, indent + author_names)
2613 if editor_names: 2613 ↛ 2623line 2613 didn't jump to line 2623, because the condition on line 2613 was never false
2614 append_in_latex(bibtex, indent + editor_names)
2616 # From numdam+/ptf-xsl/cedram/structured-biblio.xsl
2617 # < xsl:template match = "bib_entry[@doctype='article']/title|
2618 # bib_entry[@doctype='misc']/title|
2619 # bib_entry[@doctype='inproceedings']/title|
2620 # bib_entry[@doctype='booklet']/title|
2621 # bib_entry[@doctype='conference']/title" >
2622 # => All article_title in JATS become title in bibtex
2623 if self.article_title_tex: 2623 ↛ 2631line 2623 didn't jump to line 2631, because the condition on line 2623 was never false
2624 title = xml_utils.normalise_span(self.article_title_tex)
2625 append_in_latex(bibtex, indent + "title = {" + title + "},", is_title=True)
2627 # <chapter-title> in JATS becomes title or chapter according to the type
2628 # From numdam+/ptf-xsl/cedram/structured-biblio.xsl
2629 # <xsl:template match="bib_entry[@doctype='incollection']/title|bibentry/chapter">
2631 if self.chapter_title_tex: 2631 ↛ 2660line 2631 didn't jump to line 2660, because the condition on line 2631 was never false
2632 keyword = ""
2633 if self.type == "incollection": 2633 ↛ 2634line 2633 didn't jump to line 2634, because the condition on line 2633 was never true
2634 keyword += "title"
2635 else:
2636 keyword += "chapter"
2637 title = xml_utils.normalise_span(self.chapter_title_tex)
2638 append_in_latex(bibtex, indent + keyword + " = {" + title + "},")
2640 # <source> in JATS becomes title, journal, booktitle, or howpublished according to the type
2641 # From numdam+/ptf-xsl/cedram/structured-biblio.xsl
2642 # <xsl:template match="bib_entry[@doctype='article']/journal|
2643 # bib_entry[@doctype='incollection']/booktitle|
2644 # bib_entry[@doctype='inproceedings']/booktitle|
2645 # bib_entry[@doctype='conference']/booktitle|
2646 # bib_entry[@doctype='misc']/howpublished|
2647 # bib_entry[@doctype='booklet']/howpublished|
2648 # bib_entry[@doctype='book']/title|
2649 # bib_entry[@doctype='unpublished']/title|
2650 # bib_entry[@doctype='inbook']/title|
2651 # bib_entry[@doctype='phdthesis']/title|
2652 # bib_entry[@doctype='mastersthesis']/title|
2653 # bib_entry[@doctype='manual']/title|
2654 # bib_entry[@doctype='techreport']/title|
2655 # bib_entry[@doctype='masterthesis']/title|
2656 # bib_entry[@doctype='coursenotes']/title|
2657 # bib_entry[@doctype='proceedings']/title">
2658 # < xsl:template match = "bib_entry[@doctype='misc']/booktitle" > : Unable to reverse correctly ! 2 choices
2660 if self.source_tex: 2660 ↛ 2677line 2660 didn't jump to line 2677, because the condition on line 2660 was never false
2661 keyword = ""
2662 if self.type == "article": 2662 ↛ 2664line 2662 didn't jump to line 2664, because the condition on line 2662 was never false
2663 keyword += "journal"
2664 elif (
2665 self.type == "incollection"
2666 or self.type == "inproceedings"
2667 or self.type == "conference"
2668 ):
2669 keyword += "booktitle"
2670 elif self.type == "misc" or self.type == "booklet":
2671 keyword += "howpublished"
2672 else:
2673 keyword += "title"
2674 title = xml_utils.normalise_span(self.source_tex)
2675 append_in_latex(bibtex, indent + keyword + " = {" + title + "},")
2677 if self.publisher_name: 2677 ↛ 2679line 2677 didn't jump to line 2679, because the condition on line 2677 was never false
2678 append_in_latex(bibtex, indent + "publisher = {" + self.publisher_name + "},")
2679 if self.publisher_loc: 2679 ↛ 2681line 2679 didn't jump to line 2681, because the condition on line 2679 was never false
2680 append_in_latex(bibtex, indent + "address = {" + self.publisher_loc + "},")
2681 if self.institution: 2681 ↛ 2683line 2681 didn't jump to line 2683, because the condition on line 2681 was never false
2682 append_in_latex(bibtex, indent + "institution = {" + self.institution + "},")
2683 if self.series: 2683 ↛ 2685line 2683 didn't jump to line 2685, because the condition on line 2683 was never false
2684 append_in_latex(bibtex, indent + "series = {" + self.series + "},")
2685 if self.volume: 2685 ↛ 2687line 2685 didn't jump to line 2687, because the condition on line 2685 was never false
2686 append_in_latex(bibtex, indent + "volume = {" + self.volume + "},")
2687 if self.issue: 2687 ↛ 2689line 2687 didn't jump to line 2689, because the condition on line 2687 was never false
2688 append_in_latex(bibtex, indent + "number = {" + self.issue + "},")
2689 if self.year: 2689 ↛ 2692line 2689 didn't jump to line 2692, because the condition on line 2689 was never false
2690 append_in_latex(bibtex, indent + "year = {" + self.year + "},")
2692 if self.page_range: 2692 ↛ 2693line 2692 didn't jump to line 2693, because the condition on line 2692 was never true
2693 append_in_latex(bibtex, indent + "pages = {" + self.page_range + "},")
2694 elif self.fpage or self.lpage: 2694 ↛ 2702line 2694 didn't jump to line 2702, because the condition on line 2694 was never false
2695 text = self.fpage
2696 if self.fpage and self.lpage: 2696 ↛ 2698line 2696 didn't jump to line 2698, because the condition on line 2696 was never false
2697 text += "--"
2698 if self.lpage: 2698 ↛ 2700line 2698 didn't jump to line 2700, because the condition on line 2698 was never false
2699 text += self.lpage
2700 append_in_latex(bibtex, indent + "pages = {" + text + "},")
2702 if self.size: 2702 ↛ 2705line 2702 didn't jump to line 2705, because the condition on line 2702 was never false
2703 append_in_latex(bibtex, indent + "pagetotal = {" + self.size + "},")
2705 if self.comment: 2705 ↛ 2708line 2705 didn't jump to line 2708, because the condition on line 2705 was never false
2706 append_in_latex(bibtex, indent + "note = {" + self.comment + "},")
2708 for extid in BibItemId.objects.filter(bibitem=self): 2708 ↛ 2709line 2708 didn't jump to line 2709, because the loop on line 2708 never started
2709 type_ = ""
2710 if extid.id_type == "zbl-item-id":
2711 type_ = "zbl"
2712 elif extid.id_type == "mr-item-id":
2713 type_ = "mrnumber"
2714 elif extid.id_type == "doi":
2715 type_ = "doi"
2716 elif extid.id_type == "eid":
2717 type_ = "eid"
2719 if type_:
2720 append_in_latex(bibtex, indent + type_ + " = {" + extid.id_value + "},")
2722 append_in_latex(bibtex, "}")
2724 return bibtex
2727class BibItemId(models.Model):
2728 bibitem = models.ForeignKey(BibItem, on_delete=models.CASCADE)
2729 id_type = models.CharField(max_length=32, db_index=True)
2730 id_value = models.CharField(max_length=256, db_index=True)
2731 checked = models.BooleanField(default=True)
2732 false_positive = models.BooleanField(default=False)
2734 class Meta:
2735 unique_together = ["bibitem", "id_type"]
2737 def __str__(self):
2738 return f"{self.bibitem} - {self.id_type}:{self.id_value}"
2740 def get_href_display(self):
2741 value = "Article"
2742 if settings.SITE_ID == 3:
2743 value = "Numdam"
2744 else:
2745 if self.id_type in ["numdam-id", "mathdoc-id"]:
2746 value = "Numdam"
2748 return value
2750 def get_href(self):
2751 force_numdam = False
2752 if self.id_type in ["numdam-id", "mathdoc-id"] and settings.SITE_ID != 3: 2752 ↛ 2753line 2752 didn't jump to line 2753, because the condition on line 2752 was never true
2753 force_numdam = True
2755 return resolver.resolve_id(self.id_type, self.id_value, force_numdam)
2758class ContribGroup(models.Model):
2759 resource = models.ForeignKey(Resource, blank=True, null=True, on_delete=models.CASCADE)
2760 bibitem = models.ForeignKey(BibItem, blank=True, null=True, on_delete=models.CASCADE)
2761 content_type = models.CharField(max_length=32, db_index=True)
2762 # specific_use ?!
2763 seq = models.IntegerField()
2765 class Meta:
2766 ordering = ["seq"]
2769class Contrib(models.Model):
2770 group = models.ForeignKey(ContribGroup, on_delete=models.CASCADE)
2771 contrib_type = models.CharField(max_length=32, db_index=True)
2773 last_name = models.CharField(max_length=128, db_index=True)
2774 first_name = models.CharField(max_length=128, db_index=True)
2775 prefix = models.CharField(max_length=32)
2776 suffix = models.CharField(max_length=32)
2777 string_name = models.CharField(max_length=256, db_index=True)
2778 reference_name = models.CharField(max_length=256, db_index=True)
2779 deceased = models.BooleanField(default=False)
2780 orcid = models.CharField(max_length=64, db_index=True, blank=True, default="")
2781 equal_contrib = models.BooleanField(default=True)
2782 email = models.EmailField(max_length=254, db_index=True, blank=True, default="")
2784 # Could be used to export the contrib
2785 contrib_xml = models.TextField()
2787 seq = models.IntegerField()
2789 class Meta:
2790 ordering = ["seq"]
2792 @classmethod
2793 def get_fields_list(cls):
2794 return [
2795 item.attname
2796 for item in cls._meta.get_fields()
2797 if not item.auto_created
2798 and not item.many_to_many
2799 and not item.many_to_one
2800 and not item.one_to_many
2801 and not item.one_to_one
2802 ]
2804 def __str__(self):
2805 return "{} {} / {} / {}".format(
2806 self.last_name, self.first_name, self.reference_name, self.contrib_type or "None"
2807 )
2809 def get_absolute_url(self):
2810 return reverse("pretty_search", args=[f'"{self.reference_name}"-c'])
2812 def display_name(self):
2813 display_name = self.string_name
2815 if self.is_etal():
2816 display_name = "et al."
2817 elif getattr(settings, "DISPLAY_FIRST_NAME_FIRST", False) and (
2818 len(str(self.first_name)) > 0 or len(str(self.last_name)) > 0
2819 ):
2820 display_name = f"{self.first_name} {self.last_name}"
2822 return display_name
2824 def orcid_href(self):
2825 return resolver.resolve_id("orcid", self.orcid)
2827 def idref_href(self):
2828 return resolver.resolve_id("idref", self.idref)
2830 def get_addresses(self):
2831 return self.contribaddress_set.all() # .order_by('address')
2833 def is_etal(self):
2834 return self.contrib_xml.startswith("<etal")
2837class Author(models.Model):
2838 """
2839 Count the number of documents (articles,books...) written by an author
2840 """
2842 name = models.CharField(max_length=200, db_index=True, unique=True)
2843 first_letter = models.CharField(max_length=1, db_index=True)
2844 count = models.IntegerField()
2847class FrontMatter(models.Model):
2848 resource = models.OneToOneField(Resource, on_delete=models.CASCADE)
2850 value_xml = models.TextField(default="")
2851 value_html = models.TextField(default="")
2852 foreword_html = models.TextField(default="")
2855class LangTable(models.Model):
2856 code = models.CharField(max_length=3, db_index=True)
2857 name = models.CharField(max_length=64)
2860class ResourceCount(models.Model):
2861 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2862 name = models.CharField(max_length=32, db_index=True)
2863 value = models.CharField(max_length=32, db_index=True)
2864 seq = models.IntegerField()
2866 class Meta:
2867 ordering = ["seq"]
2870class Stats(models.Model):
2871 name = models.CharField(max_length=128, db_index=True, unique=True)
2872 value = models.IntegerField()
2875class History(models.Model):
2876 """pas utilisé pour l'heure"""
2878 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2879 date_type = models.CharField(max_length=32, db_index=True)
2880 date_value = models.DateTimeField()
2881 comment = models.TextField()
2884class SerialHistory(models.Model):
2885 """à revoir"""
2887 serial = models.ForeignKey("Collection", on_delete=models.CASCADE)
2888 date_deployed = models.DateField(auto_now_add=True)
2889 first_year = models.CharField(max_length=32)
2890 last_year = models.CharField(max_length=32)
2893# def matches(id_type, id_value):
2894# if id_type in ('mr-item-id', 'zbl-item-id', 'jfm-item-id'):
2895# qs = ExtId.objects.filter(
2896# id_type=id_type,
2897# id_value=id_value,
2898# ).select_related('resource', 'resource__resourceid')
2899# counter = 0
2900# match_list = []
2901# for extid in qs:
2902# resource = extid.resource
2903# for idext in resource.extid_set.all():
2904# match_list.append({'type': idext.id_type, 'value': idext.id_value})
2905# for rid in resource.resourceid_set.all():
2906# match_list.append({'type': rid.id_type, 'value': rid.id_value})
2907# counter += 1
2908# return {
2909# 'id': {'type': id_type, 'value': id_value},
2910# 'count': counter,
2911# 'ids': match_list,
2912# }
2913# resource = Resource.objects.get(resourceid__id_type=id_type,
2914# resourceid__id_value=id_value)
2915# counter = 0
2916# match_list = []
2917# for idext in resource.extid_set.all():
2918# match_list.append({'type': idext.id_type, 'value': idext.id_value})
2919# for rid in resource.resourceid_set.all():
2920# match_list.append({'type': rid.id_type, 'value': rid.id_value})
2921# counter += 1
2922# return {
2923# 'id': {'type': id_type, 'value': id_value},
2924# 'count': counter,
2925# 'ids': match_list,
2926# }
2929def parse_page_count(page_count):
2930 """
2931 page-count is not an integer but a string to be displayed.
2932 page_count may sometimes mix roman and arabic values. Ex "iv-121"
2933 """
2934 if "-" in page_count:
2935 result = 0
2936 values = page_count.split("-")
2937 for value in values:
2938 try:
2939 result += int(value)
2940 except ValueError:
2941 pass
2942 else:
2943 result = int(page_count)
2945 return result
2948@receiver(pre_delete, sender=ResourceCount)
2949def delete_resourcecount_handler(sender, instance, **kwargs):
2950 """
2951 pre_delete ResourceCount signal
2953 Stats are added manually during a addResourceCountDatabaseCmd
2954 but ResourceCount are deleted automatically by Django when its
2955 related resource is deleted
2956 (resource = models.ForeignKey(Resource) of ResourceCount)
2957 To update Stats, we use the Django signal mechanism
2958 """
2959 if instance and instance.name == "page-count" and instance.resource.classname == "Container":
2960 # page-count may sometimes mix roman and arabic values. Ex "iv-121"
2961 value = parse_page_count(instance.value)
2963 total = Stats.objects.get(name=instance.name)
2964 total.value -= value
2965 total.save()
2968# class Volume:
2969#
2970# def get_absolute_url(self):
2971# return reverse('volume-items', kwargs={'vid': self.id_})
2972#
2973# def __init__(self, id_):
2974# self.id_ = id_
2975# try:
2976# journal_pid, year_id, self.vseries_id, self.volume_id = id_.split(
2977# '_')
2978# except ValueError:
2979# raise self.DoesNotExist(_('Volume {} does not exist').format(id_))
2980# try:
2981# self.collection = Collection.objects.get(
2982# pid=journal_pid, sites__id=settings.SITE_ID)
2983# except Collection.DoesNotExist:
2984# self.issues = None
2985# try:
2986# self.issues = Container.objects.filter(
2987# my_collection__pid=journal_pid,
2988# year=year_id,
2989# vseries=self.vseries_id,
2990# volume=self.volume_id,
2991# ).order_by('number_int').all()
2992# except Container.DoesNotExist:
2993# self.issues = None
2994#
2995# class DoesNotExist(Exception):
2996# pass
2999class ContribAddress(models.Model):
3000 contrib = models.ForeignKey("Contrib", blank=True, null=True, on_delete=models.CASCADE)
3001 contribution = models.ForeignKey(
3002 "Contribution", blank=True, null=True, on_delete=models.CASCADE
3003 )
3004 address = models.TextField(null=True, blank=True)
3006 class Meta:
3007 ordering = ["pk"]
3010def cmp_container_base(a, b):
3011 return (
3012 a.year < b.year
3013 or (a.year == b.year and a.vseries_int < b.vseries_int)
3014 or (a.year == b.year and a.vseries_int == b.vseries_int and a.volume_int < b.volume_int)
3015 or (
3016 a.year == b.year
3017 and a.vseries_int == b.vseries_int
3018 and a.volume_int == b.volume_int
3019 and a.number_int < b.number_int
3020 )
3021 )
3024class PersonManager(models.Manager):
3025 @staticmethod
3026 def clean():
3027 pass
3028 # Person.objects.filter(contributions=None).delete()
3031class Person(models.Model):
3032 """
3033 A Person is a contributor (author/editor...) of a Resource (Article/Book) or a BibItem.
3034 A Person can appear with different names (ex: "John Smith", "J. Smith") and we want to preserve the differences,
3035 in particular with print papers that are digitized.
3036 A Person is unique for a person (a Person is basically the key).
3037 A Person has one or many PersonInfo which stores the different names
3039 TODO: signal similar to delete_contrib_handler
3040 """
3042 # blank=True and null=True allow unique=True with null values (on multiple Persons)
3043 orcid = models.CharField(max_length=20, blank=True, null=True)
3044 idref = models.CharField(max_length=10, blank=True, null=True)
3045 # mid (Mathdoc id) is the key set by numdam-plus
3046 mid = models.CharField(max_length=256, blank=True, null=True)
3048 last_name = models.CharField(max_length=128, blank=True, default="")
3049 first_name = models.CharField(max_length=128, blank=True, default="")
3050 prefix = models.CharField(max_length=32, blank=True, default="")
3051 suffix = models.CharField(max_length=32, blank=True, default="")
3052 first_letter = models.CharField(max_length=1, blank=True, default="")
3054 # Used when a person is not fully tagged in the XML
3055 string_name = models.CharField(max_length=256, blank=True, default="")
3057 objects = PersonManager()
3059 @classmethod
3060 def get_fields_list(cls):
3061 return [
3062 item.attname
3063 for item in cls._meta.get_fields()
3064 if not item.auto_created
3065 and not item.many_to_many
3066 and not item.many_to_one
3067 and not item.one_to_many
3068 and not item.one_to_one
3069 ]
3071 def __str__(self):
3072 return get_display_name(
3073 self.prefix, self.first_name, self.last_name, self.suffix, self.string_name
3074 )
3077class PersonInfoManager(models.Manager):
3078 @staticmethod
3079 def clean():
3080 PersonInfo.objects.filter(contributions=None).delete()
3081 Person.objects.filter(personinfo=None).delete()
3084class PersonInfo(models.Model):
3085 person = models.ForeignKey(Person, on_delete=models.CASCADE)
3087 last_name = models.CharField(max_length=128)
3088 first_name = models.CharField(max_length=128)
3089 prefix = models.CharField(max_length=32)
3090 suffix = models.CharField(max_length=32)
3092 # Used when a person is not fully tagged in the XML
3093 string_name = models.CharField(max_length=256, blank=True, default="")
3095 objects = PersonInfoManager()
3097 @classmethod
3098 def get_fields_list(cls):
3099 return [
3100 item.attname
3101 for item in cls._meta.get_fields()
3102 if not item.auto_created
3103 and not item.many_to_many
3104 and not item.many_to_one
3105 and not item.one_to_many
3106 and not item.one_to_one
3107 ]
3109 def __str__(self):
3110 return get_display_name(
3111 self.prefix, self.first_name, self.last_name, self.suffix, self.string_name
3112 )
3115class Contribution(models.Model):
3116 resource = models.ForeignKey(
3117 Resource, blank=True, null=True, on_delete=models.CASCADE, related_name="contributions"
3118 )
3119 bibitem = models.ForeignKey(
3120 BibItem, blank=True, null=True, on_delete=models.CASCADE, related_name="contributions"
3121 )
3123 # blank=True and null=True allow unique=True with null values (on multiple Persons)
3124 orcid = models.CharField(max_length=20, blank=True, null=True)
3125 idref = models.CharField(max_length=10, blank=True, null=True)
3126 # mid (Mathdoc id) is the key set by numdam-plus
3127 mid = models.CharField(max_length=256, blank=True, null=True)
3129 last_name = models.CharField(max_length=128, blank=True, default="")
3130 first_name = models.CharField(max_length=128, blank=True, default="")
3131 prefix = models.CharField(max_length=32, blank=True, default="")
3132 suffix = models.CharField(max_length=32, blank=True, default="")
3133 first_letter = models.CharField(max_length=1, blank=True, default="")
3135 # Used when a person is not fully tagged in the XML
3136 string_name = models.CharField(max_length=256, blank=True, default="")
3138 role = models.CharField(max_length=64)
3139 email = models.EmailField(max_length=254, blank=True, default="")
3140 deceased_before_publication = models.BooleanField(default=False)
3141 equal_contrib = models.BooleanField(default=True)
3142 corresponding = models.BooleanField(default=False)
3144 # Used to export the contribution
3145 contrib_xml = models.TextField()
3147 seq = models.IntegerField()
3149 class Meta:
3150 ordering = ["seq"]
3152 @classmethod
3153 def get_fields_list(cls):
3154 return [
3155 item.attname
3156 for item in cls._meta.get_fields()
3157 if not item.auto_created
3158 and not item.many_to_many
3159 and not item.many_to_one
3160 and not item.one_to_many
3161 and not item.one_to_one
3162 ]
3164 def __str__(self):
3165 return self.display_name()
3167 def is_etal(self):
3168 return self.contrib_xml.startswith("<etal")
3170 def display_name(self):
3171 return (
3172 "et al."
3173 if self.is_etal()
3174 else get_display_name(
3175 self.prefix, self.first_name, self.last_name, self.suffix, self.string_name
3176 )
3177 )
3179 def get_absolute_url(self):
3180 return reverse("pretty_search", args=[f'"{self.display_name()}"-c'])
3182 def orcid_href(self):
3183 return resolver.resolve_id("orcid", self.orcid)
3185 def idref_href(self):
3186 return resolver.resolve_id("idref", self.idref)
3188 def is_equal(self, contribution):
3189 """
3190 return True if the contribution is identical, based on orcif/idref or homonimy
3191 TODO: override the __eq__ operator ? Not sure since homonimy is not bullet proof
3192 """
3193 equal = False
3194 if self.orcid and self.orcid == contribution.orcid:
3195 equal = True
3196 elif self.idref and self.idref == contribution.idref:
3197 equal = True
3198 else:
3199 equal = self.display_name() == contribution.display_name()
3201 return True
3202 return equal
3205@receiver(pre_delete, sender=Contribution)
3206def delete_contrib_handler(sender, instance, **kwargs):
3207 """
3208 pre_delete Contrib signal
3210 Contrib and Author are added manually during a
3211 addResourceDatabaseCmd (mainly addArticleDatabaseCmd)
3212 but Contrib are deleted automatically by Django when its
3213 related resource is deleted
3214 (resource = models.ForeignKey(Resource) of ContribGroup)
3215 To update Author, we use the Django signal mechanism
3216 """
3217 if instance and instance.role == "author":
3218 try:
3219 ref_name = instance.mid if instance.mid else str(instance)
3220 author = Author.objects.get(name=ref_name)
3221 author.count -= 1
3222 if author.count == 0:
3223 author.delete()
3224 else:
3225 author.save()
3226 except Author.DoesNotExist:
3227 pass
3230def get_names(item, role):
3231 """
3232 item: resource or bibitem
3233 """
3234 return [
3235 str(contribution) for contribution in item.contributions.all() if contribution.role == role
3236 ]
3239def are_all_equal_contrib(contributions):
3240 if len(contributions) == 0:
3241 return False
3243 are_all_equal = True
3244 for contribution in contributions:
3245 are_all_equal = are_all_equal and contribution.equal_contrib
3247 return are_all_equal
3250def are_all_false_equal_contrib(contributions):
3251 are_all_equal = True
3252 for contribution in contributions:
3253 are_all_equal = are_all_equal and not contribution.equal_contrib
3255 return are_all_equal
3258class RelatedArticles(models.Model):
3259 resource = models.ForeignKey(Resource, null=True, blank=True, on_delete=models.CASCADE)
3260 # Used during reimport/redeploy to find back the RelatedArticles
3261 resource_doi = models.CharField(max_length=64, unique=True, null=True, blank=True)
3262 date_modified = models.DateTimeField(null=True, blank=True)
3263 doi_list = models.TextField(null=True, blank=True)
3264 exclusion_list = models.TextField(null=True, blank=True)
3265 automatic_list = models.BooleanField(default=True)
3267 def __str__(self):
3268 if self.resource and hasattr(self.resource, "doi"):
3269 return f"{self.resource.doi}"
3272def image_path_graphical_abstract(instance, filename):
3273 path = "images"
3274 return os.path.join(path, str(instance.id), "graphical_abstract", filename)
3277def image_path_illustration(instance, filename):
3278 path = "images"
3279 return os.path.join(path, str(instance.id), "illustration", filename)
3282class OverwriteStorage(FileSystemStorage):
3283 def get_available_name(self, name, max_length=None):
3284 if self.exists(name):
3285 os.remove(os.path.join(settings.MEDIA_ROOT, name))
3286 return name
3289class GraphicalAbstract(models.Model):
3290 resource = models.ForeignKey(Resource, null=True, blank=True, on_delete=models.CASCADE)
3291 # Used during reimport/redeploy to find back the GraphicalAbstracts
3292 resource_doi = models.CharField(max_length=64, unique=True, null=True, blank=True)
3293 date_modified = models.DateTimeField(null=True, blank=True)
3294 graphical_abstract = models.ImageField(
3295 upload_to=image_path_graphical_abstract, blank=True, null=True, storage=OverwriteStorage
3296 )
3297 illustration = models.ImageField(
3298 upload_to=image_path_illustration, blank=True, null=True, storage=OverwriteStorage
3299 )
3301 def __str__(self):
3302 if self.resource and hasattr(self.resource, "doi"):
3303 return f"{self.resource.doi}"
3306def backup_obj_not_in_metadata(article):
3307 """
3308 When you addArticleXmlCmd in the PTF, the XML does not list objects like GraphicalAbstract or RelatedArticles.
3309 Since addArticleXmlCmd deletes/re-creates the article, we need to "backup" these objects.
3310 To do so, we delete the relation between article and the object,
3311 so that the object is not deleted when the article is deleted.
3313 You need to call restore_obj_not_in_metadata after the re-creation of the article.
3314 """
3315 for class_name in ["GraphicalAbstract", "RelatedArticles"]:
3316 qs = globals()[class_name].objects.filter(resource=article)
3317 if qs.exists(): 3317 ↛ 3318line 3317 didn't jump to line 3318, because the condition on line 3317 was never true
3318 obj = qs.first()
3319 obj.resource_doi = article.doi
3320 obj.resource = None
3321 obj.save()
3324def backup_translation(article):
3325 """
3326 Translated articles are the article JATS XML, but not in the Cedrics XML
3327 We need to "backup" existing translations when importing a Cedrics Article.
3328 To do so, we delete the relation between article and the translation,
3329 so that the translation is not deleted when the article is deleted.
3331 You need to call restore_translation after the re-creation of the article
3332 """
3333 qs = TranslatedArticle.objects.filter(original_article=article)
3334 if qs.exists(): 3334 ↛ 3335line 3334 didn't jump to line 3335, because the condition on line 3334 was never true
3335 obj = qs.first()
3336 obj.original_article_doi = article.doi
3337 obj.original_article = None
3338 obj.save()
3341def restore_obj_not_in_metadata(article):
3342 for class_name in ["GraphicalAbstract", "RelatedArticles"]:
3343 qs = globals()[class_name].objects.filter(resource_doi=article.doi, resource__isnull=True)
3344 if qs.exists(): 3344 ↛ 3345line 3344 didn't jump to line 3345, because the condition on line 3344 was never true
3345 obj = qs.first()
3346 obj.resource = article
3347 obj.save()
3350def restore_translation(article):
3351 qs = TranslatedArticle.objects.filter(
3352 original_article_doi=article.doi, original_article__isnull=True
3353 )
3354 if qs.exists(): 3354 ↛ 3355line 3354 didn't jump to line 3355, because the condition on line 3354 was never true
3355 obj = qs.first()
3356 obj.original_article = article
3357 obj.save()
3360class TranslatedArticle(Article):
3361 original_article = models.ForeignKey(
3362 Article, null=True, blank=True, on_delete=models.CASCADE, related_name="translations"
3363 )
3364 # Used during reimport/redeploy in Trammel to find back the TranslatedArticle
3365 original_article_doi = models.CharField(max_length=64, unique=True, null=True, blank=True)
3367 def get_year(self):
3368 return self.original_article.get_year()
3370 def get_ojs_id(self):
3371 return self.original_article.get_ojs_id()
3373 def get_binary_file_href_full_path(self, doctype, mimetype, location):
3374 """
3375 Returns an encoded URL to the pdf of a translated article
3377 Input: doctype: language (ex: 'fr', 'en'
3378 mimetype: 'application/pdf'
3379 location is ignored
3381 Ex: /article/fr/10.5802/crmath.100.pdf
3382 """
3384 if mimetype != "application/pdf":
3385 return ""
3387 pid = (
3388 self.original_article.doi
3389 if self.original_article.doi is not None
3390 else self.original_article.pid
3391 )
3392 extension = "pdf"
3393 href = reverse(
3394 "article-translated-pdf",
3395 kwargs={"pid": pid, "extension": extension, "binary_file_type": self.lang},
3396 )
3398 return href
3400 def get_citation(self, request=None):
3401 citation = ""
3403 translator_names = get_names(self, "translator")
3404 citation += "; ".join(translator_names)
3406 if citation:
3407 if not citation.endswith("."):
3408 citation += ". "
3409 citation += " "
3411 if self.lang == self.original_article.trans_lang:
3412 citation += self.original_article.trans_title_tex
3413 else:
3414 citation += self.title_tex
3416 year = self.date_published.strftime("%Y") if self.date_published is not None else "YYYY"
3417 citation += " (" + year + ")"
3419 if self.doi is not None:
3420 citation += " doi : " + self.doi
3422 # page_text = self.get_page_text(True)
3423 # citation += self.original_article.get_citation_base(year, page_text)
3425 citation += " (" + self.original_article.get_citation()[0:-1] + ")"
3427 # author_names = get_names(self.original_article, "author")
3428 # if author_names:
3429 # authors = '; '.join(author_names)
3430 # else:
3431 # author_names = get_names(self.original_article, "editor")
3432 # if author_names:
3433 # authors = '; '.join(author_names) + ' (' + str(
3434 # _("éd.")) + ')'
3435 # else:
3436 # authors = ''
3437 # citation += authors
3438 #
3439 # if citation:
3440 # if not citation.endswith('.'):
3441 # citation += '.'
3442 # citation += ' '
3443 #
3444 # citation += self.original_article.title_tex
3445 #
3446 # if self.lang == self.original_article.trans_lang:
3447 # citation += f" [{self.original_article.trans_title_tex}]"
3448 # else:
3449 # citation += f" [{self.title_tex}]"
3450 #
3451 # citation += ' (' + str(_('Traduit par')) + ' '
3452 #
3453 # citation += '). '
3454 #
3455 # citation += self.original_article.my_container.my_collection.title_tex
3457 # if self.doi is not None:
3458 # citation += ' doi : ' + self.doi + '.'
3459 #
3460 # if request is not None:
3461 # url = "{}://{}{}".format(request.scheme, request.get_host(), self.original_article.get_absolute_url())
3462 # citation += ' ' + url
3463 #
3464 # citation += " (" + str(_('Article original publié en ')) + self.original_article.my_container.year + ')'
3466 return citation