Coverage for sites/ptf_tools/ptf_tools/views/cms_views.py: 36%

744 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2024-11-04 17:46 +0000

1import base64 

2import json 

3import os 

4import re 

5import shutil 

6from datetime import datetime 

7 

8import requests 

9from ckeditor_uploader.views import ImageUploadView 

10from ckeditor_uploader.views import browse 

11from munch import Munch 

12from requests import Timeout 

13 

14from django.conf import settings 

15from django.contrib import messages 

16from django.contrib.auth.mixins import UserPassesTestMixin 

17from django.core.exceptions import PermissionDenied 

18from django.forms.models import model_to_dict 

19from django.http import Http404 

20from django.http import HttpResponse 

21from django.http import HttpResponseBadRequest 

22from django.http import HttpResponseRedirect 

23from django.http import HttpResponseServerError 

24from django.http import JsonResponse 

25from django.shortcuts import get_object_or_404 

26from django.shortcuts import redirect 

27from django.urls import resolve 

28from django.urls import reverse 

29from django.utils import timezone 

30from django.utils.safestring import mark_safe 

31from django.views.decorators.csrf import csrf_exempt 

32from django.views.generic import CreateView 

33from django.views.generic import TemplateView 

34from django.views.generic import UpdateView 

35from django.views.generic import View 

36 

37from mersenne_cms.models import MERSENNE_ID_VIRTUAL_ISSUES 

38from mersenne_cms.models import News 

39from mersenne_cms.models import Page 

40from mersenne_cms.models import get_news_content 

41from mersenne_cms.models import get_pages_content 

42from mersenne_cms.models import import_news 

43from mersenne_cms.models import import_pages 

44from ptf import model_data_converter 

45from ptf import model_helpers 

46from ptf.cmds import solr_cmds 

47from ptf.cmds import xml_cmds 

48from ptf.cmds.ptf_cmds import base_ptf_cmds 

49from ptf.cmds.xml import xml_utils 

50from ptf.cmds.xml.jats.builder.issue import get_issue_title_xml 

51 

52# from ptf.display import resolver 

53from ptf.exceptions import ServerUnderMaintenance 

54 

55# from ptf.model_data import ArticleData 

56from ptf.model_data import IssueData 

57from ptf.model_data import create_contributor 

58from ptf.model_data import create_publisherdata 

59 

60# from ptf.models import ExtLink 

61# from ptf.models import ResourceInSpecialIssue 

62# from ptf.models import Contribution 

63# from ptf.models import Collection 

64# from ptf.models import Abstract 

65from ptf.models import Article 

66from ptf.models import Collection 

67from ptf.models import Container 

68from ptf.models import ContribAddress 

69from ptf.models import GraphicalAbstract 

70from ptf.models import RelatedArticles 

71from ptf.models import get_names 

72from ptf.site_register import SITE_REGISTER 

73from ptf_tools.forms import GraphicalAbstractForm 

74from ptf_tools.forms import NewsForm 

75from ptf_tools.forms import PageForm 

76from ptf_tools.forms import RelatedForm 

77from ptf_tools.utils import is_authorized_editor 

78 

79from .base_views import check_lock 

80 

81 

82def get_media_base_root(colid): 

83 """ 

84 Base folder where media files are stored in Trammel 

85 """ 

86 if colid in ["CRMECA", "CRBIOL", "CRGEOS", "CRCHIM", "CRMATH", "CRPHYS"]: 

87 colid = "CR" 

88 

89 return os.path.join(settings.RESOURCES_ROOT, "media", colid) 

90 

91 

92def get_media_base_root_in_test(colid): 

93 """ 

94 Base folder where media files are stored in the test website 

95 Use the same folder as the Trammel media folder so that no copy is necessary when deploy in test 

96 """ 

97 return get_media_base_root(colid) 

98 

99 

100def get_media_base_root_in_prod(colid): 

101 """ 

102 Base folder where media files are stored in the prod website 

103 """ 

104 if colid in ["CRMECA", "CRBIOL", "CRGEOS", "CRCHIM", "CRMATH", "CRPHYS"]: 

105 colid = "CR" 

106 

107 return os.path.join(settings.MERSENNE_PROD_DATA_FOLDER, "media", colid) 

108 

109 

110def get_media_base_url(colid): 

111 path = os.path.join(settings.MEDIA_URL, colid) 

112 

113 if colid in ["CRMECA", "CRBIOL", "CRGEOS", "CRCHIM", "CRMATH", "CRPHYS"]: 

114 prefixes = { 

115 "CRMECA": "mecanique", 

116 "CRBIOL": "biologies", 

117 "CRGEOS": "geoscience", 

118 "CRCHIM": "chimie", 

119 "CRMATH": "mathematique", 

120 "CRPHYS": "physique", 

121 } 

122 path = f"/{prefixes[colid]}{settings.MEDIA_URL}/CR" 

123 

124 return path 

125 

126 

127def change_ckeditor_storage(colid): 

128 """ 

129 By default, CKEditor stores all the files under 1 folder (MEDIA_ROOT) 

130 We want to store the files under a subfolder of @colid 

131 To do that we have to 

132 - change the URL calling this view to pass the site_id (info used by the Pages to filter the objects) 

133 - modify the storage location 

134 """ 

135 

136 from ckeditor_uploader import utils 

137 from ckeditor_uploader import views 

138 

139 from django.core.files.storage import FileSystemStorage 

140 

141 storage = FileSystemStorage( 

142 location=get_media_base_root(colid), base_url=get_media_base_url(colid) 

143 ) 

144 

145 utils.storage = storage 

146 views.storage = storage 

147 

148 

149class EditorRequiredMixin(UserPassesTestMixin): 

150 def test_func(self): 

151 return is_authorized_editor(self.request.user, self.kwargs.get("colid")) 

152 

153 

154class CollectionImageUploadView(EditorRequiredMixin, ImageUploadView): 

155 """ 

156 By default, CKEditor stores all the files under 1 folder (MEDIA_ROOT) 

157 We want to store the files under a subfolder of @colid 

158 To do that we have to 

159 - change the URL calling this view to pass the site_id (info used by the Pages to filter the objects) 

160 - modify the storage location 

161 """ 

162 

163 def dispatch(self, request, *args, **kwargs): 

164 colid = kwargs["colid"] 

165 

166 change_ckeditor_storage(colid) 

167 

168 return super().dispatch(request, **kwargs) 

169 

170 

171class CollectionBrowseView(EditorRequiredMixin, View): 

172 def dispatch(self, request, **kwargs): 

173 colid = kwargs["colid"] 

174 

175 change_ckeditor_storage(colid) 

176 

177 return browse(request) 

178 

179 

180file_upload_in_collection = csrf_exempt(CollectionImageUploadView.as_view()) 

181file_browse_in_collection = csrf_exempt(CollectionBrowseView.as_view()) 

182 

183 

184def deploy_cms(site, collection): 

185 colid = collection.pid 

186 base_url = getattr(collection, site)() 

187 

188 if base_url is None: 188 ↛ 191line 188 didn't jump to line 191, because the condition on line 188 was never false

189 return JsonResponse({"message": "OK"}) 

190 

191 if site == "website": 

192 from_base_path = get_media_base_root_in_test(colid) 

193 to_base_path = get_media_base_root_in_prod(colid) 

194 

195 for sub_path in ["uploads", "images"]: 

196 from_path = os.path.join(from_base_path, sub_path) 

197 to_path = os.path.join(to_base_path, sub_path) 

198 if os.path.exists(from_path): 

199 try: 

200 shutil.copytree(from_path, to_path, dirs_exist_ok=True) 

201 except OSError as exception: 

202 return HttpResponseServerError(f"Error during copy: {exception}") 

203 

204 site_id = model_helpers.get_site_id(colid) 

205 if model_helpers.get_site_default_language(site_id): 

206 from modeltranslation import fields 

207 from modeltranslation import manager 

208 

209 old_ftor = manager.get_language 

210 manager.get_language = monkey_get_language_en 

211 fields.get_language = monkey_get_language_en 

212 

213 pages = get_pages_content(colid) 

214 news = get_news_content(colid) 

215 

216 manager.get_language = old_ftor 

217 fields.get_language = old_ftor 

218 else: 

219 pages = get_pages_content(colid) 

220 news = get_news_content(colid) 

221 

222 data = json.dumps({"pages": json.loads(pages), "news": json.loads(news)}) 

223 url = getattr(collection, site)() + "/import_cms/" 

224 

225 try: 

226 response = requests.put(url, data=data, verify=False) 

227 

228 if response.status_code == 503: 

229 e = ServerUnderMaintenance( 

230 "The journal test website is under maintenance. Please try again later." 

231 ) 

232 return HttpResponseServerError(e, status=503) 

233 

234 except Timeout as exception: 

235 return HttpResponse(exception, status=408) 

236 except Exception as exception: 

237 return HttpResponseServerError(exception) 

238 

239 return JsonResponse({"message": "OK"}) 

240 

241 

242class HandleCMSMixin(EditorRequiredMixin): 

243 """ 

244 Mixin for classes that need to send request to (test) website to import/export CMS content (pages, news) 

245 """ 

246 

247 # def dispatch(self, request, *args, **kwargs): 

248 # self.colid = self.kwargs["colid"] 

249 # return super().dispatch(request, *args, **kwargs) 

250 

251 def init_data(self, kwargs): 

252 self.collection = None 

253 

254 self.colid = kwargs.get("colid", None) 

255 if self.colid: 

256 self.collection = model_helpers.get_collection(self.colid) 

257 if not self.collection: 

258 raise Http404(f"{self.colid} does not exist") 

259 

260 test_server_url = self.collection.test_website() 

261 if not test_server_url: 

262 raise Http404("The collection has no test site") 

263 

264 prod_server_url = self.collection.website() 

265 if not prod_server_url: 

266 raise Http404("The collection has no prod site") 

267 

268 

269class GetCMSFromSiteAPIView(HandleCMSMixin, View): 

270 """ 

271 Get the CMS content from the (test) website and save it on disk. 

272 It can be used if needed to restore the Trammel content with RestoreCMSAPIView below 

273 """ 

274 

275 def get(self, request, *args, **kwargs): 

276 self.init_data(self.kwargs) 

277 

278 site = kwargs.get("site", "test_website") 

279 

280 try: 

281 url = getattr(self.collection, site)() + "/export_cms/" 

282 response = requests.get(url, verify=False) 

283 

284 # Just to need to save the json on disk 

285 # Media files are already saved in MEDIA_ROOT which is equal to 

286 # /mersenne_test_data/@colid/media 

287 folder = get_media_base_root(self.colid) 

288 os.makedirs(folder, exist_ok=True) 

289 filename = os.path.join(folder, f"pages_{self.colid}.json") 

290 with open(filename, mode="w", encoding="utf-8") as file: 

291 file.write(response.content.decode(encoding="utf-8")) 

292 

293 except Timeout as exception: 

294 return HttpResponse(exception, status=408) 

295 except Exception as exception: 

296 return HttpResponseServerError(exception) 

297 

298 return JsonResponse({"message": "OK", "status": 200}) 

299 

300 

301def monkey_get_language_en(): 

302 return "en" 

303 

304 

305class RestoreCMSAPIView(HandleCMSMixin, View): 

306 """ 

307 Restore the Trammel CMS content (of a colid) from disk 

308 """ 

309 

310 def get(self, request, *args, **kwargs): 

311 self.init_data(self.kwargs) 

312 

313 folder = get_media_base_root(self.colid) 

314 filename = os.path.join(folder, f"pages_{self.colid}.json") 

315 with open(filename, encoding="utf-8") as f: 

316 json_data = json.load(f) 

317 

318 pages = json_data.get("pages") 

319 

320 site_id = model_helpers.get_site_id(self.colid) 

321 if model_helpers.get_site_default_language(site_id): 

322 from modeltranslation import fields 

323 from modeltranslation import manager 

324 

325 old_ftor = manager.get_language 

326 manager.get_language = monkey_get_language_en 

327 fields.get_language = monkey_get_language_en 

328 

329 import_pages(pages, self.colid) 

330 

331 manager.get_language = old_ftor 

332 fields.get_language = old_ftor 

333 else: 

334 import_pages(pages, self.colid) 

335 

336 if "news" in json_data: 

337 news = json_data.get("news") 

338 import_news(news, self.colid) 

339 

340 return JsonResponse({"message": "OK", "status": 200}) 

341 

342 

343class DeployCMSAPIView(HandleCMSMixin, View): 

344 def get(self, request, *args, **kwargs): 

345 self.init_data(self.kwargs) 

346 

347 if check_lock(): 

348 msg = "Trammel is under maintenance. Please try again later." 

349 messages.error(self.request, msg) 

350 return JsonResponse({"messages": msg, "status": 503}) 

351 

352 site = kwargs.get("site", "test_website") 

353 

354 response = deploy_cms(site, self.collection) 

355 

356 if response.status_code == 503: 

357 messages.error( 

358 self.request, "The journal website is under maintenance. Please try again later." 

359 ) 

360 

361 return response 

362 

363 

364def get_server_urls(collection, site="test_website"): 

365 urls = [""] 

366 if hasattr(settings, "MERSENNE_DEV_URL"): 366 ↛ 368line 366 didn't jump to line 368, because the condition on line 366 was never true

367 # set RESOURCES_ROOT and apache config accordingly (for instance with "/mersenne_dev_data") 

368 url = getattr(collection, "test_website")().split(".fr") 

369 urls = [settings.MERSENNE_DEV_URL + url[1] if len(url) == 2 else ""] 

370 elif site == "both": 370 ↛ 371line 370 didn't jump to line 371, because the condition on line 370 was never true

371 urls = [getattr(collection, "test_website")(), getattr(collection, "website")()] 

372 elif hasattr(collection, site) and getattr(collection, site)(): 372 ↛ 373line 372 didn't jump to line 373, because the condition on line 372 was never true

373 urls = [getattr(collection, site)()] 

374 return urls 

375 

376 

377class SuggestDeployView(EditorRequiredMixin, View): 

378 def post(self, request, *args, **kwargs): 

379 doi = kwargs.get("doi", "") 

380 site = kwargs.get("site", "test_website") 

381 article = get_object_or_404(Article, doi=doi) 

382 

383 obj, created = RelatedArticles.objects.get_or_create(resource=article) 

384 form = RelatedForm(request.POST or None, instance=obj) 

385 if form.is_valid(): 385 ↛ 402line 385 didn't jump to line 402, because the condition on line 385 was never false

386 data = form.cleaned_data 

387 obj.date_modified = timezone.now() 

388 form.save() 

389 collection = article.my_container.my_collection 

390 urls = get_server_urls(collection, site=site) 

391 response = requests.models.Response() 

392 for url in urls: 392 ↛ 400line 392 didn't jump to line 400, because the loop on line 392 didn't complete

393 url = url + reverse("api-update-suggest", kwargs={"doi": doi}) 

394 try: 

395 response = requests.post(url, data=data, timeout=15) 

396 except requests.exceptions.RequestException as e: 

397 response.status_code = 503 

398 response.reason = e.args[0] 

399 break 

400 return HttpResponse(status=response.status_code, reason=response.reason) 

401 else: 

402 return HttpResponseBadRequest() 

403 

404 

405def suggest_debug(results, article, message): 

406 crop_results = 5 

407 if results: 407 ↛ 408line 407 didn't jump to line 408, because the condition on line 407 was never true

408 dois = [] 

409 results["docs"] = results["docs"][:crop_results] 

410 numFound = f'({len(results["docs"])} sur {results["numFound"]} documents)' 

411 head = f"Résultats de la recherche automatique {numFound} :\n\n" 

412 for item in results["docs"]: 

413 doi = item.get("doi") 

414 if doi: 

415 explain = results["explain"][item["id"]] 

416 terms = re.findall(r"([0-9.]+?) = weight\((.+?:.+?) in", explain) 

417 terms.sort(key=lambda t: t[0], reverse=True) 

418 details = (" + ").join(f"{round(float(s), 1)}:{t}" for s, t in terms) 

419 score = f'Score : {round(float(item["score"]), 1)} (= {details})\n' 

420 url = "" 

421 suggest = Article.objects.filter(doi=doi).first() 

422 if suggest and suggest.my_container: 

423 collection = suggest.my_container.my_collection 

424 base_url = collection.website() or "" 

425 url = base_url + "/articles/" + doi 

426 dois.append((doi, url, score)) 

427 

428 tail = f'\n\nScore minimum retenu : {results["params"]["min_score"]}\n\n\n' 

429 tail += "Termes principaux utilisés pour la requête " 

430 tail = [tail + "(champ:terme recherché | pertinence du terme) :\n"] 

431 if results["params"]["mlt.fl"] == "all": 

432 tail.append(" * all = body + abstract + title + authors + keywords\n") 

433 terms = results["interestingTerms"] 

434 terms = [" | ".join((x[0], str(x[1]))) for x in zip(terms[::2], terms[1::2])] 

435 tail.extend(reversed(terms)) 

436 tail.append("\n\nParamètres de la requête :\n") 

437 tail.extend([f"{k}: {v} " for k, v in results["params"].items()]) 

438 return [(head, dois, "\n".join(tail))] 

439 else: 

440 msg = f'Erreur {message["status"]} {message["err"]} at {message["url"]}' 

441 return [(msg, [], "")] 

442 

443 

444class SuggestUpdateView(EditorRequiredMixin, TemplateView): 

445 template_name = "editorial_tools/suggested.html" 

446 

447 def get_context_data(self, **kwargs): 

448 doi = kwargs.get("doi", "") 

449 article = get_object_or_404(Article, doi=doi) 

450 

451 obj, created = RelatedArticles.objects.get_or_create(resource=article) 

452 collection = article.my_container.my_collection 

453 base_url = collection.website() or "" 

454 response = requests.models.Response() 

455 try: 

456 response = requests.get(base_url + "/mlt/" + doi, timeout=10.0) 

457 except requests.exceptions.RequestException as e: 

458 response.status_code = 503 

459 response.reason = e.args[0] 

460 msg = { 

461 "url": response.url, 

462 "status": response.status_code, 

463 "err": response.reason, 

464 } 

465 results = None 

466 if response.status_code == 200: 466 ↛ 467line 466 didn't jump to line 467, because the condition on line 466 was never true

467 results = solr_cmds.auto_suggest_doi(obj, article, response.json()) 

468 context = super().get_context_data(**kwargs) 

469 context["debug"] = suggest_debug(results, article, msg) 

470 context["form"] = RelatedForm(instance=obj) 

471 context["author"] = "; ".join(get_names(article, "author")) 

472 context["citation_base"] = article.get_citation_base().strip(", .") 

473 context["article"] = article 

474 context["date_modified"] = obj.date_modified 

475 context["url"] = base_url + "/articles/" + doi 

476 return context 

477 

478 

479class EditorialToolsVolumeItemsView(EditorRequiredMixin, TemplateView): 

480 template_name = "editorial_tools/volume-items.html" 

481 

482 def get_context_data(self, **kwargs): 

483 vid = kwargs.get("vid") 

484 site_name = settings.SITE_NAME if hasattr(settings, "SITE_NAME") else "" 

485 is_cr = len(site_name) == 6 and site_name[0:2] == "cr" 

486 issues_articles, collection = model_helpers.get_issues_in_volume(vid, is_cr) 

487 context = super().get_context_data(**kwargs) 

488 context["issues_articles"] = issues_articles 

489 context["collection"] = collection 

490 return context 

491 

492 

493class EditorialToolsArticleView(EditorRequiredMixin, TemplateView): 

494 template_name = "editorial_tools/find-article.html" 

495 

496 def get_context_data(self, **kwargs): 

497 colid = kwargs.get("colid") 

498 doi = kwargs.get("doi") 

499 article = get_object_or_404(Article, doi=doi, my_container__my_collection__pid=colid) 

500 

501 context = super().get_context_data(**kwargs) 

502 context["article"] = article 

503 context["citation_base"] = article.get_citation_base().strip(", .") 

504 return context 

505 

506 

507class GraphicalAbstractUpdateView(EditorRequiredMixin, TemplateView): 

508 template_name = "editorial_tools/graphical-abstract.html" 

509 

510 def get_context_data(self, **kwargs): 

511 doi = kwargs.get("doi", "") 

512 article = get_object_or_404(Article, doi=doi) 

513 

514 obj, created = GraphicalAbstract.objects.get_or_create(resource=article) 

515 context = super().get_context_data(**kwargs) 

516 context["author"] = "; ".join(get_names(article, "author")) 

517 context["citation_base"] = article.get_citation_base().strip(", .") 

518 context["article"] = article 

519 context["date_modified"] = obj.date_modified 

520 context["form"] = GraphicalAbstractForm(instance=obj) 

521 context["graphical_abstract"] = obj.graphical_abstract 

522 context["illustration"] = obj.illustration 

523 return context 

524 

525 

526class GraphicalAbstractDeployView(EditorRequiredMixin, View): 

527 def post(self, request, *args, **kwargs): 

528 doi = kwargs.get("doi", "") 

529 site = kwargs.get("site", "both") 

530 article = get_object_or_404(Article, doi=doi) 

531 

532 obj, created = GraphicalAbstract.objects.get_or_create(resource=article) 

533 form = GraphicalAbstractForm(request.POST, request.FILES or None, instance=obj) 

534 if form.is_valid(): 534 ↛ 561line 534 didn't jump to line 561, because the condition on line 534 was never false

535 obj.date_modified = timezone.now() 

536 data = {"date_modified": obj.date_modified} 

537 form.save() 

538 files = {} 

539 if obj.graphical_abstract and os.path.exists(obj.graphical_abstract.path): 539 ↛ 540line 539 didn't jump to line 540, because the condition on line 539 was never true

540 with open(obj.graphical_abstract.path, "rb") as fp: 

541 files.update({"graphical_abstract": (obj.graphical_abstract.name, fp.read())}) 

542 if obj.illustration and os.path.exists(obj.illustration.path): 542 ↛ 543line 542 didn't jump to line 543, because the condition on line 542 was never true

543 with open(obj.illustration.path, "rb") as fp: 

544 files.update({"illustration": (obj.illustration.name, fp.read())}) 

545 collection = article.my_container.my_collection 

546 urls = get_server_urls(collection, site=site) 

547 response = requests.models.Response() 

548 for url in urls: 548 ↛ 559line 548 didn't jump to line 559, because the loop on line 548 didn't complete

549 url = url + reverse("api-graphical-abstract", kwargs={"doi": doi}) 

550 try: 

551 if not obj.graphical_abstract and not obj.illustration: 551 ↛ 554line 551 didn't jump to line 554, because the condition on line 551 was never false

552 response = requests.delete(url, data=data, files=files, timeout=15) 

553 else: 

554 response = requests.post(url, data=data, files=files, timeout=15) 

555 except requests.exceptions.RequestException as e: 

556 response.status_code = 503 

557 response.reason = e.args[0] 

558 break 

559 return HttpResponse(status=response.status_code, reason=response.reason) 

560 else: 

561 return HttpResponseBadRequest() 

562 

563 

564def parse_content(content): 

565 table = re.search(r'(.*?)(<table id="summary".+?</table>)(.*)', content, re.DOTALL) 

566 if not table: 

567 return {"head": content, "tail": "", "articles": []} 

568 

569 articles = [] 

570 rows = re.findall(r"<tr>.+?</tr>", table.group(2), re.DOTALL) 

571 for row in rows: 

572 citation = re.search(r'<div href=".*?">(.*?)</div>', row, re.DOTALL) 

573 href = re.search(r'href="(.+?)\/?">', row) 

574 doi = re.search(r"(10[.].+)", href.group(1)) if href else "" 

575 src = re.search(r'<img.+?src="(.+?)"', row) 

576 item = {} 

577 item["citation"] = citation.group(1) if citation else "" 

578 item["doi"] = doi.group(1) if doi else href.group(1) if href else "" 

579 item["src"] = src.group(1) if src else "" 

580 item["imageName"] = item["src"].split("/")[-1] if item["src"] else "" 

581 if item["doi"] or item["src"]: 

582 articles.append(item) 

583 return {"head": table.group(1), "tail": table.group(3), "articles": articles} 

584 

585 

586class VirtualIssueParseView(EditorRequiredMixin, View): 

587 def get(self, request, *args, **kwargs): 

588 pid = kwargs.get("pid", "") 

589 page = get_object_or_404(Page, id=pid) 

590 

591 data = {"pid": pid} 

592 data["colid"] = kwargs.get("colid", "") 

593 journal = model_helpers.get_collection(data["colid"]) 

594 data["journal_title"] = journal.title_tex.replace(".", "") 

595 site_id = model_helpers.get_site_id(data["colid"]) 

596 data["page"] = model_to_dict(page) 

597 pages = Page.objects.filter(site_id=site_id).exclude(id=pid) 

598 data["parents"] = [model_to_dict(p, fields=["id", "menu_title"]) for p in pages] 

599 

600 content_fr = parse_content(page.content_fr) 

601 data["head_fr"] = content_fr["head"] 

602 data["tail_fr"] = content_fr["tail"] 

603 

604 content_en = parse_content(page.content_en) 

605 data["articles"] = content_en["articles"] 

606 data["head_en"] = content_en["head"] 

607 data["tail_en"] = content_en["tail"] 

608 return JsonResponse(data) 

609 

610 

611class VirtualIssueUpdateView(EditorRequiredMixin, TemplateView): 

612 template_name = "editorial_tools/virtual-issue.html" 

613 

614 def get(self, request, *args, **kwargs): 

615 pid = kwargs.get("pid", "") 

616 get_object_or_404(Page, id=pid) 

617 return super().get(request, *args, **kwargs) 

618 

619 

620class VirtualIssueCreateView(EditorRequiredMixin, View): 

621 def get(self, request, *args, **kwargs): 

622 colid = kwargs.get("colid", "") 

623 site_id = model_helpers.get_site_id(colid) 

624 parent, _ = Page.objects.get_or_create( 

625 mersenne_id=MERSENNE_ID_VIRTUAL_ISSUES, 

626 parent_page=None, 

627 site_id=site_id, 

628 ) 

629 page = Page.objects.create( 

630 menu_title_en="New virtual issue", 

631 menu_title_fr="Nouvelle collection transverse", 

632 parent_page=parent, 

633 site_id=site_id, 

634 state="draft", 

635 ) 

636 kwargs = {"colid": colid, "pid": page.id} 

637 return HttpResponseRedirect(reverse("virtual_issue_update", kwargs=kwargs)) 

638 

639 

640class SpecialIssuesIndex(EditorRequiredMixin, TemplateView): 

641 template_name = "editorial_tools/special-issues-index.html" 

642 

643 def get_context_data(self, **kwargs): 

644 colid = kwargs.get("colid", "") 

645 

646 context = super().get_context_data(**kwargs) 

647 context["colid"] = colid 

648 collection = Collection.objects.get(pid=colid) 

649 context["special_issues"] = Container.objects.filter(ctype="issue_special").filter( 

650 my_collection=collection 

651 ) 

652 

653 context["journal"] = model_helpers.get_collection(colid, sites=False) 

654 return context 

655 

656 

657class SpecialIssueEditView(EditorRequiredMixin, TemplateView): 

658 template_name = "editorial_tools/special-issue-edit.html" 

659 

660 def get_context_data(self, **kwargs): 

661 context = super().get_context_data(**kwargs) 

662 return context 

663 

664 

665class VirtualIssuesIndex(EditorRequiredMixin, TemplateView): 

666 template_name = "editorial_tools/virtual-issues-index.html" 

667 

668 def get_context_data(self, **kwargs): 

669 colid = kwargs.get("colid", "") 

670 site_id = model_helpers.get_site_id(colid) 

671 vi = get_object_or_404(Page, mersenne_id=MERSENNE_ID_VIRTUAL_ISSUES) 

672 pages = Page.objects.filter(site_id=site_id, parent_page=vi) 

673 context = super().get_context_data(**kwargs) 

674 context["journal"] = model_helpers.get_collection(colid) 

675 context["pages"] = pages 

676 return context 

677 

678 

679def get_citation_fr(doi, citation_en): 

680 citation_fr = citation_en 

681 article = Article.objects.filter(doi=doi).first() 

682 if article and article.trans_title_html: 

683 trans_title = article.trans_title_html 

684 try: 

685 citation_fr = re.sub( 

686 r'(<a href="https:\/\/doi\.org.*">)([^<]+)', 

687 rf"\1{trans_title}", 

688 citation_en, 

689 ) 

690 except re.error: 

691 pass 

692 return citation_fr 

693 

694 

695def summary_build(articles, colid): 

696 summary_fr = "" 

697 summary_en = "" 

698 head = '<table id="summary"><tbody>' 

699 tail = "</tbody></table>" 

700 style = "max-width:180px;max-height:200px" 

701 colid_lo = colid.lower() 

702 site_domain = SITE_REGISTER[colid_lo]["site_domain"].split("/") 

703 site_domain = "/" + site_domain[-1] if len(site_domain) == 2 else "" 

704 

705 for article in articles: 

706 image_src = article.get("src", "") 

707 image_name = article.get("imageName", "") 

708 doi = article.get("doi", "") 

709 citation_en = article.get("citation", "") 

710 if doi or citation_en: 

711 row_fr = f'<div href="{doi}">{get_citation_fr(doi, citation_en)}</div>' 

712 row_en = f'<div href="{doi}">{citation_en}</div>' 

713 if image_src: 

714 date = datetime.now().strftime("%Y/%m/%d/") 

715 base_url = get_media_base_url(colid) 

716 suffix = os.path.join(base_url, "uploads", date) 

717 image_url = os.path.join(site_domain, suffix, image_name) 

718 image_header = "^data:image/.+;base64," 

719 if re.match(image_header, image_src): 

720 image_src = re.sub(image_header, "", image_src) 

721 base64_data = base64.b64decode(image_src) 

722 base_root = get_media_base_root(colid) 

723 path = os.path.join(base_root, "uploads", date) 

724 os.makedirs(path, exist_ok=True) 

725 with open(path + image_name, "wb") as fp: 

726 fp.write(base64_data) 

727 im = f'<img src="{image_url}" style="{style}" />' 

728 # TODO mettre la vrai valeur pour le SITE_DOMAIN 

729 elif settings.SITE_DOMAIN == "http://127.0.0.1:8002": 

730 im = f'<img src="{image_src}" style="{style}" />' 

731 else: 

732 im = f'<img src="{site_domain}{image_src}" style="{style}" />' 

733 summary_fr += f"<tr><td>{im}</td><td>{row_fr}</td></tr>" 

734 summary_en += f"<tr><td>{im}</td><td>{row_en}</td></tr>" 

735 summary_fr = head + summary_fr + tail 

736 summary_en = head + summary_en + tail 

737 return {"summary_fr": summary_fr, "summary_en": summary_en} 

738 

739 

740# @method_decorator([csrf_exempt], name="dispatch") 

741class VirtualIssueDeployView(HandleCMSMixin, View): 

742 """ 

743 called by the Virtual.vue VueJS component, when the virtual issue is saved 

744 We get data in JSON and we need to update the corresponding Page. 

745 The Page is then immediately posted to the test_website. 

746 The "Apply the changes to the production website" button is then used to update the (prod) website 

747 => See DeployCMSAPIView 

748 """ 

749 

750 def post(self, request, *args, **kwargs): 

751 self.init_data(self.kwargs) 

752 

753 pid = kwargs.get("pid") 

754 colid = self.colid 

755 data = json.loads(request.body) 

756 summary = summary_build(data["articles"], colid) 

757 page = get_object_or_404(Page, id=pid) 

758 page.slug = page.slug_fr = page.slug_en = None 

759 page.menu_title_fr = data["title_fr"] 

760 page.menu_title_en = data["title_en"] 

761 page.content_fr = data["head_fr"] + summary["summary_fr"] + data["tail_fr"] 

762 page.content_en = data["head_en"] + summary["summary_en"] + data["tail_en"] 

763 page.state = data["page"]["state"] 

764 page.menu_order = data["page"]["menu_order"] 

765 page.parent_page = Page.objects.filter(id=data["page"]["parent_page"]).first() 

766 page.save() 

767 

768 response = deploy_cms("test_website", self.collection) 

769 if response.status_code == 503: 

770 messages.error( 

771 self.request, "The journal website is under maintenance. Please try again later." 

772 ) 

773 

774 return response # HttpResponse(status=response.status_code, reason=response.reason) 

775 

776 

777class SpecialIssueEditAPIView(HandleCMSMixin, TemplateView): 

778 template_name = "editorial_tools/special-issue-edit.html" 

779 

780 def get_context_data(self, **kwargs): 

781 context = super().get_context_data(**kwargs) 

782 return context 

783 

784 def set_contrib_addresses(self, contrib, contribution): 

785 for address in contrib: 

786 contrib_address = ContribAddress(contribution=contribution, address=address) 

787 contrib_address.save() 

788 

789 def delete(self, pk, colid): 

790 special_issue = Container.objects.get(id=pk) 

791 cmd = base_ptf_cmds.addContainerPtfCmd() 

792 cmd.set_object_to_be_deleted(special_issue) 

793 cmd.undo() 

794 

795 def get(self, request, *args, **kwargs): 

796 pk = kwargs.get("pk", "") 

797 

798 data = {"pk": pk} 

799 data["colid"] = kwargs.get("colid", "") 

800 

801 name = resolve(request.path_info).url_name 

802 if name == "special_issue_delete": 

803 self.delete(pk, data["colid"]) 

804 return redirect("special_issues_index", data["colid"]) 

805 

806 journal = model_helpers.get_collection(data["colid"], sites=False) 

807 data["journal_title"] = journal.title_tex.replace(".", "") 

808 

809 if pk != "create": 

810 container = get_object_or_404(Container, id=pk) 

811 # TODO: pass the lang and trans_lang as well 

812 # In VueJS (Special.vu)e, titleFr = title_html 

813 data["title"] = container.trans_title_html 

814 data["trans_title"] = container.title_html 

815 data["year"] = container.year 

816 data["volume"] = container.volume 

817 data["articles"] = [ 

818 {"doi": article.resource_doi, "citation": article.citation} 

819 for article in container.resources_in_special_issue.all() 

820 ] 

821 

822 contribs = model_data_converter.db_to_contributors(container.contributions) 

823 data["contribs"] = contribs 

824 else: 

825 data["title"] = "" 

826 data["trans_title"] = "" 

827 data["year"] = "" 

828 data["volume"] = "" 

829 data["articles"] = [] 

830 data["contribs"] = [] 

831 

832 # abstract_set = container.abstract_set.all() 

833 

834 # try: 

835 # data["head_fr"] = abstract_set.get(tag="head_fr").value_html 

836 # except: 

837 # data["head_fr"] = "" 

838 

839 # try: 

840 # data["head_en"] = abstract_set.get(tag="head_en").value_html 

841 # except: 

842 # data["head_en"] = "" 

843 

844 # try: 

845 # data["tail_fr"] = abstract_set.get(tag="tail_fr").value_html 

846 # except: 

847 # data["tail_fr"] = "" 

848 # try: 

849 # data["tail_en"] = abstract_set.get(tag="tail_en").value_html 

850 # except: 

851 # data["tail_en"] = "" 

852 

853 # try: 

854 # ExtLink.objects.get(resource=container, rel="icon") 

855 

856 # icon_dirname = ( 

857 # settings.MERSENNE_TEST_DATA_FOLDER 

858 # + "/media/" 

859 # + data["colid"] 

860 # + "/" 

861 # + container.pid 

862 # ) 

863 # # TODO mettre le settings.media_root 

864 # icon_html_src = "/media" + "/" + data["colid"] + "/" + container.pid 

865 # if os.path.isdir(icon_dirname): 

866 # data["icon_name"] = os.listdir(icon_dirname)[0] 

867 

868 # for image in os.listdir(icon_dirname): 

869 # data["icon_path"] = f"{icon_html_src}/{image}" 

870 # except ExtLink.DoesNotExist: 

871 # pass 

872 

873 # try: 

874 # data["articles"] = parse_content( 

875 # container.abstract_set.all().get(tag="summary_en").value_html 

876 # )["articles"] 

877 # except: 

878 # data["articles"] = "" 

879 

880 return JsonResponse(data) 

881 

882 def post(self, request, *args, **kwargs): 

883 # le but est de faire un IssueDAta 

884 pk = kwargs.get("pk", "") 

885 colid = kwargs.get("colid", "") 

886 journal = collection = model_helpers.get_collection(colid, sites=False) 

887 special_issue = IssueData() 

888 year = request.POST["year"] 

889 # TODO 1: the values should be the tex values, not the html ones 

890 # TODO 2: In VueJS, titleFr = title 

891 trans_title_html = request.POST["title"] 

892 title_html = request.POST["trans_title"] 

893 volume = collection.content.filter(year=year).first().volume 

894 if pk != "create": 

895 # TODO: do not use the pk, but the pid in the URLs 

896 container = get_object_or_404(Container, id=pk) 

897 lang = container.lang 

898 trans_lang = container.trans_lang 

899 xpub = create_publisherdata() 

900 xpub.name = container.my_publisher.pid 

901 special_issue.provider = container.provider 

902 special_issue.number = container.number 

903 resources_in_base = [ 

904 resource_in_special_issue 

905 for resource_in_special_issue in container.resources_in_special_issue.all() 

906 ] 

907 

908 else: 

909 lang = "en" 

910 trans_lang = "fr" 

911 xpub = create_publisherdata() 

912 xpub.name = collection.content.filter(year=year).first().my_publisher.pid 

913 special_issue.provider = collection.provider 

914 special_issue.number = ( 

915 len(collection.content.filter(year=year).exclude(title_html="")) + 1 

916 ) 

917 resources_in_base = [] 

918 

919 title_xml = get_issue_title_xml(title_html, lang, trans_title_html, trans_lang) 

920 

921 special_issue_pid = f"{colid}_{year}__{volume}_S{special_issue.number}" 

922 

923 existing_issue = model_helpers.get_resource(special_issue_pid) 

924 if pk == "create" and existing_issue is not None: 

925 raise ValueError(f"The special issue with the pid {special_issue_pid} already exists") 

926 

927 special_issue.lang = lang 

928 special_issue.trans_lang = trans_lang 

929 special_issue.year = year 

930 special_issue.volume = volume 

931 special_issue.title_html = title_html 

932 special_issue.trans_title_html = trans_title_html 

933 special_issue.title_xml = title_xml 

934 special_issue.journal = journal 

935 special_issue.ctype = "issue_special" 

936 special_issue.publisher = xpub 

937 special_issue.pid = special_issue_pid 

938 special_issue.last_modified_iso_8601_date_str = datetime.now().strftime( 

939 "%Y-%m-%d %H:%M:%S" 

940 ) 

941 

942 articles = [] 

943 contribs = [] 

944 index = 0 

945 

946 # contribs_in_base = container.contributions.all() 

947 

948 if "nb_articles" in request.POST.keys(): 

949 while index < int(request.POST["nb_articles"]): 

950 article = json.loads(request.POST[f"article[{index}]"]) 

951 article["citation"] = xml_utils.replace_html_entities(article["citation"]) 

952 articles.append(article) 

953 

954 index += 1 

955 for resource in resources_in_base: 

956 resource.delete() 

957 

958 # we delete resources that are no longer part of the special issue 

959 else: 

960 for resource in resources_in_base: 

961 resource.delete() 

962 special_issue.articles = [Munch(article) for article in articles] 

963 index = 0 

964 if "nb_contrib" in request.POST.keys(): 

965 while index < int(request.POST["nb_contrib"]): 

966 contrib = json.loads(request.POST[f"contrib[{index}]"]) 

967 contributor = create_contributor() 

968 contributor["first_name"] = contrib["first_name"] 

969 contributor["last_name"] = contrib["last_name"] 

970 contributor["orcid"] = contrib["orcid"] 

971 contributor["role"] = "editor" 

972 

973 contrib_xml = xml_utils.get_contrib_xml(contrib) 

974 contributor["contrib_xml"] = contrib_xml 

975 contribs.append(Munch(contributor)) 

976 index += 1 

977 special_issue.contributors = contribs 

978 

979 # try: 

980 # contrib_in_special_issue = Contribution.objects.get(id=contrib["author_id"]) 

981 # contrib_in_special_issue.first_name = first_name 

982 # contrib_in_special_issue.last_name = last_name 

983 # contrib_in_special_issue.corresponding = corresponding 

984 # contrib_in_special_issue.email = email 

985 # contrib_in_special_issue.orcid = orcid 

986 # contrib_in_special_issue.equal_contrib = equal_contrib 

987 # contrib_in_special_issue.deceased_before_publication = deceased 

988 # contrib_in_special_issue.seq = index 

989 # contrib_in_special_issue.contrib_xml = contrib_xml 

990 # contrib_in_special_issue.contribaddress_set.all().delete() 

991 # contrib_in_special_issue.save() 

992 # self.set_contrib_addresses(contrib["addresses"], contrib_in_special_issue) 

993 

994 # contribs_in_base = contribs_in_base.exclude(id=contrib_in_special_issue.id) 

995 # except Contribution.DoesNotExist: 

996 # contrib_in_special_issue = Contribution( 

997 # first_name=first_name, 

998 # last_name=last_name, 

999 # corresponding=corresponding, 

1000 # email=email, 

1001 # orcid=orcid, 

1002 # role=role, 

1003 # equal_contrib=equal_contrib, 

1004 # deceased_before_publication=deceased, 

1005 # seq=index, 

1006 # resource=container, 

1007 # contrib_xml=contrib_xml, 

1008 # ) 

1009 # contrib_in_special_issue.save() 

1010 # self.set_contrib_addresses(contrib["addresses"], contrib_in_special_issue) 

1011 

1012 # contribs_in_base = contribs_in_base.exclude(id=contrib_in_special_issue.id) 

1013 # index += 1 

1014 

1015 # for contribution in contribs_in_base: 

1016 # contribution.delete() 

1017 # else: 

1018 # for contrib in contribs_in_base: 

1019 # contrib.delete() 

1020 # container.year = request.POST["year"] 

1021 # container.title_html = request.POST["title"] 

1022 # container.trans_title_html = request.POST["trans_title"] 

1023 # container.title_xml = jats_parser.get_issue_title_xml( 

1024 # container.title_html, container.lang, container.trans_title_html, container.trans_lang 

1025 # ) 

1026 # container.volume = request.POST["volume"] 

1027 colid_lo = colid.lower() 

1028 site_domain = SITE_REGISTER[colid_lo]["site_domain"].split("/") 

1029 site_domain = "/" + site_domain[-1] if len(site_domain) == 2 else "" 

1030 

1031 # if "IssuesIllustration" in self.request.FILES: 

1032 # icon_name = os.path.basename(self.request.FILES["IssuesIllustration"].name) 

1033 # file_extension = icon_name.split(".")[1] 

1034 # media = container.pid 

1035 # icon_file_path = resolver.get_disk_location( 

1036 # f"{settings.MEDIA_ROOT}", 

1037 # f"{collection.pid}", 

1038 # file_extension, 

1039 # media, 

1040 # None, 

1041 # True, 

1042 # ) 

1043 # path = os.path.dirname(icon_file_path) 

1044 # if os.path.isdir(path): 

1045 # shutil.rmtree(path) 

1046 # os.mkdir(path) 

1047 # with open(icon_file_path, "wb+") as destination: 

1048 # for chunk in self.request.FILES["IssuesIllustration"].chunks(): 

1049 # destination.write(chunk) 

1050 # icon = media + "." + file_extension 

1051 # location = location = f"{collection.pid}/{container.pid}/{icon}" 

1052 # try: 

1053 # extlink = ExtLink.objects.get(resource=container, rel="icon") 

1054 # extlink.location = location 

1055 # except ExtLink.DoesNotExist: 

1056 # extlink = ExtLink( 

1057 # resource=container, 

1058 # rel="icon", 

1059 # location=location, 

1060 # ) 

1061 # extlink.save() 

1062 # elif "icon_present" in request.POST: 

1063 # pass 

1064 

1065 # else: 

1066 # try: 

1067 # location = settings.MEDIA_ROOT + collection.pid + "/" + container.pid 

1068 # if os.path.isdir(location): 

1069 # for icon in os.listdir(location): 

1070 # os.remove(location + "/" + icon) 

1071 # extlink = ExtLink.objects.get(resource=container, rel="icon") 

1072 # extlink.delete() 

1073 # except ExtLink.DoesNotExist: 

1074 # pass 

1075 

1076 # Part of the code that handle forwords and lastwords 

1077 # head_fr_html = xml_utils.replace_html_entities(request.POST["head_fr"]) 

1078 # head_en_html = xml_utils.replace_html_entities(request.POST["head_en"]) 

1079 # tail_fr_html = xml_utils.replace_html_entities(request.POST["tail_fr"]) 

1080 # tail_en_html = xml_utils.replace_html_entities(request.POST["tail_en"]) 

1081 # try: 

1082 # head_fr = container.abstract_set.all().get(tag="head_fr") 

1083 # head_fr.value_html = head_fr_html 

1084 # head_fr.value_xml = ( 

1085 # "<abstract xml:lang='fr'>" 

1086 # + head_fr.value_html.replace("<p>", "<p xml:space='preserve'>") 

1087 # + "</abstract>" 

1088 # ) 

1089 # except Abstract.DoesNotExist: 

1090 # head_fr = Abstract( 

1091 # resource=container, lang="fr", tag="head_fr", value_html=head_fr_html, seq=1 

1092 # ) 

1093 # head_fr.value_xml = ( 

1094 # "<abstract xml:lang='fr'>" 

1095 # + head_fr.value_html.replace("<p>", "<p xml:space='preserve'>") 

1096 # + "</abstract>" 

1097 # ) 

1098 # try: 

1099 # head_en = container.abstract_set.all().get(tag="head_en") 

1100 # head_en.value_html = head_en_html 

1101 # head_en.value_xml = ( 

1102 # "<trans-abstract xml:lang='en'>" 

1103 # + head_en.value_html.replace("<p>", "<p xml:space='preserve'>") 

1104 # + "</abstract>" 

1105 # ) 

1106 # except Abstract.DoesNotExist: 

1107 # head_en = Abstract( 

1108 # resource=container, lang="en", tag="head_en", value_html=head_en_html, seq=2 

1109 # ) 

1110 # head_en.value_xml = ( 

1111 # "<trans-abstract xml:lang='en'>" 

1112 # + head_en.value_html.replace("<p>", "<p xml:space='preserve'>") 

1113 # + "</abstract>" 

1114 # ) 

1115 # try: 

1116 # tail_fr = container.abstract_set.all().get(tag="tail_fr") 

1117 # tail_fr.value_html = tail_fr_html 

1118 # tail_fr.value_xml = ( 

1119 # "<abstract xml:lang='fr' abstract-type='tail_fr'>" 

1120 # + tail_fr.value_html.replace("<p>", "<p xml:space='preserve'>") 

1121 # + "</abstract>" 

1122 # ) 

1123 # except Abstract.DoesNotExist: 

1124 # tail_fr = Abstract( 

1125 # resource=container, lang="fr", tag="tail_fr", value_html=tail_fr_html, seq=5 

1126 # ) 

1127 # tail_fr.value_xml = ( 

1128 # "<abstract xml:lang='fr' abstract-type='tail_fr'>" 

1129 # + tail_fr.value_html.replace("<p>", "<p xml:space='preserve'>") 

1130 # + "</abstract>" 

1131 # ) 

1132 # try: 

1133 # tail_en = container.abstract_set.all().get(tag="tail_en") 

1134 # tail_en.value_html = tail_en_html 

1135 # tail_en.value_xml = ( 

1136 # "<abstract xml:lang='en' abstract-type='tail_en'>" 

1137 # + tail_en.value_html.replace("<p>", "<p xml:space='preserve'>") 

1138 # + "</abstract>" 

1139 # ) 

1140 # except Abstract.DoesNotExist: 

1141 # tail_en = Abstract( 

1142 # resource=container, lang="en", tag="tail_en", value_html=tail_en_html, seq=6 

1143 # ) 

1144 # tail_en.value_xml = ( 

1145 # "<abstract xml:lang='en' abstract-type='tail_en'>" 

1146 # + tail_en.value_html.replace("<p>", "<p xml:space='preserve'>") 

1147 # + "</abstract>" 

1148 # ) 

1149 

1150 # we build the summary with articles provided 

1151 # print(request.POST) 

1152 # summary = summary_build(articles, colid) 

1153 # try: 

1154 # container_summary_fr = container.abstract_set.all().get(tag="summary_fr") 

1155 # container_summary_fr.value_html = summary["summary_fr"] 

1156 # except Abstract.DoesNotExist: 

1157 # container_summary_fr = Abstract( 

1158 # resource=container, 

1159 # lang="fr", 

1160 # tag="summary_fr", 

1161 # value_html=summary["summary_fr"], 

1162 # seq=3, 

1163 # ) 

1164 # try: 

1165 # container_summary_en = container.abstract_set.all().get(tag="summary_en") 

1166 # container_summary_en.value_html = summary["summary_en"] 

1167 # except Abstract.DoesNotExist: 

1168 # container_summary_en = Abstract( 

1169 # resource=container, 

1170 # lang="en", 

1171 # tag="summary_en", 

1172 # value_html=summary["summary_en"], 

1173 # seq=4, 

1174 # ) 

1175 

1176 # head_fr.save() 

1177 # head_en.save() 

1178 # container_summary_fr.save() 

1179 # container_summary_en.save() 

1180 # tail_fr.save() 

1181 # tail_en.save() 

1182 

1183 # container.save() 

1184 # special_issue = model_data_converter.db_to_issue_data(special_issue) 

1185 special_issue = Munch(special_issue.__dict__) 

1186 params = {"xissue": special_issue, "use_body": False} 

1187 cmd = xml_cmds.addOrUpdateIssueXmlCmd(params) 

1188 new_container = cmd.do() 

1189 pk = new_container.pk 

1190 return redirect("special_issue_edit_api", colid, pk) 

1191 

1192 

1193class PageIndexView(EditorRequiredMixin, TemplateView): 

1194 template_name = "mersenne_cms/page_index.html" 

1195 

1196 def get_context_data(self, **kwargs): 

1197 colid = kwargs.get("colid", "") 

1198 site_id = model_helpers.get_site_id(colid) 

1199 vi = Page.objects.filter(site_id=site_id, mersenne_id=MERSENNE_ID_VIRTUAL_ISSUES).first() 

1200 if vi: 1200 ↛ 1201line 1200 didn't jump to line 1201, because the condition on line 1200 was never true

1201 pages = Page.objects.filter(site_id=site_id).exclude(parent_page=vi) 

1202 else: 

1203 pages = Page.objects.filter(site_id=site_id) 

1204 context = super().get_context_data(**kwargs) 

1205 context["colid"] = colid 

1206 context["journal"] = model_helpers.get_collection(colid) 

1207 context["pages"] = pages 

1208 context["news"] = News.objects.filter(site_id=site_id) 

1209 context["fields_lang"] = "fr" if model_helpers.is_site_fr_only(site_id) else "en" 

1210 return context 

1211 

1212 

1213class PageBaseView(HandleCMSMixin, View): 

1214 template_name = "mersenne_cms/page_form.html" 

1215 model = Page 

1216 form_class = PageForm 

1217 

1218 def dispatch(self, request, *args, **kwargs): 

1219 self.colid = self.kwargs["colid"] 

1220 self.collection = model_helpers.get_collection(self.colid, sites=False) 

1221 self.site_id = model_helpers.get_site_id(self.colid) 

1222 

1223 return super().dispatch(request, *args, **kwargs) 

1224 

1225 def get_success_url(self): 

1226 return reverse("page_index", kwargs={"colid": self.colid}) 

1227 

1228 def get_context_data(self, **kwargs): 

1229 context = super().get_context_data(**kwargs) 

1230 context["journal"] = self.collection 

1231 return context 

1232 

1233 def update_test_website(self): 

1234 response = deploy_cms("test_website", self.collection) 

1235 if response.status_code < 300: 1235 ↛ 1238line 1235 didn't jump to line 1238, because the condition on line 1235 was never false

1236 messages.success(self.request, "The test website has been updated") 

1237 else: 

1238 text = "ERROR: Unable to update the test website<br/>" 

1239 

1240 if response.status_code == 503: 

1241 text += "The test website is under maintenance. Please try again later.<br/>" 

1242 else: 

1243 text += f"Please contact the centre Mersenne<br/><br/>Status code: {response.status_code}<br/>" 

1244 if hasattr(response, "content") and response.content: 

1245 text += f"{response.content.decode()}<br/>" 

1246 if hasattr(response, "reason") and response.reason: 

1247 text += f"Reason: {response.reason}<br/>" 

1248 if hasattr(response, "text") and response.text: 

1249 text += f"Details: {response.text}<br/>" 

1250 messages.error(self.request, mark_safe(text)) 

1251 

1252 def get_form_kwargs(self): 

1253 kwargs = super().get_form_kwargs() 

1254 kwargs["site_id"] = self.site_id 

1255 kwargs["user"] = self.request.user 

1256 return kwargs 

1257 

1258 def form_valid(self, form): 

1259 form.save() 

1260 

1261 self.update_test_website() 

1262 

1263 return HttpResponseRedirect(self.get_success_url()) 

1264 

1265 

1266# @method_decorator([csrf_exempt], name="dispatch") 

1267class PageDeleteView(PageBaseView): 

1268 def post(self, request, *args, **kwargs): 

1269 colid = kwargs.get("colid", "") 

1270 pk = kwargs.get("pk") 

1271 page = get_object_or_404(Page, id=pk) 

1272 if page.mersenne_id: 

1273 raise PermissionDenied 

1274 

1275 page.delete() 

1276 

1277 self.update_test_website() 

1278 

1279 if page.parent_page and page.parent_page.mersenne_id == MERSENNE_ID_VIRTUAL_ISSUES: 

1280 return HttpResponseRedirect(reverse("virtual_issues_index", kwargs={"colid": colid})) 

1281 else: 

1282 return HttpResponseRedirect(reverse("page_index", kwargs={"colid": colid})) 

1283 

1284 

1285class PageCreateView(PageBaseView, CreateView): 

1286 def get_context_data(self, **kwargs): 

1287 context = super().get_context_data(**kwargs) 

1288 context["title"] = "Add a menu page" 

1289 return context 

1290 

1291 

1292class PageUpdateView(PageBaseView, UpdateView): 

1293 def get_context_data(self, **kwargs): 

1294 context = super().get_context_data(**kwargs) 

1295 context["title"] = "Edit a menu page" 

1296 return context 

1297 

1298 

1299class NewsBaseView(PageBaseView): 

1300 template_name = "mersenne_cms/news_form.html" 

1301 model = News 

1302 form_class = NewsForm 

1303 

1304 

1305class NewsDeleteView(NewsBaseView): 

1306 def post(self, request, *args, **kwargs): 

1307 pk = kwargs.get("pk") 

1308 news = get_object_or_404(News, id=pk) 

1309 

1310 news.delete() 

1311 

1312 self.update_test_website() 

1313 

1314 return HttpResponseRedirect(self.get_success_url()) 

1315 

1316 

1317class NewsCreateView(NewsBaseView, CreateView): 

1318 def get_context_data(self, **kwargs): 

1319 context = super().get_context_data(**kwargs) 

1320 context["title"] = "Add a News" 

1321 return context 

1322 

1323 

1324class NewsUpdateView(NewsBaseView, UpdateView): 

1325 def get_context_data(self, **kwargs): 

1326 context = super().get_context_data(**kwargs) 

1327 context["title"] = "Edit a News" 

1328 return context 

1329 

1330 

1331# def page_create_view(request, colid): 

1332# context = {} 

1333# if not is_authorized_editor(request.user, colid): 

1334# raise PermissionDenied 

1335# collection = model_helpers.get_collection(colid) 

1336# page = Page(site_id=model_helpers.get_site_id(colid)) 

1337# form = PageForm(request.POST or None, instance=page) 

1338# if form.is_valid(): 

1339# form.save() 

1340# response = deploy_cms("test_website", collection) 

1341# if response.status_code < 300: 

1342# messages.success(request, "Page created successfully.") 

1343# else: 

1344# text = f"ERROR: page creation failed<br/>Status code: {response.status_code}<br/>" 

1345# if hasattr(response, "reason") and response.reason: 

1346# text += f"Reason: {response.reason}<br/>" 

1347# if hasattr(response, "text") and response.text: 

1348# text += f"Details: {response.text}<br/>" 

1349# messages.error(request, mark_safe(text)) 

1350# kwargs = {"colid": colid, "pid": form.instance.id} 

1351# return HttpResponseRedirect(reverse("page_update", kwargs=kwargs)) 

1352# 

1353# context["form"] = form 

1354# context["title"] = "Add a menu page" 

1355# context["journal"] = collection 

1356# return render(request, "mersenne_cms/page_form.html", context) 

1357 

1358 

1359# def page_update_view(request, colid, pid): 

1360# context = {} 

1361# if not is_authorized_editor(request.user, colid): 

1362# raise PermissionDenied 

1363# 

1364# collection = model_helpers.get_collection(colid) 

1365# page = get_object_or_404(Page, id=pid) 

1366# form = PageForm(request.POST or None, instance=page) 

1367# if form.is_valid(): 

1368# form.save() 

1369# response = deploy_cms("test_website", collection) 

1370# if response.status_code < 300: 

1371# messages.success(request, "Page updated successfully.") 

1372# else: 

1373# text = f"ERROR: page update failed<br/>Status code: {response.status_code}<br/>" 

1374# if hasattr(response, "reason") and response.reason: 

1375# text += f"Reason: {response.reason}<br/>" 

1376# if hasattr(response, "text") and response.text: 

1377# text += f"Details: {response.text}<br/>" 

1378# messages.error(request, mark_safe(text)) 

1379# kwargs = {"colid": colid, "pid": form.instance.id} 

1380# return HttpResponseRedirect(reverse("page_update", kwargs=kwargs)) 

1381# 

1382# context["form"] = form 

1383# context["pid"] = pid 

1384# context["title"] = "Edit a menu page" 

1385# context["journal"] = collection 

1386# return render(request, "mersenne_cms/page_form.html", context)