Coverage for apps/ptf/models.py: 82%

1924 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-05-19 19:20 +0000

1import os 

2import re 

3from urllib.parse import urljoin 

4from urllib.parse import urlparse 

5 

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 _ 

20 

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 

29 

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" 

37 

38 

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/ 

42 

43 

44class UnknownRelationType(Exception): 

45 pass 

46 

47 

48class Identifier: 

49 """ 

50 descripteur 

51 """ 

52 

53 def __init__(self, id_type): 

54 self.id_type = id_type 

55 

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 

61 

62 return value 

63 

64 def __set__(self, obj, value): 

65 raise NotImplementedError("Operation not implemented") 

66 

67 

68class PtfSite(Site): 

69 """Site hébergé""" 

70 

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) 

77 

78 

79class ResourceQuerySet(models.QuerySet): 

80 def prefetch_contributors(self): 

81 return self.prefetch_related("contributions", "contributions__contribaddress_set") 

82 

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 ) 

92 

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 ) 

109 

110 def prefetch_all(self): 

111 return self.prefetch_references().prefetch_contributors().prefetch_work() 

112 

113 

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") 

135 

136 title_xml = models.TextField(default="") 

137 

138 lang = models.CharField(max_length=3, default="und") 

139 title_tex = models.TextField(default="") 

140 title_html = models.TextField(default="") 

141 

142 trans_lang = models.CharField(max_length=3, default="und") 

143 trans_title_tex = models.TextField(default="") 

144 trans_title_html = models.TextField(default="") 

145 

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="") 

151 

152 body_html = models.TextField(default="") 

153 body_tex = models.TextField(default="") 

154 body_xml = models.TextField(default="") 

155 

156 objects = ResourceQuerySet.as_manager() 

157 

158 class Meta: 

159 unique_together = ("provider", "pid") 

160 

161 def __str__(self): 

162 return self.pid 

163 

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 """ 

169 

170 return reverse("item_id", kwargs={"pid": self.pid}) 

171 

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 = "/" 

183 

184 # remove beginning / for urljoin 

185 resource_path = re.sub(r"^/*", "", self.get_absolute_url()) 

186 return urljoin(domain, resource_path) 

187 

188 def save(self, *args, **kwargs): 

189 if not self.id: 

190 self.classname = self.__class__.__name__ 

191 super().save(*args, **kwargs) 

192 

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()) 

198 

199 def get_collection(self): 

200 return None 

201 

202 def get_top_collection(self): 

203 return None 

204 

205 def get_container(self): 

206 return None 

207 

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 

215 

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 

223 

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() 

237 

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() 

245 

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 

252 

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 

259 

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() 

264 

265 return self.date_time_deployed(site) 

266 

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 

274 

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 

281 

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() 

287 

288 return href 

289 

290 def website(self): 

291 return self.get_link("website") 

292 

293 def test_website(self): 

294 return self.get_link("test_website") 

295 

296 def icon(self): 

297 return self.get_link("icon") 

298 

299 def small_icon(self): 

300 return self.get_link("small_icon") 

301 

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 

336 

337 def is_edited_book(self): 

338 return False 

339 

340 # NOTE - 12/08/2017 - Basile 

341 # utilisé nul part à delete ? 

342 # def has_errata(self): 

343 # return self.get_related('corrected-by') 

344 

345 # def is_erratum_to(self): 

346 # return self.get_related('corrects', count_only=False) 

347 

348 # def errata(self): 

349 # return self.get_related('corrected-by', count_only=False) 

350 

351 # def erratum(self): 

352 # errata = self.get_related('corrected-by', count_only=False) 

353 # if len(errata) == 1: 

354 # return errata[0] 

355 # return None 

356 

357 # def questions(self): 

358 # return self.get_related('resolves', count_only=False) 

359 

360 # def solutions(self): 

361 # return self.get_related('resolved-by', count_only=False) 

362 

363 # def complements(self): 

364 # return self.get_related('complements', count_only=False) 

365 

366 # def completed(self): 

367 # return self.get_related('complemented-by', count_only=False) 

368 

369 # def follows(self): 

370 # followed = self.get_related('follows', count_only=False) 

371 # if len(followed) == 1: 

372 # return followed[0] 

373 # return 0 

374 

375 # def followed(self): 

376 # follows = self.get_related('followed-by', count_only=False) 

377 # if len(follows) == 1: 

378 # return follows[0] 

379 # return 0 

380 

381 # def citations_count(self): 

382 # if not self.pid: 

383 # return 0 

384 # qs = BibItemId.objects.select_related().filter( 

385 # id_type=self.provider.pid_type, 

386 # id_value=self.pid, 

387 # bibitem__resource__sites__id=settings.SITE_ID 

388 # ) 

389 # return qs.count() 

390 

391 def citations(self): 

392 if not self.pid or not self.provider: 

393 return [] 

394 

395 qs = BibItemId.objects.select_related().filter( 

396 id_type=self.provider.pid_type, 

397 id_value=self.pid, 

398 bibitem__resource__sites__id=settings.SITE_ID, 

399 ) 

400 # on ne peut pas trier par date sur la requete car 

401 # on peut avoir soit des containers soit des articles 

402 # comme resource et year est sur le container uniquement 

403 result = [bid.bibitem.resource.cast() for bid in qs] 

404 result.sort(key=lambda item: item.get_year(), reverse=True) 

405 return result 

406 

407 def get_contributions(self, role): 

408 # prefetch probably has already queried the database for the contributions 

409 # Using filter on self.contributions would result in a separate SQL query 

410 return [ 

411 contribution 

412 for contribution in self.contributions.all() 

413 if contribution.role.find(role) == 0 

414 ] 

415 

416 def get_author_contributions(self, strict=True): 

417 authors = self.get_contributions("author") 

418 if not strict and len(authors) == 0: 

419 authors = self.get_editors() 

420 return authors 

421 

422 def get_authors_short(self): 

423 authors = self.get_contributions("author") 

424 if len(authors) > 2: 

425 authors = "; ".join([str(author) for author in authors[:2]]) 

426 authors += " <i>et al.</i>" 

427 return authors 

428 return "; ".join([str(author) for author in authors]) 

429 

430 def get_editors(self): 

431 return self.get_contributions("editor") 

432 

433 def get_contributors(self): 

434 return self.get_contributions("contributor") 

435 

436 def get_redaktors(self): 

437 return self.get_contributions("redaktor") 

438 

439 def get_organizers(self): 

440 return self.get_contributions("organizer") 

441 

442 def get_presenters(self): 

443 return self.get_contributions("presenter") 

444 

445 def get_kwds_by_type(self): 

446 msc = [kwd for kwd in self.kwd_set.all() if kwd.type == "msc"] 

447 kwds = [kwd for kwd in self.kwd_set.all() if kwd.type != "msc" and kwd.lang == self.lang] 

448 trans_kwds = [ 

449 kwd for kwd in self.kwd_set.all() if kwd.type != "msc" and kwd.lang != self.lang 

450 ] 

451 return msc, kwds, trans_kwds 

452 

453 def get_subjs_by_type_and_lang(self): 

454 subjs = {} 

455 for subj in self.subj_set.all(): 

456 if subj.type in subjs: 

457 if subj.lang in subjs[subj.type]: 

458 subjs[subj.type][subj.lang].append(subj) 

459 else: 

460 subjs[subj.type][subj.lang] = [subj] 

461 else: 

462 subjs[subj.type] = {subj.lang: [subj]} 

463 

464 return subjs 

465 

466 def self_uris_no_xml(self): 

467 """ 

468 Returns a list of links to the datastream of the resource (= pdf/djvu of the resource) 

469 This function is only used to export an xml (oai) 

470 HTML templates use get_binary_files_href (see below) 

471 """ 

472 links = [ 

473 { 

474 "mimetype": link.mimetype, 

475 "full_path": self.get_binary_file_href_full_path( 

476 "self", link.mimetype, link.location 

477 ), 

478 "link": link.text, 

479 } 

480 for link in self.datastream_set.all() 

481 ] 

482 return links 

483 

484 @staticmethod 

485 def append_href_to_binary_files(binary_files, key, mimetype, href): 

486 if mimetype == "application/pdf": 

487 if key in binary_files: 487 ↛ 488line 487 didn't jump to line 488, because the condition on line 487 was never true

488 binary_files[key]["pdf"] = href 

489 else: 

490 binary_files[key] = {"pdf": href} 

491 elif mimetype == "image/x.djvu": 491 ↛ 492line 491 didn't jump to line 492, because the condition on line 491 was never true

492 if key in binary_files: 

493 binary_files[key]["djvu"] = href 

494 else: 

495 binary_files[key] = {"djvu": href} 

496 elif mimetype == "application/x-tex": 

497 if key in binary_files: 497 ↛ 500line 497 didn't jump to line 500, because the condition on line 497 was never false

498 binary_files[key]["tex"] = href 

499 else: 

500 binary_files[key] = {"tex": href} 

501 elif mimetype == "video": 501 ↛ 502line 501 didn't jump to line 502, because the condition on line 501 was never true

502 if key in binary_files: 

503 binary_files[key]["video"] = href 

504 else: 

505 binary_files[key] = {"video": href} 

506 

507 def get_binary_files_location(self): 

508 binary_files = [] 

509 

510 for obj in self.extlink_set.all(): 

511 if obj.rel == "icon" or obj.rel == "small_icon": 

512 binary_files.append(obj.location) 

513 

514 for obj in self.relatedobject_set.all(): 

515 if obj.rel != "video" and obj.rel != "html-image": 515 ↛ 516line 515 didn't jump to line 516, because the condition on line 515 was never true

516 binary_files.append(obj.location) 

517 

518 for obj in self.datastream_set.all(): 

519 binary_files.append(obj.location) 

520 

521 if hasattr(self, "translations"): 

522 for translated_article in self.translations.all(): 522 ↛ 523line 522 didn't jump to line 523, because the loop on line 522 never started

523 binary_files.extend(translated_article.get_binary_files_location()) 

524 

525 return binary_files 

526 

527 def get_binary_files_href(self): 

528 """ 

529 Get all the HREFs (without http://site) of the binary files (pdf/djvu) related to the resource 

530 Result: { 'self': {'pdf':href, 'djvu':href, 'tex': href}, 

531 'toc':{'pdf':href, 'djvu':href}, 

532 'frontmatter':{'pdf':href, 'djvu':href}, 

533 'backmatter':{'pdf':href, 'djvu':href}, 

534 'translations': {<lang>: {'pdf':href,...}} } 

535 result['self'] is the main pdf/djvu of the resource (article pdf, 

536 full volume pdf, book part pdf) 

537 The information come from the DataStream 

538 The other results come from RelatedObject 

539 """ 

540 binary_files = {} 

541 

542 for related_object in self.relatedobject_set.all(): 

543 key = related_object.rel 

544 mimetype = related_object.mimetype 

545 location = related_object.location 

546 href = self.get_binary_file_href_full_path(key, mimetype, location) 

547 

548 self.append_href_to_binary_files(binary_files, key, mimetype, href) 

549 

550 allow_local_pdf = not hasattr(settings, "ALLOW_LOCAL_PDF") or settings.ALLOW_LOCAL_PDF 

551 

552 for stream in self.datastream_set.all(): 

553 key = "self" 

554 mimetype = stream.mimetype 

555 location = stream.location 

556 

557 if allow_local_pdf or mimetype != "application/pdf": 557 ↛ 552line 557 didn't jump to line 552, because the condition on line 557 was never false

558 if location.find("http") == 0: 558 ↛ 559line 558 didn't jump to line 559, because the condition on line 558 was never true

559 href = location 

560 else: 

561 href = self.get_binary_file_href_full_path("self", mimetype, location) 

562 

563 self.append_href_to_binary_files(binary_files, key, mimetype, href) 

564 

565 if not allow_local_pdf: 565 ↛ 566line 565 didn't jump to line 566, because the condition on line 565 was never true

566 qs = self.extlink_set.filter(rel="article-pdf") 

567 if qs: 

568 extlink = qs.first() 

569 href = extlink.location 

570 key = "self" 

571 mimetype = "application/pdf" 

572 

573 self.append_href_to_binary_files(binary_files, key, mimetype, href) 

574 

575 translations = {} 

576 if hasattr(self, "translations"): 

577 for trans_article in self.translations.all(): 577 ↛ 578line 577 didn't jump to line 578, because the loop on line 577 never started

578 result = trans_article.get_binary_files_href() 

579 if "self" in result: 

580 translations[trans_article.lang] = result["self"] 

581 

582 binary_files["translations"] = translations 

583 

584 return binary_files 

585 

586 def get_binary_file_href_full_path(self, doctype, mimetype, location): 

587 """ 

588 Returns an encoded URL to a pdf/djvu 

589 URLs in HTML pages do not return the exact path to a file (for safety reason) 

590 An encode URL is used instead 

591 Ex: URL to an article pdf: article/pid.pdf 

592 URL to an issue full pdf: issue/pid.pdf 

593 URL to an issue toc djvu: issue/toc/pid.pdf 

594 URL to an issue frontmatter: issue/frontmatter.pid.djvu 

595 When you click on such a link, ptf/views.py will decode the URL in get_pdf 

596 and use the DataStream/RelatedObject true location to return the file 

597 Input: doctype: 'self', 'toc', 'frontmatter', 'backmatter' 

598 mimetype: 'application/pdf', 'image/x.djvu' 

599 

600 Ex: /article/ALCO_2018__1_1_23_0.pdf 

601 /issue/MSMF_1978__55-56__1_0.pdf 

602 /issue/toc/CSHM_1980__1_.pdf 

603 /article/ALCO_2018__1_1_23_0/tex/src/tex/ALCO_Thiem_31.tex 

604 

605 """ 

606 if self.embargo(): 

607 return "" 

608 

609 pid = self.pid 

610 doi = getattr(self, "doi") 

611 if doi is not None: 

612 pid = doi 

613 

614 prefix = doctype if doctype != "self" else "" 

615 

616 if "annexe" in location: 616 ↛ 620line 616 didn't jump to line 620, because the condition on line 616 was never true

617 # 04/08/2021. Not used anymore ? 

618 

619 # Ex: /article/SMAI-JCM_2015__1__83_0/attach/Allaire-Dapogny-supp.pdf 

620 href = reverse( 

621 "annexe-pdf", kwargs={"pid": pid, "relative_path": location.split("/")[-1]} 

622 ) 

623 

624 elif mimetype in ["application/pdf", "image/x.djvu"] and doctype not in [ 

625 "review", 

626 "supplementary-material", 

627 ]: 

628 extension = "pdf" if mimetype == "application/pdf" else "djvu" 

629 if len(prefix) > 0: 

630 href = reverse( 

631 "issue-relatedobject-pdf", 

632 kwargs={"pid": pid, "extension": extension, "binary_file_type": prefix}, 

633 ) 

634 else: 

635 href = reverse("item-pdf", kwargs={"pid": pid, "extension": extension}) 

636 

637 elif mimetype == "application/x-tex": 

638 to_find = "/src/tex/" 

639 i = location.find(to_find) 

640 if i > 0: 

641 location = location[i + 1 :] 

642 

643 href = reverse("article-binary-files", kwargs={"pid": pid, "relative_path": location}) 

644 

645 else: 

646 # All other attachments (videos, zip, etc...) : 

647 

648 to_find = "/attach/" 

649 i = location.find(to_find) 

650 if i > 0: 650 ↛ 651line 650 didn't jump to line 651, because the condition on line 650 was never true

651 location = location[i + 1 :] 

652 

653 # Ex: /article/ALCO_2018__1_1_23_0/file/review_history.pdf 

654 href = reverse("article-binary-files", kwargs={"pid": pid, "relative_path": location}) 

655 

656 return href 

657 

658 def get_binary_disk_location(self, doctype, mimetype, relativepath): 

659 """ 

660 Returns the path of a binary file (pdf/djvu) on the file server 

661 This function is called when you click on a link like issue/frontmatter/pid.pdf 

662 

663 URLs in HTML pages do not return the exact path to a file (for safety reason) 

664 An encoded URL is used instead (see get_binary_file_href_full_path above) 

665 Ex: URL to an issue toc djvu: issue/toc/pid.pdf 

666 

667 When you click on such a link, ptf/views.py will decode the URL in get_pdf 

668 before calling get_binary_disk_location to get the exact pdf/djvu disk location 

669 based on the DataStream/RelatedObject location 

670 

671 Input: doctype: 'self', 'toc', 'frontmatter', 'backmatter' 

672 mimetype: 'application/pdf', 'image/x.djvu' 

673 relativepath: Ex: 'src/tex/ALCO_Thiem_31.tex' 

674 

675 Returns value: path or None if there is an embargo 

676 May raise exceptions.ResourceDoesNotExist if the resource 

677 (RelatedObject/DataStream) does not exist 

678 """ 

679 if self.embargo(): 

680 return None 

681 

682 filename = None 

683 

684 if ( 684 ↛ 689line 684 didn't jump to line 689

685 doctype not in ["self", "toc", "frontmatter", "backmatter"] 

686 and len(doctype) == 2 

687 and hasattr(self, "translations") 

688 ): 

689 for translated_article in self.translations.all(): 

690 if translated_article.lang == doctype: 

691 filename = translated_article.get_binary_disk_location( 

692 "self", mimetype, relativepath 

693 ) 

694 elif relativepath: 694 ↛ 696line 694 didn't jump to line 696, because the condition on line 694 was never true

695 # relative path are used with supplementary materials or TeX Source file 

696 filename = os.path.join(self.get_relative_folder(), relativepath) 

697 else: 

698 if doctype != "self": 

699 try: 

700 obj = self.relatedobject_set.filter(mimetype=mimetype, rel=doctype).get() 

701 

702 except RelatedObject.DoesNotExist: 

703 # status = 404 

704 raise exceptions.ResourceDoesNotExist("The binary file does not exist") 

705 else: 

706 try: 

707 obj = self.datastream_set.get(mimetype=mimetype) 

708 

709 except DataStream.DoesNotExist: 

710 # status = 404 

711 raise exceptions.ResourceDoesNotExist("The binary file does not exist") 

712 filename = obj.location 

713 

714 return filename 

715 

716 def get_abstracts(self): 

717 return self.abstract_set.filter(tag__endswith="abstract") 

718 

719 def get_avertissements(self): 

720 return self.abstract_set.filter(tag="avertissement") 

721 

722 def get_descriptions(self): 

723 return self.abstract_set.filter(tag="description") 

724 

725 def get_editorial_intros(self): 

726 return self.abstract_set.filter(tag__endswith="intro") 

727 

728 def get_toc(self): 

729 return self.abstract_set.filter(tag__endswith="toc") 

730 

731 def visit(self, visitor): 

732 return visitor.visit(self.cast()) 

733 

734 def natural_key(self): 

735 return (self.pid, self.provider.id) 

736 

737 def get_solr_body(self, field): 

738 from ptf.cmds import solr_cmds 

739 

740 body = None 

741 cmd = solr_cmds.solrGetDocumentByPidCmd({"pid": self.pid}) 

742 result = cmd.do() 

743 if result: 

744 if field in result: 744 ↛ 747line 744 didn't jump to line 747, because the condition on line 744 was never false

745 body = result[field] 

746 

747 return body 

748 

749 def get_body(self): 

750 return self.get_solr_body("body") 

751 

752 def volume_string(self): 

753 return "" 

754 

755 def update_bibtex_with_commons(self, bibtex, container, hostname, scheme, indent): 

756 # TODO chapter, howpublished? 

757 # TODO institution 

758 # if self.institution: 

759 # append_in_latex(bibtex, indent + 'institution = {' + self.institution + '},' ) 

760 

761 to_appear = container.to_appear() 

762 is_cr = container.is_cr() 

763 

764 # pages = self.pages() 

765 publisher = container.my_publisher 

766 

767 if publisher is not None: 

768 if publisher.pub_name: 768 ↛ 770line 768 didn't jump to line 770, because the condition on line 768 was never false

769 append_in_latex(bibtex, indent + "publisher = {" + publisher.pub_name + "},") 

770 if publisher.pub_loc: 

771 append_in_latex(bibtex, indent + "address = {" + publisher.pub_loc + "},") 

772 

773 if not to_appear: 

774 if container.volume: 

775 append_in_latex(bibtex, indent + "volume = {" + self.volume_string() + "},") 

776 if container.number and not (is_cr and container.number[0] == "G"): 

777 append_in_latex(bibtex, indent + "number = {" + container.number + "},") 

778 

779 if container.year != "0": 779 ↛ 781line 779 didn't jump to line 781, because the condition on line 779 was never false

780 append_in_latex(bibtex, indent + "year = {" + container.year + "},") 

781 elif hasattr(self, "date_online_first") and self.date_online_first is not None: 

782 year = self.date_online_first.strftime("%Y") 

783 append_in_latex(bibtex, indent + "year = {" + year + "},") 

784 

785 if self.doi: 

786 append_in_latex(bibtex, indent + "doi = {" + self.doi + "},") 

787 

788 ext_type = {"zbl-item-id": "zbl", "mr-item-id": "mrnumber"} 

789 for extid in self.extid_set.filter(id_type__in=["zbl-item-id", "mr-item-id"]): 

790 append_in_latex( 

791 bibtex, indent + ext_type.get(extid.id_type) + " = {" + extid.id_value + "}," 

792 ) 

793 

794 if self.lang and self.lang != "und": 

795 append_in_latex(bibtex, indent + "language = {" + self.lang + "},") 

796 

797 if to_appear: 

798 append_in_latex(bibtex, indent + "note = {Online first},") 

799 elif not is_cr and len(hostname) > 0: 

800 ## No latex encoding, so append directly in the array 

801 url = f"{scheme}://{hostname}{self.get_absolute_url()}" 

802 bibtex.append("{}url = {}{}{}".format(indent, "{", url, "}")) 

803 

804 def update_ris_with_commons(self, items, container, hostname, scheme, sep): 

805 to_appear = container.to_appear() 

806 is_cr = container.is_cr() 

807 

808 if container.year != "0": 808 ↛ 810line 808 didn't jump to line 810, because the condition on line 808 was never false

809 items.append("PY" + sep + container.year) 

810 elif hasattr(self, "date_online_first") and self.date_online_first is not None: 

811 year = self.date_online_first.strftime("%Y") 

812 items.append("PY" + sep + year) 

813 

814 if not to_appear: 

815 if hasattr(self, "pages") and callable(self.pages) and self.pages(): 

816 pages = self.pages(for_bibtex=False).split("-") 

817 items.append("SP" + sep + pages[0]) 

818 if len(pages) > 1: 818 ↛ 820line 818 didn't jump to line 820, because the condition on line 818 was never false

819 items.append("EP" + sep + pages[1]) 

820 if container.volume: 

821 items.append("VL" + sep + container.volume) 

822 if container.number and not (is_cr and container.number[0] == "G"): 822 ↛ 825line 822 didn't jump to line 825, because the condition on line 822 was never false

823 items.append("IS" + sep + container.number) 

824 

825 publisher = container.my_publisher 

826 if publisher is not None: 

827 if publisher.pub_name: 827 ↛ 829line 827 didn't jump to line 829, because the condition on line 827 was never false

828 items.append("PB" + sep + publisher.pub_name) 

829 if publisher.pub_loc: 829 ↛ 832line 829 didn't jump to line 832, because the condition on line 829 was never false

830 items.append("PP" + sep + publisher.pub_loc) 

831 

832 if to_appear: 

833 items.append("N1" + sep + "Online first") 

834 elif not is_cr and len(hostname) > 0: 

835 url = f"{scheme}://{hostname}{self.get_absolute_url()}" 

836 items.append("UR" + sep + url) 

837 

838 if self.doi: 

839 items.append("DO" + sep + self.doi) 

840 

841 if self.lang and self.lang != "und": 

842 items.append("LA" + sep + self.lang) 

843 

844 if self.pid: 844 ↛ 846line 844 didn't jump to line 846, because the condition on line 844 was never false

845 items.append("ID" + sep + self.pid) 

846 items.append("ER" + sep) 

847 

848 def update_endnote_with_commons(self, items, container, hostname, scheme, sep): 

849 to_appear = container.to_appear() 

850 is_cr = container.is_cr() 

851 

852 if container.year != "0": 852 ↛ 854line 852 didn't jump to line 854, because the condition on line 852 was never false

853 items.append("%D" + sep + container.year) 

854 elif hasattr(self, "date_online_first") and self.date_online_first is not None: 

855 year = self.date_online_first.strftime("%Y") 

856 items.append("%D" + sep + year) 

857 

858 if not to_appear: 

859 if hasattr(self, "pages") and callable(self.pages) and self.pages(): 

860 pages = self.pages(for_bibtex=False) 

861 items.append("%P" + sep + pages) 

862 if container.volume: 

863 items.append("%V" + sep + container.volume) 

864 if container.number and not (is_cr and container.number[0] == "G"): 864 ↛ 867line 864 didn't jump to line 867, because the condition on line 864 was never false

865 items.append("%N" + sep + container.number) 

866 

867 publisher = container.my_publisher 

868 if publisher is not None: 

869 if publisher.pub_name: 869 ↛ 871line 869 didn't jump to line 871, because the condition on line 869 was never false

870 items.append("%I" + sep + publisher.pub_name) 

871 if publisher.pub_loc: 871 ↛ 874line 871 didn't jump to line 874, because the condition on line 871 was never false

872 items.append("%C" + sep + publisher.pub_loc) 

873 

874 if to_appear: 

875 items.append("%Z" + sep + "Online first") 

876 elif not is_cr and len(hostname) > 0: 

877 url = f"{scheme}://{hostname}{self.get_absolute_url()}" 

878 items.append("%U" + sep + url) 

879 

880 if self.doi: 

881 items.append("%R" + sep + self.doi) 

882 

883 if self.lang and self.lang != "und": 

884 items.append("%G" + sep + self.lang) 

885 

886 if self.pid: 886 ↛ exitline 886 didn't return from function 'update_endnote_with_commons', because the condition on line 886 was never false

887 items.append("%F" + sep + self.pid) 

888 

889 

890class PublisherQuerySet(models.QuerySet): 

891 def get_by_natural_key(self, pub_key, pub_name): 

892 return self.get(pub_key=pub_key) 

893 

894 

895class Publisher(Resource): 

896 """ 

897 les classes Publisher, EventSeries et Event sont un peu à part: 

898 a priori pas de pid ni sid et même pas d'identificateur du tout 

899 d'où les slugs 

900 On peut les sortir de la hiérarchie resource -- les y laisser 

901 permet de leur attache des meta suppléméntaires. Voir Provider 

902 pour la possibilité inverse :-) 

903 """ 

904 

905 pub_key = models.CharField(max_length=128, unique=True) 

906 pub_name = models.CharField(max_length=256, db_index=True) 

907 pub_loc = models.CharField(max_length=128, db_index=True) 

908 

909 # 2016-05-18: Publisher is only used by Container: ManyToOne relation 

910 # publishes = models.ManyToManyField(Resource, related_name='Publisher') 

911 

912 def __str__(self): 

913 return self.pub_key 

914 

915 @staticmethod 

916 def get_collection(): 

917 return None 

918 

919 @staticmethod 

920 def get_top_collection(): 

921 return None 

922 

923 @staticmethod 

924 def get_container(): 

925 return None 

926 

927 def natural_key(self): 

928 return ( 

929 self.pub_key, 

930 self.pub_name, 

931 ) 

932 

933 objects = PublisherQuerySet.as_manager() 

934 

935 

936class CollectionQuerySet(models.QuerySet): 

937 def order_by_date(self): 

938 return self.annotate(year=Max("content__year")).order_by("year") 

939 

940 def get_by_natural_key(self, pid, provider): 

941 return self.get(pid=pid, provider=provider) 

942 

943 

944class Collection(Resource): 

945 # journal, acta, book-series, these, lectures 

946 coltype = models.CharField(max_length=32, db_index=True) 

947 title_sort = models.CharField(max_length=128, db_index=True) # sort key, not displayed 

948 issn = Identifier("issn") 

949 e_issn = Identifier("e-issn") 

950 wall = models.IntegerField(default=5) 

951 alive = models.BooleanField(default=True) 

952 # First/Last year of a collection. Comes from its containers. Is typically 

953 # updated when a container is imported. 

954 fyear = models.IntegerField(default=0) 

955 lyear = models.IntegerField(default=0) 

956 last_doi = models.IntegerField(default=0) 

957 

958 # Ancestors means journals that existed before the creation of the Collection: time-based relation 

959 # (the name 'ancestor' does not fit with 'parent' as 'parent' refers to a tree structure) 

960 # We don't really create a tree. 

961 # Ex: AFST is the root journal. AFST-0996-0481 is the original journal that became AFST-0240-2955 that became AFST. 

962 # We create a top node (AFST) and 2 ancestors (AFST-0996-0481, AFST-0240-2955) 

963 parent = models.ForeignKey( 

964 "self", on_delete=models.CASCADE, null=True, blank=True, related_name="ancestors" 

965 ) 

966 objects = CollectionQuerySet.as_manager() 

967 

968 class Meta: 

969 ordering = ["title_sort"] 

970 

971 # This function is used by Django: https://docs.djangoproject.com/fr/3.0/ref/models/instances/ 

972 # Unfortunately, the name is wrong: get_absolute_url must return a relative path !?! 

973 # => hence the get_url_absolute below that returns an absolute URL 

974 def get_absolute_url(self): 

975 if self.coltype == "thesis": 

976 url = reverse("pretty_thesis", args=[f'"{self.title_html}"-p']) 

977 elif self.coltype == "book-series": 

978 url = reverse("pretty-series", args=[f'"{self.title_html}"-p']) 

979 elif self.coltype == "lectures" or self.coltype == "lecture-notes": 

980 url = reverse("pretty-lectures", args=[f'"{self.title_html}"-p']) 

981 else: 

982 url = reverse(self.coltype + "-issues", kwargs={"jid": self.pid}) 

983 return url 

984 

985 def bloodline(self): 

986 # returns parent + siblings, ordered by date (of the last volume published) 

987 if self.parent or self.ancestors.exists(): 

988 parent_pid = self.parent.pid if self.parent else self.pid 

989 return Collection.objects.filter( 

990 Q(pid=parent_pid) | Q(parent__pid=parent_pid) 

991 ).order_by_date() 

992 return Collection.objects.none() 

993 

994 def preceding_journal(self): 

995 # returns my ancestor (timed-based) = the Collection that was published just before me 

996 bloodline = self.bloodline() 

997 for index, collection in enumerate(bloodline): 

998 if collection == self and index: 

999 return bloodline[index - 1] 

1000 return Collection.objects.none() 

1001 

1002 def get_wall(self): 

1003 return self.wall 

1004 

1005 def get_collection(self): 

1006 return self 

1007 

1008 def get_top_collection(self): 

1009 return self.parent if self.parent else self 

1010 

1011 def deployed_date(self, site=None): 

1012 # return the last date of metadata deployed date for all of containers 

1013 containers = self.content.all() 

1014 date = None 

1015 if containers: 

1016 site = Site.objects.get_current() 

1017 sms = ( 

1018 SiteMembership.objects.filter(resource__in=containers) 

1019 .filter(site=site) 

1020 .order_by("-deployed") 

1021 ) 

1022 date = sms.first().deployed 

1023 return date 

1024 

1025 def get_relative_folder(self): 

1026 return resolver.get_relative_folder(self.get_top_collection().pid) 

1027 

1028 

1029class ContainerQuerySet(ResourceQuerySet): 

1030 def prefetch_all(self): 

1031 return super().prefetch_all().select_related("my_collection", "my_publisher") 

1032 

1033 def prefetch_for_toc(self): 

1034 return ( 

1035 self.prefetch_contributors() 

1036 .prefetch_work() 

1037 .prefetch_related( 

1038 "article_set__datastream_set", 

1039 "article_set__subj_set", 

1040 "article_set__extlink_set", 

1041 "article_set__resourcecount_set", 

1042 "article_set__contributions", 

1043 ) 

1044 .select_related("my_collection", "my_publisher") 

1045 ) 

1046 

1047 

1048class Container(Resource): 

1049 """ 

1050 mappe issue et book (on pourrait faire deux classes) ou une hiérarchie 

1051 container <--- issue 

1052 <--- book 

1053 """ 

1054 

1055 # issue, book-monograph, book-edited-book (multiple authors, with editors), lecture-notes 

1056 ctype = models.CharField(max_length=32, db_index=True) 

1057 year = models.CharField(max_length=32, db_index=True) 

1058 

1059 last_modified = models.DateTimeField(null=False, blank=False) 

1060 ## 

1061 # if ctype == issue 

1062 number = models.CharField(max_length=32, db_index=True) # issue number 

1063 ## 

1064 # data for relation with enclosing serial, if any 

1065 my_collection = models.ForeignKey( 

1066 Collection, null=True, related_name="content", on_delete=models.CASCADE 

1067 ) 

1068 vseries = models.CharField(max_length=32, db_index=True) 

1069 volume = models.CharField(max_length=64, db_index=True) 

1070 # la même chose pour le tri 

1071 vseries_int = models.IntegerField(default=0, db_index=True) 

1072 volume_int = models.IntegerField(default=0, db_index=True) 

1073 number_int = models.IntegerField(default=0, db_index=True) 

1074 seq = models.IntegerField(db_index=True) 

1075 with_online_first = models.BooleanField(default=False) # Used by ptf-tools only 

1076 

1077 my_publisher = models.ForeignKey( 

1078 Publisher, related_name="publishes", null=True, on_delete=models.CASCADE 

1079 ) 

1080 

1081 # Initially, a container could only be in 1 collection. 

1082 # In 2018, we extended the model so that a container can be 

1083 # in multiple collections (Bourbaki and Asterisque) 

1084 # 

1085 # This is an exception, most containers are in only 1 collection. 

1086 # my_collection is kept to store the main collection, the one used in the "how to cite" field. 

1087 # my_other_collections stores the additional collections. 

1088 # 

1089 # vseries/volume/number/seq of the main collection are kept in Container. 

1090 # As a result, there is no need to fetch the CollectionMembership to get 

1091 # these info for the main collection. 

1092 my_other_collections = models.ManyToManyField( 

1093 Collection, symmetrical=False, through="CollectionMembership" 

1094 ) 

1095 

1096 objects = ContainerQuerySet.as_manager() 

1097 

1098 class Meta: 

1099 ordering = ["seq"] 

1100 get_latest_by = ["year", "vseries_int", "volume_int", "number_int"] 

1101 

1102 def allow_crossref(self): 

1103 # we need at least a doi or an issn to allow crossref record 

1104 result = self.my_collection.doi or self.my_collection.issn or self.my_collection.e_issn 

1105 

1106 # if there are unpublished articles in the volume, we block crossref record 

1107 if self.article_set.filter( 1107 ↛ 1110line 1107 didn't jump to line 1110, because the condition on line 1107 was never true

1108 date_published__isnull=True, date_online_first__isnull=True 

1109 ).exists(): 

1110 result = False 

1111 

1112 return result 

1113 

1114 def all_doi_are_registered(self): 

1115 if self.article_set.count() > 0: 

1116 # il y a des articles, on vérifie qu'ils sont tous enregistrés 

1117 return ( 

1118 self.article_set.filter(doibatch__status="Enregistré").count() 

1119 == self.article_set.count() 

1120 ) 

1121 if self.doi and self.doibatch is not None: 

1122 return self.doibatch.status == "Enregistré" 

1123 # aucun doi associé aux articles ou au container 

1124 return False 

1125 

1126 def registered_in_doaj(self): 

1127 query = Q(date_published__isnull=True, date_online_first__isnull=True) | Q( 

1128 do_not_publish__isnull=True 

1129 ) 

1130 unpublished = self.article_set.filter(query).count() 

1131 registered = self.article_set.filter(doajbatch__status="registered").count() 

1132 all_registered = True if registered == self.article_set.count() - unpublished else False 

1133 if not all_registered and hasattr(self, "doajbatch"): 

1134 self.doajbatch.status = "unregistered" 

1135 self.doajbatch.save() 

1136 

1137 def are_all_articles_published(self): 

1138 from ptf import model_helpers 

1139 

1140 result = True 

1141 

1142 for article in self.article_set.all(): 

1143 year = article.get_year() 

1144 fyear, lyear = model_helpers.get_first_last_years(year) 

1145 try: 

1146 fyear = int(fyear) 

1147 except ValueError: 

1148 fyear = 0 

1149 

1150 if fyear > 2017: 

1151 if not article.date_published: 1151 ↛ 1152line 1151 didn't jump to line 1152, because the condition on line 1151 was never true

1152 result = False 

1153 elif fyear == 0: 

1154 result = False 

1155 

1156 return result 

1157 

1158 def get_wall(self): 

1159 return self.my_collection.get_wall() 

1160 

1161 def previous(self): 

1162 issue = None 

1163 qs = self.my_collection.content.filter(seq=self.seq - 1) 

1164 if qs.count() > 0: 

1165 issue = qs.first() 

1166 return issue 

1167 

1168 def next(self): 

1169 issue = None 

1170 qs = self.my_collection.content.filter(seq=self.seq + 1) 

1171 if qs.count() > 0: 

1172 issue = qs.first() 

1173 return issue 

1174 

1175 # TODO container in multiple collections 

1176 def get_collection(self): 

1177 return self.my_collection 

1178 

1179 def get_top_collection(self): 

1180 return self.my_collection.get_top_collection() 

1181 

1182 def get_other_collections(self): 

1183 return self.my_other_collections.all() 

1184 

1185 def get_container(self): 

1186 return self 

1187 

1188 def get_volume(self): 

1189 return self.volume 

1190 

1191 def get_number(self): 

1192 return self.number 

1193 

1194 def embargo(self): 

1195 return resolver.embargo(self.get_wall(), self.year) 

1196 

1197 def to_appear(self): 

1198 return self.with_online_first or ( 

1199 hasattr(settings, "ISSUE_TO_APPEAR_PID") and settings.ISSUE_TO_APPEAR_PID == self.pid 

1200 ) 

1201 

1202 def is_cr(self): 

1203 return ( 

1204 hasattr(settings, "SITE_NAME") 

1205 and len(settings.SITE_NAME) == 6 

1206 and settings.SITE_NAME[0:2] == "cr" 

1207 ) 

1208 

1209 @staticmethod 

1210 def get_base_url(): 

1211 return resolver.get_issue_base_url() 

1212 

1213 def get_relative_folder(self): 

1214 collection = self.get_top_collection() 

1215 return resolver.get_relative_folder(collection.pid, self.pid) 

1216 

1217 def get_vid(self): 

1218 """ 

1219 08/09/2022: support of Collection ancestors 

1220 The collection.pid might no longer be the top_collection.pid 

1221 => The volume URL would change if we were to keep the same vid 

1222 To keep URLs relatively similar, we use the pid of the first_issue of the volume 

1223 

1224 VolumeDetailView (ptf/views.py) handles the vid differently and no longer decrypts the vid 

1225 """ 

1226 # vid = f"{self.my_collection.pid}_{self.year}_{self.vseries}_{self.volume}" 

1227 vid = self.pid 

1228 return vid 

1229 

1230 def get_year(self): 

1231 return self.year 

1232 

1233 def is_edited_book(self): 

1234 return self.ctype == EDITED_BOOK_TYPE 

1235 

1236 def get_citation(self, request): 

1237 citation = "" 

1238 

1239 author_names = get_names(self, "author") 

1240 authors = "" 

1241 if author_names: 

1242 authors = "; ".join(author_names) 

1243 

1244 if not author_names or authors == "Collectif": 

1245 author_names = get_names(self, "editor") 

1246 if author_names: 

1247 authors = "; ".join(author_names) + " (" + str(_("éd.")) + ")" 

1248 else: 

1249 authors = "" 

1250 

1251 if authors != "Collectif": 1251 ↛ 1254line 1251 didn't jump to line 1254, because the condition on line 1251 was never false

1252 citation += authors 

1253 

1254 if citation: 

1255 if not citation.endswith("."): 1255 ↛ 1257line 1255 didn't jump to line 1257, because the condition on line 1255 was never false

1256 citation += "." 

1257 citation += " " 

1258 

1259 citation += self.title_tex + ". " 

1260 citation += self.my_collection.title_tex 

1261 

1262 if self.vseries: 

1263 citation += f", {str(_('Série'))} {self.vseries}" 

1264 

1265 if self.volume: 

1266 citation += ", " + str(volume_display()) + " " + self.volume + " (" + self.year + ") " 

1267 

1268 if self.number: 1268 ↛ 1275line 1268 didn't jump to line 1275, because the condition on line 1268 was never false

1269 citation += "no. " + self.number + ", " 

1270 elif self.number: 

1271 citation += ", no. " + self.number + " (" + self.year + "), " 

1272 else: 

1273 citation += " (" + self.year + "), " 

1274 

1275 redactors = self.get_redaktors() 

1276 if len(redactors) > 0: 

1277 redactors_str = "; ".join(get_names(self, "redaktor")) 

1278 citation += f"{redactors_str} (red.), " 

1279 

1280 for pages in self.resourcecount_set.all(): 

1281 citation += pages.value + " p." 

1282 

1283 for resourceid in self.resourceid_set.all(): 

1284 if resourceid.id_type == "doi": 1284 ↛ 1283line 1284 didn't jump to line 1283, because the condition on line 1284 was never false

1285 citation += " doi : " + resourceid.id_value + "." 

1286 

1287 citation += " " + self.get_url_absolute() 

1288 

1289 return citation 

1290 

1291 def has_detailed_info(self): 

1292 # Ignore citations here. 

1293 

1294 result = False 

1295 

1296 if self.extid_set.exists(): 1296 ↛ 1297line 1296 didn't jump to line 1297, because the condition on line 1296 was never true

1297 result = True 

1298 elif self.kwd_set.exists(): 1298 ↛ 1299line 1298 didn't jump to line 1299, because the condition on line 1298 was never true

1299 result = True 

1300 else: 

1301 for resourceid in self.resourceid_set.all(): 

1302 if resourceid.id_type != "numdam-prod-id": 1302 ↛ 1301line 1302 didn't jump to line 1301, because the condition on line 1302 was never false

1303 result = True 

1304 

1305 return result 

1306 

1307 def get_bibtex(self, request): 

1308 """ 

1309 

1310 :param self: 

1311 :return: a string encoded in latex (with latexcodec) 

1312 """ 

1313 bibtex = [] 

1314 indent = " " 

1315 

1316 collection = self.get_collection() 

1317 

1318 is_phdthesis = False 

1319 

1320 # no bibtex for an issue, only for a book (book, these) 

1321 if self.ctype == "issue": 

1322 return bibtex 

1323 

1324 if collection is not None and collection.coltype == "thesis": 1324 ↛ 1325line 1324 didn't jump to line 1325, because the condition on line 1324 was never true

1325 is_phdthesis = True 

1326 

1327 author_names = get_bibtex_names(self, "author") 

1328 editor_names = get_bibtex_names(self, "editor") 

1329 

1330 type_ = "book" 

1331 if is_phdthesis: 1331 ↛ 1332line 1331 didn't jump to line 1332, because the condition on line 1331 was never true

1332 type_ = "phdthesis" 

1333 

1334 # Numdam meeting: Use the resource pid as the bibtex id => No latex encoding to keep the '_' 

1335 id_ = self.pid 

1336 bibtex.append("@" + type_ + "{" + id_ + ",") 

1337 

1338 if author_names: 

1339 append_in_latex(bibtex, indent + author_names) 

1340 if editor_names: 

1341 append_in_latex(bibtex, indent + editor_names) 

1342 append_in_latex(bibtex, indent + "title = {" + self.title_tex + "},", is_title=True) 

1343 

1344 if collection is not None: 1344 ↛ 1347line 1344 didn't jump to line 1347, because the condition on line 1344 was never false

1345 append_in_latex(bibtex, indent + "series = {" + collection.title_tex + "},") 

1346 

1347 self.update_bibtex_with_commons(bibtex, self, request.get_host(), request.scheme, indent) 

1348 

1349 append_in_latex(bibtex, "}") 

1350 return "\n".join(bibtex) 

1351 

1352 def get_ris(self, request): 

1353 """ 

1354 

1355 :param self: 

1356 :return: a string 

1357 """ 

1358 items = [] 

1359 sep = " - " 

1360 

1361 collection = self.get_collection() 

1362 

1363 is_phdthesis = False 

1364 

1365 # no citation for an issue, only for a book (book, these) 

1366 if self.ctype == "issue": 

1367 return "" 

1368 

1369 if collection is not None and collection.coltype == "these": 1369 ↛ 1370line 1369 didn't jump to line 1370, because the condition on line 1369 was never true

1370 is_phdthesis = True 

1371 

1372 if is_phdthesis: 1372 ↛ 1373line 1372 didn't jump to line 1373, because the condition on line 1372 was never true

1373 items.append("TY" + sep + "THES") 

1374 else: 

1375 items.append("TY" + sep + "BOOK") 

1376 

1377 author_names = get_names(self, CONTRIB_TYPE_AUTHOR) 

1378 for author in author_names: 1378 ↛ 1379line 1378 didn't jump to line 1379, because the loop on line 1378 never started

1379 items.append("AU" + sep + author) 

1380 

1381 editor_names = get_names(self, CONTRIB_TYPE_EDITOR) 

1382 for editor in editor_names: 

1383 items.append("ED" + sep + editor) 

1384 

1385 items.append("TI" + sep + self.title_tex) 

1386 

1387 if collection is not None: 1387 ↛ 1390line 1387 didn't jump to line 1390, because the condition on line 1387 was never false

1388 items.append("T3" + sep + collection.title_tex) 

1389 

1390 self.update_ris_with_commons(items, self, request.get_host(), request.scheme, sep) 

1391 return "\r\n".join(items) 

1392 

1393 def get_endnote(self, request): 

1394 """ 

1395 

1396 :param self: 

1397 :return: a string 

1398 """ 

1399 items = [] 

1400 sep = " " 

1401 

1402 collection = self.get_collection() 

1403 

1404 is_phdthesis = False 

1405 

1406 # no citation for an issue, only for a book (book, these) 

1407 if self.ctype == "issue": 

1408 return "" 

1409 

1410 if collection is not None and collection.coltype == "these": 1410 ↛ 1411line 1410 didn't jump to line 1411, because the condition on line 1410 was never true

1411 is_phdthesis = True 

1412 

1413 if is_phdthesis: 1413 ↛ 1414line 1413 didn't jump to line 1414, because the condition on line 1413 was never true

1414 items.append("%0" + sep + "Thesis") 

1415 else: 

1416 items.append("%0" + sep + "Book") 

1417 

1418 author_names = get_names(self, CONTRIB_TYPE_AUTHOR) 

1419 for author in author_names: 1419 ↛ 1420line 1419 didn't jump to line 1420, because the loop on line 1419 never started

1420 items.append("%A" + sep + author) 

1421 

1422 editor_names = get_names(self, CONTRIB_TYPE_EDITOR) 

1423 for editor in editor_names: 

1424 items.append("%E" + sep + editor) 

1425 

1426 items.append("%T" + sep + self.title_tex) 

1427 

1428 if collection is not None: 1428 ↛ 1431line 1428 didn't jump to line 1431, because the condition on line 1428 was never false

1429 items.append("%S" + sep + collection.title_tex) 

1430 

1431 self.update_endnote_with_commons(items, self, request.get_host(), request.scheme, sep) 

1432 return "\r\n".join(items) 

1433 

1434 def has_articles_excluded_from_publication(self): 

1435 result = self.article_set.filter(do_not_publish=True).count() > 0 

1436 return result 

1437 

1438 

1439class EventSeries(Resource): 

1440 """to do: clé fabriquée voir _manager.make_key done""" 

1441 

1442 slug = models.CharField(max_length=128, unique=True) 

1443 event_type = models.CharField(max_length=32, db_index=True) 

1444 acro = models.CharField(max_length=32, db_index=True) 

1445 title_sort = models.CharField(max_length=128, db_index=True) 

1446 short_title = models.CharField(max_length=64, db_index=True) 

1447 

1448 

1449class Event(Resource): 

1450 """to do: clé fabriquée voir _manager.make_key done""" 

1451 

1452 slug = models.CharField(max_length=128, unique=True) 

1453 event_type = models.CharField(max_length=32, db_index=True) 

1454 title_sort = models.CharField(max_length=128, db_index=True) 

1455 string_event = models.CharField(max_length=128, db_index=True) 

1456 year = models.CharField(max_length=32, db_index=True) 

1457 acro = models.CharField(max_length=32, db_index=True) 

1458 number = models.CharField(max_length=4, db_index=True) 

1459 loc = models.CharField(max_length=64, db_index=True) 

1460 theme = models.CharField(max_length=64, db_index=True) 

1461 contrib = models.TextField() 

1462 proceedings = models.ManyToManyField(Resource, related_name="Events", symmetrical=False) 

1463 series = models.ForeignKey(EventSeries, null=True, on_delete=models.CASCADE) 

1464 

1465 class Meta: 

1466 ordering = ["year"] # seq (int) 

1467 

1468 

1469class ArticleQuerySet(ResourceQuerySet): 

1470 def order_by_published_date(self): 

1471 return self.order_by("-date_published", "-seq") 

1472 

1473 def order_by_sequence(self): 

1474 return self.order_by("seq") 

1475 

1476 def prefetch_for_toc(self): 

1477 return ( 

1478 self.prefetch_contributors() 

1479 .prefetch_work() 

1480 .select_related("my_container", "my_container__my_collection") 

1481 ) 

1482 

1483 

1484class Article(Resource): 

1485 """mappe journal article, book-part""" 

1486 

1487 atype = models.CharField(max_length=32, db_index=True) 

1488 fpage = models.CharField(max_length=32, db_index=True) 

1489 lpage = models.CharField(max_length=32, db_index=True) 

1490 page_range = models.CharField(max_length=32, db_index=True) 

1491 page_type = models.CharField(max_length=64) 

1492 elocation = models.CharField(max_length=32, db_index=True) 

1493 article_number = models.CharField(max_length=32) 

1494 talk_number = models.CharField(max_length=32) 

1495 date_received = models.DateTimeField(null=True, blank=True) 

1496 date_accepted = models.DateTimeField(null=True, blank=True) 

1497 date_revised = models.DateTimeField(null=True, blank=True) 

1498 date_online_first = models.DateTimeField(null=True, blank=True) 

1499 date_published = models.DateTimeField(null=True, blank=True) 

1500 date_pre_published = models.DateTimeField( 

1501 null=True, blank=True 

1502 ) # Used by ptf-tools only to measure delays 

1503 coi_statement = models.TextField(null=True, blank=True) # Conflict of interest 

1504 show_body = models.BooleanField( 

1505 default=True 

1506 ) # Used by ptf-tools only (to export or not the body) 

1507 do_not_publish = models.BooleanField( 

1508 default=False 

1509 ) # Used by ptf-tools only (to export or not the article) 

1510 

1511 ## 

1512 # container 

1513 my_container = models.ForeignKey(Container, null=True, on_delete=models.CASCADE) 

1514 seq = models.IntegerField() 

1515 ## 

1516 # parent part 

1517 parent = models.ForeignKey( 

1518 "self", null=True, related_name="children", on_delete=models.CASCADE 

1519 ) 

1520 pseq = models.IntegerField() 

1521 objects = ArticleQuerySet.as_manager() 

1522 

1523 class Meta: 

1524 ordering = ["seq", "fpage"] 

1525 

1526 def __str__(self): 

1527 return self.pid 

1528 

1529 @staticmethod 

1530 def get_base_url(): 

1531 return resolver.get_article_base_url() 

1532 

1533 def get_absolute_url(self): 

1534 if self.doi is not None: 

1535 return reverse("article", kwargs={"aid": self.doi}) 

1536 else: 

1537 return reverse("item_id", kwargs={"pid": self.pid}) 

1538 

1539 def get_relative_folder(self): 

1540 collection = self.get_top_collection() 

1541 return resolver.get_relative_folder(collection.pid, self.my_container.pid, self.pid) 

1542 

1543 def embargo(self): 

1544 if self.my_container is None: 1544 ↛ 1545line 1544 didn't jump to line 1545, because the condition on line 1544 was never true

1545 return False 

1546 

1547 return self.my_container.embargo() 

1548 

1549 def get_wall(self): 

1550 if self.my_container is None: 1550 ↛ 1551line 1550 didn't jump to line 1551, because the condition on line 1550 was never true

1551 return 0 

1552 return self.my_container.get_wall() 

1553 

1554 def get_collection(self): 

1555 return self.my_container.get_collection() 

1556 

1557 def get_top_collection(self): 

1558 return self.my_container.get_top_collection() 

1559 

1560 def get_container(self): 

1561 return self.my_container 

1562 

1563 def get_volume(self): 

1564 return self.my_container.get_volume() 

1565 

1566 def get_number(self): 

1567 return self.my_container.get_number() 

1568 

1569 def get_page_count(self): 

1570 page_count = None 

1571 for resourcecount in self.resourcecount_set.all(): 

1572 if resourcecount.name == "page-count": 1572 ↛ 1571line 1572 didn't jump to line 1571, because the condition on line 1572 was never false

1573 page_count = resourcecount.value 

1574 

1575 return page_count 

1576 

1577 def get_article_page_count(self): 

1578 try: 

1579 page_count = self.get_page_count() or 0 

1580 page_count = int(page_count) 

1581 except ValueError: 

1582 page_count = 0 

1583 

1584 lpage = fpage = 0 

1585 try: 

1586 fpage = int(self.fpage) 

1587 except ValueError: 

1588 pass 

1589 try: 

1590 lpage = int(self.lpage) 

1591 except ValueError: 

1592 pass 

1593 if lpage > 0 and fpage > 0: 

1594 page_count = lpage - fpage + 1 

1595 return page_count 

1596 

1597 def pages(self, for_bibtex=False): 

1598 """ 

1599 Returns a string with the article pages. 

1600 It is used for the different exports (BibTeX, EndNote, RIS) 

1601 

1602 typically "{fpage}-{lpage}" 

1603 For BibTex, 2 '-' are used, ie "{fpage}--{lpage}" 

1604 

1605 Some articles have a page_range. Use this field instead of the fpage/lpage in this case. 

1606 """ 

1607 if self.page_range: 

1608 return self.page_range 

1609 if self.fpage or self.lpage: 

1610 if self.lpage: 

1611 return ( 

1612 f"{self.fpage}--{self.lpage}" if for_bibtex else f"{self.fpage}-{self.lpage}" 

1613 ) 

1614 else: 

1615 return self.fpage 

1616 return None 

1617 

1618 def volume_series(self): 

1619 # Use only for latex 

1620 if not self.my_container.vseries: 

1621 return "" 

1622 if self.lang == "fr": 

1623 return self.my_container.vseries + "e s{\\'e}rie, " 

1624 return f"Ser. {self.my_container.vseries}, " 

1625 

1626 def volume_string(self): 

1627 return self.volume_series() + self.my_container.volume 

1628 

1629 def get_page_text(self, use_pp=False): 

1630 if (self.talk_number or self.article_number) and self.get_page_count(): 

1631 return self.get_page_count() + " p." 

1632 

1633 if not (self.lpage or self.fpage): 

1634 return "" 

1635 

1636 if self.lpage == self.fpage: 

1637 return f"p. {self.lpage}" 

1638 

1639 text = "" 

1640 if not self.page_range: 

1641 if self.fpage and self.lpage and use_pp: 

1642 text = "pp. " 

1643 else: 

1644 text = "p. " 

1645 text += self.fpage 

1646 if self.fpage and self.lpage: 

1647 text += "-" 

1648 if self.lpage: 

1649 text += self.lpage 

1650 elif self.page_range[0] != "p": 1650 ↛ 1653line 1650 didn't jump to line 1653, because the condition on line 1650 was never false

1651 text = "p. " + self.page_range 

1652 

1653 return text 

1654 

1655 def get_summary_page_text(self): 

1656 text = "" 

1657 if self.talk_number: 

1658 text += str(_("Exposé")) + " no. " + str(self.talk_number) + ", " 

1659 if self.article_number: 

1660 text += "article no. " + str(self.article_number) + ", " 

1661 

1662 text += self.get_page_text() 

1663 

1664 return text 

1665 

1666 def get_breadcrumb_page_text(self): 

1667 text = "" 

1668 if self.my_container.with_online_first: 

1669 if self.doi is not None: 

1670 text = self.doi 

1671 else: 

1672 text = str(_("Première publication")) 

1673 elif self.talk_number: 

1674 text += str(_("Exposé")) + " no. " + str(self.talk_number) 

1675 elif self.article_number: 

1676 text += "article no. " + str(self.article_number) 

1677 else: 

1678 text += self.get_page_text() 

1679 

1680 return text 

1681 

1682 def get_year(self): 

1683 return self.my_container.year 

1684 

1685 def previous(self): 

1686 try: 

1687 return self.my_container.article_set.get(seq=(self.seq - 1)) 

1688 except (Article.DoesNotExist, MultipleObjectsReturned): 

1689 return None 

1690 

1691 def next(self): 

1692 try: 

1693 return self.my_container.article_set.get(seq=(self.seq + 1)) 

1694 except (Article.DoesNotExist, MultipleObjectsReturned): 

1695 return None 

1696 

1697 def get_citation_base(self, year=None, page_text=None): 

1698 citation = "" 

1699 if year is None: 1699 ↛ 1702line 1699 didn't jump to line 1702, because the condition on line 1699 was never false

1700 year = self.my_container.year 

1701 

1702 to_appear = self.my_container.to_appear() 

1703 is_cr = self.my_container.is_cr() 

1704 

1705 if to_appear and year != "0": 

1706 citation += f", Online first ({year})" 

1707 elif to_appear: 

1708 citation += ", Online first" 

1709 else: 

1710 if self.my_container.vseries: 

1711 citation += f", {str(_('Série'))} {self.my_container.vseries}" 

1712 

1713 if self.my_container.volume: 

1714 citation += f", {str(volume_display())} {self.my_container.volume} ({year})" 

1715 

1716 if self.my_container.number and not (is_cr and self.my_container.number[0] == "G"): 1716 ↛ 1723line 1716 didn't jump to line 1723, because the condition on line 1716 was never false

1717 citation += f" no. {self.my_container.number}" 

1718 elif self.my_container.number: 1718 ↛ 1721line 1718 didn't jump to line 1721, because the condition on line 1718 was never false

1719 citation += f", no. {self.my_container.number} ({year})" 

1720 else: 

1721 citation += f" ({year})" 

1722 

1723 if self.talk_number: 1723 ↛ 1724line 1723 didn't jump to line 1724, because the condition on line 1723 was never true

1724 citation += f", {str(_('Exposé'))} no. {str(self.talk_number)}" 

1725 if self.article_number: 1725 ↛ 1726line 1725 didn't jump to line 1726, because the condition on line 1725 was never true

1726 citation += f", article no. {str(self.article_number)}" 

1727 

1728 if page_text is None: 1728 ↛ 1730line 1728 didn't jump to line 1730, because the condition on line 1728 was never false

1729 page_text = self.get_page_text(True) 

1730 if len(page_text) > 0: 

1731 citation += f", {page_text}" 

1732 

1733 if citation[-1] != ".": 

1734 citation += "." 

1735 

1736 if not to_appear: 

1737 if self.page_type == "volant": 

1738 citation += f" ({str(_('Pages volantes'))})" 

1739 elif self.page_type == "supplement": 

1740 citation += f" ({str(_('Pages supplémentaires'))})" 

1741 elif self.page_type == "preliminaire": 

1742 citation += f" ({str(_('Pages préliminaires'))})" 

1743 elif self.page_type == "special": 

1744 citation += f" ({str(_('Pages spéciales'))})" 

1745 

1746 return citation 

1747 

1748 def get_citation(self, request=None, with_formatting=False): 

1749 citation = "" 

1750 

1751 author_names = get_names(self, "author") 

1752 if author_names: 

1753 authors = "; ".join(author_names) 

1754 else: 

1755 author_names = get_names(self, "editor") 

1756 if author_names: 

1757 authors = "; ".join(author_names) + " (" + str(_("éd.")) + ")" 

1758 else: 

1759 authors = "" 

1760 

1761 if authors != "Collectif": 1761 ↛ 1764line 1761 didn't jump to line 1764, because the condition on line 1761 was never false

1762 citation += authors 

1763 

1764 if citation: 

1765 if not citation.endswith("."): 1765 ↛ 1767line 1765 didn't jump to line 1767, because the condition on line 1765 was never false

1766 citation += "." 

1767 citation += " " 

1768 

1769 if with_formatting: 

1770 citation += f"<strong>{self.title_tex}</strong>" 

1771 else: 

1772 citation += self.title_tex 

1773 if self.my_container.ctype != "issue": 

1774 citation += ", " 

1775 citation += str(_("dans")) + " <em>" + self.my_container.title_tex + "</em>, " 

1776 else: 

1777 citation += ". " 

1778 citation += self.my_container.my_collection.title_tex 

1779 

1780 citation += self.get_citation_base() 

1781 

1782 to_appear = self.my_container.to_appear() 

1783 is_cr = self.my_container.is_cr() 

1784 

1785 if not to_appear or is_cr: 1785 ↛ 1793line 1785 didn't jump to line 1793, because the condition on line 1785 was never false

1786 if self.doi is not None: 1786 ↛ 1789line 1786 didn't jump to line 1789, because the condition on line 1786 was never false

1787 citation += " doi : " + self.doi + "." 

1788 

1789 if not to_appear and request is not None: 

1790 url = f"{request.scheme}://{request.get_host()}{self.get_absolute_url()}" 

1791 citation += " " + url 

1792 

1793 return citation 

1794 

1795 def has_detailed_info(self): 

1796 # Ignore citations here. 

1797 

1798 result = False 

1799 

1800 if ( 

1801 self.date_received 

1802 or self.date_revised 

1803 or self.date_accepted 

1804 or self.date_published 

1805 or self.date_online_first 

1806 ): 

1807 result = True 

1808 elif self.extid_set.exists(): 

1809 result = True 

1810 elif self.kwd_set.exists(): 

1811 result = True 

1812 elif self.doi is not None: 1812 ↛ 1813line 1812 didn't jump to line 1813, because the condition on line 1812 was never true

1813 result = True 

1814 else: 

1815 for resourceid in self.resourceid_set.all(): 

1816 if resourceid.id_type != "numdam-prod-id": 1816 ↛ 1815line 1816 didn't jump to line 1815, because the condition on line 1816 was never false

1817 result = True 

1818 

1819 return result 

1820 

1821 def get_ojs_id(self): 

1822 ojs_id = "" 

1823 qs = self.resourceid_set.filter(id_type="ojs-id") 

1824 if qs.count() > 0: 

1825 resourceid = qs.first() 

1826 ojs_id = resourceid.id_value 

1827 pos = ojs_id.find("$$") 

1828 if pos > 0: 1828 ↛ 1829line 1828 didn't jump to line 1829, because the condition on line 1828 was never true

1829 ojs_id = ojs_id[0:pos] 

1830 return ojs_id 

1831 

1832 def update_bibtex_with_book_contributors( 

1833 self, bibtex, collection, container, indent, author_names, editor_names 

1834 ): 

1835 if container is not None: 1835 ↛ 1847line 1835 didn't jump to line 1847, because the condition on line 1835 was never false

1836 append_in_latex(bibtex, indent + "booktitle = {" + container.title_tex + "},") 

1837 

1838 # @incollection (edited-books): add the book editors 

1839 book_author_names = get_bibtex_names(container, "author") 

1840 book_editor_names = get_bibtex_names(container, "editor") 

1841 

1842 if not author_names and book_author_names: 1842 ↛ 1843line 1842 didn't jump to line 1843, because the condition on line 1842 was never true

1843 append_in_latex(bibtex, indent + book_author_names) 

1844 if not editor_names and book_editor_names: 1844 ↛ 1847line 1844 didn't jump to line 1847, because the condition on line 1844 was never false

1845 append_in_latex(bibtex, indent + book_editor_names) 

1846 

1847 if collection is not None: 1847 ↛ exitline 1847 didn't return from function 'update_bibtex_with_book_contributors', because the condition on line 1847 was never false

1848 append_in_latex(bibtex, indent + "series = {" + collection.title_tex + "},") 

1849 

1850 def get_bibtex(self, request=None, is_title=True): 

1851 """ 

1852 

1853 :param self: 

1854 :return: string encoded in latex (with latexcodec) 

1855 """ 

1856 bibtex = [] 

1857 indent = " " 

1858 

1859 container = self.my_container 

1860 collection = self.get_collection() 

1861 

1862 is_article = True 

1863 is_incollection = False 

1864 is_inbook = False 

1865 is_phdthesis = False 

1866 

1867 if container is not None and container.ctype != "issue": 

1868 is_article = False 

1869 

1870 if collection is not None and collection.coltype == "these": 1870 ↛ 1871line 1870 didn't jump to line 1871, because the condition on line 1870 was never true

1871 is_phdthesis = True 

1872 elif container.ctype == "book-monograph": 1872 ↛ 1873line 1872 didn't jump to line 1873, because the condition on line 1872 was never true

1873 is_inbook = True 

1874 elif container.ctype in ["book-edited-book", "lecture-notes"]: 1874 ↛ 1879line 1874 didn't jump to line 1879, because the condition on line 1874 was never false

1875 is_incollection = True 

1876 elif collection.coltype == "proceeding": 1876 ↛ 1877line 1876 didn't jump to line 1877, because the condition on line 1876 was never true

1877 is_incollection = True 

1878 

1879 to_appear = container.to_appear() 

1880 is_cr = container.is_cr() 

1881 

1882 # No bibtex at the article level for a these 

1883 if is_phdthesis: 1883 ↛ 1884line 1883 didn't jump to line 1884, because the condition on line 1883 was never true

1884 return "" 

1885 

1886 author_names = get_bibtex_names(self, "author") 

1887 editor_names = get_bibtex_names(self, "editor") 

1888 

1889 type_ = "article" 

1890 if to_appear and not is_cr: 1890 ↛ 1891line 1890 didn't jump to line 1891, because the condition on line 1890 was never true

1891 type_ = "unpublished" 

1892 elif is_inbook: 1892 ↛ 1893line 1892 didn't jump to line 1893, because the condition on line 1892 was never true

1893 type_ = "inbook" 

1894 elif is_incollection: 

1895 type_ = "incollection" 

1896 elif len(author_names) == 0 and len(editor_names) == 0: 

1897 type_ = "misc" 

1898 

1899 # Numdam meeting: Use the resource pid as the bibtex id => No latex encoding to keep the '_' 

1900 bibtex.append("@" + type_ + "{" + self.pid + ",") 

1901 

1902 if author_names: 

1903 append_in_latex(bibtex, indent + author_names) 

1904 if editor_names: 

1905 append_in_latex(bibtex, indent + editor_names) 

1906 

1907 title = xml_utils.normalise_span(self.title_tex) 

1908 append_in_latex(bibtex, indent + "title = {" + title + "},", is_title=is_title) 

1909 

1910 if is_article and not is_incollection: 

1911 title = xml_utils.normalise_span(collection.title_tex) 

1912 append_in_latex(bibtex, indent + "journal = {" + title + "},") 

1913 elif is_article: 1913 ↛ 1914line 1913 didn't jump to line 1914, because the condition on line 1913 was never true

1914 title = xml_utils.normalise_span(container.title_tex) 

1915 append_in_latex(bibtex, indent + "booktitle = {" + title + "},") 

1916 title = xml_utils.normalise_span(collection.title_tex) 

1917 append_in_latex(bibtex, indent + "series = {" + title + "},") 

1918 else: 

1919 self.update_bibtex_with_book_contributors( 

1920 bibtex, collection, container, indent, author_names, editor_names 

1921 ) 

1922 

1923 if not to_appear: 

1924 if self.talk_number: 

1925 append_in_latex(bibtex, indent + "note = {talk:" + self.talk_number + "},") 

1926 if self.article_number: 1926 ↛ 1927line 1926 didn't jump to line 1927, because the condition on line 1926 was never true

1927 append_in_latex(bibtex, indent + "eid = {" + self.article_number + "},") 

1928 if self.pages(): 

1929 append_in_latex(bibtex, indent + "pages = {" + self.pages(for_bibtex=True) + "},") 

1930 

1931 hostname = request.get_host() if request is not None else "" 

1932 scheme = request.scheme if request is not None else "https" 

1933 self.update_bibtex_with_commons(bibtex, container, hostname, scheme, indent) 

1934 

1935 append_in_latex(bibtex, "}") 

1936 

1937 return "\n".join(bibtex) 

1938 

1939 def get_ris(self, request=None): 

1940 """ 

1941 

1942 :param self: 

1943 :return: string 

1944 """ 

1945 items = [] 

1946 sep = " - " 

1947 

1948 container = self.my_container 

1949 collection = self.get_collection() 

1950 

1951 is_article = True 

1952 is_incollection = False 

1953 is_inbook = False 

1954 is_phdthesis = False 

1955 

1956 if container is not None and container.ctype != "issue": 

1957 is_article = False 

1958 

1959 if collection is not None and collection.coltype == "these": 1959 ↛ 1960line 1959 didn't jump to line 1960, because the condition on line 1959 was never true

1960 is_phdthesis = True 

1961 elif container.ctype == "book-monograph": 1961 ↛ 1962line 1961 didn't jump to line 1962, because the condition on line 1961 was never true

1962 is_inbook = True 

1963 elif container.ctype == "book-edited-book": 1963 ↛ 1965line 1963 didn't jump to line 1965, because the condition on line 1963 was never false

1964 is_incollection = True 

1965 elif container.ctype == "lecture-notes": 

1966 is_incollection = True 

1967 

1968 to_appear = container.to_appear() 

1969 is_cr = container.is_cr() 

1970 

1971 # no citation at the article level for a these 

1972 if is_phdthesis: 1972 ↛ 1973line 1972 didn't jump to line 1973, because the condition on line 1972 was never true

1973 return "" 

1974 

1975 type_ = "JOUR" # "article" 

1976 if to_appear and not is_cr: 1976 ↛ 1977line 1976 didn't jump to line 1977, because the condition on line 1976 was never true

1977 type_ = "UNPB" # "unpublished" 

1978 elif is_inbook: 1978 ↛ 1979line 1978 didn't jump to line 1979, because the condition on line 1978 was never true

1979 type_ = "CHAP" 

1980 elif is_incollection: 

1981 type_ = "CHAP" 

1982 items.append("TY" + sep + type_) 

1983 

1984 author_names = get_names(self, CONTRIB_TYPE_AUTHOR) 

1985 for author in author_names: 

1986 items.append("AU" + sep + author) 

1987 

1988 editor_names = get_names(self, CONTRIB_TYPE_EDITOR) 

1989 for editor in editor_names: 

1990 items.append("ED" + sep + editor) 

1991 

1992 title = xml_utils.remove_html(self.title_tex) 

1993 items.append("TI" + sep + title) 

1994 

1995 collection_title = xml_utils.remove_html(collection.title_tex) 

1996 if collection is not None and is_article: 

1997 items.append("JO" + sep + collection_title) 

1998 else: 

1999 if container is not None: 1999 ↛ 2008line 1999 didn't jump to line 2008, because the condition on line 1999 was never false

2000 items.append("BT" + sep + container.title_tex) 

2001 author_names = get_names(container, CONTRIB_TYPE_AUTHOR) 

2002 for author in author_names: 2002 ↛ 2003line 2002 didn't jump to line 2003, because the loop on line 2002 never started

2003 items.append("AU" + sep + author) 

2004 

2005 editor_names = get_names(container, CONTRIB_TYPE_EDITOR) 

2006 for editor in editor_names: 

2007 items.append("ED" + sep + editor) 

2008 if collection is not None: 2008 ↛ 2011line 2008 didn't jump to line 2011, because the condition on line 2008 was never false

2009 items.append("T3" + sep + collection_title) 

2010 

2011 if not to_appear: 

2012 if self.talk_number: 2012 ↛ 2013line 2012 didn't jump to line 2013, because the condition on line 2012 was never true

2013 items.append("N1" + sep + "talk:" + self.talk_number) 

2014 # if self.article_number: 

2015 # items.append("M1" + sep + "eid = " + self.article_number) 

2016 

2017 hostname = request.get_host() if request is not None else "" 

2018 scheme = request.scheme if request is not None else "https" 

2019 self.update_ris_with_commons(items, container, hostname, scheme, sep) 

2020 return "\r\n".join(items) 

2021 

2022 def get_endnote(self, request=None): 

2023 """ 

2024 

2025 :param self: 

2026 :return: string 

2027 """ 

2028 items = [] 

2029 sep = " " 

2030 

2031 container = self.my_container 

2032 collection = self.get_collection() 

2033 

2034 is_article = True 

2035 is_incollection = False 

2036 is_inbook = False 

2037 is_phdthesis = False 

2038 

2039 if container is not None and container.ctype != "issue": 

2040 is_article = False 

2041 

2042 if collection is not None and collection.coltype == "these": 2042 ↛ 2043line 2042 didn't jump to line 2043, because the condition on line 2042 was never true

2043 is_phdthesis = True 

2044 elif container.ctype == "book-monograph": 2044 ↛ 2045line 2044 didn't jump to line 2045, because the condition on line 2044 was never true

2045 is_inbook = True 

2046 elif container.ctype == "book-edited-book": 2046 ↛ 2048line 2046 didn't jump to line 2048, because the condition on line 2046 was never false

2047 is_incollection = True 

2048 elif container.ctype == "lecture-notes": 

2049 is_incollection = True 

2050 

2051 to_appear = container.to_appear() 

2052 is_cr = container.is_cr() 

2053 

2054 # no citation at the article level for a these 

2055 if is_phdthesis: 2055 ↛ 2056line 2055 didn't jump to line 2056, because the condition on line 2055 was never true

2056 return "" 

2057 

2058 type_ = "Journal Article" # "article" 

2059 if to_appear and not is_cr: 2059 ↛ 2060line 2059 didn't jump to line 2060, because the condition on line 2059 was never true

2060 type_ = "Unpublished Work" # "unpublished" 

2061 elif is_inbook: 2061 ↛ 2062line 2061 didn't jump to line 2062, because the condition on line 2061 was never true

2062 type_ = "Book Section" 

2063 elif is_incollection: 

2064 type_ = "Book Section" 

2065 items.append("%0" + sep + type_) 

2066 

2067 author_names = get_names(self, CONTRIB_TYPE_AUTHOR) 

2068 for author in author_names: 

2069 items.append("%A" + sep + author) 

2070 

2071 editor_names = get_names(self, CONTRIB_TYPE_EDITOR) 

2072 for editor in editor_names: 

2073 items.append("%E" + sep + editor) 

2074 

2075 title = xml_utils.remove_html(self.title_tex) 

2076 items.append("%T" + sep + title) 

2077 

2078 collection_title = xml_utils.remove_html(collection.title_tex) 

2079 if collection is not None and is_article: 

2080 items.append("%J" + sep + collection_title) 

2081 else: 

2082 if container is not None: 2082 ↛ 2091line 2082 didn't jump to line 2091, because the condition on line 2082 was never false

2083 items.append("%B" + sep + container.title_tex) 

2084 author_names = get_names(container, CONTRIB_TYPE_AUTHOR) 

2085 for author in author_names: 2085 ↛ 2086line 2085 didn't jump to line 2086, because the loop on line 2085 never started

2086 items.append("%A" + sep + author) 

2087 

2088 editor_names = get_names(container, CONTRIB_TYPE_EDITOR) 

2089 for editor in editor_names: 

2090 items.append("%E" + sep + editor) 

2091 if collection is not None: 2091 ↛ 2094line 2091 didn't jump to line 2094, because the condition on line 2091 was never false

2092 items.append("%S" + sep + collection_title) 

2093 

2094 if not to_appear: 

2095 if self.talk_number: 2095 ↛ 2096line 2095 didn't jump to line 2096, because the condition on line 2095 was never true

2096 items.append("%Z" + sep + "talk:" + self.talk_number) 

2097 # if self.article_number: 

2098 # items.append("%1" + sep + "eid = " + self.article_number) 

2099 

2100 hostname = request.get_host() if request is not None else "" 

2101 scheme = request.scheme if request is not None else "https" 

2102 self.update_endnote_with_commons(items, container, hostname, scheme, sep) 

2103 return "\r\n".join(items) 

2104 

2105 def get_conference(self): 

2106 text = "" 

2107 

2108 subjs = [] 

2109 for subj in self.subj_set.all(): 

2110 if subj.type == "conference": 

2111 subjs.append(subj.value) 

2112 

2113 text = ", ".join(subjs) 

2114 

2115 return text 

2116 

2117 def get_topics(self): 

2118 text = "" 

2119 

2120 subjs = [] 

2121 for subj in self.subj_set.all(): 

2122 if subj.type == "topic": 

2123 subjs.append(subj.value) 

2124 

2125 text = ", ".join(subjs) 

2126 

2127 return text 

2128 

2129 def get_subj_text(self): 

2130 text = "" 

2131 lang = get_language() 

2132 

2133 subj_types = ["subject", "type", "pci", "heading", "conference"] 

2134 if self.my_container.my_collection.pid == "CRMATH": 

2135 subj_types = ["subject", "heading"] 

2136 

2137 subj_groups = self.get_subjs_by_type_and_lang() 

2138 for type_ in subj_types: 

2139 if type_ in subj_groups: 

2140 sg_type_langs = subj_groups[type_] 

2141 if type_ == "pci": 

2142 subtext = ", ".join( 

2143 list( 

2144 [ 

2145 resolver.get_pci(subj.value) 

2146 for lang_ in sg_type_langs 

2147 for subj in sg_type_langs[lang_] 

2148 ] 

2149 ) 

2150 ) 

2151 else: 

2152 if lang in sg_type_langs: 

2153 subtext = ", ".join( 

2154 list([subj.value for subj in subj_groups[type_][lang]]) 

2155 ) 

2156 else: 

2157 subtext = ", ".join( 

2158 list( 

2159 [ 

2160 subj.value 

2161 for lang_ in sg_type_langs 

2162 if lang_ != lang 

2163 for subj in sg_type_langs[lang_] 

2164 ] 

2165 ) 

2166 ) 

2167 

2168 if text: 

2169 text += " - " 

2170 text += subtext 

2171 

2172 return text 

2173 

2174 def get_pci_section(self): 

2175 pci = "" 

2176 for subj in self.subj_set.all(): 

2177 if subj.type == "pci": 

2178 pci = subj.value 

2179 

2180 return pci 

2181 

2182 def get_pci_value(self): 

2183 return resolver.get_pci(self.get_pci_section()) 

2184 

2185 def is_uga_pci(self): 

2186 return self.get_pci_section() in resolver.PCJ_UGA_SECTION 

2187 

2188 def allow_crossref(self): 

2189 # we need at least a doi or an issn to allow crossref record 

2190 doi = self.my_container.my_collection.doi 

2191 issn = self.my_container.my_collection.issn 

2192 e_issn = self.my_container.my_collection.e_issn 

2193 result = bool(doi) or bool(issn) or bool(e_issn) 

2194 

2195 # and we need a published date (online_first or final) 

2196 result = result and (self.date_published is not None or self.date_online_first is not None) 

2197 

2198 return result 

2199 

2200 def get_illustrations(self): 

2201 return GraphicalAbstract.objects.filter(resource=self).first() 

2202 

2203 def has_graphical_abstract(self): 

2204 collections = ["CRCHIM"] 

2205 return True if self.my_container.my_collection.pid in collections else False 

2206 

2207 

2208class ResourceCategory(models.Model): 

2209 """non utilisé""" 

2210 

2211 category = models.CharField(max_length=32, db_index=True) 

2212 

2213 

2214class Provider(models.Model): 

2215 """ 

2216 en faire une resource permettrait d'attacher des metadonnées 

2217 supplémentaires -- à voir 

2218 """ 

2219 

2220 name = models.CharField(max_length=32, unique=True) 

2221 pid_type = models.CharField(max_length=32, unique=True) 

2222 sid_type = models.CharField(max_length=64, unique=True, null=True, blank=True) 

2223 

2224 def __str__(self): 

2225 return self.name 

2226 

2227 

2228class SiteMembership(models.Model): 

2229 """ 

2230 Warning: As of July 3018, only 1 site id is stored in a SolR document 

2231 Although the SolR schema is already OK to store multiple sites ("sites" is an array) 

2232 no Solr commands have been written to add/remove sites 

2233 We only have add commands. 

2234 Search only works if the Solr instance is meant for individual or ALL sites 

2235 """ 

2236 

2237 resource = models.ForeignKey(Resource, on_delete=models.CASCADE) 

2238 deployed = models.DateTimeField(null=True) 

2239 online = models.DateTimeField(db_index=True, null=True) # pour cedram 

2240 site = models.ForeignKey(PtfSite, on_delete=models.CASCADE) 

2241 

2242 class Meta: 

2243 unique_together = ( 

2244 "resource", 

2245 "site", 

2246 ) 

2247 

2248 

2249class CollectionMembership(models.Model): 

2250 collection = models.ForeignKey(Collection, on_delete=models.CASCADE) 

2251 container = models.ForeignKey(Container, on_delete=models.CASCADE) 

2252 

2253 vseries = models.CharField(max_length=32, db_index=True) 

2254 volume = models.CharField(max_length=64, db_index=True) 

2255 number = models.CharField(max_length=32, db_index=True) # issue number 

2256 # la même chose pour le tri 

2257 vseries_int = models.IntegerField(default=0, db_index=True) 

2258 volume_int = models.IntegerField(default=0, db_index=True) 

2259 number_int = models.IntegerField(default=0, db_index=True) 

2260 

2261 seq = models.IntegerField(db_index=True) 

2262 

2263 class Meta: 

2264 unique_together = ( 

2265 "collection", 

2266 "container", 

2267 ) 

2268 

2269 def __str__(self): 

2270 return f"{self.collection} - {self.container}" 

2271 

2272 

2273class RelationName(models.Model): 

2274 """ 

2275 Triple store ;-) 

2276 """ 

2277 

2278 left = models.CharField(max_length=32, unique=True) 

2279 right = models.CharField(max_length=32, unique=True) 

2280 gauche = models.CharField(max_length=64, unique=True) 

2281 droite = models.CharField(max_length=64, unique=True) 

2282 

2283 

2284class Relationship(models.Model): 

2285 resource = models.ForeignKey( 

2286 Resource, null=True, related_name="subject_of", on_delete=models.CASCADE 

2287 ) 

2288 related = models.ForeignKey( 

2289 Resource, null=True, related_name="object_of", on_delete=models.CASCADE 

2290 ) 

2291 subject_pid = models.CharField(max_length=64, db_index=True) 

2292 object_pid = models.CharField(max_length=64, db_index=True) 

2293 rel_info = models.ForeignKey(RelationName, null=True, on_delete=models.CASCADE) 

2294 

2295 

2296class ExtRelationship(models.Model): 

2297 """ 

2298 Triple store (resources externes) 

2299 """ 

2300 

2301 resource = models.ForeignKey(Resource, related_name="subject", on_delete=models.CASCADE) 

2302 ext_object = models.CharField(max_length=256) # uri 

2303 predicate = models.CharField(max_length=256, db_index=True) # predicate uri 

2304 

2305 

2306class XmlBase(models.Model): 

2307 base = models.CharField(max_length=200, unique=True) 

2308 

2309 def __str__(self): 

2310 return self.base 

2311 

2312 

2313class DataStream(models.Model): 

2314 resource = models.ForeignKey(Resource, on_delete=models.CASCADE) 

2315 rel = models.CharField(max_length=32) 

2316 mimetype = models.CharField(max_length=32) 

2317 base = models.ForeignKey(XmlBase, blank=True, null=True, on_delete=models.CASCADE) 

2318 location = models.URLField() 

2319 text = models.CharField(max_length=32, default="Link") 

2320 seq = models.IntegerField(db_index=True) 

2321 

2322 class Meta: 

2323 ordering = ["seq"] 

2324 

2325 def __str__(self): 

2326 return " - ".join([self.resource.pid, self.mimetype]) 

2327 

2328 

2329class ExtLink(models.Model): 

2330 resource = models.ForeignKey(Resource, on_delete=models.CASCADE) 

2331 rel = models.CharField(max_length=50) 

2332 mimetype = models.CharField(max_length=32) 

2333 base = models.ForeignKey(XmlBase, blank=True, null=True, on_delete=models.CASCADE) 

2334 location = models.CharField(max_length=200) 

2335 metadata = models.TextField() 

2336 seq = models.IntegerField(db_index=True) 

2337 

2338 class Meta: 

2339 ordering = ["seq"] 

2340 

2341 def __str__(self): 

2342 return f"{self.rel}: {self.get_href()}" 

2343 

2344 def save(self, *args, **kwargs): 

2345 if "website" in self.rel: 

2346 self.metadata = "website" 

2347 if not self.seq: 2347 ↛ 2348line 2347 didn't jump to line 2348, because the condition on line 2347 was never true

2348 self.seq = self.generate_seq() 

2349 super().save(*args, **kwargs) 

2350 

2351 def generate_seq(self): 

2352 max_seq = ExtLink.objects.filter(resource=self.resource).aggregate(Max("seq"))["seq__max"] 

2353 if max_seq: 

2354 return max_seq + 1 

2355 else: 

2356 return 1 

2357 

2358 def get_href(self): 

2359 if self.rel in ("small_icon", "icon"): 

2360 # construction du chemin vers l'icone 

2361 # filename = os.path.basename(self.location) 

2362 resource_pid = self.resource.pid 

2363 href = resolver.get_icon_url(resource_pid, self.location) 

2364 return href 

2365 return self.location 

2366 

2367 

2368class RelatedObject(models.Model): 

2369 """ 

2370 Related Objects are used to store pdf/djvu related to an issue (tdm, preliminary pages) 

2371 """ 

2372 

2373 resource = models.ForeignKey(Resource, on_delete=models.CASCADE) 

2374 rel = models.CharField(max_length=32) 

2375 mimetype = models.CharField(max_length=32) 

2376 base = models.ForeignKey(XmlBase, blank=True, null=True, on_delete=models.CASCADE) 

2377 location = models.CharField(max_length=200) 

2378 metadata = models.TextField() 

2379 seq = models.IntegerField(db_index=True) 

2380 

2381 class Meta: 

2382 ordering = ["seq"] 

2383 

2384 def __str__(self): 

2385 return " - ".join([self.resource.pid, self.mimetype]) 

2386 

2387 def get_href(self): 

2388 the_dynamic_object = self.resource.cast() 

2389 href = the_dynamic_object.get_binary_file_href_full_path( 

2390 self.rel, self.mimetype, self.location 

2391 ) 

2392 return href 

2393 

2394 

2395class SupplementaryMaterial(RelatedObject): 

2396 caption = models.TextField() 

2397 

2398 def __str__(self): 

2399 return self.location.split("/")[-1] 

2400 

2401 def embeded_link(self): 

2402 if "youtube" in self.location: 

2403 video_id = urlparse(self.location).query.split("=")[1] 

2404 return f"https://www.youtube-nocookie.com/embed/{video_id}" 

2405 return self.get_href() 

2406 

2407 

2408class MetaDataPart(models.Model): 

2409 """ 

2410 stockage des metadonnées 

2411 qui ne sont pas aiilleurs (!) 

2412 non utilisé - à voir 

2413 3 classes qui font sans doute doublon: 

2414 MetadataPart, ResourceAttribute, CustomMeta -- à voir 

2415 """ 

2416 

2417 resource = models.ForeignKey(Resource, on_delete=models.CASCADE) 

2418 name = models.CharField(max_length=32, db_index=True) 

2419 seq = models.IntegerField(db_index=True) 

2420 data = models.TextField() 

2421 

2422 class Meta: 

2423 ordering = ["seq"] 

2424 

2425 

2426class ResourceAttribute(models.Model): 

2427 """ 

2428 not used 

2429 """ 

2430 

2431 resource = models.ForeignKey(Resource, on_delete=models.CASCADE) 

2432 name = models.CharField(max_length=32, db_index=True) 

2433 value = models.TextField() 

2434 

2435 

2436class CustomMeta(models.Model): 

2437 resource = models.ForeignKey(Resource, on_delete=models.CASCADE) 

2438 name = models.CharField(max_length=32, db_index=True) 

2439 value = models.CharField(max_length=128, db_index=True) 

2440 

2441 

2442class ResourceId(models.Model): 

2443 resource = models.ForeignKey(Resource, on_delete=models.CASCADE) 

2444 id_type = models.CharField(max_length=32, db_index=True) 

2445 id_value = models.CharField(max_length=64, db_index=True) 

2446 

2447 class Meta: 

2448 unique_together = ("id_type", "id_value") 

2449 

2450 def __str__(self): 

2451 return f"{self.id_type} {self.id_value}" 

2452 

2453 def get_href(self): 

2454 href = "" 

2455 if self.id_type == "doi": 2455 ↛ 2457line 2455 didn't jump to line 2457, because the condition on line 2455 was never false

2456 href = resolver.get_doi_url(self.id_value) 

2457 return href 

2458 

2459 

2460class ExtId(models.Model): 

2461 """ 

2462 zbl, mr, jfm, etc.. 

2463 mis à part car non uniques 

2464 """ 

2465 

2466 resource = models.ForeignKey(Resource, on_delete=models.CASCADE) 

2467 id_type = models.CharField(max_length=32, db_index=True) 

2468 id_value = models.CharField(max_length=64, db_index=True) 

2469 checked = models.BooleanField(default=True) 

2470 false_positive = models.BooleanField(default=False) 

2471 

2472 class Meta: 

2473 unique_together = ["resource", "id_type"] 

2474 

2475 def __str__(self): 

2476 return f"{self.resource} - {self.id_type}:{self.id_value}" 

2477 

2478 def get_href(self): 

2479 return resolver.resolve_id(self.id_type, self.id_value) 

2480 

2481 

2482class Abstract(models.Model): 

2483 resource = models.ForeignKey(Resource, on_delete=models.CASCADE) 

2484 lang = models.CharField(max_length=3, default="und") 

2485 tag = models.CharField(max_length=32, db_index=True) 

2486 # specific use, content_type, label ? 

2487 seq = models.IntegerField(db_index=True) 

2488 value_xml = models.TextField(default="") 

2489 value_tex = models.TextField(default="") 

2490 value_html = models.TextField(default="") 

2491 

2492 class Meta: 

2493 ordering = ["seq"] 

2494 

2495 def __str__(self): 

2496 return f"{self.resource} - {self.tag}" 

2497 

2498 

2499class Kwd(models.Model): 

2500 resource = models.ForeignKey(Resource, on_delete=models.CASCADE) 

2501 lang = models.CharField(max_length=3, default="und") 

2502 type = models.CharField(max_length=32, db_index=True) 

2503 value = models.TextField(default="") 

2504 seq = models.IntegerField(db_index=True) 

2505 

2506 class Meta: 

2507 ordering = ["seq"] 

2508 

2509 def __str__(self): 

2510 return f"{self.type} - {self.lang} - {self.value}" 

2511 

2512 

2513class Subj(models.Model): 

2514 resource = models.ForeignKey(Resource, on_delete=models.CASCADE) 

2515 lang = models.CharField(max_length=3, default="und") 

2516 type = models.CharField(max_length=32, db_index=True) 

2517 value = models.TextField(default="") 

2518 seq = models.IntegerField(db_index=True) 

2519 

2520 class Meta: 

2521 ordering = ["seq"] 

2522 

2523 def __str__(self): 

2524 return f"{self.type} - {self.lang} - {self.value}" 

2525 

2526 

2527class Award(models.Model): 

2528 resource = models.ForeignKey(Resource, on_delete=models.CASCADE) 

2529 abbrev = models.CharField(max_length=600, db_index=True) 

2530 award_id = models.CharField(max_length=600, db_index=True) 

2531 seq = models.IntegerField(db_index=True) 

2532 

2533 class Meta: 

2534 ordering = ["seq"] 

2535 

2536 

2537class BibItem(models.Model): 

2538 resource = models.ForeignKey(Resource, on_delete=models.CASCADE) 

2539 sequence = models.PositiveIntegerField(db_index=True) 

2540 label = models.CharField(max_length=128, default="") 

2541 citation_xml = models.TextField(default="") 

2542 citation_tex = models.TextField(default="") 

2543 citation_html = models.TextField(default="") 

2544 

2545 type = models.CharField(max_length=32, default="", db_index=True) 

2546 user_id = models.TextField(default="") 

2547 

2548 # article_title, chapter_title and source can contain formulas. Save the tex version. 

2549 # artile_title, chapter_title and source were created from title, booktitle and chapter. 

2550 # We need to do reverse engineering when exporting the bibtex (see 

2551 # get_bibtex) 

2552 article_title_tex = models.TextField(default="") 

2553 chapter_title_tex = models.TextField(default="") 

2554 source_tex = models.TextField(default="") 

2555 

2556 publisher_name = models.TextField(default="") 

2557 publisher_loc = models.TextField(default="") 

2558 institution = models.TextField(default="") 

2559 series = models.TextField(default="") 

2560 volume = models.TextField(default="") 

2561 issue = models.TextField(default="") 

2562 month = models.CharField(max_length=32, default="") 

2563 year = models.TextField(default="") 

2564 comment = models.TextField(default="") 

2565 annotation = models.CharField(max_length=255, default="") 

2566 fpage = models.TextField(default="") 

2567 lpage = models.TextField(default="") 

2568 page_range = models.TextField(default="") 

2569 size = models.TextField(default="") 

2570 

2571 class Meta: 

2572 ordering = ["sequence"] 

2573 

2574 def __str__(self): 

2575 return f"{self.resource} - {self.label}" 

2576 

2577 def get_ref_id(self): 

2578 ref_id = "" 

2579 # self.resource.body_html is for PCJ case when SHOW_BODY is at false but we have a body html to display 

2580 if (hasattr(settings, "SHOW_BODY") and settings.SHOW_BODY) or self.resource.body_html: 2580 ↛ 2581line 2580 didn't jump to line 2581, because the condition on line 2580 was never true

2581 ref_id = "r" + str(self.sequence) 

2582 else: 

2583 ref_id = self.user_id 

2584 return ref_id 

2585 

2586 def get_doi(self): 

2587 bibitemId_doi = BibItemId.objects.filter(bibitem=self, id_type="doi") 

2588 if bibitemId_doi: 2588 ↛ 2589line 2588 didn't jump to line 2589, because the condition on line 2588 was never true

2589 return bibitemId_doi.get().id_value 

2590 return None 

2591 

2592 def get_bibtex(self): 

2593 """ 

2594 

2595 :param self: 

2596 :return: an array of strings encoded in latex (with latexcodec) 

2597 """ 

2598 bibtex = [] 

2599 indent = " " 

2600 

2601 author_names = get_bibtex_names(self, "author") 

2602 editor_names = get_bibtex_names(self, "editor") 

2603 

2604 if self.user_id: 2604 ↛ 2607line 2604 didn't jump to line 2607, because the condition on line 2604 was never false

2605 id_ = self.user_id 

2606 else: 

2607 id_ = get_bibtex_id(self, self.year) 

2608 

2609 append_in_latex(bibtex, "@" + self.type + "{" + id_ + ",") 

2610 

2611 if author_names: 2611 ↛ 2613line 2611 didn't jump to line 2613, because the condition on line 2611 was never false

2612 append_in_latex(bibtex, indent + author_names) 

2613 if editor_names: 2613 ↛ 2623line 2613 didn't jump to line 2623, because the condition on line 2613 was never false

2614 append_in_latex(bibtex, indent + editor_names) 

2615 

2616 # From numdam+/ptf-xsl/cedram/structured-biblio.xsl 

2617 # < xsl:template match = "bib_entry[@doctype='article']/title| 

2618 # bib_entry[@doctype='misc']/title| 

2619 # bib_entry[@doctype='inproceedings']/title| 

2620 # bib_entry[@doctype='booklet']/title| 

2621 # bib_entry[@doctype='conference']/title" > 

2622 # => All article_title in JATS become title in bibtex 

2623 if self.article_title_tex: 2623 ↛ 2631line 2623 didn't jump to line 2631, because the condition on line 2623 was never false

2624 title = xml_utils.normalise_span(self.article_title_tex) 

2625 append_in_latex(bibtex, indent + "title = {" + title + "},", is_title=True) 

2626 

2627 # <chapter-title> in JATS becomes title or chapter according to the type 

2628 # From numdam+/ptf-xsl/cedram/structured-biblio.xsl 

2629 # <xsl:template match="bib_entry[@doctype='incollection']/title|bibentry/chapter"> 

2630 

2631 if self.chapter_title_tex: 2631 ↛ 2660line 2631 didn't jump to line 2660, because the condition on line 2631 was never false

2632 keyword = "" 

2633 if self.type == "incollection": 2633 ↛ 2634line 2633 didn't jump to line 2634, because the condition on line 2633 was never true

2634 keyword += "title" 

2635 else: 

2636 keyword += "chapter" 

2637 title = xml_utils.normalise_span(self.chapter_title_tex) 

2638 append_in_latex(bibtex, indent + keyword + " = {" + title + "},") 

2639 

2640 # <source> in JATS becomes title, journal, booktitle, or howpublished according to the type 

2641 # From numdam+/ptf-xsl/cedram/structured-biblio.xsl 

2642 # <xsl:template match="bib_entry[@doctype='article']/journal| 

2643 # bib_entry[@doctype='incollection']/booktitle| 

2644 # bib_entry[@doctype='inproceedings']/booktitle| 

2645 # bib_entry[@doctype='conference']/booktitle| 

2646 # bib_entry[@doctype='misc']/howpublished| 

2647 # bib_entry[@doctype='booklet']/howpublished| 

2648 # bib_entry[@doctype='book']/title| 

2649 # bib_entry[@doctype='unpublished']/title| 

2650 # bib_entry[@doctype='inbook']/title| 

2651 # bib_entry[@doctype='phdthesis']/title| 

2652 # bib_entry[@doctype='mastersthesis']/title| 

2653 # bib_entry[@doctype='manual']/title| 

2654 # bib_entry[@doctype='techreport']/title| 

2655 # bib_entry[@doctype='masterthesis']/title| 

2656 # bib_entry[@doctype='coursenotes']/title| 

2657 # bib_entry[@doctype='proceedings']/title"> 

2658 # < xsl:template match = "bib_entry[@doctype='misc']/booktitle" > : Unable to reverse correctly ! 2 choices 

2659 

2660 if self.source_tex: 2660 ↛ 2677line 2660 didn't jump to line 2677, because the condition on line 2660 was never false

2661 keyword = "" 

2662 if self.type == "article": 2662 ↛ 2664line 2662 didn't jump to line 2664, because the condition on line 2662 was never false

2663 keyword += "journal" 

2664 elif ( 

2665 self.type == "incollection" 

2666 or self.type == "inproceedings" 

2667 or self.type == "conference" 

2668 ): 

2669 keyword += "booktitle" 

2670 elif self.type == "misc" or self.type == "booklet": 

2671 keyword += "howpublished" 

2672 else: 

2673 keyword += "title" 

2674 title = xml_utils.normalise_span(self.source_tex) 

2675 append_in_latex(bibtex, indent + keyword + " = {" + title + "},") 

2676 

2677 if self.publisher_name: 2677 ↛ 2679line 2677 didn't jump to line 2679, because the condition on line 2677 was never false

2678 append_in_latex(bibtex, indent + "publisher = {" + self.publisher_name + "},") 

2679 if self.publisher_loc: 2679 ↛ 2681line 2679 didn't jump to line 2681, because the condition on line 2679 was never false

2680 append_in_latex(bibtex, indent + "address = {" + self.publisher_loc + "},") 

2681 if self.institution: 2681 ↛ 2683line 2681 didn't jump to line 2683, because the condition on line 2681 was never false

2682 append_in_latex(bibtex, indent + "institution = {" + self.institution + "},") 

2683 if self.series: 2683 ↛ 2685line 2683 didn't jump to line 2685, because the condition on line 2683 was never false

2684 append_in_latex(bibtex, indent + "series = {" + self.series + "},") 

2685 if self.volume: 2685 ↛ 2687line 2685 didn't jump to line 2687, because the condition on line 2685 was never false

2686 append_in_latex(bibtex, indent + "volume = {" + self.volume + "},") 

2687 if self.issue: 2687 ↛ 2689line 2687 didn't jump to line 2689, because the condition on line 2687 was never false

2688 append_in_latex(bibtex, indent + "number = {" + self.issue + "},") 

2689 if self.year: 2689 ↛ 2692line 2689 didn't jump to line 2692, because the condition on line 2689 was never false

2690 append_in_latex(bibtex, indent + "year = {" + self.year + "},") 

2691 

2692 if self.page_range: 2692 ↛ 2693line 2692 didn't jump to line 2693, because the condition on line 2692 was never true

2693 append_in_latex(bibtex, indent + "pages = {" + self.page_range + "},") 

2694 elif self.fpage or self.lpage: 2694 ↛ 2702line 2694 didn't jump to line 2702, because the condition on line 2694 was never false

2695 text = self.fpage 

2696 if self.fpage and self.lpage: 2696 ↛ 2698line 2696 didn't jump to line 2698, because the condition on line 2696 was never false

2697 text += "--" 

2698 if self.lpage: 2698 ↛ 2700line 2698 didn't jump to line 2700, because the condition on line 2698 was never false

2699 text += self.lpage 

2700 append_in_latex(bibtex, indent + "pages = {" + text + "},") 

2701 

2702 if self.size: 2702 ↛ 2705line 2702 didn't jump to line 2705, because the condition on line 2702 was never false

2703 append_in_latex(bibtex, indent + "pagetotal = {" + self.size + "},") 

2704 

2705 if self.comment: 2705 ↛ 2708line 2705 didn't jump to line 2708, because the condition on line 2705 was never false

2706 append_in_latex(bibtex, indent + "note = {" + self.comment + "},") 

2707 

2708 for extid in BibItemId.objects.filter(bibitem=self): 2708 ↛ 2709line 2708 didn't jump to line 2709, because the loop on line 2708 never started

2709 type_ = "" 

2710 if extid.id_type == "zbl-item-id": 

2711 type_ = "zbl" 

2712 elif extid.id_type == "mr-item-id": 

2713 type_ = "mrnumber" 

2714 elif extid.id_type == "doi": 

2715 type_ = "doi" 

2716 elif extid.id_type == "eid": 

2717 type_ = "eid" 

2718 

2719 if type_: 

2720 append_in_latex(bibtex, indent + type_ + " = {" + extid.id_value + "},") 

2721 

2722 append_in_latex(bibtex, "}") 

2723 

2724 return bibtex 

2725 

2726 

2727class BibItemId(models.Model): 

2728 bibitem = models.ForeignKey(BibItem, on_delete=models.CASCADE) 

2729 id_type = models.CharField(max_length=32, db_index=True) 

2730 id_value = models.CharField(max_length=256, db_index=True) 

2731 checked = models.BooleanField(default=True) 

2732 false_positive = models.BooleanField(default=False) 

2733 

2734 class Meta: 

2735 unique_together = ["bibitem", "id_type"] 

2736 

2737 def __str__(self): 

2738 return f"{self.bibitem} - {self.id_type}:{self.id_value}" 

2739 

2740 def get_href_display(self): 

2741 value = "Article" 

2742 if settings.SITE_ID == 3: 

2743 value = "Numdam" 

2744 else: 

2745 if self.id_type in ["numdam-id", "mathdoc-id"]: 

2746 value = "Numdam" 

2747 

2748 return value 

2749 

2750 def get_href(self): 

2751 force_numdam = False 

2752 if self.id_type in ["numdam-id", "mathdoc-id"] and settings.SITE_ID != 3: 2752 ↛ 2753line 2752 didn't jump to line 2753, because the condition on line 2752 was never true

2753 force_numdam = True 

2754 

2755 return resolver.resolve_id(self.id_type, self.id_value, force_numdam) 

2756 

2757 

2758class ContribGroup(models.Model): 

2759 resource = models.ForeignKey(Resource, blank=True, null=True, on_delete=models.CASCADE) 

2760 bibitem = models.ForeignKey(BibItem, blank=True, null=True, on_delete=models.CASCADE) 

2761 content_type = models.CharField(max_length=32, db_index=True) 

2762 # specific_use ?! 

2763 seq = models.IntegerField() 

2764 

2765 class Meta: 

2766 ordering = ["seq"] 

2767 

2768 

2769class Contrib(models.Model): 

2770 group = models.ForeignKey(ContribGroup, on_delete=models.CASCADE) 

2771 contrib_type = models.CharField(max_length=32, db_index=True) 

2772 

2773 last_name = models.CharField(max_length=128, db_index=True) 

2774 first_name = models.CharField(max_length=128, db_index=True) 

2775 prefix = models.CharField(max_length=32) 

2776 suffix = models.CharField(max_length=32) 

2777 string_name = models.CharField(max_length=256, db_index=True) 

2778 reference_name = models.CharField(max_length=256, db_index=True) 

2779 deceased = models.BooleanField(default=False) 

2780 orcid = models.CharField(max_length=64, db_index=True, blank=True, default="") 

2781 equal_contrib = models.BooleanField(default=True) 

2782 email = models.EmailField(max_length=254, db_index=True, blank=True, default="") 

2783 

2784 # Could be used to export the contrib 

2785 contrib_xml = models.TextField() 

2786 

2787 seq = models.IntegerField() 

2788 

2789 class Meta: 

2790 ordering = ["seq"] 

2791 

2792 @classmethod 

2793 def get_fields_list(cls): 

2794 return [ 

2795 item.attname 

2796 for item in cls._meta.get_fields() 

2797 if not item.auto_created 

2798 and not item.many_to_many 

2799 and not item.many_to_one 

2800 and not item.one_to_many 

2801 and not item.one_to_one 

2802 ] 

2803 

2804 def __str__(self): 

2805 return "{} {} / {} / {}".format( 

2806 self.last_name, self.first_name, self.reference_name, self.contrib_type or "None" 

2807 ) 

2808 

2809 def get_absolute_url(self): 

2810 return reverse("pretty_search", args=[f'"{self.reference_name}"-c']) 

2811 

2812 def display_name(self): 

2813 display_name = self.string_name 

2814 

2815 if self.is_etal(): 

2816 display_name = "et al." 

2817 elif getattr(settings, "DISPLAY_FIRST_NAME_FIRST", False) and ( 

2818 len(str(self.first_name)) > 0 or len(str(self.last_name)) > 0 

2819 ): 

2820 display_name = f"{self.first_name} {self.last_name}" 

2821 

2822 return display_name 

2823 

2824 def orcid_href(self): 

2825 return resolver.resolve_id("orcid", self.orcid) 

2826 

2827 def idref_href(self): 

2828 return resolver.resolve_id("idref", self.idref) 

2829 

2830 def get_addresses(self): 

2831 return self.contribaddress_set.all() # .order_by('address') 

2832 

2833 def is_etal(self): 

2834 return self.contrib_xml.startswith("<etal") 

2835 

2836 

2837class Author(models.Model): 

2838 """ 

2839 Count the number of documents (articles,books...) written by an author 

2840 """ 

2841 

2842 name = models.CharField(max_length=200, db_index=True, unique=True) 

2843 first_letter = models.CharField(max_length=1, db_index=True) 

2844 count = models.IntegerField() 

2845 

2846 

2847class FrontMatter(models.Model): 

2848 resource = models.OneToOneField(Resource, on_delete=models.CASCADE) 

2849 

2850 value_xml = models.TextField(default="") 

2851 value_html = models.TextField(default="") 

2852 foreword_html = models.TextField(default="") 

2853 

2854 

2855class LangTable(models.Model): 

2856 code = models.CharField(max_length=3, db_index=True) 

2857 name = models.CharField(max_length=64) 

2858 

2859 

2860class ResourceCount(models.Model): 

2861 resource = models.ForeignKey(Resource, on_delete=models.CASCADE) 

2862 name = models.CharField(max_length=32, db_index=True) 

2863 value = models.CharField(max_length=32, db_index=True) 

2864 seq = models.IntegerField() 

2865 

2866 class Meta: 

2867 ordering = ["seq"] 

2868 

2869 

2870class Stats(models.Model): 

2871 name = models.CharField(max_length=128, db_index=True, unique=True) 

2872 value = models.IntegerField() 

2873 

2874 

2875class History(models.Model): 

2876 """pas utilisé pour l'heure""" 

2877 

2878 resource = models.ForeignKey(Resource, on_delete=models.CASCADE) 

2879 date_type = models.CharField(max_length=32, db_index=True) 

2880 date_value = models.DateTimeField() 

2881 comment = models.TextField() 

2882 

2883 

2884class SerialHistory(models.Model): 

2885 """à revoir""" 

2886 

2887 serial = models.ForeignKey("Collection", on_delete=models.CASCADE) 

2888 date_deployed = models.DateField(auto_now_add=True) 

2889 first_year = models.CharField(max_length=32) 

2890 last_year = models.CharField(max_length=32) 

2891 

2892 

2893# def matches(id_type, id_value): 

2894# if id_type in ('mr-item-id', 'zbl-item-id', 'jfm-item-id'): 

2895# qs = ExtId.objects.filter( 

2896# id_type=id_type, 

2897# id_value=id_value, 

2898# ).select_related('resource', 'resource__resourceid') 

2899# counter = 0 

2900# match_list = [] 

2901# for extid in qs: 

2902# resource = extid.resource 

2903# for idext in resource.extid_set.all(): 

2904# match_list.append({'type': idext.id_type, 'value': idext.id_value}) 

2905# for rid in resource.resourceid_set.all(): 

2906# match_list.append({'type': rid.id_type, 'value': rid.id_value}) 

2907# counter += 1 

2908# return { 

2909# 'id': {'type': id_type, 'value': id_value}, 

2910# 'count': counter, 

2911# 'ids': match_list, 

2912# } 

2913# resource = Resource.objects.get(resourceid__id_type=id_type, 

2914# resourceid__id_value=id_value) 

2915# counter = 0 

2916# match_list = [] 

2917# for idext in resource.extid_set.all(): 

2918# match_list.append({'type': idext.id_type, 'value': idext.id_value}) 

2919# for rid in resource.resourceid_set.all(): 

2920# match_list.append({'type': rid.id_type, 'value': rid.id_value}) 

2921# counter += 1 

2922# return { 

2923# 'id': {'type': id_type, 'value': id_value}, 

2924# 'count': counter, 

2925# 'ids': match_list, 

2926# } 

2927 

2928 

2929def parse_page_count(page_count): 

2930 """ 

2931 page-count is not an integer but a string to be displayed. 

2932 page_count may sometimes mix roman and arabic values. Ex "iv-121" 

2933 """ 

2934 if "-" in page_count: 

2935 result = 0 

2936 values = page_count.split("-") 

2937 for value in values: 

2938 try: 

2939 result += int(value) 

2940 except ValueError: 

2941 pass 

2942 else: 

2943 result = int(page_count) 

2944 

2945 return result 

2946 

2947 

2948@receiver(pre_delete, sender=ResourceCount) 

2949def delete_resourcecount_handler(sender, instance, **kwargs): 

2950 """ 

2951 pre_delete ResourceCount signal 

2952 

2953 Stats are added manually during a addResourceCountDatabaseCmd 

2954 but ResourceCount are deleted automatically by Django when its 

2955 related resource is deleted 

2956 (resource = models.ForeignKey(Resource) of ResourceCount) 

2957 To update Stats, we use the Django signal mechanism 

2958 """ 

2959 if instance and instance.name == "page-count" and instance.resource.classname == "Container": 

2960 # page-count may sometimes mix roman and arabic values. Ex "iv-121" 

2961 value = parse_page_count(instance.value) 

2962 

2963 total = Stats.objects.get(name=instance.name) 

2964 total.value -= value 

2965 total.save() 

2966 

2967 

2968# class Volume: 

2969# 

2970# def get_absolute_url(self): 

2971# return reverse('volume-items', kwargs={'vid': self.id_}) 

2972# 

2973# def __init__(self, id_): 

2974# self.id_ = id_ 

2975# try: 

2976# journal_pid, year_id, self.vseries_id, self.volume_id = id_.split( 

2977# '_') 

2978# except ValueError: 

2979# raise self.DoesNotExist(_('Volume {} does not exist').format(id_)) 

2980# try: 

2981# self.collection = Collection.objects.get( 

2982# pid=journal_pid, sites__id=settings.SITE_ID) 

2983# except Collection.DoesNotExist: 

2984# self.issues = None 

2985# try: 

2986# self.issues = Container.objects.filter( 

2987# my_collection__pid=journal_pid, 

2988# year=year_id, 

2989# vseries=self.vseries_id, 

2990# volume=self.volume_id, 

2991# ).order_by('number_int').all() 

2992# except Container.DoesNotExist: 

2993# self.issues = None 

2994# 

2995# class DoesNotExist(Exception): 

2996# pass 

2997 

2998 

2999class ContribAddress(models.Model): 

3000 contrib = models.ForeignKey("Contrib", blank=True, null=True, on_delete=models.CASCADE) 

3001 contribution = models.ForeignKey( 

3002 "Contribution", blank=True, null=True, on_delete=models.CASCADE 

3003 ) 

3004 address = models.TextField(null=True, blank=True) 

3005 

3006 class Meta: 

3007 ordering = ["pk"] 

3008 

3009 

3010def cmp_container_base(a, b): 

3011 return ( 

3012 a.year < b.year 

3013 or (a.year == b.year and a.vseries_int < b.vseries_int) 

3014 or (a.year == b.year and a.vseries_int == b.vseries_int and a.volume_int < b.volume_int) 

3015 or ( 

3016 a.year == b.year 

3017 and a.vseries_int == b.vseries_int 

3018 and a.volume_int == b.volume_int 

3019 and a.number_int < b.number_int 

3020 ) 

3021 ) 

3022 

3023 

3024class PersonManager(models.Manager): 

3025 @staticmethod 

3026 def clean(): 

3027 pass 

3028 # Person.objects.filter(contributions=None).delete() 

3029 

3030 

3031class Person(models.Model): 

3032 """ 

3033 A Person is a contributor (author/editor...) of a Resource (Article/Book) or a BibItem. 

3034 A Person can appear with different names (ex: "John Smith", "J. Smith") and we want to preserve the differences, 

3035 in particular with print papers that are digitized. 

3036 A Person is unique for a person (a Person is basically the key). 

3037 A Person has one or many PersonInfo which stores the different names 

3038 

3039 TODO: signal similar to delete_contrib_handler 

3040 """ 

3041 

3042 # blank=True and null=True allow unique=True with null values (on multiple Persons) 

3043 orcid = models.CharField(max_length=20, blank=True, null=True) 

3044 idref = models.CharField(max_length=10, blank=True, null=True) 

3045 # mid (Mathdoc id) is the key set by numdam-plus 

3046 mid = models.CharField(max_length=256, blank=True, null=True) 

3047 

3048 last_name = models.CharField(max_length=128, blank=True, default="") 

3049 first_name = models.CharField(max_length=128, blank=True, default="") 

3050 prefix = models.CharField(max_length=32, blank=True, default="") 

3051 suffix = models.CharField(max_length=32, blank=True, default="") 

3052 first_letter = models.CharField(max_length=1, blank=True, default="") 

3053 

3054 # Used when a person is not fully tagged in the XML 

3055 string_name = models.CharField(max_length=256, blank=True, default="") 

3056 

3057 objects = PersonManager() 

3058 

3059 @classmethod 

3060 def get_fields_list(cls): 

3061 return [ 

3062 item.attname 

3063 for item in cls._meta.get_fields() 

3064 if not item.auto_created 

3065 and not item.many_to_many 

3066 and not item.many_to_one 

3067 and not item.one_to_many 

3068 and not item.one_to_one 

3069 ] 

3070 

3071 def __str__(self): 

3072 return get_display_name( 

3073 self.prefix, self.first_name, self.last_name, self.suffix, self.string_name 

3074 ) 

3075 

3076 

3077class PersonInfoManager(models.Manager): 

3078 @staticmethod 

3079 def clean(): 

3080 PersonInfo.objects.filter(contributions=None).delete() 

3081 Person.objects.filter(personinfo=None).delete() 

3082 

3083 

3084class PersonInfo(models.Model): 

3085 person = models.ForeignKey(Person, on_delete=models.CASCADE) 

3086 

3087 last_name = models.CharField(max_length=128) 

3088 first_name = models.CharField(max_length=128) 

3089 prefix = models.CharField(max_length=32) 

3090 suffix = models.CharField(max_length=32) 

3091 

3092 # Used when a person is not fully tagged in the XML 

3093 string_name = models.CharField(max_length=256, blank=True, default="") 

3094 

3095 objects = PersonInfoManager() 

3096 

3097 @classmethod 

3098 def get_fields_list(cls): 

3099 return [ 

3100 item.attname 

3101 for item in cls._meta.get_fields() 

3102 if not item.auto_created 

3103 and not item.many_to_many 

3104 and not item.many_to_one 

3105 and not item.one_to_many 

3106 and not item.one_to_one 

3107 ] 

3108 

3109 def __str__(self): 

3110 return get_display_name( 

3111 self.prefix, self.first_name, self.last_name, self.suffix, self.string_name 

3112 ) 

3113 

3114 

3115class Contribution(models.Model): 

3116 resource = models.ForeignKey( 

3117 Resource, blank=True, null=True, on_delete=models.CASCADE, related_name="contributions" 

3118 ) 

3119 bibitem = models.ForeignKey( 

3120 BibItem, blank=True, null=True, on_delete=models.CASCADE, related_name="contributions" 

3121 ) 

3122 

3123 # blank=True and null=True allow unique=True with null values (on multiple Persons) 

3124 orcid = models.CharField(max_length=20, blank=True, null=True) 

3125 idref = models.CharField(max_length=10, blank=True, null=True) 

3126 # mid (Mathdoc id) is the key set by numdam-plus 

3127 mid = models.CharField(max_length=256, blank=True, null=True) 

3128 

3129 last_name = models.CharField(max_length=128, blank=True, default="") 

3130 first_name = models.CharField(max_length=128, blank=True, default="") 

3131 prefix = models.CharField(max_length=32, blank=True, default="") 

3132 suffix = models.CharField(max_length=32, blank=True, default="") 

3133 first_letter = models.CharField(max_length=1, blank=True, default="") 

3134 

3135 # Used when a person is not fully tagged in the XML 

3136 string_name = models.CharField(max_length=256, blank=True, default="") 

3137 

3138 role = models.CharField(max_length=64) 

3139 email = models.EmailField(max_length=254, blank=True, default="") 

3140 deceased_before_publication = models.BooleanField(default=False) 

3141 equal_contrib = models.BooleanField(default=True) 

3142 corresponding = models.BooleanField(default=False) 

3143 

3144 # Used to export the contribution 

3145 contrib_xml = models.TextField() 

3146 

3147 seq = models.IntegerField() 

3148 

3149 class Meta: 

3150 ordering = ["seq"] 

3151 

3152 @classmethod 

3153 def get_fields_list(cls): 

3154 return [ 

3155 item.attname 

3156 for item in cls._meta.get_fields() 

3157 if not item.auto_created 

3158 and not item.many_to_many 

3159 and not item.many_to_one 

3160 and not item.one_to_many 

3161 and not item.one_to_one 

3162 ] 

3163 

3164 def __str__(self): 

3165 return self.display_name() 

3166 

3167 def is_etal(self): 

3168 return self.contrib_xml.startswith("<etal") 

3169 

3170 def display_name(self): 

3171 return ( 

3172 "et al." 

3173 if self.is_etal() 

3174 else get_display_name( 

3175 self.prefix, self.first_name, self.last_name, self.suffix, self.string_name 

3176 ) 

3177 ) 

3178 

3179 def get_absolute_url(self): 

3180 return reverse("pretty_search", args=[f'"{self.display_name()}"-c']) 

3181 

3182 def orcid_href(self): 

3183 return resolver.resolve_id("orcid", self.orcid) 

3184 

3185 def idref_href(self): 

3186 return resolver.resolve_id("idref", self.idref) 

3187 

3188 def is_equal(self, contribution): 

3189 """ 

3190 return True if the contribution is identical, based on orcif/idref or homonimy 

3191 TODO: override the __eq__ operator ? Not sure since homonimy is not bullet proof 

3192 """ 

3193 equal = False 

3194 if self.orcid and self.orcid == contribution.orcid: 

3195 equal = True 

3196 elif self.idref and self.idref == contribution.idref: 

3197 equal = True 

3198 else: 

3199 equal = self.display_name() == contribution.display_name() 

3200 

3201 return True 

3202 return equal 

3203 

3204 

3205@receiver(pre_delete, sender=Contribution) 

3206def delete_contrib_handler(sender, instance, **kwargs): 

3207 """ 

3208 pre_delete Contrib signal 

3209 

3210 Contrib and Author are added manually during a 

3211 addResourceDatabaseCmd (mainly addArticleDatabaseCmd) 

3212 but Contrib are deleted automatically by Django when its 

3213 related resource is deleted 

3214 (resource = models.ForeignKey(Resource) of ContribGroup) 

3215 To update Author, we use the Django signal mechanism 

3216 """ 

3217 if instance and instance.role == "author": 

3218 try: 

3219 ref_name = instance.mid if instance.mid else str(instance) 

3220 author = Author.objects.get(name=ref_name) 

3221 author.count -= 1 

3222 if author.count == 0: 

3223 author.delete() 

3224 else: 

3225 author.save() 

3226 except Author.DoesNotExist: 

3227 pass 

3228 

3229 

3230def get_names(item, role): 

3231 """ 

3232 item: resource or bibitem 

3233 """ 

3234 return [ 

3235 str(contribution) for contribution in item.contributions.all() if contribution.role == role 

3236 ] 

3237 

3238 

3239def are_all_equal_contrib(contributions): 

3240 if len(contributions) == 0: 

3241 return False 

3242 

3243 are_all_equal = True 

3244 for contribution in contributions: 

3245 are_all_equal = are_all_equal and contribution.equal_contrib 

3246 

3247 return are_all_equal 

3248 

3249 

3250def are_all_false_equal_contrib(contributions): 

3251 are_all_equal = True 

3252 for contribution in contributions: 

3253 are_all_equal = are_all_equal and not contribution.equal_contrib 

3254 

3255 return are_all_equal 

3256 

3257 

3258class RelatedArticles(models.Model): 

3259 resource = models.ForeignKey(Resource, null=True, blank=True, on_delete=models.CASCADE) 

3260 # Used during reimport/redeploy to find back the RelatedArticles 

3261 resource_doi = models.CharField(max_length=64, unique=True, null=True, blank=True) 

3262 date_modified = models.DateTimeField(null=True, blank=True) 

3263 doi_list = models.TextField(null=True, blank=True) 

3264 exclusion_list = models.TextField(null=True, blank=True) 

3265 automatic_list = models.BooleanField(default=True) 

3266 

3267 def __str__(self): 

3268 if self.resource and hasattr(self.resource, "doi"): 

3269 return f"{self.resource.doi}" 

3270 

3271 

3272def image_path_graphical_abstract(instance, filename): 

3273 path = "images" 

3274 return os.path.join(path, str(instance.id), "graphical_abstract", filename) 

3275 

3276 

3277def image_path_illustration(instance, filename): 

3278 path = "images" 

3279 return os.path.join(path, str(instance.id), "illustration", filename) 

3280 

3281 

3282class OverwriteStorage(FileSystemStorage): 

3283 def get_available_name(self, name, max_length=None): 

3284 if self.exists(name): 

3285 os.remove(os.path.join(settings.MEDIA_ROOT, name)) 

3286 return name 

3287 

3288 

3289class GraphicalAbstract(models.Model): 

3290 resource = models.ForeignKey(Resource, null=True, blank=True, on_delete=models.CASCADE) 

3291 # Used during reimport/redeploy to find back the GraphicalAbstracts 

3292 resource_doi = models.CharField(max_length=64, unique=True, null=True, blank=True) 

3293 date_modified = models.DateTimeField(null=True, blank=True) 

3294 graphical_abstract = models.ImageField( 

3295 upload_to=image_path_graphical_abstract, blank=True, null=True, storage=OverwriteStorage 

3296 ) 

3297 illustration = models.ImageField( 

3298 upload_to=image_path_illustration, blank=True, null=True, storage=OverwriteStorage 

3299 ) 

3300 

3301 def __str__(self): 

3302 if self.resource and hasattr(self.resource, "doi"): 

3303 return f"{self.resource.doi}" 

3304 

3305 

3306def backup_obj_not_in_metadata(article): 

3307 """ 

3308 When you addArticleXmlCmd in the PTF, the XML does not list objects like GraphicalAbstract or RelatedArticles. 

3309 Since addArticleXmlCmd deletes/re-creates the article, we need to "backup" these objects. 

3310 To do so, we delete the relation between article and the object, 

3311 so that the object is not deleted when the article is deleted. 

3312 

3313 You need to call restore_obj_not_in_metadata after the re-creation of the article. 

3314 """ 

3315 for class_name in ["GraphicalAbstract", "RelatedArticles"]: 

3316 qs = globals()[class_name].objects.filter(resource=article) 

3317 if qs.exists(): 3317 ↛ 3318line 3317 didn't jump to line 3318, because the condition on line 3317 was never true

3318 obj = qs.first() 

3319 obj.resource_doi = article.doi 

3320 obj.resource = None 

3321 obj.save() 

3322 

3323 

3324def backup_translation(article): 

3325 """ 

3326 Translated articles are the article JATS XML, but not in the Cedrics XML 

3327 We need to "backup" existing translations when importing a Cedrics Article. 

3328 To do so, we delete the relation between article and the translation, 

3329 so that the translation is not deleted when the article is deleted. 

3330 

3331 You need to call restore_translation after the re-creation of the article 

3332 """ 

3333 qs = TranslatedArticle.objects.filter(original_article=article) 

3334 if qs.exists(): 3334 ↛ 3335line 3334 didn't jump to line 3335, because the condition on line 3334 was never true

3335 obj = qs.first() 

3336 obj.original_article_doi = article.doi 

3337 obj.original_article = None 

3338 obj.save() 

3339 

3340 

3341def restore_obj_not_in_metadata(article): 

3342 for class_name in ["GraphicalAbstract", "RelatedArticles"]: 

3343 qs = globals()[class_name].objects.filter(resource_doi=article.doi, resource__isnull=True) 

3344 if qs.exists(): 3344 ↛ 3345line 3344 didn't jump to line 3345, because the condition on line 3344 was never true

3345 obj = qs.first() 

3346 obj.resource = article 

3347 obj.save() 

3348 

3349 

3350def restore_translation(article): 

3351 qs = TranslatedArticle.objects.filter( 

3352 original_article_doi=article.doi, original_article__isnull=True 

3353 ) 

3354 if qs.exists(): 3354 ↛ 3355line 3354 didn't jump to line 3355, because the condition on line 3354 was never true

3355 obj = qs.first() 

3356 obj.original_article = article 

3357 obj.save() 

3358 

3359 

3360class TranslatedArticle(Article): 

3361 original_article = models.ForeignKey( 

3362 Article, null=True, blank=True, on_delete=models.CASCADE, related_name="translations" 

3363 ) 

3364 # Used during reimport/redeploy in Trammel to find back the TranslatedArticle 

3365 original_article_doi = models.CharField(max_length=64, unique=True, null=True, blank=True) 

3366 

3367 def get_year(self): 

3368 return self.original_article.get_year() 

3369 

3370 def get_ojs_id(self): 

3371 return self.original_article.get_ojs_id() 

3372 

3373 def get_binary_file_href_full_path(self, doctype, mimetype, location): 

3374 """ 

3375 Returns an encoded URL to the pdf of a translated article 

3376 

3377 Input: doctype: language (ex: 'fr', 'en' 

3378 mimetype: 'application/pdf' 

3379 location is ignored 

3380 

3381 Ex: /article/fr/10.5802/crmath.100.pdf 

3382 """ 

3383 

3384 if mimetype != "application/pdf": 

3385 return "" 

3386 

3387 pid = ( 

3388 self.original_article.doi 

3389 if self.original_article.doi is not None 

3390 else self.original_article.pid 

3391 ) 

3392 extension = "pdf" 

3393 href = reverse( 

3394 "article-translated-pdf", 

3395 kwargs={"pid": pid, "extension": extension, "binary_file_type": self.lang}, 

3396 ) 

3397 

3398 return href 

3399 

3400 def get_citation(self, request=None): 

3401 citation = "" 

3402 

3403 translator_names = get_names(self, "translator") 

3404 citation += "; ".join(translator_names) 

3405 

3406 if citation: 

3407 if not citation.endswith("."): 

3408 citation += ". " 

3409 citation += " " 

3410 

3411 if self.lang == self.original_article.trans_lang: 

3412 citation += self.original_article.trans_title_tex 

3413 else: 

3414 citation += self.title_tex 

3415 

3416 year = self.date_published.strftime("%Y") if self.date_published is not None else "YYYY" 

3417 citation += " (" + year + ")" 

3418 

3419 if self.doi is not None: 

3420 citation += " doi : " + self.doi 

3421 

3422 # page_text = self.get_page_text(True) 

3423 # citation += self.original_article.get_citation_base(year, page_text) 

3424 

3425 citation += " (" + self.original_article.get_citation()[0:-1] + ")" 

3426 

3427 # author_names = get_names(self.original_article, "author") 

3428 # if author_names: 

3429 # authors = '; '.join(author_names) 

3430 # else: 

3431 # author_names = get_names(self.original_article, "editor") 

3432 # if author_names: 

3433 # authors = '; '.join(author_names) + ' (' + str( 

3434 # _("éd.")) + ')' 

3435 # else: 

3436 # authors = '' 

3437 # citation += authors 

3438 # 

3439 # if citation: 

3440 # if not citation.endswith('.'): 

3441 # citation += '.' 

3442 # citation += ' ' 

3443 # 

3444 # citation += self.original_article.title_tex 

3445 # 

3446 # if self.lang == self.original_article.trans_lang: 

3447 # citation += f" [{self.original_article.trans_title_tex}]" 

3448 # else: 

3449 # citation += f" [{self.title_tex}]" 

3450 # 

3451 # citation += ' (' + str(_('Traduit par')) + ' ' 

3452 # 

3453 # citation += '). ' 

3454 # 

3455 # citation += self.original_article.my_container.my_collection.title_tex 

3456 

3457 # if self.doi is not None: 

3458 # citation += ' doi : ' + self.doi + '.' 

3459 # 

3460 # if request is not None: 

3461 # url = "{}://{}{}".format(request.scheme, request.get_host(), self.original_article.get_absolute_url()) 

3462 # citation += ' ' + url 

3463 # 

3464 # citation += " (" + str(_('Article original publié en ')) + self.original_article.my_container.year + ')' 

3465 

3466 return citation