Coverage for src/ptf/models.py: 75%
2037 statements
« prev ^ index » next coverage.py v7.6.4, created at 2024-11-05 09:56 +0000
« prev ^ index » next coverage.py v7.6.4, created at 2024-11-05 09:56 +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 def get_document_type(self):
341 """
342 Is used to classify the resources in the
343 - (TODO) SOLR Facet Document Type
344 - Geodesic menu (Journals/Books/Seminars...)
345 """
346 return ""
348 collection = self.get_top_collection()
349 if collection.coltype == "journal":
350 document_type = "Article de revue"
351 elif collection.coltype == "acta":
352 document_type = "Acte de séminaire"
353 elif collection.coltype == "thesis":
354 self.data["classname"] = "Thèse"
355 document_type = "Thèse"
356 elif collection.coltype == "lecture-notes":
357 self.data["classname"] = "Notes de cours"
358 document_type = "Notes de cours"
359 elif collection.coltype == "proceeding":
360 self.data["classname"] = "Acte de rencontre"
361 document_type = "Acte de rencontre"
362 else:
363 self.data["classname"] = "Livre"
364 document_type = "Livre"
366 return document_type
368 # NOTE - 12/08/2017 - Basile
369 # utilisé nul part à delete ?
370 # def has_errata(self):
371 # return self.get_related('corrected-by')
373 # def is_erratum_to(self):
374 # return self.get_related('corrects', count_only=False)
376 # def errata(self):
377 # return self.get_related('corrected-by', count_only=False)
379 # def erratum(self):
380 # errata = self.get_related('corrected-by', count_only=False)
381 # if len(errata) == 1:
382 # return errata[0]
383 # return None
385 # def questions(self):
386 # return self.get_related('resolves', count_only=False)
388 # def solutions(self):
389 # return self.get_related('resolved-by', count_only=False)
391 # def complements(self):
392 # return self.get_related('complements', count_only=False)
394 # def completed(self):
395 # return self.get_related('complemented-by', count_only=False)
397 # def follows(self):
398 # followed = self.get_related('follows', count_only=False)
399 # if len(followed) == 1:
400 # return followed[0]
401 # return 0
403 # def followed(self):
404 # follows = self.get_related('followed-by', count_only=False)
405 # if len(follows) == 1:
406 # return follows[0]
407 # return 0
409 # def citations_count(self):
410 # if not self.pid:
411 # return 0
412 # qs = BibItemId.objects.select_related().filter(
413 # id_type=self.provider.pid_type,
414 # id_value=self.pid,
415 # bibitem__resource__sites__id=settings.SITE_ID
416 # )
417 # return qs.count()
419 def citations(self):
420 if not self.pid or not self.provider:
421 return []
423 qs = BibItemId.objects.select_related().filter(
424 id_type=self.provider.pid_type,
425 id_value=self.pid,
426 bibitem__resource__sites__id=settings.SITE_ID,
427 )
428 # on ne peut pas trier par date sur la requete car
429 # on peut avoir soit des containers soit des articles
430 # comme resource et year est sur le container uniquement
431 result = [bid.bibitem.resource.cast() for bid in qs]
432 result.sort(key=lambda item: item.get_year(), reverse=True)
433 return result
435 def get_contributions(self, role):
436 # prefetch probably has already queried the database for the contributions
437 # Using filter on self.contributions would result in a separate SQL query
438 return [
439 contribution
440 for contribution in self.contributions.all()
441 if contribution.role.find(role) == 0
442 ]
444 def get_author_contributions(self, strict=True):
445 authors = self.get_contributions("author")
446 if not strict and len(authors) == 0:
447 authors = self.get_editors()
448 return authors
450 def get_authors_short(self):
451 authors = self.get_contributions("author")
452 if len(authors) > 2:
453 authors = "; ".join([str(author) for author in authors[:2]])
454 authors += " <i>et al.</i>"
455 return authors
456 return "; ".join([str(author) for author in authors])
458 def get_editors(self):
459 return self.get_contributions("editor")
461 def get_contributors(self):
462 return self.get_contributions("contributor")
464 def get_redaktors(self):
465 return self.get_contributions("redaktor")
467 def get_organizers(self):
468 return self.get_contributions("organizer")
470 def get_presenters(self):
471 return self.get_contributions("presenter")
473 def get_kwds_by_type(self):
474 msc = [kwd for kwd in self.kwd_set.all() if kwd.type == "msc"]
475 kwds = [kwd for kwd in self.kwd_set.all() if kwd.type != "msc" and kwd.lang == self.lang]
476 trans_kwds = [
477 kwd for kwd in self.kwd_set.all() if kwd.type != "msc" and kwd.lang != self.lang
478 ]
479 return msc, kwds, trans_kwds
481 def get_subjs_by_type_and_lang(self):
482 subjs = {}
483 for subj in self.subj_set.all():
484 if subj.type in subjs:
485 if subj.lang in subjs[subj.type]:
486 subjs[subj.type][subj.lang].append(subj)
487 else:
488 subjs[subj.type][subj.lang] = [subj]
489 else:
490 subjs[subj.type] = {subj.lang: [subj]}
492 return subjs
494 def self_uris_no_xml(self):
495 """
496 Returns a list of links to the datastream of the resource (= pdf/djvu of the resource)
497 This function is only used to export an xml (oai)
498 HTML templates use get_binary_files_href (see below)
499 """
500 links = [
501 {
502 "mimetype": link.mimetype,
503 "full_path": self.get_binary_file_href_full_path(
504 "self", link.mimetype, link.location
505 ),
506 "link": link.text,
507 }
508 for link in self.datastream_set.all()
509 ]
510 return links
512 @staticmethod
513 def append_href_to_binary_files(binary_files, key, mimetype, href):
514 if mimetype == "application/pdf":
515 if key in binary_files:
516 binary_files[key]["pdf"] = href
517 else:
518 binary_files[key] = {"pdf": href}
519 elif mimetype == "image/x.djvu":
520 if key in binary_files:
521 binary_files[key]["djvu"] = href
522 else:
523 binary_files[key] = {"djvu": href}
524 elif mimetype == "application/x-tex":
525 if key in binary_files:
526 binary_files[key]["tex"] = href
527 else:
528 binary_files[key] = {"tex": href}
529 elif mimetype == "video":
530 if key in binary_files:
531 binary_files[key]["video"] = href
532 else:
533 binary_files[key] = {"video": href}
535 def get_binary_files_location(self):
536 binary_files = []
538 for obj in self.extlink_set.all(): 538 ↛ 539line 538 didn't jump to line 539 because the loop on line 538 never started
539 if obj.rel == "icon" or obj.rel == "small_icon":
540 binary_files.append(obj.location)
542 for obj in self.relatedobject_set.all(): 542 ↛ 543line 542 didn't jump to line 543 because the loop on line 542 never started
543 if obj.rel != "video" and obj.rel != "html-image":
544 binary_files.append(obj.location)
546 for obj in self.datastream_set.all(): 546 ↛ 547line 546 didn't jump to line 547 because the loop on line 546 never started
547 binary_files.append(obj.location)
549 if hasattr(self, "translations"): 549 ↛ 553line 549 didn't jump to line 553 because the condition on line 549 was always true
550 for translated_article in self.translations.all(): 550 ↛ 551line 550 didn't jump to line 551 because the loop on line 550 never started
551 binary_files.extend(translated_article.get_binary_files_location())
553 return binary_files
555 def get_binary_files_href(self):
556 """
557 Get all the HREFs (without http://site) of the binary files (pdf/djvu) related to the resource
558 Result: { 'self': {'pdf':href, 'djvu':href, 'tex': href},
559 'toc':{'pdf':href, 'djvu':href},
560 'frontmatter':{'pdf':href, 'djvu':href},
561 'backmatter':{'pdf':href, 'djvu':href},
562 'translations': {<lang>: {'pdf':href,...}} }
563 result['self'] is the main pdf/djvu of the resource (article pdf,
564 full volume pdf, book part pdf)
565 The information come from the DataStream
566 The other results come from RelatedObject
567 """
568 binary_files = {}
570 for related_object in self.relatedobject_set.all(): 570 ↛ 571line 570 didn't jump to line 571 because the loop on line 570 never started
571 key = related_object.rel
572 mimetype = related_object.mimetype
573 location = related_object.location
574 href = self.get_binary_file_href_full_path(key, mimetype, location)
576 self.append_href_to_binary_files(binary_files, key, mimetype, href)
578 allow_local_pdf = not hasattr(settings, "ALLOW_LOCAL_PDF") or settings.ALLOW_LOCAL_PDF
580 for stream in self.datastream_set.all(): 580 ↛ 581line 580 didn't jump to line 581 because the loop on line 580 never started
581 key = "self"
582 mimetype = stream.mimetype
583 location = stream.location
585 if allow_local_pdf or mimetype != "application/pdf":
586 if location.find("http") == 0:
587 href = location
588 else:
589 href = self.get_binary_file_href_full_path("self", mimetype, location)
591 self.append_href_to_binary_files(binary_files, key, mimetype, href)
593 if not allow_local_pdf: 593 ↛ 594line 593 didn't jump to line 594 because the condition on line 593 was never true
594 qs = self.extlink_set.filter(rel="article-pdf")
595 if qs:
596 extlink = qs.first()
597 href = extlink.location
598 key = "self"
599 mimetype = "application/pdf"
601 self.append_href_to_binary_files(binary_files, key, mimetype, href)
603 translations = {}
604 if hasattr(self, "translations"):
605 for trans_article in self.translations.all(): 605 ↛ 606line 605 didn't jump to line 606 because the loop on line 605 never started
606 result = trans_article.get_binary_files_href()
607 if "self" in result:
608 translations[trans_article.lang] = result["self"]
610 binary_files["translations"] = translations
612 return binary_files
614 def get_binary_file_href_full_path(self, doctype, mimetype, location):
615 """
616 Returns an encoded URL to a pdf/djvu
617 URLs in HTML pages do not return the exact path to a file (for safety reason)
618 An encode URL is used instead
619 Ex: URL to an article pdf: article/pid.pdf
620 URL to an issue full pdf: issue/pid.pdf
621 URL to an issue toc djvu: issue/toc/pid.pdf
622 URL to an issue frontmatter: issue/frontmatter.pid.djvu
623 When you click on such a link, ptf/views.py will decode the URL in get_pdf
624 and use the DataStream/RelatedObject true location to return the file
625 Input: doctype: 'self', 'toc', 'frontmatter', 'backmatter'
626 mimetype: 'application/pdf', 'image/x.djvu'
628 Ex: /article/ALCO_2018__1_1_23_0.pdf
629 /issue/MSMF_1978__55-56__1_0.pdf
630 /issue/toc/CSHM_1980__1_.pdf
631 /article/ALCO_2018__1_1_23_0/tex/src/tex/ALCO_Thiem_31.tex
633 """
634 if self.embargo():
635 return ""
637 pid = self.pid
638 doi = getattr(self, "doi")
639 if doi is not None:
640 pid = doi
642 prefix = doctype if doctype != "self" else ""
644 if "annexe" in location: 644 ↛ 648line 644 didn't jump to line 648 because the condition on line 644 was never true
645 # 04/08/2021. Not used anymore ?
647 # Ex: /article/SMAI-JCM_2015__1__83_0/attach/Allaire-Dapogny-supp.pdf
648 href = reverse(
649 "annexe-pdf", kwargs={"pid": pid, "relative_path": location.split("/")[-1]}
650 )
652 elif mimetype in ["application/pdf", "image/x.djvu"] and doctype not in [
653 "review",
654 "supplementary-material",
655 ]:
656 extension = "pdf" if mimetype == "application/pdf" else "djvu"
657 if len(prefix) > 0:
658 href = reverse(
659 "issue-relatedobject-pdf",
660 kwargs={"pid": pid, "extension": extension, "binary_file_type": prefix},
661 )
662 else:
663 href = reverse("item-pdf", kwargs={"pid": pid, "extension": extension})
665 elif mimetype == "application/x-tex": 665 ↛ 676line 665 didn't jump to line 676 because the condition on line 665 was always true
666 to_find = "/src/tex/"
667 i = location.find(to_find)
668 if i > 0: 668 ↛ 669line 668 didn't jump to line 669 because the condition on line 668 was never true
669 location = location[i + 1 :]
671 href = reverse("article-binary-files", kwargs={"pid": pid, "relative_path": location})
673 else:
674 # All other attachments (videos, zip, etc...) :
676 to_find = "/attach/"
677 i = location.find(to_find)
678 if i > 0:
679 location = location[i + 1 :]
681 # Ex: /article/ALCO_2018__1_1_23_0/file/review_history.pdf
682 href = reverse("article-binary-files", kwargs={"pid": pid, "relative_path": location})
684 return href
686 def get_binary_disk_location(self, doctype, mimetype, relativepath):
687 """
688 Returns the path of a binary file (pdf/djvu) on the file server
689 This function is called when you click on a link like issue/frontmatter/pid.pdf
691 URLs in HTML pages do not return the exact path to a file (for safety reason)
692 An encoded URL is used instead (see get_binary_file_href_full_path above)
693 Ex: URL to an issue toc djvu: issue/toc/pid.pdf
695 When you click on such a link, ptf/views.py will decode the URL in get_pdf
696 before calling get_binary_disk_location to get the exact pdf/djvu disk location
697 based on the DataStream/RelatedObject location
699 Input: doctype: 'self', 'toc', 'frontmatter', 'backmatter'
700 mimetype: 'application/pdf', 'image/x.djvu'
701 relativepath: Ex: 'src/tex/ALCO_Thiem_31.tex'
703 Returns value: path or None if there is an embargo
704 May raise exceptions.ResourceDoesNotExist if the resource
705 (RelatedObject/DataStream) does not exist
706 """
707 if self.embargo():
708 return None
710 filename = None
712 if ( 712 ↛ 717line 712 didn't jump to line 717
713 doctype not in ["self", "toc", "frontmatter", "backmatter"]
714 and len(doctype) == 2
715 and hasattr(self, "translations")
716 ):
717 for translated_article in self.translations.all():
718 if translated_article.lang == doctype:
719 filename = translated_article.get_binary_disk_location(
720 "self", mimetype, relativepath
721 )
722 elif relativepath: 722 ↛ 724line 722 didn't jump to line 724 because the condition on line 722 was never true
723 # relative path are used with supplementary materials or TeX Source file
724 filename = os.path.join(self.get_relative_folder(), relativepath)
725 else:
726 if doctype != "self":
727 try:
728 obj = self.relatedobject_set.filter(mimetype=mimetype, rel=doctype).get()
730 except RelatedObject.DoesNotExist:
731 # status = 404
732 raise exceptions.ResourceDoesNotExist("The binary file does not exist")
733 else:
734 try:
735 obj = self.datastream_set.get(mimetype=mimetype)
737 except DataStream.DoesNotExist:
738 # status = 404
739 raise exceptions.ResourceDoesNotExist("The binary file does not exist")
740 filename = obj.location
742 return filename
744 def get_abstracts(self):
745 return self.abstract_set.filter(tag__endswith="abstract")
747 def get_avertissements(self):
748 return self.abstract_set.filter(tag="avertissement")
750 def get_notes(self):
751 return self.abstract_set.filter(tag="note")
753 def get_descriptions(self):
754 return self.abstract_set.filter(tag="description")
756 def get_editorial_intros(self):
757 return self.abstract_set.filter(tag__endswith="intro")
759 def get_toc(self):
760 return self.abstract_set.filter(tag__endswith="toc")
762 def get_biblio(self):
763 return self.abstract_set.filter(tag="biblio")
765 def accept(self, visitor):
766 return visitor.visit(self.cast())
768 def natural_key(self):
769 return (self.pid, self.provider.id)
771 def get_solr_body(self, field):
772 from ptf.cmds import solr_cmds
774 body = None
775 cmd = solr_cmds.solrGetDocumentByPidCmd({"pid": self.pid})
776 result = cmd.do()
777 if result:
778 if field in result: 778 ↛ 781line 778 didn't jump to line 781 because the condition on line 778 was always true
779 body = result[field]
781 return body
783 def get_body(self):
784 return self.get_solr_body("body")
786 def volume_string(self):
787 return ""
789 def update_bibtex_with_commons(self, bibtex, container, hostname, scheme, indent):
790 # TODO chapter, howpublished?
791 # TODO institution
792 # if self.institution:
793 # append_in_latex(bibtex, indent + 'institution = {' + self.institution + '},' )
795 to_appear = container.to_appear()
796 is_cr = container.is_cr()
798 # pages = self.pages()
799 publisher = container.my_publisher
801 if publisher is not None:
802 if publisher.pub_name: 802 ↛ 804line 802 didn't jump to line 804 because the condition on line 802 was always true
803 append_in_latex(bibtex, indent + "publisher = {" + publisher.pub_name + "},")
804 if publisher.pub_loc:
805 append_in_latex(bibtex, indent + "address = {" + publisher.pub_loc + "},")
807 if not to_appear:
808 if container.volume:
809 append_in_latex(bibtex, indent + "volume = {" + self.volume_string() + "},")
810 if container.number and not (is_cr and container.number[0] == "G"):
811 append_in_latex(bibtex, indent + "number = {" + container.number + "},")
813 if container.year != "0": 813 ↛ 815line 813 didn't jump to line 815 because the condition on line 813 was always true
814 append_in_latex(bibtex, indent + "year = {" + container.year + "},")
815 elif hasattr(self, "date_online_first") and self.date_online_first is not None:
816 year = self.date_online_first.strftime("%Y")
817 append_in_latex(bibtex, indent + "year = {" + year + "},")
819 if self.doi:
820 append_in_latex(bibtex, indent + "doi = {" + self.doi + "},")
822 ext_type = {"zbl-item-id": "zbl", "mr-item-id": "mrnumber"}
823 for extid in self.extid_set.filter(id_type__in=["zbl-item-id", "mr-item-id"]):
824 append_in_latex(
825 bibtex, indent + ext_type.get(extid.id_type) + " = {" + extid.id_value + "},"
826 )
828 if self.lang and self.lang != "und":
829 append_in_latex(bibtex, indent + "language = {" + self.lang + "},")
831 if to_appear:
832 append_in_latex(bibtex, indent + "note = {Online first},")
833 elif not is_cr and len(hostname) > 0:
834 # No latex encoding, so append directly in the array
835 url = f"{scheme}://{hostname}{self.get_absolute_url()}"
836 bibtex.append("{}url = {}{}{}".format(indent, "{", url, "}"))
838 def update_ris_with_commons(self, items, container, hostname, scheme, sep):
839 to_appear = container.to_appear()
840 is_cr = container.is_cr()
842 if container.year != "0": 842 ↛ 844line 842 didn't jump to line 844 because the condition on line 842 was always true
843 items.append("PY" + sep + container.year)
844 elif hasattr(self, "date_online_first") and self.date_online_first is not None:
845 year = self.date_online_first.strftime("%Y")
846 items.append("PY" + sep + year)
848 if not to_appear:
849 if hasattr(self, "pages") and callable(self.pages) and self.pages():
850 pages = self.pages(for_bibtex=False).split("-")
851 items.append("SP" + sep + pages[0])
852 if len(pages) > 1: 852 ↛ 854line 852 didn't jump to line 854 because the condition on line 852 was always true
853 items.append("EP" + sep + pages[1])
854 if container.volume:
855 items.append("VL" + sep + container.volume)
856 if container.number and not (is_cr and container.number[0] == "G"): 856 ↛ 859line 856 didn't jump to line 859 because the condition on line 856 was always true
857 items.append("IS" + sep + container.number)
859 publisher = container.my_publisher
860 if publisher is not None:
861 if publisher.pub_name: 861 ↛ 863line 861 didn't jump to line 863 because the condition on line 861 was always true
862 items.append("PB" + sep + publisher.pub_name)
863 if publisher.pub_loc: 863 ↛ 866line 863 didn't jump to line 866 because the condition on line 863 was always true
864 items.append("PP" + sep + publisher.pub_loc)
866 if to_appear:
867 items.append("N1" + sep + "Online first")
868 elif not is_cr and len(hostname) > 0:
869 url = f"{scheme}://{hostname}{self.get_absolute_url()}"
870 items.append("UR" + sep + url)
872 if self.doi:
873 items.append("DO" + sep + self.doi)
875 if self.lang and self.lang != "und":
876 items.append("LA" + sep + self.lang)
878 if self.pid: 878 ↛ 880line 878 didn't jump to line 880 because the condition on line 878 was always true
879 items.append("ID" + sep + self.pid)
880 items.append("ER" + sep)
882 def update_endnote_with_commons(self, items, container, hostname, scheme, sep):
883 to_appear = container.to_appear()
884 is_cr = container.is_cr()
886 if container.year != "0": 886 ↛ 888line 886 didn't jump to line 888 because the condition on line 886 was always true
887 items.append("%D" + sep + container.year)
888 elif hasattr(self, "date_online_first") and self.date_online_first is not None:
889 year = self.date_online_first.strftime("%Y")
890 items.append("%D" + sep + year)
892 if not to_appear:
893 if hasattr(self, "pages") and callable(self.pages) and self.pages():
894 pages = self.pages(for_bibtex=False)
895 items.append("%P" + sep + pages)
896 if container.volume:
897 items.append("%V" + sep + container.volume)
898 if container.number and not (is_cr and container.number[0] == "G"): 898 ↛ 901line 898 didn't jump to line 901 because the condition on line 898 was always true
899 items.append("%N" + sep + container.number)
901 publisher = container.my_publisher
902 if publisher is not None:
903 if publisher.pub_name: 903 ↛ 905line 903 didn't jump to line 905 because the condition on line 903 was always true
904 items.append("%I" + sep + publisher.pub_name)
905 if publisher.pub_loc: 905 ↛ 908line 905 didn't jump to line 908 because the condition on line 905 was always true
906 items.append("%C" + sep + publisher.pub_loc)
908 if to_appear:
909 items.append("%Z" + sep + "Online first")
910 elif not is_cr and len(hostname) > 0:
911 url = f"{scheme}://{hostname}{self.get_absolute_url()}"
912 items.append("%U" + sep + url)
914 if self.doi:
915 items.append("%R" + sep + self.doi)
917 if self.lang and self.lang != "und":
918 items.append("%G" + sep + self.lang)
920 if self.pid: 920 ↛ exitline 920 didn't return from function 'update_endnote_with_commons' because the condition on line 920 was always true
921 items.append("%F" + sep + self.pid)
923 def _next_in_qs(self, qs):
924 next_item = None
926 if qs.count() > 1: 926 ↛ 927line 926 didn't jump to line 927 because the condition on line 926 was never true
927 ready_for_next = False
928 for item in qs:
929 if ready_for_next:
930 next_item = item
931 ready_for_next = False
932 if item.pid == self.pid:
933 ready_for_next = True
934 return next_item
936 def get_next_resource(self):
937 return None
939 def get_previous_resource(self):
940 return None
943class PublisherQuerySet(models.QuerySet):
944 def get_by_natural_key(self, pub_key, pub_name):
945 return self.get(pub_key=pub_key)
948class Publisher(Resource):
949 """
950 les classes Publisher, EventSeries et Event sont un peu à part:
951 a priori pas de pid ni sid et même pas d'identificateur du tout
952 d'où les slugs
953 On peut les sortir de la hiérarchie resource -- les y laisser
954 permet de leur attache des meta suppléméntaires. Voir Provider
955 pour la possibilité inverse :-)
956 """
958 pub_key = models.CharField(max_length=128, unique=True)
959 pub_name = models.CharField(max_length=256, db_index=True)
960 pub_loc = models.CharField(max_length=128, db_index=True)
962 # 2016-05-18: Publisher is only used by Container: ManyToOne relation
963 # publishes = models.ManyToManyField(Resource, related_name='Publisher')
965 def __str__(self):
966 return self.pub_key
968 @staticmethod
969 def get_collection():
970 return None
972 @staticmethod
973 def get_top_collection():
974 return None
976 @staticmethod
977 def get_container():
978 return None
980 def natural_key(self):
981 return (
982 self.pub_key,
983 self.pub_name,
984 )
986 objects = PublisherQuerySet.as_manager()
989class CollectionQuerySet(models.QuerySet):
990 def order_by_date(self):
991 return self.annotate(year=Max("content__year")).order_by("year")
993 def get_by_natural_key(self, pid, provider):
994 return self.get(pid=pid, provider=provider)
997class Collection(Resource):
998 # journal, acta, book-series, these, lectures
999 coltype = models.CharField(max_length=32, db_index=True)
1000 title_sort = models.CharField(max_length=128, db_index=True) # sort key, not displayed
1001 issn = Identifier("issn")
1002 e_issn = Identifier("e-issn")
1003 wall = models.IntegerField(default=5)
1004 alive = models.BooleanField(default=True)
1005 # First/Last year of a collection. Comes from its containers. Is typically
1006 # updated when a container is imported.
1007 fyear = models.IntegerField(default=0)
1008 lyear = models.IntegerField(default=0)
1009 last_doi = models.IntegerField(default=0)
1011 # Ancestors means journals that existed before the creation of the Collection: time-based relation
1012 # (the name 'ancestor' does not fit with 'parent' as 'parent' refers to a tree structure)
1013 # We don't really create a tree.
1014 # Ex: AFST is the root journal. AFST-0996-0481 is the original journal that became AFST-0240-2955 that became AFST.
1015 # We create a top node (AFST) and 2 ancestors (AFST-0996-0481, AFST-0240-2955)
1016 parent = models.ForeignKey(
1017 "self", on_delete=models.CASCADE, null=True, blank=True, related_name="ancestors"
1018 )
1019 objects = CollectionQuerySet.as_manager()
1021 class Meta:
1022 ordering = ["title_sort"]
1024 # This function is used by Django: https://docs.djangoproject.com/fr/3.0/ref/models/instances/
1025 # Unfortunately, the name is wrong: get_absolute_url must return a relative path !?!
1026 # => hence the get_url_absolute below that returns an absolute URL
1027 # def get_absolute_url(self):
1028 # if self.coltype == "thesis":
1029 # url = reverse("pretty_thesis", args=[f'"{self.title_html}"-p'])
1030 # elif self.coltype == "book-series":
1031 # url = reverse("pretty-series", args=[f'"{self.title_html}"-p'])
1032 # elif self.coltype == "lectures" or self.coltype == "lecture-notes":
1033 # url = reverse("pretty-lectures", args=[f'"{self.title_html}"-p'])
1034 # else:
1035 # url = reverse(self.coltype + "-issues", kwargs={"jid": self.pid})
1036 # return url
1038 def bloodline(self):
1039 # returns parent + siblings, ordered by date (of the last volume published)
1040 if self.parent or self.ancestors.exists():
1041 parent_pid = self.parent.pid if self.parent else self.pid
1042 return Collection.objects.filter(
1043 Q(pid=parent_pid) | Q(parent__pid=parent_pid)
1044 ).order_by_date()
1045 return Collection.objects.none()
1047 def preceding_journal(self):
1048 # returns my ancestor (timed-based) = the Collection that was published just before me
1049 bloodline = self.bloodline()
1050 for index, collection in enumerate(bloodline):
1051 if collection == self and index:
1052 return bloodline[index - 1]
1053 return Collection.objects.none()
1055 def get_wall(self):
1056 return self.wall
1058 def get_collection(self):
1059 return self
1061 def get_top_collection(self):
1062 return self.parent if self.parent else self
1064 def get_document_type(self):
1065 """
1066 Is used to classify the resources in the
1067 - (TODO) SOLR Facet Document Type
1068 - Geodesic menu (Journals/Books/Seminars...)
1069 """
1071 if self.coltype == "journal":
1072 document_type = "Revue"
1073 elif self.coltype == "acta":
1074 document_type = "Séminaire"
1075 elif self.coltype == "thesis":
1076 document_type = "Thèse"
1077 elif self.coltype == "lecture-notes":
1078 document_type = "Notes de cours"
1079 elif self.coltype == "proceeding":
1080 self.data["classname"] = "Acte de rencontre"
1081 document_type = "Actes de rencontres"
1082 else:
1083 document_type = "Livre"
1085 return document_type
1087 def deployed_date(self, site=None):
1088 # return the last date of metadata deployed date for all of containers
1089 containers = self.content.all()
1090 date = None
1091 if containers:
1092 site = Site.objects.get_current()
1093 sms = (
1094 SiteMembership.objects.filter(resource__in=containers)
1095 .filter(site=site)
1096 .order_by("-deployed")
1097 )
1098 date = sms.first().deployed
1099 return date
1101 def get_relative_folder(self):
1102 return resolver.get_relative_folder(self.get_top_collection().pid)
1105class ContainerQuerySet(ResourceQuerySet):
1106 def prefetch_all(self):
1107 return super().prefetch_all().select_related("my_collection", "my_publisher")
1109 def prefetch_for_toc(self):
1110 return (
1111 self.prefetch_contributors()
1112 .prefetch_work()
1113 .prefetch_related(
1114 "article_set__datastream_set",
1115 "article_set__subj_set",
1116 "article_set__extlink_set",
1117 "article_set__resourcecount_set",
1118 "article_set__contributions",
1119 )
1120 .select_related("my_collection", "my_publisher")
1121 )
1124class Container(Resource):
1125 """
1126 mappe issue et book (on pourrait faire deux classes) ou une hiérarchie
1127 container <--- issue
1128 <--- book
1129 """
1131 # issue, book-monograph, book-edited-book (multiple authors, with editors), lecture-notes
1132 ctype = models.CharField(max_length=32, db_index=True)
1133 year = models.CharField(max_length=32, db_index=True)
1135 last_modified = models.DateTimeField(null=False, blank=False)
1136 ##
1137 # if ctype == issue
1138 number = models.CharField(max_length=32, db_index=True) # issue number
1139 ##
1140 # data for relation with enclosing serial, if any
1141 my_collection = models.ForeignKey(
1142 Collection, null=True, related_name="content", on_delete=models.CASCADE
1143 )
1144 vseries = models.CharField(max_length=32, db_index=True)
1145 volume = models.CharField(max_length=64, db_index=True)
1146 # la même chose pour le tri
1147 vseries_int = models.IntegerField(default=0, db_index=True)
1148 volume_int = models.IntegerField(default=0, db_index=True)
1149 number_int = models.IntegerField(default=0, db_index=True)
1150 seq = models.IntegerField(db_index=True)
1151 with_online_first = models.BooleanField(default=False) # Used by ptf-tools only
1153 my_publisher = models.ForeignKey(
1154 Publisher, related_name="publishes", null=True, on_delete=models.CASCADE
1155 )
1157 # Initially, a container could only be in 1 collection.
1158 # In 2018, we extended the model so that a container can be
1159 # in multiple collections (Bourbaki and Asterisque)
1160 #
1161 # This is an exception, most containers are in only 1 collection.
1162 # my_collection is kept to store the main collection, the one used in the "how to cite" field.
1163 # my_other_collections stores the additional collections.
1164 #
1165 # vseries/volume/number/seq of the main collection are kept in Container.
1166 # As a result, there is no need to fetch the CollectionMembership to get
1167 # these info for the main collection.
1168 my_other_collections = models.ManyToManyField(
1169 Collection, symmetrical=False, through="CollectionMembership"
1170 )
1172 objects = ContainerQuerySet.as_manager()
1174 class Meta:
1175 ordering = ["seq"]
1176 get_latest_by = ["year", "vseries_int", "volume_int", "number_int"]
1178 def allow_crossref(self):
1179 # we need at least a doi or an issn to allow crossref record
1180 result = self.my_collection.doi or self.my_collection.issn or self.my_collection.e_issn
1182 # if there are unpublished articles in the volume, we block crossref record
1183 if self.article_set.filter( 1183 ↛ 1186line 1183 didn't jump to line 1186 because the condition on line 1183 was never true
1184 date_published__isnull=True, date_online_first__isnull=True
1185 ).exists():
1186 result = False
1188 return result
1190 def all_doi_are_registered(self):
1191 if self.article_set.count() > 0:
1192 # il y a des articles, on vérifie qu'ils sont tous enregistrés
1193 return (
1194 self.article_set.filter(doibatch__status="Enregistré").count()
1195 == self.article_set.count()
1196 )
1197 if self.doi and self.doibatch is not None:
1198 return self.doibatch.status == "Enregistré"
1199 # aucun doi associé aux articles ou au container
1200 return False
1202 def registered_in_doaj(self):
1203 query = Q(date_published__isnull=True, date_online_first__isnull=True) | Q(
1204 do_not_publish__isnull=True
1205 )
1206 unpublished = self.article_set.filter(query).count()
1207 registered = self.article_set.filter(doajbatch__status="registered").count()
1208 all_registered = True if registered == self.article_set.count() - unpublished else False
1209 if not all_registered and hasattr(self, "doajbatch"):
1210 self.doajbatch.status = "unregistered"
1211 self.doajbatch.save()
1213 def are_all_articles_published(self):
1214 from ptf import model_helpers
1216 result = True
1218 for article in self.article_set.all():
1219 year = article.get_year()
1220 fyear, lyear = model_helpers.get_first_last_years(year)
1221 try:
1222 fyear = int(fyear)
1223 except ValueError:
1224 fyear = 0
1226 if fyear > 2017:
1227 if not article.date_published: 1227 ↛ 1228line 1227 didn't jump to line 1228 because the condition on line 1227 was never true
1228 result = False
1229 elif fyear == 0:
1230 result = False
1232 return result
1234 def get_wall(self):
1235 return self.my_collection.get_wall()
1237 def previous(self):
1238 if self.get_top_collection().pid not in settings.COLLECTIONS_SEQUENCED:
1239 return None
1241 issue = None
1242 qs = self.my_collection.content.filter(seq=self.seq - 1)
1243 if qs.count() > 0:
1244 issue = qs.first()
1245 return issue
1247 def next(self):
1248 if self.get_top_collection().pid not in settings.COLLECTIONS_SEQUENCED:
1249 return None
1251 issue = None
1252 qs = self.my_collection.content.filter(seq=self.seq + 1)
1253 if qs.count() > 0:
1254 issue = qs.first()
1255 return issue
1257 def get_next_resource(self):
1258 # No Next/Previous for CRAS Special Issues or "Volume articles"
1259 colid = self.get_top_collection().pid
1260 if colid in settings.CRAS_COLLECTIONS:
1261 year = int(self.year)
1262 if ( 1262 ↛ 1267line 1262 didn't jump to line 1267
1263 self.title_html
1264 or (colid != "CRBIOL" and year > 2020)
1265 or (colid == "CRBIOL" and year > 2022)
1266 ):
1267 return None
1269 collection = self.get_top_collection()
1270 if collection.pid in settings.COLLECTIONS_SEQUENCED: 1270 ↛ 1271line 1270 didn't jump to line 1271 because the condition on line 1270 was never true
1271 qs = collection.content.order_by("seq")
1272 else:
1273 qs = collection.content.order_by("vseries_int", "year", "volume_int", "number_int")
1275 next_issue = self._next_in_qs(qs)
1276 return next_issue
1278 def get_previous_resource(self):
1279 # No Next/Previous for CRAS Special Issues or "Volume articles"
1280 colid = self.get_top_collection().pid
1281 if colid in settings.CRAS_COLLECTIONS:
1282 year = int(self.year)
1283 if ( 1283 ↛ 1288line 1283 didn't jump to line 1288
1284 self.title_html
1285 or (colid != "CRBIOL" and year > 2020)
1286 or (colid == "CRBIOL" and year > 2022)
1287 ):
1288 return None
1290 collection = self.get_top_collection()
1291 if collection.pid in settings.COLLECTIONS_SEQUENCED: 1291 ↛ 1292line 1291 didn't jump to line 1292 because the condition on line 1291 was never true
1292 qs = collection.content.order_by("-seq")
1293 else:
1294 qs = collection.content.order_by("-vseries_int", "-year", "-volume_int", "-number_int")
1295 next_issue = self._next_in_qs(qs)
1296 return next_issue
1298 # TODO container in multiple collections
1299 def get_collection(self):
1300 return self.my_collection
1302 def get_top_collection(self):
1303 return self.my_collection.get_top_collection()
1305 def get_other_collections(self):
1306 return self.my_other_collections.all()
1308 def get_container(self):
1309 return self
1311 def get_document_type(self):
1312 """
1313 Is used to classify the resources in the
1314 - (TODO) SOLR Facet Document Type
1315 - Geodesic menu (Journals/Books/Seminars...)
1316 """
1317 return self.get_top_collection().get_document_type()
1319 def get_volume(self):
1320 return self.volume
1322 def get_number(self):
1323 return self.number
1325 def embargo(self):
1326 return resolver.embargo(self.get_wall(), self.year)
1328 def to_appear(self):
1329 return self.with_online_first or (
1330 hasattr(settings, "ISSUE_TO_APPEAR_PID") and settings.ISSUE_TO_APPEAR_PID == self.pid
1331 )
1333 def is_cr(self):
1334 return (
1335 hasattr(settings, "SITE_NAME")
1336 and len(settings.SITE_NAME) == 6
1337 and settings.SITE_NAME[0:2] == "cr"
1338 )
1340 @staticmethod
1341 def get_base_url():
1342 return resolver.get_issue_base_url()
1344 def get_relative_folder(self):
1345 collection = self.get_top_collection()
1346 return resolver.get_relative_folder(collection.pid, self.pid)
1348 def get_vid(self):
1349 """
1350 08/09/2022: support of Collection ancestors
1351 The collection.pid might no longer be the top_collection.pid
1352 => The volume URL would change if we were to keep the same vid
1353 To keep URLs relatively similar, we use the pid of the first_issue of the volume
1355 VolumeDetailView (ptf/views.py) handles the vid differently and no longer decrypts the vid
1356 """
1357 # vid = f"{self.my_collection.pid}_{self.year}_{self.vseries}_{self.volume}"
1358 vid = self.pid
1359 return vid
1361 def get_year(self):
1362 return self.year
1364 def is_edited_book(self):
1365 return self.ctype == EDITED_BOOK_TYPE
1367 def get_citation(self, request):
1368 citation = ""
1370 author_names = get_names(self, "author")
1371 authors = ""
1372 if author_names:
1373 authors = "; ".join(author_names)
1375 if not author_names or authors == "Collectif":
1376 author_names = get_names(self, "editor")
1377 if author_names:
1378 authors = "; ".join(author_names) + " (" + str(_("éd.")) + ")"
1379 else:
1380 authors = ""
1382 if authors != "Collectif": 1382 ↛ 1385line 1382 didn't jump to line 1385 because the condition on line 1382 was always true
1383 citation += authors
1385 if citation:
1386 if not citation.endswith("."): 1386 ↛ 1388line 1386 didn't jump to line 1388 because the condition on line 1386 was always true
1387 citation += "."
1388 citation += " "
1390 citation += self.title_tex + ". "
1391 citation += self.my_collection.title_tex
1393 if self.vseries:
1394 citation += f", {str(_('Série'))} {self.vseries}"
1396 if self.volume:
1397 citation += ", " + str(volume_display()) + " " + self.volume + " (" + self.year + ") "
1399 if self.number: 1399 ↛ 1406line 1399 didn't jump to line 1406 because the condition on line 1399 was always true
1400 citation += "no. " + self.number + ", "
1401 elif self.number:
1402 citation += ", no. " + self.number + " (" + self.year + "), "
1403 else:
1404 citation += " (" + self.year + "), "
1406 redactors = self.get_redaktors()
1407 if len(redactors) > 0:
1408 redactors_str = "; ".join(get_names(self, "redaktor"))
1409 citation += f"{redactors_str} (red.), "
1411 for pages in self.resourcecount_set.all():
1412 citation += pages.value + " p."
1414 for resourceid in self.resourceid_set.all():
1415 if resourceid.id_type == "doi": 1415 ↛ 1414line 1415 didn't jump to line 1414 because the condition on line 1415 was always true
1416 citation += " doi : " + resourceid.id_value + "."
1418 citation += " " + self.get_url_absolute()
1420 return citation
1422 def has_detailed_info(self):
1423 # Ignore citations here.
1425 result = False
1427 if self.extid_set.exists(): 1427 ↛ 1428line 1427 didn't jump to line 1428 because the condition on line 1427 was never true
1428 result = True
1429 elif self.kwd_set.exists(): 1429 ↛ 1430line 1429 didn't jump to line 1430 because the condition on line 1429 was never true
1430 result = True
1431 else:
1432 for resourceid in self.resourceid_set.all():
1433 if resourceid.id_type != "numdam-prod-id": 1433 ↛ 1432line 1433 didn't jump to line 1432 because the condition on line 1433 was always true
1434 result = True
1436 return result
1438 def get_bibtex(self, request):
1439 """
1441 :param self:
1442 :return: a string encoded in latex (with latexcodec)
1443 """
1444 bibtex = []
1445 indent = " "
1447 collection = self.get_collection()
1449 is_phdthesis = False
1451 # no bibtex for an issue, only for a book (book, these)
1452 if self.ctype == "issue":
1453 return bibtex
1455 if collection is not None and collection.coltype == "thesis": 1455 ↛ 1456line 1455 didn't jump to line 1456 because the condition on line 1455 was never true
1456 is_phdthesis = True
1458 author_names = get_bibtex_names(self, "author")
1459 editor_names = get_bibtex_names(self, "editor")
1461 type_ = "book"
1462 if is_phdthesis: 1462 ↛ 1463line 1462 didn't jump to line 1463 because the condition on line 1462 was never true
1463 type_ = "phdthesis"
1465 # Numdam meeting: Use the resource pid as the bibtex id => No latex encoding to keep the '_'
1466 id_ = self.pid
1467 bibtex.append("@" + type_ + "{" + id_ + ",")
1469 if author_names:
1470 append_in_latex(bibtex, indent + author_names)
1471 if editor_names:
1472 append_in_latex(bibtex, indent + editor_names)
1473 append_in_latex(bibtex, indent + "title = {" + self.title_tex + "},", is_title=True)
1475 if collection is not None: 1475 ↛ 1478line 1475 didn't jump to line 1478 because the condition on line 1475 was always true
1476 append_in_latex(bibtex, indent + "series = {" + collection.title_tex + "},")
1478 self.update_bibtex_with_commons(bibtex, self, request.get_host(), request.scheme, indent)
1480 append_in_latex(bibtex, "}")
1481 return "\n".join(bibtex)
1483 def get_ris(self, request):
1484 """
1486 :param self:
1487 :return: a string
1488 """
1489 items = []
1490 sep = " - "
1492 collection = self.get_collection()
1494 is_phdthesis = False
1496 # no citation for an issue, only for a book (book, these)
1497 if self.ctype == "issue":
1498 return ""
1500 if collection is not None and collection.coltype == "these": 1500 ↛ 1501line 1500 didn't jump to line 1501 because the condition on line 1500 was never true
1501 is_phdthesis = True
1503 if is_phdthesis: 1503 ↛ 1504line 1503 didn't jump to line 1504 because the condition on line 1503 was never true
1504 items.append("TY" + sep + "THES")
1505 else:
1506 items.append("TY" + sep + "BOOK")
1508 author_names = get_names(self, CONTRIB_TYPE_AUTHOR)
1509 for author in author_names: 1509 ↛ 1510line 1509 didn't jump to line 1510 because the loop on line 1509 never started
1510 items.append("AU" + sep + author)
1512 editor_names = get_names(self, CONTRIB_TYPE_EDITOR)
1513 for editor in editor_names:
1514 items.append("ED" + sep + editor)
1516 items.append("TI" + sep + self.title_tex)
1518 if collection is not None: 1518 ↛ 1521line 1518 didn't jump to line 1521 because the condition on line 1518 was always true
1519 items.append("T3" + sep + collection.title_tex)
1521 self.update_ris_with_commons(items, self, request.get_host(), request.scheme, sep)
1522 return "\r\n".join(items)
1524 def get_endnote(self, request):
1525 """
1527 :param self:
1528 :return: a string
1529 """
1530 items = []
1531 sep = " "
1533 collection = self.get_collection()
1535 is_phdthesis = False
1537 # no citation for an issue, only for a book (book, these)
1538 if self.ctype == "issue":
1539 return ""
1541 if collection is not None and collection.coltype == "these": 1541 ↛ 1542line 1541 didn't jump to line 1542 because the condition on line 1541 was never true
1542 is_phdthesis = True
1544 if is_phdthesis: 1544 ↛ 1545line 1544 didn't jump to line 1545 because the condition on line 1544 was never true
1545 items.append("%0" + sep + "Thesis")
1546 else:
1547 items.append("%0" + sep + "Book")
1549 author_names = get_names(self, CONTRIB_TYPE_AUTHOR)
1550 for author in author_names: 1550 ↛ 1551line 1550 didn't jump to line 1551 because the loop on line 1550 never started
1551 items.append("%A" + sep + author)
1553 editor_names = get_names(self, CONTRIB_TYPE_EDITOR)
1554 for editor in editor_names:
1555 items.append("%E" + sep + editor)
1557 items.append("%T" + sep + self.title_tex)
1559 if collection is not None: 1559 ↛ 1562line 1559 didn't jump to line 1562 because the condition on line 1559 was always true
1560 items.append("%S" + sep + collection.title_tex)
1562 self.update_endnote_with_commons(items, self, request.get_host(), request.scheme, sep)
1563 return "\r\n".join(items)
1565 def has_articles_excluded_from_publication(self):
1566 result = self.article_set.filter(do_not_publish=True).count() > 0
1567 return result
1570class EventSeries(Resource):
1571 """to do: clé fabriquée voir _manager.make_key done"""
1573 slug = models.CharField(max_length=128, unique=True)
1574 event_type = models.CharField(max_length=32, db_index=True)
1575 acro = models.CharField(max_length=32, db_index=True)
1576 title_sort = models.CharField(max_length=128, db_index=True)
1577 short_title = models.CharField(max_length=64, db_index=True)
1580class Event(Resource):
1581 """to do: clé fabriquée voir _manager.make_key done"""
1583 slug = models.CharField(max_length=128, unique=True)
1584 event_type = models.CharField(max_length=32, db_index=True)
1585 title_sort = models.CharField(max_length=128, db_index=True)
1586 string_event = models.CharField(max_length=128, db_index=True)
1587 year = models.CharField(max_length=32, db_index=True)
1588 acro = models.CharField(max_length=32, db_index=True)
1589 number = models.CharField(max_length=4, db_index=True)
1590 loc = models.CharField(max_length=64, db_index=True)
1591 theme = models.CharField(max_length=64, db_index=True)
1592 contrib = models.TextField()
1593 proceedings = models.ManyToManyField(Resource, related_name="Events", symmetrical=False)
1594 series = models.ForeignKey(EventSeries, null=True, on_delete=models.CASCADE)
1596 class Meta:
1597 ordering = ["year"] # seq (int)
1600class ArticleQuerySet(ResourceQuerySet):
1601 def order_by_published_date(self):
1602 return self.order_by("-date_published", "-seq")
1604 def order_by_sequence(self):
1605 return self.order_by("seq")
1607 def prefetch_for_toc(self):
1608 return (
1609 self.prefetch_contributors()
1610 .prefetch_work()
1611 .select_related("my_container", "my_container__my_collection")
1612 )
1615class Article(Resource):
1616 """mappe journal article, book-part"""
1618 atype = models.CharField(max_length=32, db_index=True)
1619 fpage = models.CharField(max_length=32, db_index=True)
1620 lpage = models.CharField(max_length=32, db_index=True)
1621 page_range = models.CharField(max_length=32, db_index=True)
1622 page_type = models.CharField(max_length=64)
1623 elocation = models.CharField(max_length=32, db_index=True)
1624 article_number = models.CharField(max_length=32)
1625 talk_number = models.CharField(max_length=32)
1626 date_received = models.DateTimeField(null=True, blank=True)
1627 date_accepted = models.DateTimeField(null=True, blank=True)
1628 date_revised = models.DateTimeField(null=True, blank=True)
1629 date_online_first = models.DateTimeField(null=True, blank=True)
1630 date_published = models.DateTimeField(null=True, blank=True)
1631 date_pre_published = models.DateTimeField(
1632 null=True, blank=True
1633 ) # Used by ptf-tools only to measure delays
1634 coi_statement = models.TextField(null=True, blank=True) # Conflict of interest
1635 show_body = models.BooleanField(
1636 default=True
1637 ) # Used by ptf-tools only (to export or not the body)
1638 do_not_publish = models.BooleanField(
1639 default=False
1640 ) # Used by ptf-tools only (to export or not the article)
1642 ##
1643 # container
1644 my_container = models.ForeignKey(Container, null=True, on_delete=models.CASCADE)
1645 seq = models.IntegerField()
1646 ##
1647 # parent part
1648 parent = models.ForeignKey(
1649 "self", null=True, related_name="children", on_delete=models.CASCADE
1650 )
1651 pseq = models.IntegerField()
1652 objects = ArticleQuerySet.as_manager()
1654 class Meta:
1655 ordering = ["seq", "fpage"]
1657 def __str__(self):
1658 return self.pid
1660 @staticmethod
1661 def get_base_url():
1662 return resolver.get_article_base_url()
1664 def get_absolute_url(self):
1665 if self.doi is not None:
1666 return reverse("article", kwargs={"aid": self.doi})
1667 else:
1668 return reverse("item_id", kwargs={"pid": self.pid})
1670 def get_relative_folder(self):
1671 collection = self.get_top_collection()
1672 return resolver.get_relative_folder(collection.pid, self.my_container.pid, self.pid)
1674 def embargo(self):
1675 if self.my_container is None: 1675 ↛ 1676line 1675 didn't jump to line 1676 because the condition on line 1675 was never true
1676 return False
1678 return self.my_container.embargo()
1680 def get_wall(self):
1681 if self.my_container is None: 1681 ↛ 1682line 1681 didn't jump to line 1682 because the condition on line 1681 was never true
1682 return 0
1683 return self.my_container.get_wall()
1685 def get_collection(self):
1686 return self.my_container.get_collection()
1688 def get_top_collection(self):
1689 return self.my_container.get_top_collection()
1691 def get_container(self):
1692 return self.my_container
1694 def get_document_type(self):
1695 """
1696 Is used to classify the resources in the
1697 - (TODO) SOLR Facet Document Type
1698 - Geodesic menu (Journals/Books/Seminars...)
1699 """
1701 collection = self.get_top_collection()
1702 if collection.coltype == "journal":
1703 document_type = "Article de revue"
1704 elif collection.coltype == "acta":
1705 document_type = "Acte de séminaire"
1706 elif collection.coltype == "thesis":
1707 document_type = "Thèse"
1708 elif collection.coltype == "lecture-notes":
1709 document_type = "Notes de cours"
1710 elif collection.coltype == "proceeding":
1711 document_type = "Acte de rencontre"
1712 else:
1713 document_type = "Chapitre de livre"
1715 return document_type
1717 def get_volume(self):
1718 return self.my_container.get_volume()
1720 def get_number(self):
1721 return self.my_container.get_number()
1723 def get_page_count(self):
1724 page_count = None
1725 for resourcecount in self.resourcecount_set.all():
1726 if resourcecount.name == "page-count": 1726 ↛ 1725line 1726 didn't jump to line 1725 because the condition on line 1726 was always true
1727 page_count = resourcecount.value
1729 return page_count
1731 def get_article_page_count(self):
1732 try:
1733 page_count = self.get_page_count() or 0
1734 page_count = int(page_count)
1735 except ValueError:
1736 page_count = 0
1738 lpage = fpage = 0
1739 try:
1740 fpage = int(self.fpage)
1741 except ValueError:
1742 pass
1743 try:
1744 lpage = int(self.lpage)
1745 except ValueError:
1746 pass
1747 if lpage > 0 and fpage > 0:
1748 page_count = lpage - fpage + 1
1749 return page_count
1751 def pages(self, for_bibtex=False):
1752 """
1753 Returns a string with the article pages.
1754 It is used for the different exports (BibTeX, EndNote, RIS)
1756 typically "{fpage}-{lpage}"
1757 For BibTex, 2 '-' are used, ie "{fpage}--{lpage}"
1759 Some articles have a page_range. Use this field instead of the fpage/lpage in this case.
1760 """
1761 if self.page_range:
1762 return self.page_range
1763 if self.fpage or self.lpage:
1764 if self.lpage:
1765 return (
1766 f"{self.fpage}--{self.lpage}" if for_bibtex else f"{self.fpage}-{self.lpage}"
1767 )
1768 else:
1769 return self.fpage
1770 return None
1772 def volume_series(self):
1773 # Use only for latex
1774 if not self.my_container.vseries:
1775 return ""
1776 if self.lang == "fr":
1777 return self.my_container.vseries + "e s{\\'e}rie, "
1778 return f"Ser. {self.my_container.vseries}, "
1780 def volume_string(self):
1781 return self.volume_series() + self.my_container.volume
1783 def get_page_text(self, use_pp=False):
1784 if (self.talk_number or self.article_number) and self.get_page_count():
1785 return self.get_page_count() + " p."
1787 if not (self.lpage or self.fpage):
1788 return ""
1790 if self.lpage == self.fpage:
1791 return f"p. {self.lpage}"
1793 text = ""
1794 if not self.page_range:
1795 if self.fpage and self.lpage and use_pp:
1796 text = "pp. "
1797 else:
1798 text = "p. "
1799 text += self.fpage
1800 if self.fpage and self.lpage:
1801 text += "-"
1802 if self.lpage:
1803 text += self.lpage
1804 elif self.page_range[0] != "p": 1804 ↛ 1807line 1804 didn't jump to line 1807 because the condition on line 1804 was always true
1805 text = "p. " + self.page_range
1807 return text
1809 def get_summary_page_text(self):
1810 text = ""
1811 if self.talk_number:
1812 text += str(_("Exposé")) + " no. " + str(self.talk_number) + ", "
1813 if self.article_number:
1814 text += "article no. " + str(self.article_number) + ", "
1816 text += self.get_page_text()
1818 return text
1820 def get_breadcrumb_page_text(self):
1821 text = ""
1822 if self.my_container.with_online_first:
1823 if self.doi is not None:
1824 text = self.doi
1825 else:
1826 text = str(_("Première publication"))
1827 elif self.talk_number:
1828 text += str(_("Exposé")) + " no. " + str(self.talk_number)
1829 elif self.article_number:
1830 text += "article no. " + str(self.article_number)
1831 else:
1832 text += self.get_page_text()
1834 return text
1836 def get_year(self):
1837 return self.my_container.year
1839 def previous(self):
1840 try:
1841 return self.my_container.article_set.get(seq=(self.seq - 1))
1842 except (Article.DoesNotExist, MultipleObjectsReturned):
1843 return None
1845 def next(self):
1846 try:
1847 return self.my_container.article_set.get(seq=(self.seq + 1))
1848 except (Article.DoesNotExist, MultipleObjectsReturned):
1849 return None
1851 def get_next_resource(self):
1852 # TODO: rename the next function defined just above by this function
1853 next_article = None
1855 try:
1856 next_article = self.my_container.article_set.get(seq=(self.seq + 1))
1857 except (Article.DoesNotExist, MultipleObjectsReturned):
1858 pass
1860 if next_article is None and not self.my_container.title_html and self.my_container.volume: 1860 ↛ 1861line 1860 didn't jump to line 1861 because the condition on line 1860 was never true
1861 qs = Container.objects.filter(volume=self.my_container.volume)
1862 if qs.count() > 1:
1863 qs = qs.order_by("number_int")
1864 next_issue = self._next_in_qs(qs)
1865 if next_issue:
1866 qs = next_issue.article_set.order_by("seq")
1867 if qs.exists():
1868 next_article = qs.first()
1870 return next_article
1872 def get_previous_resource(self):
1873 previous_article = None
1875 try:
1876 previous_article = self.my_container.article_set.get(seq=(self.seq - 1))
1877 except (Article.DoesNotExist, MultipleObjectsReturned):
1878 pass
1880 if ( 1880 ↛ 1885line 1880 didn't jump to line 1885
1881 previous_article is None
1882 and not self.my_container.title_html
1883 and self.my_container.volume
1884 ):
1885 qs = Container.objects.filter(volume=self.my_container.volume)
1886 if qs.count() > 1:
1887 qs = qs.order_by("-number_int")
1888 previous_issue = self._next_in_qs(qs)
1889 if previous_issue:
1890 qs = previous_issue.article_set.order_by("-seq")
1891 if qs.exists():
1892 previous_article = qs.first()
1894 return previous_article
1896 def get_citation_base(self, year=None, page_text=None):
1897 citation = ""
1898 if year is None: 1898 ↛ 1901line 1898 didn't jump to line 1901 because the condition on line 1898 was always true
1899 year = self.my_container.year
1901 to_appear = self.my_container.to_appear()
1902 is_cr = self.my_container.is_cr()
1904 if to_appear and year != "0":
1905 citation += f", Online first ({year})"
1906 elif to_appear:
1907 citation += ", Online first"
1908 else:
1909 if self.my_container.vseries:
1910 citation += f", {str(_('Série'))} {self.my_container.vseries}"
1912 if self.my_container.volume:
1913 citation += f", {str(volume_display())} {self.my_container.volume} ({year})"
1915 if self.my_container.number and not (is_cr and self.my_container.number[0] == "G"): 1915 ↛ 1922line 1915 didn't jump to line 1922 because the condition on line 1915 was always true
1916 citation += f" no. {self.my_container.number}"
1917 elif self.my_container.number: 1917 ↛ 1920line 1917 didn't jump to line 1920 because the condition on line 1917 was always true
1918 citation += f", no. {self.my_container.number} ({year})"
1919 else:
1920 citation += f" ({year})"
1922 if self.talk_number: 1922 ↛ 1923line 1922 didn't jump to line 1923 because the condition on line 1922 was never true
1923 citation += f", {str(_('Exposé'))} no. {str(self.talk_number)}"
1924 if self.article_number: 1924 ↛ 1925line 1924 didn't jump to line 1925 because the condition on line 1924 was never true
1925 citation += f", article no. {str(self.article_number)}"
1927 if page_text is None: 1927 ↛ 1929line 1927 didn't jump to line 1929 because the condition on line 1927 was always true
1928 page_text = self.get_page_text(True)
1929 if len(page_text) > 0:
1930 citation += f", {page_text}"
1932 if citation[-1] != ".":
1933 citation += "."
1935 if not to_appear:
1936 if self.page_type == "volant":
1937 citation += f" ({str(_('Pages volantes'))})"
1938 elif self.page_type == "supplement":
1939 citation += f" ({str(_('Pages supplémentaires'))})"
1940 elif self.page_type == "preliminaire":
1941 citation += f" ({str(_('Pages préliminaires'))})"
1942 elif self.page_type == "special":
1943 citation += f" ({str(_('Pages spéciales'))})"
1945 return citation
1947 def get_citation(self, request=None, with_formatting=False):
1948 citation = ""
1950 author_names = get_names(self, "author")
1951 if author_names:
1952 authors = "; ".join(author_names)
1953 else:
1954 author_names = get_names(self, "editor")
1955 if author_names:
1956 authors = "; ".join(author_names) + " (" + str(_("éd.")) + ")"
1957 else:
1958 authors = ""
1960 if authors != "Collectif": 1960 ↛ 1963line 1960 didn't jump to line 1963 because the condition on line 1960 was always true
1961 citation += authors
1963 if citation:
1964 if not citation.endswith("."): 1964 ↛ 1966line 1964 didn't jump to line 1966 because the condition on line 1964 was always true
1965 citation += "."
1966 citation += " "
1968 if with_formatting:
1969 citation += f"<strong>{self.title_tex}</strong>"
1970 else:
1971 citation += self.title_tex
1972 if self.my_container.ctype != "issue":
1973 citation += ", "
1974 citation += str(_("dans")) + " <em>" + self.my_container.title_tex + "</em>, "
1975 else:
1976 citation += ". "
1977 citation += self.my_container.my_collection.title_tex
1979 citation += self.get_citation_base()
1981 to_appear = self.my_container.to_appear()
1982 is_cr = self.my_container.is_cr()
1984 if not to_appear or is_cr: 1984 ↛ 1992line 1984 didn't jump to line 1992 because the condition on line 1984 was always true
1985 if self.doi is not None: 1985 ↛ 1988line 1985 didn't jump to line 1988 because the condition on line 1985 was always true
1986 citation += " doi : " + self.doi + "."
1988 if not to_appear and request is not None:
1989 url = f"{request.scheme}://{request.get_host()}{self.get_absolute_url()}"
1990 citation += " " + url
1992 return citation
1994 def has_detailed_info(self):
1995 # Ignore citations here.
1997 result = False
1999 if (
2000 self.date_received
2001 or self.date_revised
2002 or self.date_accepted
2003 or self.date_published
2004 or self.date_online_first
2005 ):
2006 result = True
2007 elif self.extid_set.exists():
2008 result = True
2009 elif self.kwd_set.exists():
2010 result = True
2011 elif self.doi is not None: 2011 ↛ 2012line 2011 didn't jump to line 2012 because the condition on line 2011 was never true
2012 result = True
2013 else:
2014 for resourceid in self.resourceid_set.all():
2015 if resourceid.id_type != "numdam-prod-id": 2015 ↛ 2014line 2015 didn't jump to line 2014 because the condition on line 2015 was always true
2016 result = True
2018 return result
2020 def get_ojs_id(self):
2021 ojs_id = ""
2022 qs = self.resourceid_set.filter(id_type="ojs-id")
2023 if qs.count() > 0: 2023 ↛ 2024line 2023 didn't jump to line 2024 because the condition on line 2023 was never true
2024 resourceid = qs.first()
2025 ojs_id = resourceid.id_value
2026 pos = ojs_id.find("$$")
2027 if pos > 0: 2027 ↛ 2028line 2027 didn't jump to line 2028 because the condition on line 2027 was never true
2028 ojs_id = ojs_id[0:pos]
2029 return ojs_id
2031 def update_bibtex_with_book_contributors(
2032 self, bibtex, collection, container, indent, author_names, editor_names
2033 ):
2034 if container is not None: 2034 ↛ 2046line 2034 didn't jump to line 2046 because the condition on line 2034 was always true
2035 append_in_latex(bibtex, indent + "booktitle = {" + container.title_tex + "},")
2037 # @incollection (edited-books): add the book editors
2038 book_author_names = get_bibtex_names(container, "author")
2039 book_editor_names = get_bibtex_names(container, "editor")
2041 if not author_names and book_author_names: 2041 ↛ 2042line 2041 didn't jump to line 2042 because the condition on line 2041 was never true
2042 append_in_latex(bibtex, indent + book_author_names)
2043 if not editor_names and book_editor_names: 2043 ↛ 2046line 2043 didn't jump to line 2046 because the condition on line 2043 was always true
2044 append_in_latex(bibtex, indent + book_editor_names)
2046 if collection is not None: 2046 ↛ exitline 2046 didn't return from function 'update_bibtex_with_book_contributors' because the condition on line 2046 was always true
2047 append_in_latex(bibtex, indent + "series = {" + collection.title_tex + "},")
2049 def get_bibtex(self, request=None, is_title=True):
2050 """
2052 :param self:
2053 :return: string encoded in latex (with latexcodec)
2054 """
2055 bibtex = []
2056 indent = " "
2058 container = self.my_container
2059 collection = self.get_collection()
2061 is_article = True
2062 is_incollection = False
2063 is_inbook = False
2064 is_phdthesis = False
2066 if container is not None and container.ctype != "issue":
2067 is_article = False
2069 if collection is not None and collection.coltype == "these": 2069 ↛ 2070line 2069 didn't jump to line 2070 because the condition on line 2069 was never true
2070 is_phdthesis = True
2071 elif container.ctype == "book-monograph": 2071 ↛ 2072line 2071 didn't jump to line 2072 because the condition on line 2071 was never true
2072 is_inbook = True
2073 elif container.ctype in ["book-edited-book", "lecture-notes"]: 2073 ↛ 2078line 2073 didn't jump to line 2078 because the condition on line 2073 was always true
2074 is_incollection = True
2075 elif collection.coltype == "proceeding": 2075 ↛ 2076line 2075 didn't jump to line 2076 because the condition on line 2075 was never true
2076 is_incollection = True
2078 to_appear = container.to_appear()
2079 is_cr = container.is_cr()
2081 # No bibtex at the article level for a these
2082 if is_phdthesis: 2082 ↛ 2083line 2082 didn't jump to line 2083 because the condition on line 2082 was never true
2083 return ""
2085 author_names = get_bibtex_names(self, "author")
2086 editor_names = get_bibtex_names(self, "editor")
2088 type_ = "article"
2089 if to_appear and not is_cr: 2089 ↛ 2090line 2089 didn't jump to line 2090 because the condition on line 2089 was never true
2090 type_ = "unpublished"
2091 elif is_inbook: 2091 ↛ 2092line 2091 didn't jump to line 2092 because the condition on line 2091 was never true
2092 type_ = "inbook"
2093 elif is_incollection:
2094 type_ = "incollection"
2095 elif len(author_names) == 0 and len(editor_names) == 0:
2096 type_ = "misc"
2098 # Numdam meeting: Use the resource pid as the bibtex id => No latex encoding to keep the '_'
2099 bibtex.append("@" + type_ + "{" + self.pid + ",")
2101 if author_names:
2102 append_in_latex(bibtex, indent + author_names)
2103 if editor_names:
2104 append_in_latex(bibtex, indent + editor_names)
2106 title = xml_utils.normalise_span(self.title_tex)
2107 append_in_latex(bibtex, indent + "title = {" + title + "},", is_title=is_title)
2109 if is_article and not is_incollection:
2110 title = xml_utils.normalise_span(collection.title_tex)
2111 append_in_latex(bibtex, indent + "journal = {" + title + "},")
2112 elif is_article: 2112 ↛ 2113line 2112 didn't jump to line 2113 because the condition on line 2112 was never true
2113 title = xml_utils.normalise_span(container.title_tex)
2114 append_in_latex(bibtex, indent + "booktitle = {" + title + "},")
2115 title = xml_utils.normalise_span(collection.title_tex)
2116 append_in_latex(bibtex, indent + "series = {" + title + "},")
2117 else:
2118 self.update_bibtex_with_book_contributors(
2119 bibtex, collection, container, indent, author_names, editor_names
2120 )
2122 if not to_appear:
2123 if self.talk_number:
2124 append_in_latex(bibtex, indent + "note = {talk:" + self.talk_number + "},")
2125 if self.article_number: 2125 ↛ 2126line 2125 didn't jump to line 2126 because the condition on line 2125 was never true
2126 append_in_latex(bibtex, indent + "eid = {" + self.article_number + "},")
2127 if self.pages():
2128 append_in_latex(bibtex, indent + "pages = {" + self.pages(for_bibtex=True) + "},")
2130 hostname = request.get_host() if request is not None else ""
2131 scheme = request.scheme if request is not None else "https"
2132 self.update_bibtex_with_commons(bibtex, container, hostname, scheme, indent)
2134 append_in_latex(bibtex, "}")
2136 return "\n".join(bibtex)
2138 def get_ris(self, request=None):
2139 """
2141 :param self:
2142 :return: string
2143 """
2144 items = []
2145 sep = " - "
2147 container = self.my_container
2148 collection = self.get_collection()
2150 is_article = True
2151 is_incollection = False
2152 is_inbook = False
2153 is_phdthesis = False
2155 if container is not None and container.ctype != "issue":
2156 is_article = False
2158 if collection is not None and collection.coltype == "these": 2158 ↛ 2159line 2158 didn't jump to line 2159 because the condition on line 2158 was never true
2159 is_phdthesis = True
2160 elif container.ctype == "book-monograph": 2160 ↛ 2161line 2160 didn't jump to line 2161 because the condition on line 2160 was never true
2161 is_inbook = True
2162 elif container.ctype == "book-edited-book": 2162 ↛ 2164line 2162 didn't jump to line 2164 because the condition on line 2162 was always true
2163 is_incollection = True
2164 elif container.ctype == "lecture-notes":
2165 is_incollection = True
2167 to_appear = container.to_appear()
2168 is_cr = container.is_cr()
2170 # no citation at the article level for a these
2171 if is_phdthesis: 2171 ↛ 2172line 2171 didn't jump to line 2172 because the condition on line 2171 was never true
2172 return ""
2174 type_ = "JOUR" # "article"
2175 if to_appear and not is_cr: 2175 ↛ 2176line 2175 didn't jump to line 2176 because the condition on line 2175 was never true
2176 type_ = "UNPB" # "unpublished"
2177 elif is_inbook: 2177 ↛ 2178line 2177 didn't jump to line 2178 because the condition on line 2177 was never true
2178 type_ = "CHAP"
2179 elif is_incollection:
2180 type_ = "CHAP"
2181 items.append("TY" + sep + type_)
2183 author_names = get_names(self, CONTRIB_TYPE_AUTHOR)
2184 for author in author_names:
2185 items.append("AU" + sep + author)
2187 editor_names = get_names(self, CONTRIB_TYPE_EDITOR)
2188 for editor in editor_names:
2189 items.append("ED" + sep + editor)
2191 title = xml_utils.remove_html(self.title_tex)
2192 items.append("TI" + sep + title)
2194 collection_title = xml_utils.remove_html(collection.title_tex)
2195 if collection is not None and is_article:
2196 items.append("JO" + sep + collection_title)
2197 else:
2198 if container is not None: 2198 ↛ 2207line 2198 didn't jump to line 2207 because the condition on line 2198 was always true
2199 items.append("BT" + sep + container.title_tex)
2200 author_names = get_names(container, CONTRIB_TYPE_AUTHOR)
2201 for author in author_names: 2201 ↛ 2202line 2201 didn't jump to line 2202 because the loop on line 2201 never started
2202 items.append("AU" + sep + author)
2204 editor_names = get_names(container, CONTRIB_TYPE_EDITOR)
2205 for editor in editor_names:
2206 items.append("ED" + sep + editor)
2207 if collection is not None: 2207 ↛ 2210line 2207 didn't jump to line 2210 because the condition on line 2207 was always true
2208 items.append("T3" + sep + collection_title)
2210 if not to_appear:
2211 if self.talk_number: 2211 ↛ 2212line 2211 didn't jump to line 2212 because the condition on line 2211 was never true
2212 items.append("N1" + sep + "talk:" + self.talk_number)
2213 # if self.article_number:
2214 # items.append("M1" + sep + "eid = " + self.article_number)
2216 hostname = request.get_host() if request is not None else ""
2217 scheme = request.scheme if request is not None else "https"
2218 self.update_ris_with_commons(items, container, hostname, scheme, sep)
2219 return "\r\n".join(items)
2221 def get_endnote(self, request=None):
2222 """
2224 :param self:
2225 :return: string
2226 """
2227 items = []
2228 sep = " "
2230 container = self.my_container
2231 collection = self.get_collection()
2233 is_article = True
2234 is_incollection = False
2235 is_inbook = False
2236 is_phdthesis = False
2238 if container is not None and container.ctype != "issue":
2239 is_article = False
2241 if collection is not None and collection.coltype == "these": 2241 ↛ 2242line 2241 didn't jump to line 2242 because the condition on line 2241 was never true
2242 is_phdthesis = True
2243 elif container.ctype == "book-monograph": 2243 ↛ 2244line 2243 didn't jump to line 2244 because the condition on line 2243 was never true
2244 is_inbook = True
2245 elif container.ctype == "book-edited-book": 2245 ↛ 2247line 2245 didn't jump to line 2247 because the condition on line 2245 was always true
2246 is_incollection = True
2247 elif container.ctype == "lecture-notes":
2248 is_incollection = True
2250 to_appear = container.to_appear()
2251 is_cr = container.is_cr()
2253 # no citation at the article level for a these
2254 if is_phdthesis: 2254 ↛ 2255line 2254 didn't jump to line 2255 because the condition on line 2254 was never true
2255 return ""
2257 type_ = "Journal Article" # "article"
2258 if to_appear and not is_cr: 2258 ↛ 2259line 2258 didn't jump to line 2259 because the condition on line 2258 was never true
2259 type_ = "Unpublished Work" # "unpublished"
2260 elif is_inbook: 2260 ↛ 2261line 2260 didn't jump to line 2261 because the condition on line 2260 was never true
2261 type_ = "Book Section"
2262 elif is_incollection:
2263 type_ = "Book Section"
2264 items.append("%0" + sep + type_)
2266 author_names = get_names(self, CONTRIB_TYPE_AUTHOR)
2267 for author in author_names:
2268 items.append("%A" + sep + author)
2270 editor_names = get_names(self, CONTRIB_TYPE_EDITOR)
2271 for editor in editor_names:
2272 items.append("%E" + sep + editor)
2274 title = xml_utils.remove_html(self.title_tex)
2275 items.append("%T" + sep + title)
2277 collection_title = xml_utils.remove_html(collection.title_tex)
2278 if collection is not None and is_article:
2279 items.append("%J" + sep + collection_title)
2280 else:
2281 if container is not None: 2281 ↛ 2290line 2281 didn't jump to line 2290 because the condition on line 2281 was always true
2282 items.append("%B" + sep + container.title_tex)
2283 author_names = get_names(container, CONTRIB_TYPE_AUTHOR)
2284 for author in author_names: 2284 ↛ 2285line 2284 didn't jump to line 2285 because the loop on line 2284 never started
2285 items.append("%A" + sep + author)
2287 editor_names = get_names(container, CONTRIB_TYPE_EDITOR)
2288 for editor in editor_names:
2289 items.append("%E" + sep + editor)
2290 if collection is not None: 2290 ↛ 2293line 2290 didn't jump to line 2293 because the condition on line 2290 was always true
2291 items.append("%S" + sep + collection_title)
2293 if not to_appear:
2294 if self.talk_number: 2294 ↛ 2295line 2294 didn't jump to line 2295 because the condition on line 2294 was never true
2295 items.append("%Z" + sep + "talk:" + self.talk_number)
2296 # if self.article_number:
2297 # items.append("%1" + sep + "eid = " + self.article_number)
2299 hostname = request.get_host() if request is not None else ""
2300 scheme = request.scheme if request is not None else "https"
2301 self.update_endnote_with_commons(items, container, hostname, scheme, sep)
2302 return "\r\n".join(items)
2304 def get_conference(self):
2305 text = ""
2307 subjs = []
2308 for subj in self.subj_set.all():
2309 if subj.type == "conference":
2310 subjs.append(subj.value)
2312 text = ", ".join(subjs)
2314 return text
2316 def get_topics(self):
2317 text = ""
2319 subjs = []
2320 for subj in self.subj_set.all():
2321 if subj.type == "topic":
2322 subjs.append(subj.value)
2324 text = ", ".join(subjs)
2326 return text
2328 def get_subj_text(self):
2329 text = ""
2330 lang = get_language()
2332 subj_types = ["subject", "type", "pci", "heading", "conference"]
2333 if self.my_container.my_collection.pid == "CRMATH":
2334 subj_types = ["subject", "heading"]
2336 subj_groups = self.get_subjs_by_type_and_lang()
2337 for type_ in subj_types:
2338 if type_ in subj_groups:
2339 sg_type_langs = subj_groups[type_]
2340 if type_ == "pci":
2341 subtext = ", ".join(
2342 list(
2343 [
2344 resolver.get_pci(subj.value)
2345 for lang_ in sg_type_langs
2346 for subj in sg_type_langs[lang_]
2347 ]
2348 )
2349 )
2350 else:
2351 if lang in sg_type_langs:
2352 subtext = ", ".join(
2353 list([subj.value for subj in subj_groups[type_][lang]])
2354 )
2355 else:
2356 subtext = ", ".join(
2357 list(
2358 [
2359 subj.value
2360 for lang_ in sg_type_langs
2361 if lang_ != lang
2362 for subj in sg_type_langs[lang_]
2363 ]
2364 )
2365 )
2367 if text:
2368 text += " - "
2369 text += subtext
2371 return text
2373 def get_pci_section(self):
2374 pci = ""
2375 for subj in self.subj_set.all():
2376 if subj.type == "pci":
2377 pci = subj.value
2379 return pci
2381 def get_pci_value(self):
2382 return resolver.get_pci(self.get_pci_section())
2384 def is_uga_pci(self):
2385 return self.get_pci_section() in resolver.PCJ_UGA_SECTION
2387 def allow_crossref(self):
2388 # we need at least a doi or an issn to allow crossref record
2389 doi = self.my_container.my_collection.doi
2390 issn = self.my_container.my_collection.issn
2391 e_issn = self.my_container.my_collection.e_issn
2392 result = bool(doi) or bool(issn) or bool(e_issn)
2394 # and we need a published date (online_first or final)
2395 result = result and (self.date_published is not None or self.date_online_first is not None)
2397 return result
2399 def get_illustrations(self):
2400 return GraphicalAbstract.objects.filter(resource=self).first()
2402 def has_graphical_abstract(self):
2403 collections = ["CRCHIM"]
2404 return True if self.my_container.my_collection.pid in collections else False
2407class ResourceCategory(models.Model):
2408 """non utilisé"""
2410 category = models.CharField(max_length=32, db_index=True)
2413class Provider(models.Model):
2414 """
2415 en faire une resource permettrait d'attacher des metadonnées
2416 supplémentaires -- à voir
2417 """
2419 name = models.CharField(max_length=32, unique=True)
2420 pid_type = models.CharField(max_length=32, unique=True)
2421 sid_type = models.CharField(max_length=64, unique=True, null=True, blank=True)
2423 def __str__(self):
2424 return self.name
2427class SiteMembership(models.Model):
2428 """
2429 Warning: As of July 3018, only 1 site id is stored in a SolR document
2430 Although the SolR schema is already OK to store multiple sites ("sites" is an array)
2431 no Solr commands have been written to add/remove sites
2432 We only have add commands.
2433 Search only works if the Solr instance is meant for individual or ALL sites
2434 """
2436 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2437 deployed = models.DateTimeField(null=True)
2438 online = models.DateTimeField(db_index=True, null=True) # pour cedram
2439 site = models.ForeignKey(PtfSite, on_delete=models.CASCADE)
2441 class Meta:
2442 unique_together = (
2443 "resource",
2444 "site",
2445 )
2448class CollectionMembership(models.Model):
2449 collection = models.ForeignKey(Collection, on_delete=models.CASCADE)
2450 container = models.ForeignKey(Container, on_delete=models.CASCADE)
2452 vseries = models.CharField(max_length=32, db_index=True)
2453 volume = models.CharField(max_length=64, db_index=True)
2454 number = models.CharField(max_length=32, db_index=True) # issue number
2455 # la même chose pour le tri
2456 vseries_int = models.IntegerField(default=0, db_index=True)
2457 volume_int = models.IntegerField(default=0, db_index=True)
2458 number_int = models.IntegerField(default=0, db_index=True)
2460 seq = models.IntegerField(db_index=True)
2462 class Meta:
2463 unique_together = (
2464 "collection",
2465 "container",
2466 )
2468 def __str__(self):
2469 return f"{self.collection} - {self.container}"
2472class RelationName(models.Model):
2473 """
2474 Triple store ;-)
2475 """
2477 left = models.CharField(max_length=32, unique=True)
2478 right = models.CharField(max_length=32, unique=True)
2479 gauche = models.CharField(max_length=64, unique=True)
2480 droite = models.CharField(max_length=64, unique=True)
2483class Relationship(models.Model):
2484 resource = models.ForeignKey(
2485 Resource, null=True, related_name="subject_of", on_delete=models.CASCADE
2486 )
2487 related = models.ForeignKey(
2488 Resource, null=True, related_name="object_of", on_delete=models.CASCADE
2489 )
2490 subject_pid = models.CharField(max_length=64, db_index=True)
2491 object_pid = models.CharField(max_length=64, db_index=True)
2492 rel_info = models.ForeignKey(RelationName, null=True, on_delete=models.CASCADE)
2495class ExtRelationship(models.Model):
2496 """
2497 Triple store (resources externes)
2498 """
2500 resource = models.ForeignKey(Resource, related_name="subject", on_delete=models.CASCADE)
2501 ext_object = models.CharField(max_length=256) # uri
2502 predicate = models.CharField(max_length=256, db_index=True) # predicate uri
2505class XmlBase(models.Model):
2506 base = models.CharField(max_length=200, unique=True)
2508 def __str__(self):
2509 return self.base
2512class DataStream(models.Model):
2513 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2514 rel = models.CharField(max_length=32)
2515 mimetype = models.CharField(max_length=32)
2516 base = models.ForeignKey(XmlBase, blank=True, null=True, on_delete=models.CASCADE)
2517 location = models.URLField()
2518 text = models.CharField(max_length=32, default="Link")
2519 seq = models.IntegerField(db_index=True)
2521 class Meta:
2522 ordering = ["seq"]
2524 def __str__(self):
2525 return " - ".join([self.resource.pid, self.mimetype])
2528class ExtLink(models.Model):
2529 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2530 rel = models.CharField(max_length=50)
2531 mimetype = models.CharField(max_length=32)
2532 base = models.ForeignKey(XmlBase, blank=True, null=True, on_delete=models.CASCADE)
2533 location = models.CharField(max_length=300)
2534 metadata = models.TextField()
2535 seq = models.IntegerField(db_index=True)
2537 class Meta:
2538 ordering = ["seq"]
2540 def __str__(self):
2541 return f"{self.rel}: {self.get_href()}"
2543 def save(self, *args, **kwargs):
2544 if "website" in self.rel:
2545 self.metadata = "website"
2546 if not self.seq: 2546 ↛ 2547line 2546 didn't jump to line 2547 because the condition on line 2546 was never true
2547 self.seq = self.generate_seq()
2548 super().save(*args, **kwargs)
2550 def generate_seq(self):
2551 max_seq = ExtLink.objects.filter(resource=self.resource).aggregate(Max("seq"))["seq__max"]
2552 if max_seq:
2553 return max_seq + 1
2554 else:
2555 return 1
2557 def get_href(self):
2558 if self.rel in ("small_icon", "icon"):
2559 # construction du chemin vers l'icone
2560 # filename = os.path.basename(self.location)
2561 resource_pid = self.resource.pid
2562 href = resolver.get_icon_url(resource_pid, self.location)
2563 return href
2564 return self.location
2567class RelatedObject(models.Model):
2568 """
2569 Related Objects are used to store pdf/djvu related to an issue (tdm, preliminary pages)
2570 """
2572 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2573 rel = models.CharField(max_length=32)
2574 mimetype = models.CharField(max_length=32)
2575 base = models.ForeignKey(XmlBase, blank=True, null=True, on_delete=models.CASCADE)
2576 location = models.CharField(max_length=200)
2577 metadata = models.TextField()
2578 seq = models.IntegerField(db_index=True)
2580 class Meta:
2581 ordering = ["seq"]
2583 def __str__(self):
2584 return " - ".join([self.resource.pid, self.mimetype])
2586 def get_href(self):
2587 the_dynamic_object = self.resource.cast()
2588 href = the_dynamic_object.get_binary_file_href_full_path(
2589 self.rel, self.mimetype, self.location
2590 )
2591 return href
2594class SupplementaryMaterial(RelatedObject):
2595 caption = models.TextField()
2597 def __str__(self):
2598 return self.location.split("/")[-1]
2600 def embeded_link(self):
2601 if "youtube" in self.location:
2602 video_id = urlparse(self.location).query.split("=")[1]
2603 return f"https://www.youtube-nocookie.com/embed/{video_id}"
2604 return self.get_href()
2607class MetaDataPart(models.Model):
2608 """
2609 stockage des metadonnées
2610 qui ne sont pas aiilleurs (!)
2611 non utilisé - à voir
2612 3 classes qui font sans doute doublon:
2613 MetadataPart, ResourceAttribute, CustomMeta -- à voir
2614 """
2616 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2617 name = models.CharField(max_length=32, db_index=True)
2618 seq = models.IntegerField(db_index=True)
2619 data = models.TextField()
2621 class Meta:
2622 ordering = ["seq"]
2625class ResourceAttribute(models.Model):
2626 """
2627 not used
2628 """
2630 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2631 name = models.CharField(max_length=32, db_index=True)
2632 value = models.TextField()
2635class CustomMeta(models.Model):
2636 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2637 name = models.CharField(max_length=32, db_index=True)
2638 value = models.CharField(max_length=128, db_index=True)
2641class ResourceId(models.Model):
2642 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2643 id_type = models.CharField(max_length=32, db_index=True)
2644 id_value = models.CharField(max_length=64, db_index=True)
2646 class Meta:
2647 unique_together = ("id_type", "id_value")
2649 def __str__(self):
2650 return f"{self.id_type} {self.id_value}"
2652 def get_href(self):
2653 href = ""
2654 if self.id_type == "doi": 2654 ↛ 2656line 2654 didn't jump to line 2656 because the condition on line 2654 was always true
2655 href = resolver.get_doi_url(self.id_value)
2656 return href
2659class ExtId(models.Model):
2660 """
2661 zbl, mr, jfm, etc..
2662 mis à part car non uniques
2663 """
2665 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2666 id_type = models.CharField(max_length=32, db_index=True)
2667 id_value = models.CharField(max_length=64, db_index=True)
2668 checked = models.BooleanField(default=True)
2669 false_positive = models.BooleanField(default=False)
2671 class Meta:
2672 unique_together = ["resource", "id_type"]
2674 def __str__(self):
2675 return f"{self.resource} - {self.id_type}:{self.id_value}"
2677 def get_href(self):
2678 return resolver.resolve_id(self.id_type, self.id_value)
2681class Abstract(models.Model):
2682 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2683 lang = models.CharField(max_length=3, default="und")
2684 tag = models.CharField(max_length=32, db_index=True)
2685 # specific use, content_type, label ?
2686 seq = models.IntegerField(db_index=True)
2687 value_xml = models.TextField(default="")
2688 value_tex = models.TextField(default="")
2689 value_html = models.TextField(default="")
2691 class Meta:
2692 ordering = ["seq"]
2694 def __str__(self):
2695 return f"{self.resource} - {self.tag}"
2698class Kwd(models.Model):
2699 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2700 lang = models.CharField(max_length=3, default="und")
2701 type = models.CharField(max_length=32, db_index=True)
2702 value = models.TextField(default="")
2703 seq = models.IntegerField(db_index=True)
2705 class Meta:
2706 ordering = ["seq"]
2708 def __str__(self):
2709 return f"{self.type} - {self.lang} - {self.value}"
2712class Subj(models.Model):
2713 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2714 lang = models.CharField(max_length=3, default="und")
2715 type = models.CharField(max_length=32, db_index=True)
2716 value = models.TextField(default="")
2717 seq = models.IntegerField(db_index=True)
2719 class Meta:
2720 ordering = ["seq"]
2722 def __str__(self):
2723 return f"{self.type} - {self.lang} - {self.value}"
2726class Award(models.Model):
2727 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2728 abbrev = models.CharField(max_length=600, db_index=True)
2729 award_id = models.CharField(max_length=600, db_index=True)
2730 seq = models.IntegerField(db_index=True)
2732 class Meta:
2733 ordering = ["seq"]
2736class BibItem(models.Model):
2737 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
2738 sequence = models.PositiveIntegerField(db_index=True)
2739 label = models.CharField(max_length=128, default="")
2740 citation_xml = models.TextField(default="")
2741 citation_tex = models.TextField(default="")
2742 citation_html = models.TextField(default="")
2744 type = models.CharField(max_length=32, default="", db_index=True)
2745 user_id = models.TextField(default="")
2747 # article_title, chapter_title and source can contain formulas. Save the tex version.
2748 # artile_title, chapter_title and source were created from title, booktitle and chapter.
2749 # We need to do reverse engineering when exporting the bibtex (see
2750 # get_bibtex)
2751 article_title_tex = models.TextField(default="")
2752 chapter_title_tex = models.TextField(default="")
2753 source_tex = models.TextField(default="")
2755 publisher_name = models.TextField(default="")
2756 publisher_loc = models.TextField(default="")
2757 institution = models.TextField(default="")
2758 series = models.TextField(default="")
2759 volume = models.TextField(default="")
2760 issue = models.TextField(default="")
2761 month = models.CharField(max_length=32, default="")
2762 year = models.TextField(default="")
2763 comment = models.TextField(default="")
2764 annotation = models.CharField(max_length=255, default="")
2765 fpage = models.TextField(default="")
2766 lpage = models.TextField(default="")
2767 page_range = models.TextField(default="")
2768 size = models.TextField(default="")
2770 class Meta:
2771 ordering = ["sequence"]
2773 def __str__(self):
2774 return f"{self.resource} - {self.label}"
2776 def get_ref_id(self):
2777 ref_id = ""
2778 # self.resource.body_html is for PCJ case when SHOW_BODY is at false but we have a body html to display
2779 if (hasattr(settings, "SHOW_BODY") and settings.SHOW_BODY) or self.resource.body_html: 2779 ↛ 2780line 2779 didn't jump to line 2780 because the condition on line 2779 was never true
2780 ref_id = "r" + str(self.sequence)
2781 else:
2782 ref_id = self.user_id
2783 return ref_id
2785 def get_doi(self):
2786 bibitemId_doi = BibItemId.objects.filter(bibitem=self, id_type="doi")
2787 if bibitemId_doi:
2788 return bibitemId_doi.get().id_value
2789 return None
2791 def get_bibtex(self):
2792 """
2794 :param self:
2795 :return: an array of strings encoded in latex (with latexcodec)
2796 """
2797 bibtex = []
2798 indent = " "
2800 author_names = get_bibtex_names(self, "author")
2801 editor_names = get_bibtex_names(self, "editor")
2803 if self.user_id: 2803 ↛ 2806line 2803 didn't jump to line 2806 because the condition on line 2803 was always true
2804 id_ = self.user_id
2805 else:
2806 id_ = get_bibtex_id(self, self.year)
2808 append_in_latex(bibtex, "@" + self.type + "{" + id_ + ",")
2810 if author_names: 2810 ↛ 2812line 2810 didn't jump to line 2812 because the condition on line 2810 was always true
2811 append_in_latex(bibtex, indent + author_names)
2812 if editor_names: 2812 ↛ 2822line 2812 didn't jump to line 2822 because the condition on line 2812 was always true
2813 append_in_latex(bibtex, indent + editor_names)
2815 # From numdam+/ptf-xsl/cedram/structured-biblio.xsl
2816 # < xsl:template match = "bib_entry[@doctype='article']/title|
2817 # bib_entry[@doctype='misc']/title|
2818 # bib_entry[@doctype='inproceedings']/title|
2819 # bib_entry[@doctype='booklet']/title|
2820 # bib_entry[@doctype='conference']/title" >
2821 # => All article_title in JATS become title in bibtex
2822 if self.article_title_tex: 2822 ↛ 2830line 2822 didn't jump to line 2830 because the condition on line 2822 was always true
2823 title = xml_utils.normalise_span(self.article_title_tex)
2824 append_in_latex(bibtex, indent + "title = {" + title + "},", is_title=True)
2826 # <chapter-title> in JATS becomes title or chapter according to the type
2827 # From numdam+/ptf-xsl/cedram/structured-biblio.xsl
2828 # <xsl:template match="bib_entry[@doctype='incollection']/title|bibentry/chapter">
2830 if self.chapter_title_tex: 2830 ↛ 2859line 2830 didn't jump to line 2859 because the condition on line 2830 was always true
2831 keyword = ""
2832 if self.type == "incollection": 2832 ↛ 2833line 2832 didn't jump to line 2833 because the condition on line 2832 was never true
2833 keyword += "title"
2834 else:
2835 keyword += "chapter"
2836 title = xml_utils.normalise_span(self.chapter_title_tex)
2837 append_in_latex(bibtex, indent + keyword + " = {" + title + "},")
2839 # <source> in JATS becomes title, journal, booktitle, or howpublished according to the type
2840 # From numdam+/ptf-xsl/cedram/structured-biblio.xsl
2841 # <xsl:template match="bib_entry[@doctype='article']/journal|
2842 # bib_entry[@doctype='incollection']/booktitle|
2843 # bib_entry[@doctype='inproceedings']/booktitle|
2844 # bib_entry[@doctype='conference']/booktitle|
2845 # bib_entry[@doctype='misc']/howpublished|
2846 # bib_entry[@doctype='booklet']/howpublished|
2847 # bib_entry[@doctype='book']/title|
2848 # bib_entry[@doctype='unpublished']/title|
2849 # bib_entry[@doctype='inbook']/title|
2850 # bib_entry[@doctype='phdthesis']/title|
2851 # bib_entry[@doctype='mastersthesis']/title|
2852 # bib_entry[@doctype='manual']/title|
2853 # bib_entry[@doctype='techreport']/title|
2854 # bib_entry[@doctype='masterthesis']/title|
2855 # bib_entry[@doctype='coursenotes']/title|
2856 # bib_entry[@doctype='proceedings']/title">
2857 # < xsl:template match = "bib_entry[@doctype='misc']/booktitle" > : Unable to reverse correctly ! 2 choices
2859 if self.source_tex: 2859 ↛ 2876line 2859 didn't jump to line 2876 because the condition on line 2859 was always true
2860 keyword = ""
2861 if self.type == "article": 2861 ↛ 2863line 2861 didn't jump to line 2863 because the condition on line 2861 was always true
2862 keyword += "journal"
2863 elif (
2864 self.type == "incollection"
2865 or self.type == "inproceedings"
2866 or self.type == "conference"
2867 ):
2868 keyword += "booktitle"
2869 elif self.type == "misc" or self.type == "booklet":
2870 keyword += "howpublished"
2871 else:
2872 keyword += "title"
2873 title = xml_utils.normalise_span(self.source_tex)
2874 append_in_latex(bibtex, indent + keyword + " = {" + title + "},")
2876 if self.publisher_name: 2876 ↛ 2878line 2876 didn't jump to line 2878 because the condition on line 2876 was always true
2877 append_in_latex(bibtex, indent + "publisher = {" + self.publisher_name + "},")
2878 if self.publisher_loc: 2878 ↛ 2880line 2878 didn't jump to line 2880 because the condition on line 2878 was always true
2879 append_in_latex(bibtex, indent + "address = {" + self.publisher_loc + "},")
2880 if self.institution: 2880 ↛ 2882line 2880 didn't jump to line 2882 because the condition on line 2880 was always true
2881 append_in_latex(bibtex, indent + "institution = {" + self.institution + "},")
2882 if self.series: 2882 ↛ 2884line 2882 didn't jump to line 2884 because the condition on line 2882 was always true
2883 append_in_latex(bibtex, indent + "series = {" + self.series + "},")
2884 if self.volume: 2884 ↛ 2886line 2884 didn't jump to line 2886 because the condition on line 2884 was always true
2885 append_in_latex(bibtex, indent + "volume = {" + self.volume + "},")
2886 if self.issue: 2886 ↛ 2888line 2886 didn't jump to line 2888 because the condition on line 2886 was always true
2887 append_in_latex(bibtex, indent + "number = {" + self.issue + "},")
2888 if self.year: 2888 ↛ 2891line 2888 didn't jump to line 2891 because the condition on line 2888 was always true
2889 append_in_latex(bibtex, indent + "year = {" + self.year + "},")
2891 if self.page_range: 2891 ↛ 2892line 2891 didn't jump to line 2892 because the condition on line 2891 was never true
2892 append_in_latex(bibtex, indent + "pages = {" + self.page_range + "},")
2893 elif self.fpage or self.lpage: 2893 ↛ 2901line 2893 didn't jump to line 2901 because the condition on line 2893 was always true
2894 text = self.fpage
2895 if self.fpage and self.lpage: 2895 ↛ 2897line 2895 didn't jump to line 2897 because the condition on line 2895 was always true
2896 text += "--"
2897 if self.lpage: 2897 ↛ 2899line 2897 didn't jump to line 2899 because the condition on line 2897 was always true
2898 text += self.lpage
2899 append_in_latex(bibtex, indent + "pages = {" + text + "},")
2901 if self.size: 2901 ↛ 2904line 2901 didn't jump to line 2904 because the condition on line 2901 was always true
2902 append_in_latex(bibtex, indent + "pagetotal = {" + self.size + "},")
2904 if self.comment: 2904 ↛ 2907line 2904 didn't jump to line 2907 because the condition on line 2904 was always true
2905 append_in_latex(bibtex, indent + "note = {" + self.comment + "},")
2907 for extid in BibItemId.objects.filter(bibitem=self): 2907 ↛ 2908line 2907 didn't jump to line 2908 because the loop on line 2907 never started
2908 type_ = ""
2909 if extid.id_type == "zbl-item-id":
2910 type_ = "zbl"
2911 elif extid.id_type == "mr-item-id":
2912 type_ = "mrnumber"
2913 elif extid.id_type == "doi":
2914 type_ = "doi"
2915 elif extid.id_type == "eid":
2916 type_ = "eid"
2918 if type_:
2919 append_in_latex(bibtex, indent + type_ + " = {" + extid.id_value + "},")
2921 append_in_latex(bibtex, "}")
2923 return bibtex
2926class BibItemId(models.Model):
2927 bibitem = models.ForeignKey(BibItem, on_delete=models.CASCADE)
2928 id_type = models.CharField(max_length=32, db_index=True)
2929 id_value = models.CharField(max_length=256, db_index=True)
2930 checked = models.BooleanField(default=True)
2931 false_positive = models.BooleanField(default=False)
2933 class Meta:
2934 unique_together = ["bibitem", "id_type"]
2936 def __str__(self):
2937 return f"{self.bibitem} - {self.id_type}:{self.id_value}"
2939 def get_href_display(self):
2940 value = "Article"
2941 if settings.SITE_ID == 3:
2942 value = "Numdam"
2943 else:
2944 if self.id_type in ["numdam-id", "mathdoc-id"]:
2945 value = "Numdam"
2947 return value
2949 def get_href(self):
2950 force_numdam = False
2951 if self.id_type in ["numdam-id", "mathdoc-id"] and settings.SITE_ID != 3:
2952 force_numdam = True
2954 return resolver.resolve_id(self.id_type, self.id_value, force_numdam)
2957class ContribGroup(models.Model):
2958 resource = models.ForeignKey(Resource, blank=True, null=True, on_delete=models.CASCADE)
2959 bibitem = models.ForeignKey(BibItem, blank=True, null=True, on_delete=models.CASCADE)
2960 content_type = models.CharField(max_length=32, db_index=True)
2961 # specific_use ?!
2962 seq = models.IntegerField()
2964 class Meta:
2965 ordering = ["seq"]
2968class Contrib(models.Model):
2969 group = models.ForeignKey(ContribGroup, on_delete=models.CASCADE)
2970 contrib_type = models.CharField(max_length=32, db_index=True)
2972 last_name = models.CharField(max_length=128, db_index=True)
2973 first_name = models.CharField(max_length=128, db_index=True)
2974 prefix = models.CharField(max_length=32)
2975 suffix = models.CharField(max_length=32)
2976 string_name = models.CharField(max_length=256, db_index=True)
2977 reference_name = models.CharField(max_length=256, db_index=True)
2978 deceased = models.BooleanField(default=False)
2979 orcid = models.CharField(max_length=64, db_index=True, blank=True, default="")
2980 equal_contrib = models.BooleanField(default=True)
2981 email = models.EmailField(max_length=254, db_index=True, blank=True, default="")
2983 # Could be used to export the contrib
2984 contrib_xml = models.TextField()
2986 seq = models.IntegerField()
2988 class Meta:
2989 ordering = ["seq"]
2991 @classmethod
2992 def get_fields_list(cls):
2993 return [
2994 item.attname
2995 for item in cls._meta.get_fields()
2996 if not item.auto_created
2997 and not item.many_to_many
2998 and not item.many_to_one
2999 and not item.one_to_many
3000 and not item.one_to_one
3001 ]
3003 def __str__(self):
3004 return "{} {} / {} / {}".format(
3005 self.last_name, self.first_name, self.reference_name, self.contrib_type or "None"
3006 )
3008 def get_absolute_url(self):
3009 from ptf.templatetags.helpers import pretty_search
3011 return pretty_search("search", "c", f'"{self.reference_name()}"')
3013 def display_name(self):
3014 display_name = self.string_name
3016 if self.is_etal():
3017 display_name = "et al."
3018 elif getattr(settings, "DISPLAY_FIRST_NAME_FIRST", False) and (
3019 len(str(self.first_name)) > 0 or len(str(self.last_name)) > 0
3020 ):
3021 display_name = f"{self.first_name} {self.last_name}"
3023 return display_name
3025 def orcid_href(self):
3026 return resolver.resolve_id("orcid", self.orcid)
3028 def idref_href(self):
3029 return resolver.resolve_id("idref", self.idref)
3031 def get_addresses(self):
3032 return self.contribaddress_set.all() # .order_by('address')
3034 def is_etal(self):
3035 return self.contrib_xml.startswith("<etal")
3038class Author(models.Model):
3039 """
3040 Count the number of documents (articles,books...) written by an author
3041 """
3043 name = models.CharField(max_length=200, db_index=True, unique=True)
3044 first_letter = models.CharField(max_length=1, db_index=True)
3045 count = models.IntegerField()
3048class FrontMatter(models.Model):
3049 resource = models.OneToOneField(Resource, on_delete=models.CASCADE)
3051 value_xml = models.TextField(default="")
3052 value_html = models.TextField(default="")
3053 foreword_html = models.TextField(default="")
3056class LangTable(models.Model):
3057 code = models.CharField(max_length=3, db_index=True)
3058 name = models.CharField(max_length=64)
3061class ResourceCount(models.Model):
3062 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
3063 name = models.CharField(max_length=32, db_index=True)
3064 value = models.CharField(max_length=32, db_index=True)
3065 seq = models.IntegerField()
3067 class Meta:
3068 ordering = ["seq"]
3071class Stats(models.Model):
3072 name = models.CharField(max_length=128, db_index=True, unique=True)
3073 value = models.IntegerField()
3076class History(models.Model):
3077 """pas utilisé pour l'heure"""
3079 resource = models.ForeignKey(Resource, on_delete=models.CASCADE)
3080 date_type = models.CharField(max_length=32, db_index=True)
3081 date_value = models.DateTimeField()
3082 comment = models.TextField()
3085class SerialHistory(models.Model):
3086 """à revoir"""
3088 serial = models.ForeignKey("Collection", on_delete=models.CASCADE)
3089 date_deployed = models.DateField(auto_now_add=True)
3090 first_year = models.CharField(max_length=32)
3091 last_year = models.CharField(max_length=32)
3094# def matches(id_type, id_value):
3095# if id_type in ('mr-item-id', 'zbl-item-id', 'jfm-item-id'):
3096# qs = ExtId.objects.filter(
3097# id_type=id_type,
3098# id_value=id_value,
3099# ).select_related('resource', 'resource__resourceid')
3100# counter = 0
3101# match_list = []
3102# for extid in qs:
3103# resource = extid.resource
3104# for idext in resource.extid_set.all():
3105# match_list.append({'type': idext.id_type, 'value': idext.id_value})
3106# for rid in resource.resourceid_set.all():
3107# match_list.append({'type': rid.id_type, 'value': rid.id_value})
3108# counter += 1
3109# return {
3110# 'id': {'type': id_type, 'value': id_value},
3111# 'count': counter,
3112# 'ids': match_list,
3113# }
3114# resource = Resource.objects.get(resourceid__id_type=id_type,
3115# resourceid__id_value=id_value)
3116# counter = 0
3117# match_list = []
3118# for idext in resource.extid_set.all():
3119# match_list.append({'type': idext.id_type, 'value': idext.id_value})
3120# for rid in resource.resourceid_set.all():
3121# match_list.append({'type': rid.id_type, 'value': rid.id_value})
3122# counter += 1
3123# return {
3124# 'id': {'type': id_type, 'value': id_value},
3125# 'count': counter,
3126# 'ids': match_list,
3127# }
3130def parse_page_count(page_count):
3131 """
3132 page-count is not an integer but a string to be displayed.
3133 page_count may sometimes mix roman and arabic values. Ex "iv-121"
3134 """
3135 if "-" in page_count:
3136 result = 0
3137 values = page_count.split("-")
3138 for value in values:
3139 try:
3140 result += int(value)
3141 except ValueError:
3142 pass
3143 else:
3144 result = int(page_count)
3146 return result
3149@receiver(pre_delete, sender=ResourceCount)
3150def delete_resourcecount_handler(sender, instance, **kwargs):
3151 """
3152 pre_delete ResourceCount signal
3154 Stats are added manually during a addResourceCountDatabaseCmd
3155 but ResourceCount are deleted automatically by Django when its
3156 related resource is deleted
3157 (resource = models.ForeignKey(Resource) of ResourceCount)
3158 To update Stats, we use the Django signal mechanism
3159 """
3160 if instance and instance.name == "page-count" and instance.resource.classname == "Container":
3161 # page-count may sometimes mix roman and arabic values. Ex "iv-121"
3162 value = parse_page_count(instance.value)
3164 total = Stats.objects.get(name=instance.name)
3165 total.value -= value
3166 total.save()
3169# class Volume:
3170#
3171# def get_absolute_url(self):
3172# return reverse('volume-items', kwargs={'vid': self.id_})
3173#
3174# def __init__(self, id_):
3175# self.id_ = id_
3176# try:
3177# journal_pid, year_id, self.vseries_id, self.volume_id = id_.split(
3178# '_')
3179# except ValueError:
3180# raise self.DoesNotExist(_('Volume {} does not exist').format(id_))
3181# try:
3182# self.collection = Collection.objects.get(
3183# pid=journal_pid, sites__id=settings.SITE_ID)
3184# except Collection.DoesNotExist:
3185# self.issues = None
3186# try:
3187# self.issues = Container.objects.filter(
3188# my_collection__pid=journal_pid,
3189# year=year_id,
3190# vseries=self.vseries_id,
3191# volume=self.volume_id,
3192# ).order_by('number_int').all()
3193# except Container.DoesNotExist:
3194# self.issues = None
3195#
3196# class DoesNotExist(Exception):
3197# pass
3200class ContribAddress(models.Model):
3201 contrib = models.ForeignKey("Contrib", blank=True, null=True, on_delete=models.CASCADE)
3202 contribution = models.ForeignKey(
3203 "Contribution", blank=True, null=True, on_delete=models.CASCADE
3204 )
3205 address = models.TextField(null=True, blank=True)
3207 class Meta:
3208 ordering = ["pk"]
3211def cmp_container_base(a, b):
3212 return (
3213 a.year < b.year
3214 or (a.year == b.year and a.vseries_int < b.vseries_int)
3215 or (a.year == b.year and a.vseries_int == b.vseries_int and a.volume_int < b.volume_int)
3216 or (
3217 a.year == b.year
3218 and a.vseries_int == b.vseries_int
3219 and a.volume_int == b.volume_int
3220 and a.number_int < b.number_int
3221 )
3222 )
3225class PersonManager(models.Manager):
3226 @staticmethod
3227 def clean():
3228 pass
3229 # Person.objects.filter(contributions=None).delete()
3232class Person(models.Model):
3233 """
3234 A Person is a contributor (author/editor...) of a Resource (Article/Book) or a BibItem.
3235 A Person can appear with different names (ex: "John Smith", "J. Smith") and we want to preserve the differences,
3236 in particular with print papers that are digitized.
3237 A Person is unique for a person (a Person is basically the key).
3238 A Person has one or many PersonInfo which stores the different names
3240 TODO: signal similar to delete_contrib_handler
3241 """
3243 # blank=True and null=True allow unique=True with null values (on multiple Persons)
3244 orcid = models.CharField(max_length=20, blank=True, null=True)
3245 idref = models.CharField(max_length=10, blank=True, null=True)
3246 # mid (Mathdoc id) is the key set by numdam-plus
3247 mid = models.CharField(max_length=256, blank=True, null=True)
3249 last_name = models.CharField(max_length=128, blank=True, default="")
3250 first_name = models.CharField(max_length=128, blank=True, default="")
3251 prefix = models.CharField(max_length=32, blank=True, default="")
3252 suffix = models.CharField(max_length=32, blank=True, default="")
3253 first_letter = models.CharField(max_length=1, blank=True, default="")
3255 # Used when a person is not fully tagged in the XML
3256 string_name = models.CharField(max_length=256, blank=True, default="")
3258 objects = PersonManager()
3260 @classmethod
3261 def get_fields_list(cls):
3262 return [
3263 item.attname
3264 for item in cls._meta.get_fields()
3265 if not item.auto_created
3266 and not item.many_to_many
3267 and not item.many_to_one
3268 and not item.one_to_many
3269 and not item.one_to_one
3270 ]
3272 def __str__(self):
3273 return get_display_name(
3274 self.prefix, self.first_name, self.last_name, self.suffix, self.string_name
3275 )
3278class PersonInfoManager(models.Manager):
3279 @staticmethod
3280 def clean():
3281 PersonInfo.objects.filter(contributions=None).delete()
3282 Person.objects.filter(personinfo=None).delete()
3285class PersonInfo(models.Model):
3286 person = models.ForeignKey(Person, on_delete=models.CASCADE)
3288 last_name = models.CharField(max_length=128)
3289 first_name = models.CharField(max_length=128)
3290 prefix = models.CharField(max_length=32)
3291 suffix = models.CharField(max_length=32)
3293 # Used when a person is not fully tagged in the XML
3294 string_name = models.CharField(max_length=256, blank=True, default="")
3296 objects = PersonInfoManager()
3298 @classmethod
3299 def get_fields_list(cls):
3300 return [
3301 item.attname
3302 for item in cls._meta.get_fields()
3303 if not item.auto_created
3304 and not item.many_to_many
3305 and not item.many_to_one
3306 and not item.one_to_many
3307 and not item.one_to_one
3308 ]
3310 def __str__(self):
3311 return get_display_name(
3312 self.prefix, self.first_name, self.last_name, self.suffix, self.string_name
3313 )
3316class Contribution(models.Model):
3317 resource = models.ForeignKey(
3318 Resource, blank=True, null=True, on_delete=models.CASCADE, related_name="contributions"
3319 )
3320 bibitem = models.ForeignKey(
3321 BibItem, blank=True, null=True, on_delete=models.CASCADE, related_name="contributions"
3322 )
3324 # blank=True and null=True allow unique=True with null values (on multiple Persons)
3325 orcid = models.CharField(max_length=20, blank=True, null=True)
3326 idref = models.CharField(max_length=10, blank=True, null=True)
3327 # mid (Mathdoc id) is the key set by numdam-plus
3328 mid = models.CharField(max_length=256, blank=True, null=True)
3330 last_name = models.CharField(max_length=128, blank=True, default="")
3331 first_name = models.CharField(max_length=128, blank=True, default="")
3332 prefix = models.CharField(max_length=32, blank=True, default="")
3333 suffix = models.CharField(max_length=32, blank=True, default="")
3334 first_letter = models.CharField(max_length=1, blank=True, default="")
3336 # Used when a person is not fully tagged in the XML
3337 string_name = models.CharField(max_length=256, blank=True, default="")
3339 role = models.CharField(max_length=64)
3340 email = models.EmailField(max_length=254, blank=True, default="")
3341 deceased_before_publication = models.BooleanField(default=False)
3342 equal_contrib = models.BooleanField(default=True)
3343 corresponding = models.BooleanField(default=False)
3345 # Used to export the contribution
3346 contrib_xml = models.TextField()
3348 seq = models.IntegerField()
3350 class Meta:
3351 ordering = ["seq"]
3353 @classmethod
3354 def get_fields_list(cls):
3355 return [
3356 item.attname
3357 for item in cls._meta.get_fields()
3358 if not item.auto_created
3359 and not item.many_to_many
3360 and not item.many_to_one
3361 and not item.one_to_many
3362 and not item.one_to_one
3363 ]
3365 def __str__(self):
3366 return self.display_name()
3368 def is_etal(self):
3369 return self.contrib_xml.startswith("<etal")
3371 def display_name(self):
3372 return (
3373 "et al."
3374 if self.is_etal()
3375 else get_display_name(
3376 self.prefix, self.first_name, self.last_name, self.suffix, self.string_name
3377 )
3378 )
3380 def get_absolute_url(self):
3381 from ptf.templatetags.helpers import pretty_search
3383 return pretty_search("search", "c", f'"{self.display_name()}"')
3385 def orcid_href(self):
3386 return resolver.resolve_id("orcid", self.orcid)
3388 def idref_href(self):
3389 return resolver.resolve_id("idref", self.idref)
3391 def is_equal(self, contribution):
3392 """
3393 return True if the contribution is identical, based on orcif/idref or homonimy
3394 TODO: override the __eq__ operator ? Not sure since homonimy is not bullet proof
3395 """
3396 equal = False
3397 if self.orcid and self.orcid == contribution.orcid:
3398 equal = True
3399 elif self.idref and self.idref == contribution.idref:
3400 equal = True
3401 else:
3402 equal = self.display_name() == contribution.display_name()
3404 return True
3405 return equal
3408@receiver(pre_delete, sender=Contribution)
3409def delete_contrib_handler(sender, instance, **kwargs):
3410 """
3411 pre_delete Contrib signal
3413 Contrib and Author are added manually during a
3414 addResourceDatabaseCmd (mainly addArticleDatabaseCmd)
3415 but Contrib are deleted automatically by Django when its
3416 related resource is deleted
3417 (resource = models.ForeignKey(Resource) of ContribGroup)
3418 To update Author, we use the Django signal mechanism
3419 """
3420 if instance and instance.role == "author":
3421 try:
3422 ref_name = instance.mid if instance.mid else str(instance)
3423 author = Author.objects.get(name=ref_name)
3424 author.count -= 1
3425 if author.count == 0:
3426 author.delete()
3427 else:
3428 author.save()
3429 except Author.DoesNotExist:
3430 pass
3433def get_names(item, role):
3434 """
3435 item: resource or bibitem
3436 """
3437 return [
3438 str(contribution) for contribution in item.contributions.all() if contribution.role == role
3439 ]
3442def are_all_equal_contrib(contributions):
3443 if len(contributions) == 0:
3444 return False
3446 are_all_equal = True
3447 for contribution in contributions:
3448 are_all_equal = are_all_equal and contribution.equal_contrib
3450 return are_all_equal
3453def are_all_false_equal_contrib(contributions):
3454 are_all_equal = True
3455 for contribution in contributions:
3456 are_all_equal = are_all_equal and not contribution.equal_contrib
3458 return are_all_equal
3461class ResourceInSpecialIssue(models.Model):
3462 my_container = models.ForeignKey(
3463 Container, null=False, related_name="resources_in_special_issue", on_delete=models.CASCADE
3464 )
3465 resource = models.ForeignKey(
3466 Resource, null=True, related_name="used_in_special_issues", on_delete=models.CASCADE
3467 )
3468 resource_doi = models.CharField(max_length=64, unique=True)
3469 seq = models.IntegerField(db_index=True)
3470 citation = models.TextField(null=True, blank=True)
3472 def __str__(self):
3473 if self.resource and hasattr(self.resource, "doi"):
3474 return f"{self.resource.doi}, {self.my_container}"
3477class RelatedArticles(models.Model):
3478 resource = models.ForeignKey(Resource, null=True, blank=True, on_delete=models.CASCADE)
3479 # Used during reimport/redeploy to find back the RelatedArticles
3480 resource_doi = models.CharField(max_length=64, unique=True, null=True, blank=True)
3481 date_modified = models.DateTimeField(null=True, blank=True)
3482 doi_list = models.TextField(null=True, blank=True)
3483 exclusion_list = models.TextField(null=True, blank=True)
3484 automatic_list = models.BooleanField(default=True)
3486 def __str__(self):
3487 if self.resource and hasattr(self.resource, "doi"):
3488 return f"{self.resource.doi}"
3491def image_path_graphical_abstract(instance, filename):
3492 path = "images"
3493 return os.path.join(path, str(instance.id), "graphical_abstract", filename)
3496def image_path_illustration(instance, filename):
3497 path = "images"
3498 return os.path.join(path, str(instance.id), "illustration", filename)
3501class OverwriteStorage(FileSystemStorage):
3502 def get_available_name(self, name, max_length=None):
3503 if self.exists(name):
3504 os.remove(os.path.join(settings.MEDIA_ROOT, name))
3505 return name
3508class GraphicalAbstract(models.Model):
3509 resource = models.ForeignKey(Resource, null=True, blank=True, on_delete=models.CASCADE)
3510 # Used during reimport/redeploy to find back the GraphicalAbstracts
3511 resource_doi = models.CharField(max_length=64, unique=True, null=True, blank=True)
3512 date_modified = models.DateTimeField(null=True, blank=True)
3513 graphical_abstract = models.ImageField(
3514 upload_to=image_path_graphical_abstract, blank=True, null=True, storage=OverwriteStorage
3515 )
3516 illustration = models.ImageField(
3517 upload_to=image_path_illustration, blank=True, null=True, storage=OverwriteStorage
3518 )
3520 def __str__(self):
3521 if self.resource and hasattr(self.resource, "doi"):
3522 return f"{self.resource.doi}"
3525def backup_obj_not_in_metadata(article):
3526 """
3527 When you addArticleXmlCmd in the PTF, the XML does not list objects like GraphicalAbstract or RelatedArticles.
3528 Since addArticleXmlCmd deletes/re-creates the article, we need to "backup" these objects.
3529 To do so, we delete the relation between article and the object,
3530 so that the object is not deleted when the article is deleted.
3532 You need to call restore_obj_not_in_metadata after the re-creation of the article.
3533 """
3534 for class_name in ["GraphicalAbstract", "RelatedArticles", "ResourceInSpecialIssue"]:
3535 qs = globals()[class_name].objects.filter(resource=article)
3536 if qs.exists(): 3536 ↛ 3537line 3536 didn't jump to line 3537 because the condition on line 3536 was never true
3537 obj = qs.first()
3538 obj.resource_doi = article.doi
3539 obj.resource = None
3540 obj.save()
3543def backup_translation(article):
3544 """
3545 Translated articles are the article JATS XML, but not in the Cedrics XML
3546 We need to "backup" existing translations when importing a Cedrics Article.
3547 To do so, we delete the relation between article and the translation,
3548 so that the translation is not deleted when the article is deleted.
3550 You need to call restore_translation after the re-creation of the article
3551 """
3552 qs = TranslatedArticle.objects.filter(original_article=article)
3553 if qs.exists():
3554 obj = qs.first()
3555 obj.original_article_doi = article.doi
3556 obj.original_article = None
3557 obj.save()
3560def restore_obj_not_in_metadata(article):
3561 for class_name in ["GraphicalAbstract", "RelatedArticles", "ResourceInSpecialIssue"]:
3562 qs = globals()[class_name].objects.filter(resource_doi=article.doi, resource__isnull=True)
3563 if qs.exists(): 3563 ↛ 3564line 3563 didn't jump to line 3564 because the condition on line 3563 was never true
3564 obj = qs.first()
3565 obj.resource = article
3566 obj.save()
3569def restore_translation(article):
3570 qs = TranslatedArticle.objects.filter(
3571 original_article_doi=article.doi, original_article__isnull=True
3572 )
3573 if qs.exists():
3574 obj = qs.first()
3575 obj.original_article = article
3576 obj.save()
3579class TranslatedArticle(Article):
3580 original_article = models.ForeignKey(
3581 Article, null=True, blank=True, on_delete=models.CASCADE, related_name="translations"
3582 )
3583 # Used during reimport/redeploy in Trammel to find back the TranslatedArticle
3584 original_article_doi = models.CharField(max_length=64, unique=True, null=True, blank=True)
3586 def get_year(self):
3587 return self.original_article.get_year()
3589 def get_ojs_id(self):
3590 return self.original_article.get_ojs_id()
3592 def get_binary_file_href_full_path(self, doctype, mimetype, location):
3593 """
3594 Returns an encoded URL to the pdf of a translated article
3596 Input: doctype: language (ex: 'fr', 'en'
3597 mimetype: 'application/pdf'
3598 location is ignored
3600 Ex: /article/fr/10.5802/crmath.100.pdf
3601 """
3603 if mimetype != "application/pdf":
3604 return ""
3606 pid = (
3607 self.original_article.doi
3608 if self.original_article.doi is not None
3609 else self.original_article.pid
3610 )
3611 extension = "pdf"
3612 href = reverse(
3613 "article-translated-pdf",
3614 kwargs={"pid": pid, "extension": extension, "binary_file_type": self.lang},
3615 )
3617 return href
3619 def get_citation(self, request=None):
3620 citation = ""
3622 translator_names = get_names(self, "translator")
3623 citation += "; ".join(translator_names)
3625 if citation:
3626 if not citation.endswith("."):
3627 citation += ". "
3628 citation += " "
3630 if self.lang == self.original_article.trans_lang:
3631 citation += self.original_article.trans_title_tex
3632 else:
3633 citation += self.title_tex
3635 year = self.date_published.strftime("%Y") if self.date_published is not None else "YYYY"
3636 citation += " (" + year + ")"
3638 if self.doi is not None:
3639 citation += " doi : " + self.doi
3641 # page_text = self.get_page_text(True)
3642 # citation += self.original_article.get_citation_base(year, page_text)
3644 citation += " (" + self.original_article.get_citation()[0:-1] + ")"
3646 # author_names = get_names(self.original_article, "author")
3647 # if author_names:
3648 # authors = '; '.join(author_names)
3649 # else:
3650 # author_names = get_names(self.original_article, "editor")
3651 # if author_names:
3652 # authors = '; '.join(author_names) + ' (' + str(
3653 # _("éd.")) + ')'
3654 # else:
3655 # authors = ''
3656 # citation += authors
3657 #
3658 # if citation:
3659 # if not citation.endswith('.'):
3660 # citation += '.'
3661 # citation += ' '
3662 #
3663 # citation += self.original_article.title_tex
3664 #
3665 # if self.lang == self.original_article.trans_lang:
3666 # citation += f" [{self.original_article.trans_title_tex}]"
3667 # else:
3668 # citation += f" [{self.title_tex}]"
3669 #
3670 # citation += ' (' + str(_('Traduit par')) + ' '
3671 #
3672 # citation += '). '
3673 #
3674 # citation += self.original_article.my_container.my_collection.title_tex
3676 # if self.doi is not None:
3677 # citation += ' doi : ' + self.doi + '.'
3678 #
3679 # if request is not None:
3680 # url = "{}://{}{}".format(request.scheme, request.get_host(), self.original_article.get_absolute_url())
3681 # citation += ' ' + url
3682 #
3683 # citation += " (" + str(_('Article original publié en ')) + self.original_article.my_container.year + ')'
3685 return citation