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
« 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
8import requests
9from ckeditor_uploader.views import ImageUploadView
10from ckeditor_uploader.views import browse
11from munch import Munch
12from requests import Timeout
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
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
52# from ptf.display import resolver
53from ptf.exceptions import ServerUnderMaintenance
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
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
79from .base_views import check_lock
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"
89 return os.path.join(settings.RESOURCES_ROOT, "media", colid)
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)
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"
107 return os.path.join(settings.MERSENNE_PROD_DATA_FOLDER, "media", colid)
110def get_media_base_url(colid):
111 path = os.path.join(settings.MEDIA_URL, colid)
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"
124 return path
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 """
136 from ckeditor_uploader import utils
137 from ckeditor_uploader import views
139 from django.core.files.storage import FileSystemStorage
141 storage = FileSystemStorage(
142 location=get_media_base_root(colid), base_url=get_media_base_url(colid)
143 )
145 utils.storage = storage
146 views.storage = storage
149class EditorRequiredMixin(UserPassesTestMixin):
150 def test_func(self):
151 return is_authorized_editor(self.request.user, self.kwargs.get("colid"))
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 """
163 def dispatch(self, request, *args, **kwargs):
164 colid = kwargs["colid"]
166 change_ckeditor_storage(colid)
168 return super().dispatch(request, **kwargs)
171class CollectionBrowseView(EditorRequiredMixin, View):
172 def dispatch(self, request, **kwargs):
173 colid = kwargs["colid"]
175 change_ckeditor_storage(colid)
177 return browse(request)
180file_upload_in_collection = csrf_exempt(CollectionImageUploadView.as_view())
181file_browse_in_collection = csrf_exempt(CollectionBrowseView.as_view())
184def deploy_cms(site, collection):
185 colid = collection.pid
186 base_url = getattr(collection, site)()
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"})
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)
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}")
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
209 old_ftor = manager.get_language
210 manager.get_language = monkey_get_language_en
211 fields.get_language = monkey_get_language_en
213 pages = get_pages_content(colid)
214 news = get_news_content(colid)
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)
222 data = json.dumps({"pages": json.loads(pages), "news": json.loads(news)})
223 url = getattr(collection, site)() + "/import_cms/"
225 try:
226 response = requests.put(url, data=data, verify=False)
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)
234 except Timeout as exception:
235 return HttpResponse(exception, status=408)
236 except Exception as exception:
237 return HttpResponseServerError(exception)
239 return JsonResponse({"message": "OK"})
242class HandleCMSMixin(EditorRequiredMixin):
243 """
244 Mixin for classes that need to send request to (test) website to import/export CMS content (pages, news)
245 """
247 # def dispatch(self, request, *args, **kwargs):
248 # self.colid = self.kwargs["colid"]
249 # return super().dispatch(request, *args, **kwargs)
251 def init_data(self, kwargs):
252 self.collection = None
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")
260 test_server_url = self.collection.test_website()
261 if not test_server_url:
262 raise Http404("The collection has no test site")
264 prod_server_url = self.collection.website()
265 if not prod_server_url:
266 raise Http404("The collection has no prod site")
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 """
275 def get(self, request, *args, **kwargs):
276 self.init_data(self.kwargs)
278 site = kwargs.get("site", "test_website")
280 try:
281 url = getattr(self.collection, site)() + "/export_cms/"
282 response = requests.get(url, verify=False)
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"))
293 except Timeout as exception:
294 return HttpResponse(exception, status=408)
295 except Exception as exception:
296 return HttpResponseServerError(exception)
298 return JsonResponse({"message": "OK", "status": 200})
301def monkey_get_language_en():
302 return "en"
305class RestoreCMSAPIView(HandleCMSMixin, View):
306 """
307 Restore the Trammel CMS content (of a colid) from disk
308 """
310 def get(self, request, *args, **kwargs):
311 self.init_data(self.kwargs)
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)
318 pages = json_data.get("pages")
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
325 old_ftor = manager.get_language
326 manager.get_language = monkey_get_language_en
327 fields.get_language = monkey_get_language_en
329 import_pages(pages, self.colid)
331 manager.get_language = old_ftor
332 fields.get_language = old_ftor
333 else:
334 import_pages(pages, self.colid)
336 if "news" in json_data:
337 news = json_data.get("news")
338 import_news(news, self.colid)
340 return JsonResponse({"message": "OK", "status": 200})
343class DeployCMSAPIView(HandleCMSMixin, View):
344 def get(self, request, *args, **kwargs):
345 self.init_data(self.kwargs)
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})
352 site = kwargs.get("site", "test_website")
354 response = deploy_cms(site, self.collection)
356 if response.status_code == 503:
357 messages.error(
358 self.request, "The journal website is under maintenance. Please try again later."
359 )
361 return response
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
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)
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()
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))
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, [], "")]
444class SuggestUpdateView(EditorRequiredMixin, TemplateView):
445 template_name = "editorial_tools/suggested.html"
447 def get_context_data(self, **kwargs):
448 doi = kwargs.get("doi", "")
449 article = get_object_or_404(Article, doi=doi)
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
479class EditorialToolsVolumeItemsView(EditorRequiredMixin, TemplateView):
480 template_name = "editorial_tools/volume-items.html"
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
493class EditorialToolsArticleView(EditorRequiredMixin, TemplateView):
494 template_name = "editorial_tools/find-article.html"
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)
501 context = super().get_context_data(**kwargs)
502 context["article"] = article
503 context["citation_base"] = article.get_citation_base().strip(", .")
504 return context
507class GraphicalAbstractUpdateView(EditorRequiredMixin, TemplateView):
508 template_name = "editorial_tools/graphical-abstract.html"
510 def get_context_data(self, **kwargs):
511 doi = kwargs.get("doi", "")
512 article = get_object_or_404(Article, doi=doi)
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
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)
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()
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": []}
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}
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)
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]
600 content_fr = parse_content(page.content_fr)
601 data["head_fr"] = content_fr["head"]
602 data["tail_fr"] = content_fr["tail"]
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)
611class VirtualIssueUpdateView(EditorRequiredMixin, TemplateView):
612 template_name = "editorial_tools/virtual-issue.html"
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)
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))
640class SpecialIssuesIndex(EditorRequiredMixin, TemplateView):
641 template_name = "editorial_tools/special-issues-index.html"
643 def get_context_data(self, **kwargs):
644 colid = kwargs.get("colid", "")
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 )
653 context["journal"] = model_helpers.get_collection(colid, sites=False)
654 return context
657class SpecialIssueEditView(EditorRequiredMixin, TemplateView):
658 template_name = "editorial_tools/special-issue-edit.html"
660 def get_context_data(self, **kwargs):
661 context = super().get_context_data(**kwargs)
662 return context
665class VirtualIssuesIndex(EditorRequiredMixin, TemplateView):
666 template_name = "editorial_tools/virtual-issues-index.html"
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
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
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 ""
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}
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 """
750 def post(self, request, *args, **kwargs):
751 self.init_data(self.kwargs)
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()
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 )
774 return response # HttpResponse(status=response.status_code, reason=response.reason)
777class SpecialIssueEditAPIView(HandleCMSMixin, TemplateView):
778 template_name = "editorial_tools/special-issue-edit.html"
780 def get_context_data(self, **kwargs):
781 context = super().get_context_data(**kwargs)
782 return context
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()
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()
795 def get(self, request, *args, **kwargs):
796 pk = kwargs.get("pk", "")
798 data = {"pk": pk}
799 data["colid"] = kwargs.get("colid", "")
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"])
806 journal = model_helpers.get_collection(data["colid"], sites=False)
807 data["journal_title"] = journal.title_tex.replace(".", "")
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 ]
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"] = []
832 # abstract_set = container.abstract_set.all()
834 # try:
835 # data["head_fr"] = abstract_set.get(tag="head_fr").value_html
836 # except:
837 # data["head_fr"] = ""
839 # try:
840 # data["head_en"] = abstract_set.get(tag="head_en").value_html
841 # except:
842 # data["head_en"] = ""
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"] = ""
853 # try:
854 # ExtLink.objects.get(resource=container, rel="icon")
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]
868 # for image in os.listdir(icon_dirname):
869 # data["icon_path"] = f"{icon_html_src}/{image}"
870 # except ExtLink.DoesNotExist:
871 # pass
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"] = ""
880 return JsonResponse(data)
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 ]
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 = []
919 title_xml = get_issue_title_xml(title_html, lang, trans_title_html, trans_lang)
921 special_issue_pid = f"{colid}_{year}__{volume}_S{special_issue.number}"
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")
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 )
942 articles = []
943 contribs = []
944 index = 0
946 # contribs_in_base = container.contributions.all()
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)
954 index += 1
955 for resource in resources_in_base:
956 resource.delete()
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"
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
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)
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)
1012 # contribs_in_base = contribs_in_base.exclude(id=contrib_in_special_issue.id)
1013 # index += 1
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 ""
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
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
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 # )
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 # )
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()
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)
1193class PageIndexView(EditorRequiredMixin, TemplateView):
1194 template_name = "mersenne_cms/page_index.html"
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
1213class PageBaseView(HandleCMSMixin, View):
1214 template_name = "mersenne_cms/page_form.html"
1215 model = Page
1216 form_class = PageForm
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)
1223 return super().dispatch(request, *args, **kwargs)
1225 def get_success_url(self):
1226 return reverse("page_index", kwargs={"colid": self.colid})
1228 def get_context_data(self, **kwargs):
1229 context = super().get_context_data(**kwargs)
1230 context["journal"] = self.collection
1231 return context
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/>"
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))
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
1258 def form_valid(self, form):
1259 form.save()
1261 self.update_test_website()
1263 return HttpResponseRedirect(self.get_success_url())
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
1275 page.delete()
1277 self.update_test_website()
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}))
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
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
1299class NewsBaseView(PageBaseView):
1300 template_name = "mersenne_cms/news_form.html"
1301 model = News
1302 form_class = NewsForm
1305class NewsDeleteView(NewsBaseView):
1306 def post(self, request, *args, **kwargs):
1307 pk = kwargs.get("pk")
1308 news = get_object_or_404(News, id=pk)
1310 news.delete()
1312 self.update_test_website()
1314 return HttpResponseRedirect(self.get_success_url())
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
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
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)
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)