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

1907 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2024-02-28 09:09 +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 return self.__getattribute__(self.classname.lower()) 

195 

196 def get_collection(self): 

197 return None 

198 

199 def get_top_collection(self): 

200 return None 

201 

202 def get_container(self): 

203 return None 

204 

205 def is_deployed(self, site): 

206 try: 

207 site = self.sites.get(id=site.id) 

208 except Site.DoesNotExist: 

209 return False 

210 else: 

211 return True 

212 

213 def deploy(self, site, deployed_date=None): 

214 """ 

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

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

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

218 We only have add commands. 

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

220 

221 :param site: 

222 :param deployed_date: 

223 :return: 

224 """ 

225 if deployed_date is None: 

226 deployed_date = timezone.now() 

227 try: 

228 membership = SiteMembership.objects.get(resource=self, site=site) 

229 membership.deployed = deployed_date 

230 membership.save() 

231 except SiteMembership.DoesNotExist: 

232 membership = SiteMembership(resource=self, site=site, deployed=deployed_date) 

233 membership.save() 

234 

235 def undeploy(self, site): 

236 try: 

237 membership = SiteMembership.objects.get(resource=self, site=site) 

238 except SiteMembership.DoesNotExist: 

239 pass 

240 else: 

241 membership.delete() 

242 

243 def date_time_deployed(self, site): 

244 try: 

245 membership = SiteMembership.objects.get(resource=self, site=site) 

246 except SiteMembership.DoesNotExist: 

247 return None 

248 return membership.deployed 

249 

250 def deployed_date(self, site=None): 

251 if site is None and settings.SITE_NAME == "ptf_tools": 

252 # on est sur ptf-tools et dans un template on fait appel à deployed_date 

253 # si le site lié à la collection est créé on renvoie la date de déploiement sur ce site 

254 # sinon None 

255 from ptf import model_helpers 

256 

257 site = model_helpers.get_site_mersenne(self.get_collection().pid) 

258 return self.date_time_deployed(site) 

259 if not site: 

260 site = Site.objects.get_current() 

261 

262 return self.date_time_deployed(site) 

263 

264 def get_id_value(self, id_type): 

265 try: 

266 rid = self.resourceid_set.get(id_type=id_type) 

267 except ResourceId.DoesNotExist: 

268 return None 

269 else: 

270 return rid.id_value 

271 

272 # TODO : doi is in ResourceId and in Resource ? maybe use only one ... 

273 def get_doi_href(self): 

274 href = None 

275 if self.doi: 

276 href = resolver.get_doi_url(self.doi) 

277 return href 

278 

279 def get_link(self, link_type): 

280 href = None 

281 for link in self.extlink_set.all(): 

282 if link.rel == link_type: 

283 href = link.get_href() 

284 

285 return href 

286 

287 def website(self): 

288 return self.get_link("website") 

289 

290 def test_website(self): 

291 return self.get_link("test_website") 

292 

293 def icon(self): 

294 return self.get_link("icon") 

295 

296 def small_icon(self): 

297 return self.get_link("small_icon") 

298 

299 # def relation_names(self): 

300 # names = set() 

301 # for rel in self.subject_of.select_related('rel_info').all(): 

302 # name = rel.rel_info.left 

303 # names.add(name) 

304 # for rel in self.object_of.select_related('rel_info').all(): 

305 # name = rel.rel_info.right 

306 # names.add(name) 

307 # return names 

308 # 

309 # def get_related(self, rel_type, count_only=True): 

310 # is_subject = False 

311 # try: 

312 # rel = RelationName.objects.get(left=rel_type) 

313 # except RelationName.DoesNotExist: 

314 # try: 

315 # rel = RelationName.objects.get(right=rel_type) 

316 # except RelationName.DoesNotExist: 

317 # raise UnknownRelationType(rel_type) 

318 # else: 

319 # pass 

320 # else: 

321 # is_subject = True 

322 # if is_subject: 

323 # qs = self.subject_of.filter(rel_info=rel) 

324 # if count_only: 

325 # return qs.count() 

326 # result = [x.related.cast() for x in qs] 

327 # else: 

328 # qs = self.object_of.filter(rel_info=rel) 

329 # if count_only: 

330 # return qs.count() 

331 # result = [x.resource.cast() for x in qs] 

332 # return result 

333 

334 def is_edited_book(self): 

335 return False 

336 

337 # NOTE - 12/08/2017 - Basile 

338 # utilisé nul part à delete ? 

339 # def has_errata(self): 

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

341 

342 # def is_erratum_to(self): 

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

344 

345 # def errata(self): 

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

347 

348 # def erratum(self): 

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

350 # if len(errata) == 1: 

351 # return errata[0] 

352 # return None 

353 

354 # def questions(self): 

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

356 

357 # def solutions(self): 

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

359 

360 # def complements(self): 

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

362 

363 # def completed(self): 

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

365 

366 # def follows(self): 

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

368 # if len(followed) == 1: 

369 # return followed[0] 

370 # return 0 

371 

372 # def followed(self): 

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

374 # if len(follows) == 1: 

375 # return follows[0] 

376 # return 0 

377 

378 # def citations_count(self): 

379 # if not self.pid: 

380 # return 0 

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

382 # id_type=self.provider.pid_type, 

383 # id_value=self.pid, 

384 # bibitem__resource__sites__id=settings.SITE_ID 

385 # ) 

386 # return qs.count() 

387 

388 def citations(self): 

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

390 return [] 

391 

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

393 id_type=self.provider.pid_type, 

394 id_value=self.pid, 

395 bibitem__resource__sites__id=settings.SITE_ID, 

396 ) 

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

398 # on peut avoir soit des containers soit des articles 

399 # comme resource et year est sur le container uniquement 

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

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

402 return result 

403 

404 def get_contributions(self, role): 

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

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

407 return [ 

408 contribution 

409 for contribution in self.contributions.all() 

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

411 ] 

412 

413 def get_author_contributions(self, strict=True): 

414 authors = self.get_contributions("author") 

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

416 authors = self.get_editors() 

417 return authors 

418 

419 def get_authors_short(self): 

420 authors = self.get_contributions("author") 

421 if len(authors) > 2: 

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

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

424 return authors 

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

426 

427 def get_editors(self): 

428 return self.get_contributions("editor") 

429 

430 def get_contributors(self): 

431 return self.get_contributions("contributor") 

432 

433 def get_redaktors(self): 

434 return self.get_contributions("redaktor") 

435 

436 def get_organizers(self): 

437 return self.get_contributions("organizer") 

438 

439 def get_presenters(self): 

440 return self.get_contributions("presenter") 

441 

442 def get_kwds_by_type(self): 

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

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

445 trans_kwds = [ 

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

447 ] 

448 return msc, kwds, trans_kwds 

449 

450 def get_subjs_by_type_and_lang(self): 

451 subjs = {} 

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

453 if subj.type in subjs: 

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

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

456 else: 

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

458 else: 

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

460 

461 return subjs 

462 

463 def self_uris_no_xml(self): 

464 """ 

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

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

467 HTML templates use get_binary_files_href (see below) 

468 """ 

469 links = [ 

470 { 

471 "mimetype": link.mimetype, 

472 "full_path": self.get_binary_file_href_full_path( 

473 "self", link.mimetype, link.location 

474 ), 

475 "link": link.text, 

476 } 

477 for link in self.datastream_set.all() 

478 ] 

479 return links 

480 

481 @staticmethod 

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

483 if mimetype == "application/pdf": 

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

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

486 else: 

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

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

489 if key in binary_files: 

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

491 else: 

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

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

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

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

496 else: 

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

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

499 if key in binary_files: 

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

501 else: 

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

503 

504 def get_binary_files_location(self): 

505 binary_files = [] 

506 

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

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

509 binary_files.append(obj.location) 

510 

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

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

513 binary_files.append(obj.location) 

514 

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

516 binary_files.append(obj.location) 

517 

518 if hasattr(self, "translations"): 

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

520 binary_files.extend(translated_article.get_binary_files_location()) 

521 

522 return binary_files 

523 

524 def get_binary_files_href(self): 

525 """ 

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

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

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

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

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

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

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

533 full volume pdf, book part pdf) 

534 The information come from the DataStream 

535 The other results come from RelatedObject 

536 """ 

537 binary_files = {} 

538 

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

540 key = related_object.rel 

541 mimetype = related_object.mimetype 

542 location = related_object.location 

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

544 

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

546 

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

548 

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

550 key = "self" 

551 mimetype = stream.mimetype 

552 location = stream.location 

553 

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

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

556 href = location 

557 else: 

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

559 

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

561 

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

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

564 if qs: 

565 extlink = qs.first() 

566 href = extlink.location 

567 key = "self" 

568 mimetype = "application/pdf" 

569 

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

571 

572 translations = {} 

573 if hasattr(self, "translations"): 

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

575 result = trans_article.get_binary_files_href() 

576 if "self" in result: 

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

578 

579 binary_files["translations"] = translations 

580 

581 return binary_files 

582 

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

584 """ 

585 Returns an encoded URL to a pdf/djvu 

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

587 An encode URL is used instead 

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

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

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

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

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

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

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

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

596 

597 Ex: /article/ALCO_2018__1_1_23_0.pdf 

598 /issue/MSMF_1978__55-56__1_0.pdf 

599 /issue/toc/CSHM_1980__1_.pdf 

600 /article/ALCO_2018__1_1_23_0/tex/src/tex/ALCO_Thiem_31.tex 

601 

602 """ 

603 if self.embargo(): 

604 return "" 

605 

606 pid = self.pid 

607 doi = getattr(self, "doi") 

608 if doi is not None: 

609 pid = doi 

610 

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

612 

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

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

615 

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

617 href = reverse( 

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

619 ) 

620 

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

622 "review", 

623 "supplementary-material", 

624 ]: 

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

626 if len(prefix) > 0: 

627 href = reverse( 

628 "issue-relatedobject-pdf", 

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

630 ) 

631 else: 

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

633 

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

635 to_find = "/src/tex/" 

636 i = location.find(to_find) 

637 if i > 0: 

638 location = location[i + 1 :] 

639 

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

641 

642 else: 

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

644 

645 to_find = "/attach/" 

646 i = location.find(to_find) 

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

648 location = location[i + 1 :] 

649 

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

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

652 

653 return href 

654 

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

656 """ 

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

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

659 

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

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

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

663 

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

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

666 based on the DataStream/RelatedObject location 

667 

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

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

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

671 

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

673 May raise exceptions.ResourceDoesNotExist if the resource 

674 (RelatedObject/DataStream) does not exist 

675 """ 

676 if self.embargo(): 

677 return None 

678 

679 filename = None 

680 

681 if ( 681 ↛ 686line 681 didn't jump to line 686

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

683 and len(doctype) == 2 

684 and hasattr(self, "translations") 

685 ): 

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

687 if translated_article.lang == doctype: 

688 filename = translated_article.get_binary_disk_location( 

689 "self", mimetype, relativepath 

690 ) 

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

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

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

694 else: 

695 if doctype != "self": 

696 try: 

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

698 

699 except RelatedObject.DoesNotExist: 

700 # status = 404 

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

702 else: 

703 try: 

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

705 

706 except DataStream.DoesNotExist: 

707 # status = 404 

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

709 filename = obj.location 

710 

711 return filename 

712 

713 def get_abstracts(self): 

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

715 

716 def get_avertissements(self): 

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

718 

719 def get_descriptions(self): 

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

721 

722 def get_editorial_intros(self): 

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

724 

725 def get_toc(self): 

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

727 

728 def visit(self, visitor): 

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

730 

731 def natural_key(self): 

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

733 

734 def get_solr_body(self, field): 

735 from ptf.cmds import solr_cmds 

736 

737 body = None 

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

739 result = cmd.do() 

740 if result: 

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

742 body = result[field] 

743 

744 return body 

745 

746 def get_body(self): 

747 return self.get_solr_body("body") 

748 

749 def volume_string(self): 

750 return "" 

751 

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

753 # TODO chapter, howpublished? 

754 # TODO institution 

755 # if self.institution: 

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

757 

758 to_appear = container.to_appear() 

759 is_cr = container.is_cr() 

760 

761 # pages = self.pages() 

762 publisher = container.my_publisher 

763 

764 if publisher is not None: 

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

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

767 if publisher.pub_loc: 

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

769 

770 if not to_appear: 

771 if container.volume: 

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

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

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

775 

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

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

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

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

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

781 

782 if self.doi: 

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

784 

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

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

787 append_in_latex( 

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

789 ) 

790 

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

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

793 

794 if to_appear: 

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

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

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

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

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

800 

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

802 to_appear = container.to_appear() 

803 is_cr = container.is_cr() 

804 

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

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

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

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

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

810 

811 if not to_appear: 

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

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

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

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

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

817 if container.volume: 

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

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

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

821 

822 publisher = container.my_publisher 

823 if publisher is not None: 

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

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

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

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

828 

829 if to_appear: 

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

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

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

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

834 

835 if self.doi: 

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

837 

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

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

840 

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

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

843 items.append("ER" + sep) 

844 

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

846 to_appear = container.to_appear() 

847 is_cr = container.is_cr() 

848 

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

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

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

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

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

854 

855 if not to_appear: 

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

857 pages = self.pages(for_bibtex=False) 

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

859 if container.volume: 

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

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

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

863 

864 publisher = container.my_publisher 

865 if publisher is not None: 

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

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

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

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

870 

871 if to_appear: 

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

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

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

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

876 

877 if self.doi: 

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

879 

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

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

882 

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

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

885 

886 

887class Publisher(Resource): 

888 """ 

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

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

891 d'où les slugs 

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

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

894 pour la possibilité inverse :-) 

895 """ 

896 

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

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

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

900 

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

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

903 

904 def __str__(self): 

905 return self.pub_key 

906 

907 @staticmethod 

908 def get_collection(): 

909 return None 

910 

911 @staticmethod 

912 def get_top_collection(): 

913 return None 

914 

915 @staticmethod 

916 def get_container(): 

917 return None 

918 

919 

920class CollectionQuerySet(models.QuerySet): 

921 def order_by_date(self): 

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

923 

924 

925class Collection(Resource): 

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

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

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

929 issn = Identifier("issn") 

930 e_issn = Identifier("e-issn") 

931 wall = models.IntegerField(default=5) 

932 alive = models.BooleanField(default=True) 

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

934 # updated when a container is imported. 

935 fyear = models.IntegerField(default=0) 

936 lyear = models.IntegerField(default=0) 

937 last_doi = models.IntegerField(default=0) 

938 

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

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

941 # We don't really create a tree. 

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

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

944 parent = models.ForeignKey( 

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

946 ) 

947 objects = CollectionQuerySet.as_manager() 

948 

949 class Meta: 

950 ordering = ["title_sort"] 

951 

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

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

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

955 def get_absolute_url(self): 

956 if self.coltype == "thesis": 

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

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

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

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

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

962 else: 

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

964 return url 

965 

966 def bloodline(self): 

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

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

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

970 return Collection.objects.filter( 

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

972 ).order_by_date() 

973 return Collection.objects.none() 

974 

975 def preceding_journal(self): 

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

977 bloodline = self.bloodline() 

978 for index, collection in enumerate(bloodline): 

979 if collection == self and index: 

980 return bloodline[index - 1] 

981 return Collection.objects.none() 

982 

983 def get_wall(self): 

984 return self.wall 

985 

986 def get_collection(self): 

987 return self 

988 

989 def get_top_collection(self): 

990 return self.parent if self.parent else self 

991 

992 def deployed_date(self, site=None): 

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

994 containers = self.content.all() 

995 date = None 

996 if containers: 

997 site = Site.objects.get_current() 

998 sms = ( 

999 SiteMembership.objects.filter(resource__in=containers) 

1000 .filter(site=site) 

1001 .order_by("-deployed") 

1002 ) 

1003 date = sms.first().deployed 

1004 return date 

1005 

1006 def get_relative_folder(self): 

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

1008 

1009 

1010class ContainerQuerySet(ResourceQuerySet): 

1011 def prefetch_all(self): 

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

1013 

1014 def prefetch_for_toc(self): 

1015 return ( 

1016 self.prefetch_contributors() 

1017 .prefetch_work() 

1018 .prefetch_related( 

1019 "article_set__datastream_set", 

1020 "article_set__subj_set", 

1021 "article_set__extlink_set", 

1022 "article_set__resourcecount_set", 

1023 "article_set__contributions", 

1024 ) 

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

1026 ) 

1027 

1028 

1029class Container(Resource): 

1030 """ 

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

1032 container <--- issue 

1033 <--- book 

1034 """ 

1035 

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

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

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

1039 

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

1041 ## 

1042 # if ctype == issue 

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

1044 ## 

1045 # data for relation with enclosing serial, if any 

1046 my_collection = models.ForeignKey( 

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

1048 ) 

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

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

1051 # la même chose pour le tri 

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

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

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

1055 seq = models.IntegerField(db_index=True) 

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

1057 

1058 my_publisher = models.ForeignKey( 

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

1060 ) 

1061 

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

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

1064 # in multiple collections (Bourbaki and Asterisque) 

1065 # 

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

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

1068 # my_other_collections stores the additional collections. 

1069 # 

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

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

1072 # these info for the main collection. 

1073 my_other_collections = models.ManyToManyField( 

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

1075 ) 

1076 

1077 objects = ContainerQuerySet.as_manager() 

1078 

1079 class Meta: 

1080 ordering = ["seq"] 

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

1082 

1083 def allow_crossref(self): 

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

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

1086 

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

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

1089 date_published__isnull=True, date_online_first__isnull=True 

1090 ).exists(): 

1091 result = False 

1092 

1093 return result 

1094 

1095 def all_doi_are_registered(self): 

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

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

1098 return ( 

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

1100 == self.article_set.count() 

1101 ) 

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

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

1104 # aucun doi associé aux articles ou au container 

1105 return False 

1106 

1107 def registered_in_doaj(self): 

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

1109 do_not_publish__isnull=True 

1110 ) 

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

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

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

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

1115 self.doajbatch.status = "unregistered" 

1116 self.doajbatch.save() 

1117 

1118 def are_all_articles_published(self): 

1119 from ptf import model_helpers 

1120 

1121 result = True 

1122 

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

1124 year = article.get_year() 

1125 fyear, lyear = model_helpers.get_first_last_years(year) 

1126 try: 

1127 fyear = int(fyear) 

1128 except ValueError: 

1129 fyear = 0 

1130 

1131 if fyear > 2017: 

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

1133 result = False 

1134 elif fyear == 0: 

1135 result = False 

1136 

1137 return result 

1138 

1139 def get_wall(self): 

1140 return self.my_collection.get_wall() 

1141 

1142 def previous(self): 

1143 issue = None 

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

1145 if qs.count() > 0: 

1146 issue = qs.first() 

1147 return issue 

1148 

1149 def next(self): 

1150 issue = None 

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

1152 if qs.count() > 0: 

1153 issue = qs.first() 

1154 return issue 

1155 

1156 # TODO container in multiple collections 

1157 def get_collection(self): 

1158 return self.my_collection 

1159 

1160 def get_top_collection(self): 

1161 return self.my_collection.get_top_collection() 

1162 

1163 def get_other_collections(self): 

1164 return self.my_other_collections.all() 

1165 

1166 def get_container(self): 

1167 return self 

1168 

1169 def get_volume(self): 

1170 return self.volume 

1171 

1172 def get_number(self): 

1173 return self.number 

1174 

1175 def embargo(self): 

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

1177 

1178 def to_appear(self): 

1179 return self.with_online_first or ( 

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

1181 ) 

1182 

1183 def is_cr(self): 

1184 return ( 

1185 hasattr(settings, "SITE_NAME") 

1186 and len(settings.SITE_NAME) == 6 

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

1188 ) 

1189 

1190 @staticmethod 

1191 def get_base_url(): 

1192 return resolver.get_issue_base_url() 

1193 

1194 def get_relative_folder(self): 

1195 collection = self.get_top_collection() 

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

1197 

1198 def get_vid(self): 

1199 """ 

1200 08/09/2022: support of Collection ancestors 

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

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

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

1204 

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

1206 """ 

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

1208 vid = self.pid 

1209 return vid 

1210 

1211 def get_year(self): 

1212 return self.year 

1213 

1214 def is_edited_book(self): 

1215 return self.ctype == EDITED_BOOK_TYPE 

1216 

1217 def get_citation(self, request): 

1218 citation = "" 

1219 

1220 author_names = get_names(self, "author") 

1221 authors = "" 

1222 if author_names: 

1223 authors = "; ".join(author_names) 

1224 

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

1226 author_names = get_names(self, "editor") 

1227 if author_names: 

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

1229 else: 

1230 authors = "" 

1231 

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

1233 citation += authors 

1234 

1235 if citation: 

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

1237 citation += "." 

1238 citation += " " 

1239 

1240 citation += self.title_tex + ". " 

1241 citation += self.my_collection.title_tex 

1242 

1243 if self.vseries: 

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

1245 

1246 if self.volume: 

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

1248 

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

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

1251 elif self.number: 

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

1253 else: 

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

1255 

1256 redactors = self.get_redaktors() 

1257 if len(redactors) > 0: 

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

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

1260 

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

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

1263 

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

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

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

1267 

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

1269 

1270 return citation 

1271 

1272 def has_detailed_info(self): 

1273 # Ignore citations here. 

1274 

1275 result = False 

1276 

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

1278 result = True 

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

1280 result = True 

1281 else: 

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

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

1284 result = True 

1285 

1286 return result 

1287 

1288 def get_bibtex(self, request): 

1289 """ 

1290 

1291 :param self: 

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

1293 """ 

1294 bibtex = [] 

1295 indent = " " 

1296 

1297 collection = self.get_collection() 

1298 

1299 is_phdthesis = False 

1300 

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

1302 if self.ctype == "issue": 

1303 return bibtex 

1304 

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

1306 is_phdthesis = True 

1307 

1308 author_names = get_bibtex_names(self, "author") 

1309 editor_names = get_bibtex_names(self, "editor") 

1310 

1311 type_ = "book" 

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

1313 type_ = "phdthesis" 

1314 

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

1316 id_ = self.pid 

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

1318 

1319 if author_names: 

1320 append_in_latex(bibtex, indent + author_names) 

1321 if editor_names: 

1322 append_in_latex(bibtex, indent + editor_names) 

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

1324 

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

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

1327 

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

1329 

1330 append_in_latex(bibtex, "}") 

1331 return "\n".join(bibtex) 

1332 

1333 def get_ris(self, request): 

1334 """ 

1335 

1336 :param self: 

1337 :return: a string 

1338 """ 

1339 items = [] 

1340 sep = " - " 

1341 

1342 collection = self.get_collection() 

1343 

1344 is_phdthesis = False 

1345 

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

1347 if self.ctype == "issue": 

1348 return "" 

1349 

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

1351 is_phdthesis = True 

1352 

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

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

1355 else: 

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

1357 

1358 author_names = get_names(self, CONTRIB_TYPE_AUTHOR) 

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

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

1361 

1362 editor_names = get_names(self, CONTRIB_TYPE_EDITOR) 

1363 for editor in editor_names: 

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

1365 

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

1367 

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

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

1370 

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

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

1373 

1374 def get_endnote(self, request): 

1375 """ 

1376 

1377 :param self: 

1378 :return: a string 

1379 """ 

1380 items = [] 

1381 sep = " " 

1382 

1383 collection = self.get_collection() 

1384 

1385 is_phdthesis = False 

1386 

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

1388 if self.ctype == "issue": 

1389 return "" 

1390 

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

1392 is_phdthesis = True 

1393 

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

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

1396 else: 

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

1398 

1399 author_names = get_names(self, CONTRIB_TYPE_AUTHOR) 

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

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

1402 

1403 editor_names = get_names(self, CONTRIB_TYPE_EDITOR) 

1404 for editor in editor_names: 

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

1406 

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

1408 

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

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

1411 

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

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

1414 

1415 def has_articles_excluded_from_publication(self): 

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

1417 return result 

1418 

1419 

1420class EventSeries(Resource): 

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

1422 

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

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

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

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

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

1428 

1429 

1430class Event(Resource): 

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

1432 

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

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

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

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

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

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

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

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

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

1442 contrib = models.TextField() 

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

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

1445 

1446 class Meta: 

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

1448 

1449 

1450class ArticleQuerySet(ResourceQuerySet): 

1451 def order_by_published_date(self): 

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

1453 

1454 def order_by_sequence(self): 

1455 return self.order_by("seq") 

1456 

1457 def prefetch_for_toc(self): 

1458 return ( 

1459 self.prefetch_contributors() 

1460 .prefetch_work() 

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

1462 ) 

1463 

1464 

1465class Article(Resource): 

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

1467 

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

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

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

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

1472 page_type = models.CharField(max_length=64) 

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

1474 article_number = models.CharField(max_length=32) 

1475 talk_number = models.CharField(max_length=32) 

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

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

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

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

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

1481 date_pre_published = models.DateTimeField( 

1482 null=True, blank=True 

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

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

1485 show_body = models.BooleanField( 

1486 default=True 

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

1488 do_not_publish = models.BooleanField( 

1489 default=False 

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

1491 

1492 ## 

1493 # container 

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

1495 seq = models.IntegerField() 

1496 ## 

1497 # parent part 

1498 parent = models.ForeignKey( 

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

1500 ) 

1501 pseq = models.IntegerField() 

1502 objects = ArticleQuerySet.as_manager() 

1503 

1504 class Meta: 

1505 ordering = ["seq"] 

1506 

1507 def __str__(self): 

1508 return self.pid 

1509 

1510 @staticmethod 

1511 def get_base_url(): 

1512 return resolver.get_article_base_url() 

1513 

1514 def get_absolute_url(self): 

1515 if self.doi is not None: 

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

1517 else: 

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

1519 

1520 def get_relative_folder(self): 

1521 collection = self.get_top_collection() 

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

1523 

1524 def embargo(self): 

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

1526 return False 

1527 

1528 return self.my_container.embargo() 

1529 

1530 def get_wall(self): 

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

1532 return 0 

1533 return self.my_container.get_wall() 

1534 

1535 def get_collection(self): 

1536 return self.my_container.get_collection() 

1537 

1538 def get_top_collection(self): 

1539 return self.my_container.get_top_collection() 

1540 

1541 def get_container(self): 

1542 return self.my_container 

1543 

1544 def get_volume(self): 

1545 return self.my_container.get_volume() 

1546 

1547 def get_number(self): 

1548 return self.my_container.get_number() 

1549 

1550 def get_page_count(self): 

1551 page_count = None 

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

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

1554 page_count = resourcecount.value 

1555 

1556 return page_count 

1557 

1558 def get_article_page_count(self): 

1559 try: 

1560 page_count = self.get_page_count() or 0 

1561 page_count = int(page_count) 

1562 except ValueError: 

1563 page_count = 0 

1564 

1565 lpage = fpage = 0 

1566 try: 

1567 fpage = int(self.fpage) 

1568 except ValueError: 

1569 pass 

1570 try: 

1571 lpage = int(self.lpage) 

1572 except ValueError: 

1573 pass 

1574 if lpage > 0 and fpage > 0: 

1575 page_count = lpage - fpage + 1 

1576 return page_count 

1577 

1578 def pages(self, for_bibtex=False): 

1579 """ 

1580 Returns a string with the article pages. 

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

1582 

1583 typically "{fpage}-{lpage}" 

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

1585 

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

1587 """ 

1588 if self.page_range: 

1589 return self.page_range 

1590 if self.fpage or self.lpage: 

1591 if self.lpage: 

1592 return ( 

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

1594 ) 

1595 else: 

1596 return self.fpage 

1597 return None 

1598 

1599 def volume_series(self): 

1600 # Use only for latex 

1601 if not self.my_container.vseries: 

1602 return "" 

1603 if self.lang == "fr": 

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

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

1606 

1607 def volume_string(self): 

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

1609 

1610 def get_page_text(self, use_pp=False): 

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

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

1613 

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

1615 return "" 

1616 

1617 if self.lpage == self.fpage: 

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

1619 

1620 text = "" 

1621 if not self.page_range: 

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

1623 text = "pp. " 

1624 else: 

1625 text = "p. " 

1626 text += self.fpage 

1627 if self.fpage and self.lpage: 

1628 text += "-" 

1629 if self.lpage: 

1630 text += self.lpage 

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

1632 text = "p. " + self.page_range 

1633 

1634 return text 

1635 

1636 def get_summary_page_text(self): 

1637 text = "" 

1638 if self.talk_number: 

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

1640 if self.article_number: 

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

1642 

1643 text += self.get_page_text() 

1644 

1645 return text 

1646 

1647 def get_breadcrumb_page_text(self): 

1648 text = "" 

1649 if self.my_container.with_online_first: 

1650 if self.doi is not None: 

1651 text = self.doi 

1652 else: 

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

1654 elif self.talk_number: 

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

1656 elif self.article_number: 

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

1658 else: 

1659 text += self.get_page_text() 

1660 

1661 return text 

1662 

1663 def get_year(self): 

1664 return self.my_container.year 

1665 

1666 def previous(self): 

1667 try: 

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

1669 except (Article.DoesNotExist, MultipleObjectsReturned): 

1670 return None 

1671 

1672 def next(self): 

1673 try: 

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

1675 except (Article.DoesNotExist, MultipleObjectsReturned): 

1676 return None 

1677 

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

1679 citation = "" 

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

1681 year = self.my_container.year 

1682 

1683 to_appear = self.my_container.to_appear() 

1684 is_cr = self.my_container.is_cr() 

1685 

1686 if to_appear and year != "0": 

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

1688 elif to_appear: 

1689 citation += ", Online first" 

1690 else: 

1691 if self.my_container.vseries: 

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

1693 

1694 if self.my_container.volume: 

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

1696 

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

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

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

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

1701 else: 

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

1703 

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

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

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

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

1708 

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

1710 page_text = self.get_page_text(True) 

1711 if len(page_text) > 0: 

1712 citation += f", {page_text}" 

1713 

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

1715 citation += "." 

1716 

1717 if not to_appear: 

1718 if self.page_type == "volant": 

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

1720 elif self.page_type == "supplement": 

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

1722 elif self.page_type == "preliminaire": 

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

1724 elif self.page_type == "special": 

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

1726 

1727 return citation 

1728 

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

1730 citation = "" 

1731 

1732 author_names = get_names(self, "author") 

1733 if author_names: 

1734 authors = "; ".join(author_names) 

1735 else: 

1736 author_names = get_names(self, "editor") 

1737 if author_names: 

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

1739 else: 

1740 authors = "" 

1741 

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

1743 citation += authors 

1744 

1745 if citation: 

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

1747 citation += "." 

1748 citation += " " 

1749 

1750 if with_formatting: 

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

1752 else: 

1753 citation += self.title_tex 

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

1755 citation += ", " 

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

1757 else: 

1758 citation += ". " 

1759 citation += self.my_container.my_collection.title_tex 

1760 

1761 citation += self.get_citation_base() 

1762 

1763 to_appear = self.my_container.to_appear() 

1764 is_cr = self.my_container.is_cr() 

1765 

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

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

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

1769 

1770 if not to_appear and request is not None: 

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

1772 citation += " " + url 

1773 

1774 return citation 

1775 

1776 def has_detailed_info(self): 

1777 # Ignore citations here. 

1778 

1779 result = False 

1780 

1781 if ( 

1782 self.date_received 

1783 or self.date_revised 

1784 or self.date_accepted 

1785 or self.date_published 

1786 or self.date_online_first 

1787 ): 

1788 result = True 

1789 elif self.extid_set.exists(): 

1790 result = True 

1791 elif self.kwd_set.exists(): 

1792 result = True 

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

1794 result = True 

1795 else: 

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

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

1798 result = True 

1799 

1800 return result 

1801 

1802 def get_ojs_id(self): 

1803 ojs_id = "" 

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

1805 if qs.count() > 0: 

1806 resourceid = qs.first() 

1807 ojs_id = resourceid.id_value 

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

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

1810 ojs_id = ojs_id[0:pos] 

1811 return ojs_id 

1812 

1813 def update_bibtex_with_book_contributors( 

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

1815 ): 

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

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

1818 

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

1820 book_author_names = get_bibtex_names(container, "author") 

1821 book_editor_names = get_bibtex_names(container, "editor") 

1822 

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

1824 append_in_latex(bibtex, indent + book_author_names) 

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

1826 append_in_latex(bibtex, indent + book_editor_names) 

1827 

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

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

1830 

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

1832 """ 

1833 

1834 :param self: 

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

1836 """ 

1837 bibtex = [] 

1838 indent = " " 

1839 

1840 container = self.my_container 

1841 collection = self.get_collection() 

1842 

1843 is_article = True 

1844 is_incollection = False 

1845 is_inbook = False 

1846 is_phdthesis = False 

1847 

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

1849 is_article = False 

1850 

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

1852 is_phdthesis = True 

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

1854 is_inbook = True 

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

1856 is_incollection = True 

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

1858 is_incollection = True 

1859 

1860 to_appear = container.to_appear() 

1861 is_cr = container.is_cr() 

1862 

1863 # No bibtex at the article level for a these 

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

1865 return "" 

1866 

1867 author_names = get_bibtex_names(self, "author") 

1868 editor_names = get_bibtex_names(self, "editor") 

1869 

1870 type_ = "article" 

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

1872 type_ = "unpublished" 

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

1874 type_ = "inbook" 

1875 elif is_incollection: 

1876 type_ = "incollection" 

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

1878 type_ = "misc" 

1879 

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

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

1882 

1883 if author_names: 

1884 append_in_latex(bibtex, indent + author_names) 

1885 if editor_names: 

1886 append_in_latex(bibtex, indent + editor_names) 

1887 

1888 title = xml_utils.normalise_span(self.title_tex) 

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

1890 

1891 if is_article and not is_incollection: 

1892 title = xml_utils.normalise_span(collection.title_tex) 

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

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

1895 title = xml_utils.normalise_span(container.title_tex) 

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

1897 title = xml_utils.normalise_span(collection.title_tex) 

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

1899 else: 

1900 self.update_bibtex_with_book_contributors( 

1901 bibtex, collection, container, indent, author_names, editor_names 

1902 ) 

1903 

1904 if not to_appear: 

1905 if self.talk_number: 

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

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

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

1909 if self.pages(): 

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

1911 

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

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

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

1915 

1916 append_in_latex(bibtex, "}") 

1917 

1918 return "\n".join(bibtex) 

1919 

1920 def get_ris(self, request=None): 

1921 """ 

1922 

1923 :param self: 

1924 :return: string 

1925 """ 

1926 items = [] 

1927 sep = " - " 

1928 

1929 container = self.my_container 

1930 collection = self.get_collection() 

1931 

1932 is_article = True 

1933 is_incollection = False 

1934 is_inbook = False 

1935 is_phdthesis = False 

1936 

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

1938 is_article = False 

1939 

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

1941 is_phdthesis = True 

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

1943 is_inbook = True 

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

1945 is_incollection = True 

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

1947 is_incollection = True 

1948 

1949 to_appear = container.to_appear() 

1950 is_cr = container.is_cr() 

1951 

1952 # no citation at the article level for a these 

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

1954 return "" 

1955 

1956 type_ = "JOUR" # "article" 

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

1958 type_ = "UNPB" # "unpublished" 

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

1960 type_ = "CHAP" 

1961 elif is_incollection: 

1962 type_ = "CHAP" 

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

1964 

1965 author_names = get_names(self, CONTRIB_TYPE_AUTHOR) 

1966 for author in author_names: 

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

1968 

1969 editor_names = get_names(self, CONTRIB_TYPE_EDITOR) 

1970 for editor in editor_names: 

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

1972 

1973 title = xml_utils.remove_html(self.title_tex) 

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

1975 

1976 collection_title = xml_utils.remove_html(collection.title_tex) 

1977 if collection is not None and is_article: 

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

1979 else: 

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

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

1982 author_names = get_names(container, CONTRIB_TYPE_AUTHOR) 

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

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

1985 

1986 editor_names = get_names(container, CONTRIB_TYPE_EDITOR) 

1987 for editor in editor_names: 

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

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

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

1991 

1992 if not to_appear: 

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

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

1995 # if self.article_number: 

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

1997 

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

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

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

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

2002 

2003 def get_endnote(self, request=None): 

2004 """ 

2005 

2006 :param self: 

2007 :return: string 

2008 """ 

2009 items = [] 

2010 sep = " " 

2011 

2012 container = self.my_container 

2013 collection = self.get_collection() 

2014 

2015 is_article = True 

2016 is_incollection = False 

2017 is_inbook = False 

2018 is_phdthesis = False 

2019 

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

2021 is_article = False 

2022 

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

2024 is_phdthesis = True 

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

2026 is_inbook = True 

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

2028 is_incollection = True 

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

2030 is_incollection = True 

2031 

2032 to_appear = container.to_appear() 

2033 is_cr = container.is_cr() 

2034 

2035 # no citation at the article level for a these 

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

2037 return "" 

2038 

2039 type_ = "Journal Article" # "article" 

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

2041 type_ = "Unpublished Work" # "unpublished" 

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

2043 type_ = "Book Section" 

2044 elif is_incollection: 

2045 type_ = "Book Section" 

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

2047 

2048 author_names = get_names(self, CONTRIB_TYPE_AUTHOR) 

2049 for author in author_names: 

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

2051 

2052 editor_names = get_names(self, CONTRIB_TYPE_EDITOR) 

2053 for editor in editor_names: 

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

2055 

2056 title = xml_utils.remove_html(self.title_tex) 

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

2058 

2059 collection_title = xml_utils.remove_html(collection.title_tex) 

2060 if collection is not None and is_article: 

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

2062 else: 

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

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

2065 author_names = get_names(container, CONTRIB_TYPE_AUTHOR) 

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

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

2068 

2069 editor_names = get_names(container, CONTRIB_TYPE_EDITOR) 

2070 for editor in editor_names: 

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

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

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

2074 

2075 if not to_appear: 

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

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

2078 # if self.article_number: 

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

2080 

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

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

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

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

2085 

2086 def get_conference(self): 

2087 text = "" 

2088 

2089 subjs = [] 

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

2091 if subj.type == "conference": 

2092 subjs.append(subj.value) 

2093 

2094 text = ", ".join(subjs) 

2095 

2096 return text 

2097 

2098 def get_topics(self): 

2099 text = "" 

2100 

2101 subjs = [] 

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

2103 if subj.type == "topic": 

2104 subjs.append(subj.value) 

2105 

2106 text = ", ".join(subjs) 

2107 

2108 return text 

2109 

2110 def get_subj_text(self): 

2111 text = "" 

2112 lang = get_language() 

2113 

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

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

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

2117 

2118 subj_groups = self.get_subjs_by_type_and_lang() 

2119 for type_ in subj_types: 

2120 if type_ in subj_groups: 

2121 sg_type_langs = subj_groups[type_] 

2122 if type_ == "pci": 

2123 subtext = ", ".join( 

2124 list( 

2125 [ 

2126 resolver.get_pci(subj.value) 

2127 for lang_ in sg_type_langs 

2128 for subj in sg_type_langs[lang_] 

2129 ] 

2130 ) 

2131 ) 

2132 else: 

2133 if lang in sg_type_langs: 

2134 subtext = ", ".join( 

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

2136 ) 

2137 else: 

2138 subtext = ", ".join( 

2139 list( 

2140 [ 

2141 subj.value 

2142 for lang_ in sg_type_langs 

2143 if lang_ != lang 

2144 for subj in sg_type_langs[lang_] 

2145 ] 

2146 ) 

2147 ) 

2148 

2149 if text: 

2150 text += " - " 

2151 text += subtext 

2152 

2153 return text 

2154 

2155 def get_pci_section(self): 

2156 pci = "" 

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

2158 if subj.type == "pci": 

2159 pci = subj.value 

2160 

2161 return pci 

2162 

2163 def get_pci_value(self): 

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

2165 

2166 def is_uga_pci(self): 

2167 return self.get_pci_section() in resolver.PCJ_UGA_SECTION 

2168 

2169 def allow_crossref(self): 

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

2171 doi = self.my_container.my_collection.doi 

2172 issn = self.my_container.my_collection.issn 

2173 e_issn = self.my_container.my_collection.e_issn 

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

2175 

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

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

2178 

2179 return result 

2180 

2181 def get_illustrations(self): 

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

2183 

2184 def has_graphical_abstract(self): 

2185 collections = ["CRCHIM"] 

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

2187 

2188 

2189class ResourceCategory(models.Model): 

2190 """non utilisé""" 

2191 

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

2193 

2194 

2195class Provider(models.Model): 

2196 """ 

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

2198 supplémentaires -- à voir 

2199 """ 

2200 

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

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

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

2204 

2205 def __str__(self): 

2206 return self.name 

2207 

2208 

2209class SiteMembership(models.Model): 

2210 """ 

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

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

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

2214 We only have add commands. 

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

2216 """ 

2217 

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

2219 deployed = models.DateTimeField(null=True) 

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

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

2222 

2223 class Meta: 

2224 unique_together = ( 

2225 "resource", 

2226 "site", 

2227 ) 

2228 

2229 

2230class CollectionMembership(models.Model): 

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

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

2233 

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

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

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

2237 # la même chose pour le tri 

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

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

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

2241 

2242 seq = models.IntegerField(db_index=True) 

2243 

2244 class Meta: 

2245 unique_together = ( 

2246 "collection", 

2247 "container", 

2248 ) 

2249 

2250 def __str__(self): 

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

2252 

2253 

2254class RelationName(models.Model): 

2255 """ 

2256 Triple store ;-) 

2257 """ 

2258 

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

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

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

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

2263 

2264 

2265class Relationship(models.Model): 

2266 resource = models.ForeignKey( 

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

2268 ) 

2269 related = models.ForeignKey( 

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

2271 ) 

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

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

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

2275 

2276 

2277class ExtRelationship(models.Model): 

2278 """ 

2279 Triple store (resources externes) 

2280 """ 

2281 

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

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

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

2285 

2286 

2287class XmlBase(models.Model): 

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

2289 

2290 def __str__(self): 

2291 return self.base 

2292 

2293 

2294class DataStream(models.Model): 

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

2296 rel = models.CharField(max_length=32) 

2297 mimetype = models.CharField(max_length=32) 

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

2299 location = models.URLField() 

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

2301 seq = models.IntegerField(db_index=True) 

2302 

2303 class Meta: 

2304 ordering = ["seq"] 

2305 

2306 def __str__(self): 

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

2308 

2309 

2310class ExtLink(models.Model): 

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

2312 rel = models.CharField(max_length=50) 

2313 mimetype = models.CharField(max_length=32) 

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

2315 location = models.CharField(max_length=200) 

2316 metadata = models.TextField() 

2317 seq = models.IntegerField(db_index=True) 

2318 

2319 class Meta: 

2320 ordering = ["seq"] 

2321 

2322 def __str__(self): 

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

2324 

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

2326 if "website" in self.rel: 

2327 self.metadata = "website" 

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

2329 self.seq = self.generate_seq() 

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

2331 

2332 def generate_seq(self): 

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

2334 if max_seq: 

2335 return max_seq + 1 

2336 else: 

2337 return 1 

2338 

2339 def get_href(self): 

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

2341 # construction du chemin vers l'icone 

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

2343 resource_pid = self.resource.pid 

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

2345 return href 

2346 return self.location 

2347 

2348 

2349class RelatedObject(models.Model): 

2350 """ 

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

2352 """ 

2353 

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

2355 rel = models.CharField(max_length=32) 

2356 mimetype = models.CharField(max_length=32) 

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

2358 location = models.CharField(max_length=200) 

2359 metadata = models.TextField() 

2360 seq = models.IntegerField(db_index=True) 

2361 

2362 class Meta: 

2363 ordering = ["seq"] 

2364 

2365 def __str__(self): 

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

2367 

2368 def get_href(self): 

2369 the_dynamic_object = self.resource.cast() 

2370 href = the_dynamic_object.get_binary_file_href_full_path( 

2371 self.rel, self.mimetype, self.location 

2372 ) 

2373 return href 

2374 

2375 

2376class SupplementaryMaterial(RelatedObject): 

2377 caption = models.TextField() 

2378 

2379 def __str__(self): 

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

2381 

2382 def embeded_link(self): 

2383 if "youtube" in self.location: 

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

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

2386 return self.get_href() 

2387 

2388 

2389class MetaDataPart(models.Model): 

2390 """ 

2391 stockage des metadonnées 

2392 qui ne sont pas aiilleurs (!) 

2393 non utilisé - à voir 

2394 3 classes qui font sans doute doublon: 

2395 MetadataPart, ResourceAttribute, CustomMeta -- à voir 

2396 """ 

2397 

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

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

2400 seq = models.IntegerField(db_index=True) 

2401 data = models.TextField() 

2402 

2403 class Meta: 

2404 ordering = ["seq"] 

2405 

2406 

2407class ResourceAttribute(models.Model): 

2408 """ 

2409 not used 

2410 """ 

2411 

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

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

2414 value = models.TextField() 

2415 

2416 

2417class CustomMeta(models.Model): 

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

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

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

2421 

2422 

2423class ResourceId(models.Model): 

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

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

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

2427 

2428 class Meta: 

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

2430 

2431 def __str__(self): 

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

2433 

2434 def get_href(self): 

2435 href = "" 

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

2437 href = resolver.get_doi_url(self.id_value) 

2438 return href 

2439 

2440 

2441class ExtId(models.Model): 

2442 """ 

2443 zbl, mr, jfm, etc.. 

2444 mis à part car non uniques 

2445 """ 

2446 

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

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

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

2450 checked = models.BooleanField(default=True) 

2451 false_positive = models.BooleanField(default=False) 

2452 

2453 class Meta: 

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

2455 

2456 def __str__(self): 

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

2458 

2459 def get_href(self): 

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

2461 

2462 

2463class Abstract(models.Model): 

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

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

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

2467 # specific use, content_type, label ? 

2468 seq = models.IntegerField(db_index=True) 

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

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

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

2472 

2473 class Meta: 

2474 ordering = ["seq"] 

2475 

2476 def __str__(self): 

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

2478 

2479 

2480class Kwd(models.Model): 

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

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

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

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

2485 seq = models.IntegerField(db_index=True) 

2486 

2487 class Meta: 

2488 ordering = ["seq"] 

2489 

2490 def __str__(self): 

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

2492 

2493 

2494class Subj(models.Model): 

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

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

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

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

2499 seq = models.IntegerField(db_index=True) 

2500 

2501 class Meta: 

2502 ordering = ["seq"] 

2503 

2504 def __str__(self): 

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

2506 

2507 

2508class Award(models.Model): 

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

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

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

2512 seq = models.IntegerField(db_index=True) 

2513 

2514 class Meta: 

2515 ordering = ["seq"] 

2516 

2517 

2518class BibItem(models.Model): 

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

2520 sequence = models.PositiveIntegerField(db_index=True) 

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

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

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

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

2525 

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

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

2528 

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

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

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

2532 # get_bibtex) 

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

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

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

2536 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2551 

2552 class Meta: 

2553 ordering = ["sequence"] 

2554 

2555 def __str__(self): 

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

2557 

2558 def get_ref_id(self): 

2559 ref_id = "" 

2560 if hasattr(settings, "SHOW_BODY") and settings.SHOW_BODY: 2560 ↛ 2561line 2560 didn't jump to line 2561, because the condition on line 2560 was never true

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

2562 else: 

2563 ref_id = self.user_id 

2564 return ref_id 

2565 

2566 def get_doi(self): 

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

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

2569 return bibitemId_doi.get().id_value 

2570 return None 

2571 

2572 def get_bibtex(self): 

2573 """ 

2574 

2575 :param self: 

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

2577 """ 

2578 bibtex = [] 

2579 indent = " " 

2580 

2581 author_names = get_bibtex_names(self, "author") 

2582 editor_names = get_bibtex_names(self, "editor") 

2583 

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

2585 id_ = self.user_id 

2586 else: 

2587 id_ = get_bibtex_id(self, self.year) 

2588 

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

2590 

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

2592 append_in_latex(bibtex, indent + author_names) 

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

2594 append_in_latex(bibtex, indent + editor_names) 

2595 

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

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

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

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

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

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

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

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

2604 title = xml_utils.normalise_span(self.article_title_tex) 

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

2606 

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

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

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

2610 

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

2612 keyword = "" 

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

2614 keyword += "title" 

2615 else: 

2616 keyword += "chapter" 

2617 title = xml_utils.normalise_span(self.chapter_title_tex) 

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

2619 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2639 

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

2641 keyword = "" 

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

2643 keyword += "journal" 

2644 elif ( 

2645 self.type == "incollection" 

2646 or self.type == "inproceedings" 

2647 or self.type == "conference" 

2648 ): 

2649 keyword += "booktitle" 

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

2651 keyword += "howpublished" 

2652 else: 

2653 keyword += "title" 

2654 title = xml_utils.normalise_span(self.source_tex) 

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

2656 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2671 

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

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

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

2675 text = self.fpage 

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

2677 text += "--" 

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

2679 text += self.lpage 

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

2681 

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

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

2684 

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

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

2687 

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

2689 type_ = "" 

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

2691 type_ = "zbl" 

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

2693 type_ = "mrnumber" 

2694 elif extid.id_type == "doi": 

2695 type_ = "doi" 

2696 elif extid.id_type == "eid": 

2697 type_ = "eid" 

2698 

2699 if type_: 

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

2701 

2702 append_in_latex(bibtex, "}") 

2703 

2704 return bibtex 

2705 

2706 

2707class BibItemId(models.Model): 

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

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

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

2711 checked = models.BooleanField(default=True) 

2712 false_positive = models.BooleanField(default=False) 

2713 

2714 class Meta: 

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

2716 

2717 def __str__(self): 

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

2719 

2720 def get_href_display(self): 

2721 value = "Article" 

2722 if settings.SITE_ID == 3: 

2723 value = "Numdam" 

2724 else: 

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

2726 value = "Numdam" 

2727 

2728 return value 

2729 

2730 def get_href(self): 

2731 force_numdam = False 

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

2733 force_numdam = True 

2734 

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

2736 

2737 

2738class ContribGroup(models.Model): 

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

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

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

2742 # specific_use ?! 

2743 seq = models.IntegerField() 

2744 

2745 class Meta: 

2746 ordering = ["seq"] 

2747 

2748 

2749class Contrib(models.Model): 

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

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

2752 

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

2754 first_name = models.CharField(max_length=128, db_index=True) 

2755 prefix = models.CharField(max_length=32) 

2756 suffix = models.CharField(max_length=32) 

2757 string_name = models.CharField(max_length=256, db_index=True) 

2758 reference_name = models.CharField(max_length=256, db_index=True) 

2759 deceased = models.BooleanField(default=False) 

2760 orcid = models.CharField(max_length=64, db_index=True, blank=True, default="") 

2761 equal_contrib = models.BooleanField(default=True) 

2762 email = models.EmailField(max_length=254, db_index=True, blank=True, default="") 

2763 

2764 # Could be used to export the contrib 

2765 contrib_xml = models.TextField() 

2766 

2767 seq = models.IntegerField() 

2768 

2769 class Meta: 

2770 ordering = ["seq"] 

2771 

2772 @classmethod 

2773 def get_fields_list(cls): 

2774 return [ 

2775 item.attname 

2776 for item in cls._meta.get_fields() 

2777 if not item.auto_created 

2778 and not item.many_to_many 

2779 and not item.many_to_one 

2780 and not item.one_to_many 

2781 and not item.one_to_one 

2782 ] 

2783 

2784 def __str__(self): 

2785 return "{} {} / {} / {}".format( 

2786 self.last_name, self.first_name, self.reference_name, self.contrib_type or "None" 

2787 ) 

2788 

2789 def get_absolute_url(self): 

2790 return reverse("pretty_search", args=[f'"{self.reference_name}"-c']) 

2791 

2792 def display_name(self): 

2793 display_name = self.string_name 

2794 

2795 if self.is_etal(): 

2796 display_name = "et al." 

2797 elif getattr(settings, "DISPLAY_FIRST_NAME_FIRST", False) and ( 

2798 len(str(self.first_name)) > 0 or len(str(self.last_name)) > 0 

2799 ): 

2800 display_name = f"{self.first_name} {self.last_name}" 

2801 

2802 return display_name 

2803 

2804 def orcid_href(self): 

2805 return resolver.resolve_id("orcid", self.orcid) 

2806 

2807 def get_addresses(self): 

2808 return self.contribaddress_set.all() # .order_by('address') 

2809 

2810 def is_etal(self): 

2811 return self.contrib_xml.startswith("<etal") 

2812 

2813 

2814class Author(models.Model): 

2815 """ 

2816 Count the number of documents (articles,books...) written by an author 

2817 """ 

2818 

2819 name = models.CharField(max_length=200, db_index=True, unique=True) 

2820 first_letter = models.CharField(max_length=1, db_index=True) 

2821 count = models.IntegerField() 

2822 

2823 

2824class FrontMatter(models.Model): 

2825 resource = models.OneToOneField(Resource, on_delete=models.CASCADE) 

2826 

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

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

2829 foreword_html = models.TextField(default="") 

2830 

2831 

2832class LangTable(models.Model): 

2833 code = models.CharField(max_length=3, db_index=True) 

2834 name = models.CharField(max_length=64) 

2835 

2836 

2837class ResourceCount(models.Model): 

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

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

2840 value = models.CharField(max_length=32, db_index=True) 

2841 seq = models.IntegerField() 

2842 

2843 class Meta: 

2844 ordering = ["seq"] 

2845 

2846 

2847class Stats(models.Model): 

2848 name = models.CharField(max_length=128, db_index=True, unique=True) 

2849 value = models.IntegerField() 

2850 

2851 

2852class History(models.Model): 

2853 """pas utilisé pour l'heure""" 

2854 

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

2856 date_type = models.CharField(max_length=32, db_index=True) 

2857 date_value = models.DateTimeField() 

2858 comment = models.TextField() 

2859 

2860 

2861class SerialHistory(models.Model): 

2862 """à revoir""" 

2863 

2864 serial = models.ForeignKey("Collection", on_delete=models.CASCADE) 

2865 date_deployed = models.DateField(auto_now_add=True) 

2866 first_year = models.CharField(max_length=32) 

2867 last_year = models.CharField(max_length=32) 

2868 

2869 

2870# def matches(id_type, id_value): 

2871# if id_type in ('mr-item-id', 'zbl-item-id', 'jfm-item-id'): 

2872# qs = ExtId.objects.filter( 

2873# id_type=id_type, 

2874# id_value=id_value, 

2875# ).select_related('resource', 'resource__resourceid') 

2876# counter = 0 

2877# match_list = [] 

2878# for extid in qs: 

2879# resource = extid.resource 

2880# for idext in resource.extid_set.all(): 

2881# match_list.append({'type': idext.id_type, 'value': idext.id_value}) 

2882# for rid in resource.resourceid_set.all(): 

2883# match_list.append({'type': rid.id_type, 'value': rid.id_value}) 

2884# counter += 1 

2885# return { 

2886# 'id': {'type': id_type, 'value': id_value}, 

2887# 'count': counter, 

2888# 'ids': match_list, 

2889# } 

2890# resource = Resource.objects.get(resourceid__id_type=id_type, 

2891# resourceid__id_value=id_value) 

2892# counter = 0 

2893# match_list = [] 

2894# for idext in resource.extid_set.all(): 

2895# match_list.append({'type': idext.id_type, 'value': idext.id_value}) 

2896# for rid in resource.resourceid_set.all(): 

2897# match_list.append({'type': rid.id_type, 'value': rid.id_value}) 

2898# counter += 1 

2899# return { 

2900# 'id': {'type': id_type, 'value': id_value}, 

2901# 'count': counter, 

2902# 'ids': match_list, 

2903# } 

2904 

2905 

2906def parse_page_count(page_count): 

2907 """ 

2908 page-count is not an integer but a string to be displayed. 

2909 page_count may sometimes mix roman and arabic values. Ex "iv-121" 

2910 """ 

2911 if "-" in page_count: 

2912 result = 0 

2913 values = page_count.split("-") 

2914 for value in values: 

2915 try: 

2916 result += int(value) 

2917 except ValueError: 

2918 pass 

2919 else: 

2920 result = int(page_count) 

2921 

2922 return result 

2923 

2924 

2925@receiver(pre_delete, sender=ResourceCount) 

2926def delete_resourcecount_handler(sender, instance, **kwargs): 

2927 """ 

2928 pre_delete ResourceCount signal 

2929 

2930 Stats are added manually during a addResourceCountDatabaseCmd 

2931 but ResourceCount are deleted automatically by Django when its 

2932 related resource is deleted 

2933 (resource = models.ForeignKey(Resource) of ResourceCount) 

2934 To update Stats, we use the Django signal mechanism 

2935 """ 

2936 if instance and instance.name == "page-count" and instance.resource.classname == "Container": 

2937 # page-count may sometimes mix roman and arabic values. Ex "iv-121" 

2938 value = parse_page_count(instance.value) 

2939 

2940 total = Stats.objects.get(name=instance.name) 

2941 total.value -= value 

2942 total.save() 

2943 

2944 

2945# class Volume: 

2946# 

2947# def get_absolute_url(self): 

2948# return reverse('volume-items', kwargs={'vid': self.id_}) 

2949# 

2950# def __init__(self, id_): 

2951# self.id_ = id_ 

2952# try: 

2953# journal_pid, year_id, self.vseries_id, self.volume_id = id_.split( 

2954# '_') 

2955# except ValueError: 

2956# raise self.DoesNotExist(_('Volume {} does not exist').format(id_)) 

2957# try: 

2958# self.collection = Collection.objects.get( 

2959# pid=journal_pid, sites__id=settings.SITE_ID) 

2960# except Collection.DoesNotExist: 

2961# self.issues = None 

2962# try: 

2963# self.issues = Container.objects.filter( 

2964# my_collection__pid=journal_pid, 

2965# year=year_id, 

2966# vseries=self.vseries_id, 

2967# volume=self.volume_id, 

2968# ).order_by('number_int').all() 

2969# except Container.DoesNotExist: 

2970# self.issues = None 

2971# 

2972# class DoesNotExist(Exception): 

2973# pass 

2974 

2975 

2976class ContribAddress(models.Model): 

2977 contrib = models.ForeignKey("Contrib", blank=True, null=True, on_delete=models.CASCADE) 

2978 contribution = models.ForeignKey( 

2979 "Contribution", blank=True, null=True, on_delete=models.CASCADE 

2980 ) 

2981 address = models.TextField(null=True, blank=True) 

2982 

2983 class Meta: 

2984 ordering = ["pk"] 

2985 

2986 

2987def cmp_container_base(a, b): 

2988 return ( 

2989 a.year < b.year 

2990 or (a.year == b.year and a.vseries_int < b.vseries_int) 

2991 or (a.year == b.year and a.vseries_int == b.vseries_int and a.volume_int < b.volume_int) 

2992 or ( 

2993 a.year == b.year 

2994 and a.vseries_int == b.vseries_int 

2995 and a.volume_int == b.volume_int 

2996 and a.number_int < b.number_int 

2997 ) 

2998 ) 

2999 

3000 

3001class PersonManager(models.Manager): 

3002 @staticmethod 

3003 def clean(): 

3004 pass 

3005 # Person.objects.filter(contributions=None).delete() 

3006 

3007 

3008class Person(models.Model): 

3009 """ 

3010 A Person is a contributor (author/editor...) of a Resource (Article/Book) or a BibItem. 

3011 A Person can appear with different names (ex: "John Smith", "J. Smith") and we want to preserve the differences, 

3012 in particular with print papers that are digitized. 

3013 A Person is unique for a person (a Person is basically the key). 

3014 A Person has one or many PersonInfo which stores the different names 

3015 

3016 TODO: signal similar to delete_contrib_handler 

3017 """ 

3018 

3019 # blank=True and null=True allow unique=True with null values (on multiple Persons) 

3020 orcid = models.CharField(max_length=20, blank=True, null=True) 

3021 idref = models.CharField(max_length=10, blank=True, null=True) 

3022 # mid (Mathdoc id) is the key set by numdam-plus 

3023 mid = models.CharField(max_length=256, blank=True, null=True) 

3024 

3025 last_name = models.CharField(max_length=128, blank=True, default="") 

3026 first_name = models.CharField(max_length=128, blank=True, default="") 

3027 prefix = models.CharField(max_length=32, blank=True, default="") 

3028 suffix = models.CharField(max_length=32, blank=True, default="") 

3029 first_letter = models.CharField(max_length=1, blank=True, default="") 

3030 

3031 # Used when a person is not fully tagged in the XML 

3032 string_name = models.CharField(max_length=256, blank=True, default="") 

3033 

3034 objects = PersonManager() 

3035 

3036 @classmethod 

3037 def get_fields_list(cls): 

3038 return [ 

3039 item.attname 

3040 for item in cls._meta.get_fields() 

3041 if not item.auto_created 

3042 and not item.many_to_many 

3043 and not item.many_to_one 

3044 and not item.one_to_many 

3045 and not item.one_to_one 

3046 ] 

3047 

3048 def __str__(self): 

3049 return get_display_name( 

3050 self.prefix, self.first_name, self.last_name, self.suffix, self.string_name 

3051 ) 

3052 

3053 

3054class PersonInfoManager(models.Manager): 

3055 @staticmethod 

3056 def clean(): 

3057 PersonInfo.objects.filter(contributions=None).delete() 

3058 Person.objects.filter(personinfo=None).delete() 

3059 

3060 

3061class PersonInfo(models.Model): 

3062 person = models.ForeignKey(Person, on_delete=models.CASCADE) 

3063 

3064 last_name = models.CharField(max_length=128) 

3065 first_name = models.CharField(max_length=128) 

3066 prefix = models.CharField(max_length=32) 

3067 suffix = models.CharField(max_length=32) 

3068 

3069 # Used when a person is not fully tagged in the XML 

3070 string_name = models.CharField(max_length=256, blank=True, default="") 

3071 

3072 objects = PersonInfoManager() 

3073 

3074 @classmethod 

3075 def get_fields_list(cls): 

3076 return [ 

3077 item.attname 

3078 for item in cls._meta.get_fields() 

3079 if not item.auto_created 

3080 and not item.many_to_many 

3081 and not item.many_to_one 

3082 and not item.one_to_many 

3083 and not item.one_to_one 

3084 ] 

3085 

3086 def __str__(self): 

3087 return get_display_name( 

3088 self.prefix, self.first_name, self.last_name, self.suffix, self.string_name 

3089 ) 

3090 

3091 

3092class Contribution(models.Model): 

3093 resource = models.ForeignKey( 

3094 Resource, blank=True, null=True, on_delete=models.CASCADE, related_name="contributions" 

3095 ) 

3096 bibitem = models.ForeignKey( 

3097 BibItem, blank=True, null=True, on_delete=models.CASCADE, related_name="contributions" 

3098 ) 

3099 

3100 # blank=True and null=True allow unique=True with null values (on multiple Persons) 

3101 orcid = models.CharField(max_length=20, blank=True, null=True) 

3102 idref = models.CharField(max_length=10, blank=True, null=True) 

3103 # mid (Mathdoc id) is the key set by numdam-plus 

3104 mid = models.CharField(max_length=256, blank=True, null=True) 

3105 

3106 last_name = models.CharField(max_length=128, blank=True, default="") 

3107 first_name = models.CharField(max_length=128, blank=True, default="") 

3108 prefix = models.CharField(max_length=32, blank=True, default="") 

3109 suffix = models.CharField(max_length=32, blank=True, default="") 

3110 first_letter = models.CharField(max_length=1, blank=True, default="") 

3111 

3112 # Used when a person is not fully tagged in the XML 

3113 string_name = models.CharField(max_length=256, blank=True, default="") 

3114 

3115 role = models.CharField(max_length=64) 

3116 email = models.EmailField(max_length=254, blank=True, default="") 

3117 deceased_before_publication = models.BooleanField(default=False) 

3118 equal_contrib = models.BooleanField(default=True) 

3119 corresponding = models.BooleanField(default=False) 

3120 

3121 # Used to export the contribution 

3122 contrib_xml = models.TextField() 

3123 

3124 seq = models.IntegerField() 

3125 

3126 class Meta: 

3127 ordering = ["seq"] 

3128 

3129 @classmethod 

3130 def get_fields_list(cls): 

3131 return [ 

3132 item.attname 

3133 for item in cls._meta.get_fields() 

3134 if not item.auto_created 

3135 and not item.many_to_many 

3136 and not item.many_to_one 

3137 and not item.one_to_many 

3138 and not item.one_to_one 

3139 ] 

3140 

3141 def __str__(self): 

3142 return self.display_name() 

3143 

3144 def is_etal(self): 

3145 return self.contrib_xml.startswith("<etal") 

3146 

3147 def display_name(self): 

3148 return ( 

3149 "et al." 

3150 if self.is_etal() 

3151 else get_display_name( 

3152 self.prefix, self.first_name, self.last_name, self.suffix, self.string_name 

3153 ) 

3154 ) 

3155 

3156 def get_absolute_url(self): 

3157 return reverse("pretty_search", args=[f'"{self.display_name()}"-c']) 

3158 

3159 def orcid_href(self): 

3160 return resolver.resolve_id("orcid", self.orcid) 

3161 

3162 def is_equal(self, contribution): 

3163 """ 

3164 return True if the contribution is identical, based on orcif/idref or homonimy 

3165 TODO: override the __eq__ operator ? Not sure since homonimy is not bullet proof 

3166 """ 

3167 equal = False 

3168 if self.orcid and self.orcid == contribution.orcid: 

3169 equal = True 

3170 elif self.idref and self.idref == contribution.idref: 

3171 equal = True 

3172 else: 

3173 equal = self.display_name() == contribution.display_name() 

3174 

3175 return True 

3176 return equal 

3177 

3178 

3179@receiver(pre_delete, sender=Contribution) 

3180def delete_contrib_handler(sender, instance, **kwargs): 

3181 """ 

3182 pre_delete Contrib signal 

3183 

3184 Contrib and Author are added manually during a 

3185 addResourceDatabaseCmd (mainly addArticleDatabaseCmd) 

3186 but Contrib are deleted automatically by Django when its 

3187 related resource is deleted 

3188 (resource = models.ForeignKey(Resource) of ContribGroup) 

3189 To update Author, we use the Django signal mechanism 

3190 """ 

3191 if instance and instance.role == "author": 

3192 try: 

3193 ref_name = instance.mid if instance.mid else str(instance) 

3194 author = Author.objects.get(name=ref_name) 

3195 author.count -= 1 

3196 if author.count == 0: 

3197 author.delete() 

3198 else: 

3199 author.save() 

3200 except Author.DoesNotExist: 

3201 pass 

3202 

3203 

3204def get_names(item, role): 

3205 """ 

3206 item: resource or bibitem 

3207 """ 

3208 return [ 

3209 str(contribution) for contribution in item.contributions.all() if contribution.role == role 

3210 ] 

3211 

3212 

3213def are_all_equal_contrib(contributions): 

3214 if len(contributions) == 0: 

3215 return False 

3216 

3217 are_all_equal = True 

3218 for contribution in contributions: 

3219 are_all_equal = are_all_equal and contribution.equal_contrib 

3220 

3221 return are_all_equal 

3222 

3223 

3224def are_all_false_equal_contrib(contributions): 

3225 are_all_equal = True 

3226 for contribution in contributions: 

3227 are_all_equal = are_all_equal and not contribution.equal_contrib 

3228 

3229 return are_all_equal 

3230 

3231 

3232class RelatedArticles(models.Model): 

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

3234 # Used during reimport/redeploy to find back the RelatedArticles 

3235 resource_doi = models.CharField(max_length=64, unique=True, null=True, blank=True) 

3236 date_modified = models.DateTimeField(null=True, blank=True) 

3237 doi_list = models.TextField(null=True, blank=True) 

3238 exclusion_list = models.TextField(null=True, blank=True) 

3239 automatic_list = models.BooleanField(default=True) 

3240 

3241 def __str__(self): 

3242 if self.resource and hasattr(self.resource, "doi"): 

3243 return f"{self.resource.doi}" 

3244 

3245 

3246def image_path_graphical_abstract(instance, filename): 

3247 path = "images" 

3248 return os.path.join(path, str(instance.id), "graphical_abstract", filename) 

3249 

3250 

3251def image_path_illustration(instance, filename): 

3252 path = "images" 

3253 return os.path.join(path, str(instance.id), "illustration", filename) 

3254 

3255 

3256class OverwriteStorage(FileSystemStorage): 

3257 def get_available_name(self, name, max_length=None): 

3258 if self.exists(name): 

3259 os.remove(os.path.join(settings.MEDIA_ROOT, name)) 

3260 return name 

3261 

3262 

3263class GraphicalAbstract(models.Model): 

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

3265 # Used during reimport/redeploy to find back the GraphicalAbstracts 

3266 resource_doi = models.CharField(max_length=64, unique=True, null=True, blank=True) 

3267 date_modified = models.DateTimeField(null=True, blank=True) 

3268 graphical_abstract = models.ImageField( 

3269 upload_to=image_path_graphical_abstract, blank=True, null=True, storage=OverwriteStorage 

3270 ) 

3271 illustration = models.ImageField( 

3272 upload_to=image_path_illustration, blank=True, null=True, storage=OverwriteStorage 

3273 ) 

3274 

3275 def __str__(self): 

3276 if self.resource and hasattr(self.resource, "doi"): 

3277 return f"{self.resource.doi}" 

3278 

3279 

3280def backup_obj_not_in_metadata(article): 

3281 for class_name in ["GraphicalAbstract", "RelatedArticles"]: 

3282 qs = globals()[class_name].objects.filter(resource=article) 

3283 if qs.exists(): 3283 ↛ 3284line 3283 didn't jump to line 3284, because the condition on line 3283 was never true

3284 obj = qs.first() 

3285 obj.resource_doi = article.doi 

3286 obj.resource = None 

3287 obj.save() 

3288 

3289 qs = TranslatedArticle.objects.filter(original_article=article) 

3290 if qs.exists(): 3290 ↛ 3291line 3290 didn't jump to line 3291, because the condition on line 3290 was never true

3291 obj = qs.first() 

3292 obj.original_article_doi = article.doi 

3293 obj.original_article = None 

3294 obj.save() 

3295 

3296 

3297def restore_obj_not_in_metadata(article): 

3298 for class_name in ["GraphicalAbstract", "RelatedArticles"]: 

3299 qs = globals()[class_name].objects.filter(resource_doi=article.doi, resource__isnull=True) 

3300 if qs.exists(): 3300 ↛ 3301line 3300 didn't jump to line 3301, because the condition on line 3300 was never true

3301 obj = qs.first() 

3302 obj.resource = article 

3303 obj.save() 

3304 

3305 qs = TranslatedArticle.objects.filter( 

3306 original_article_doi=article.doi, original_article__isnull=True 

3307 ) 

3308 if qs.exists(): 3308 ↛ 3309line 3308 didn't jump to line 3309, because the condition on line 3308 was never true

3309 obj = qs.first() 

3310 obj.original_article = article 

3311 obj.save() 

3312 

3313 

3314class TranslatedArticle(Article): 

3315 original_article = models.ForeignKey( 

3316 Article, null=True, blank=True, on_delete=models.CASCADE, related_name="translations" 

3317 ) 

3318 # Used during reimport/redeploy in Trammel to find back the TranslatedArticle 

3319 original_article_doi = models.CharField(max_length=64, unique=True, null=True, blank=True) 

3320 

3321 def get_year(self): 

3322 return self.original_article.get_year() 

3323 

3324 def get_ojs_id(self): 

3325 return self.original_article.get_ojs_id() 

3326 

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

3328 """ 

3329 Returns an encoded URL to the pdf of a translated article 

3330 

3331 Input: doctype: language (ex: 'fr', 'en' 

3332 mimetype: 'application/pdf' 

3333 location is ignored 

3334 

3335 Ex: /article/fr/10.5802/crmath.100.pdf 

3336 """ 

3337 

3338 if mimetype != "application/pdf": 

3339 return "" 

3340 

3341 pid = ( 

3342 self.original_article.doi 

3343 if self.original_article.doi is not None 

3344 else self.original_article.pid 

3345 ) 

3346 extension = "pdf" 

3347 href = reverse( 

3348 "article-translated-pdf", 

3349 kwargs={"pid": pid, "extension": extension, "binary_file_type": self.lang}, 

3350 ) 

3351 

3352 return href 

3353 

3354 def get_citation(self, request=None): 

3355 citation = "" 

3356 

3357 translator_names = get_names(self, "translator") 

3358 citation += "; ".join(translator_names) 

3359 

3360 if citation: 

3361 if not citation.endswith("."): 

3362 citation += ". " 

3363 citation += " " 

3364 

3365 if self.lang == self.original_article.trans_lang: 

3366 citation += self.original_article.trans_title_tex 

3367 else: 

3368 citation += self.title_tex 

3369 

3370 year = self.date_published.strftime("%Y") if self.date_published is not None else "YYYY" 

3371 citation += " (" + year + ")" 

3372 

3373 if self.doi is not None: 

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

3375 

3376 # page_text = self.get_page_text(True) 

3377 # citation += self.original_article.get_citation_base(year, page_text) 

3378 

3379 citation += " (" + self.original_article.get_citation()[0:-1] + ")" 

3380 

3381 # author_names = get_names(self.original_article, "author") 

3382 # if author_names: 

3383 # authors = '; '.join(author_names) 

3384 # else: 

3385 # author_names = get_names(self.original_article, "editor") 

3386 # if author_names: 

3387 # authors = '; '.join(author_names) + ' (' + str( 

3388 # _("éd.")) + ')' 

3389 # else: 

3390 # authors = '' 

3391 # citation += authors 

3392 # 

3393 # if citation: 

3394 # if not citation.endswith('.'): 

3395 # citation += '.' 

3396 # citation += ' ' 

3397 # 

3398 # citation += self.original_article.title_tex 

3399 # 

3400 # if self.lang == self.original_article.trans_lang: 

3401 # citation += f" [{self.original_article.trans_title_tex}]" 

3402 # else: 

3403 # citation += f" [{self.title_tex}]" 

3404 # 

3405 # citation += ' (' + str(_('Traduit par')) + ' ' 

3406 # 

3407 # citation += '). ' 

3408 # 

3409 # citation += self.original_article.my_container.my_collection.title_tex 

3410 

3411 # if self.doi is not None: 

3412 # citation += ' doi : ' + self.doi + '.' 

3413 # 

3414 # if request is not None: 

3415 # url = "{}://{}{}".format(request.scheme, request.get_host(), self.original_article.get_absolute_url()) 

3416 # citation += ' ' + url 

3417 # 

3418 # citation += " (" + str(_('Article original publié en ')) + self.original_article.my_container.year + ')' 

3419 

3420 return citation