tg-prepare 1.1.0__py3-none-any.whl → 2.1.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tg-prepare might be problematic. Click here for more details.

Files changed (46) hide show
  1. {tg_prepare-1.1.0.dist-info → tg_prepare-2.1.0b1.dist-info}/METADATA +3 -3
  2. tg_prepare-2.1.0b1.dist-info/RECORD +54 -0
  3. {tg_prepare-1.1.0.dist-info → tg_prepare-2.1.0b1.dist-info}/WHEEL +1 -1
  4. tg_prepare-2.1.0b1.dist-info/projects/.secret_key +1 -0
  5. tgp_backend/__init__.py +3 -4
  6. tgp_backend/config.py +31 -0
  7. tgp_backend/directories.py +6 -8
  8. tgp_backend/nextcloud.py +40 -19
  9. tgp_backend/project.py +172 -45
  10. tgp_backend/session_manager.py +47 -0
  11. tgp_backend/util.py +73 -25
  12. tgp_ui/app.py +43 -335
  13. tgp_ui/routes/__init__.py +0 -0
  14. tgp_ui/routes/collection.py +272 -0
  15. tgp_ui/routes/data.py +228 -0
  16. tgp_ui/routes/project.py +102 -0
  17. tgp_ui/routes/publication.py +129 -0
  18. tgp_ui/routes/tabs.py +34 -0
  19. tgp_ui/routes/views.py +62 -0
  20. tgp_ui/static/css/navbar.css +92 -0
  21. tgp_ui/static/js/collectionManager.js +60 -0
  22. tgp_ui/static/js/fileManager.js +186 -0
  23. tgp_ui/static/js/main.js +32 -485
  24. tgp_ui/static/js/modalManager.js +105 -0
  25. tgp_ui/static/js/navbarManager.js +151 -0
  26. tgp_ui/static/js/projectManager.js +60 -0
  27. tgp_ui/static/js/require.js +5 -0
  28. tgp_ui/static/js/sidebarManager.js +32 -0
  29. tgp_ui/static/js/tabManager.js +79 -0
  30. tgp_ui/templates/layout.html +9 -48
  31. tgp_ui/templates/macros.html +79 -72
  32. tgp_ui/templates/project_main.html +36 -0
  33. tgp_ui/templates/project_navbar.html +81 -0
  34. tgp_ui/templates/{projects.html → projects_main.html} +13 -28
  35. tgp_ui/templates/tab_final_upload.html +29 -0
  36. tg_prepare-1.1.0.dist-info/RECORD +0 -39
  37. tgp_ui/templates/collection.html +0 -194
  38. tgp_ui/templates/file_upload.html +0 -24
  39. tgp_ui/templates/nxc_file_tree.html +0 -33
  40. tgp_ui/templates/project.html +0 -26
  41. tgp_ui/templates/storage.html +0 -49
  42. tgp_ui/templates/tei_explorer.html +0 -48
  43. tgp_ui/templates/xpath_parser_modal_content.html +0 -37
  44. {tg_prepare-1.1.0.dist-info → tg_prepare-2.1.0b1.dist-info}/entry_points.txt +0 -0
  45. {tg_prepare-1.1.0.dist-info → tg_prepare-2.1.0b1.dist-info}/licenses/LICENSE +0 -0
  46. {tg_prepare-1.1.0.dist-info → tg_prepare-2.1.0b1.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tg_prepare
3
- Version: 1.1.0
3
+ Version: 2.1.0b1
4
4
  Summary: Simple UI to handle TextGrid imports visually.
5
5
  Author: Ralf Klammer, Moritz Wilhelm
6
6
  Author-email: ralf.klammer@tu-dresden.de, moritz.wilhelm@tu-dresden.de
@@ -8,8 +8,8 @@ License-File: LICENSE
8
8
  Requires-Dist: click
9
9
  Requires-Dist: flask
10
10
  Requires-Dist: flask_json
11
- Requires-Dist: tg_model>=3.6.0
12
- Requires-Dist: tgclients==0.17.0
11
+ Requires-Dist: tg_model==3.8.0.b2
12
+ Requires-Dist: tgclients
13
13
  Requires-Dist: tgadmin
14
14
  Requires-Dist: nextcloud-api-wrapper
15
15
  Dynamic: author
@@ -0,0 +1,54 @@
1
+ tg_prepare-2.1.0b1.dist-info/licenses/LICENSE,sha256=deYu6g1OGKm0VUwhM4Octoh8qlzjsiHHxoI-KkCBVBE,11351
2
+ tg_prepare-2.1.0b1.dist-info/projects/.secret_key,sha256=PF3sI74fctNP5J8NoHmvl6nRsvyTeGMU82UTY_E3re4,66
3
+ tgp_backend/__init__.py,sha256=Hsy1huPlOgpNffJVJpxGZzUKdHU8X837K-cOW_DkEy0,373
4
+ tgp_backend/auth.py,sha256=hUPd0rc01BTT003xpkdmhNc6o0JztbAdimyDb0B9oTQ,2293
5
+ tgp_backend/cli.py,sha256=iw_gknn-_m_UFZZUKfkV9aJixG3sVcHIONybNtOG40I,551
6
+ tgp_backend/config.py,sha256=9rFr7bxZN4r1OOKaxs8eBYUnZ0_ccVHU6oqoEsNTKBE,922
7
+ tgp_backend/directories.py,sha256=wNmwc2Uhd9ivZYIM9UkfCv2HkQY8pmDQk2y39wOgBQo,2366
8
+ tgp_backend/interfaces.py,sha256=SO7mOdhkd1f7irUBrwFhp5aKx3UKkkUzU9QBYDl0Tvc,91
9
+ tgp_backend/nextcloud.py,sha256=JNhyJfuR26o33r5QV37ClTALQm_fMQn451SMV5lixNc,5133
10
+ tgp_backend/project.py,sha256=_qebCEkC_wRfSknOIL-c4BAPG830EUZ5m4HW_gkHRn4,12707
11
+ tgp_backend/session_manager.py,sha256=pQGhnXtGPqwdw6xQhZItjGgurx9JBUsIW8mzfSKk_WM,1395
12
+ tgp_backend/tgclient.py,sha256=QG2iFPrv2qLL4FRXoEfXzEMJriroiIaNL9rkreGtzuQ,2961
13
+ tgp_backend/util.py,sha256=4lTaC3liik2ixRcx5wDQS4-FW3QpPhRdXQm32_64J_E,3648
14
+ tgp_ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ tgp_ui/app.py,sha256=6elwUZ_hSM08_FstTerrLX2rB4QurQUSqu359rkhxrI,3704
16
+ tgp_ui/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ tgp_ui/routes/collection.py,sha256=NzfLnF-ZDlZI23ulDhvLXJXELlTlcJw0Z_LONHcU2bA,7861
18
+ tgp_ui/routes/data.py,sha256=xnw-0v72SK8Z2zjwCYNAHp8omkfsZKRtvUKr3YZSSFg,6891
19
+ tgp_ui/routes/project.py,sha256=AyCYI76V4vkEfZQPjSgXmPOIWClpryEEBTQoSjxSXkM,2794
20
+ tgp_ui/routes/publication.py,sha256=K_wDy3t_iwL9TMLLFXR7P2PdDWyHNQUMY9FArJBsmN4,3791
21
+ tgp_ui/routes/tabs.py,sha256=JaZpoY03S_p8uz_sVRCR-fIOfl-ml0M6CJ7_KaYN9aI,908
22
+ tgp_ui/routes/views.py,sha256=ZgxcfEZoyE_4ABrnRtHc8FFR11Rufs8HVaUr4cOBshA,1814
23
+ tgp_ui/static/css/bootstrap-icons.min.css,sha256=XxUvYRPQvG4RTCkukUmXwvf1N0YF9KQ_ocuWV6O1dk8,85797
24
+ tgp_ui/static/css/bootstrap.min.css,sha256=djO3wMl9GeaC_u6K-ic4Uj_LKhRUSlUFcsruzS7v5ms,155845
25
+ tgp_ui/static/css/main.css,sha256=EK8euwgxX8I3qsBiDmIvj8UBIxkSKQZz1mo_OX4Q1FQ,2641
26
+ tgp_ui/static/css/navbar.css,sha256=D68mfVJ88zruV1sRH1D6zjwQ2MoHRy2HQ7BAn_8OSkc,1609
27
+ tgp_ui/static/css/simpleXML.css,sha256=dfY7UZCiC79BkNOT1hM0GQ6v_yLSxhKCnmV0RZIT9S8,961
28
+ tgp_ui/static/js/bootstrap.bundle.min.js,sha256=fh8VA992XMpeCZiRuU4xii75UIG6KvHrbUF8yIS_2_4,78743
29
+ tgp_ui/static/js/collectionManager.js,sha256=gbeXe7CtEdCdka3WLfBx7cVRcT8cveUP_t3TDwRdgvo,2291
30
+ tgp_ui/static/js/fileManager.js,sha256=_plx7zrC039-3y0ZqLd77ymDhTtAiJC3XOLSeAooYAc,7095
31
+ tgp_ui/static/js/jquery.min.js,sha256=_xUj-3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej_m4,89501
32
+ tgp_ui/static/js/main.js,sha256=VCaLVlGFml8ObO0mbyV_kT1aDVH-OB4jqVlzsfT42xM,1107
33
+ tgp_ui/static/js/modalManager.js,sha256=8q4Dtx88aO4zbG5EgZ6Nw1NJa36-RKoRcVzy4DVHEVg,4030
34
+ tgp_ui/static/js/navbarManager.js,sha256=E1_SF262uU4I405vxVCrEXhAE7aEZt9ASixG7huL1As,6713
35
+ tgp_ui/static/js/projectManager.js,sha256=LoT8lZnyvrgquSD7aSNkYaAdggH5kUZYRXVFD-d3dzk,2296
36
+ tgp_ui/static/js/require.js,sha256=DCwpJYECNmnwjgb2xsUmuvSrkNPXRYgYADz7nlUc_-Y,20781
37
+ tgp_ui/static/js/sidebarManager.js,sha256=sfYPwLvKUQA8vMhyVKsA7CJe5cUJliCao8ccwfLKAV0,928
38
+ tgp_ui/static/js/simpleXML.js,sha256=jEkZe3TuiHuEt6V7If_1EmqYIgTywssJ3OwOQzOFYNg,8065
39
+ tgp_ui/static/js/tabManager.js,sha256=spS-Hr-LylqpW0nQz7ontsPfX64b3whilr6038VMq18,3304
40
+ tgp_ui/templates/file_tree.html,sha256=IKWp5MLUvnFF65cIUIxU4Jf5wi9Fg8koSXD96Mk328E,1640
41
+ tgp_ui/templates/layout.html,sha256=ClI6xHMBy6auir72i3Sm6SCJI97XUXhpPrzJSgf_7wM,2727
42
+ tgp_ui/templates/macros.html,sha256=3qetLd7wC__iAoWZ6g4--82Zd2t36XOZcwiLbi7DEAA,4463
43
+ tgp_ui/templates/nxc_login.html,sha256=tDxqHYPZqsY2ZNnkn4a5bMOYiD8ybcNRpzSq7MvMWCo,1269
44
+ tgp_ui/templates/nxc_tab.html,sha256=9HWCa7UZKxL0fZasT_g6uozI64L-497Y2LppPPTHF2M,517
45
+ tgp_ui/templates/project_main.html,sha256=Vdvr9QDfpnc9EU38uTm0H4pT0pzLdi7DckL3d9DC_u4,1878
46
+ tgp_ui/templates/project_navbar.html,sha256=WyS8WNI7tgNDdYOkuFaXeu9A58ER3OK7puVNO-Sfz-0,4412
47
+ tgp_ui/templates/projects_main.html,sha256=2c6PNdomNV1w6j6e-FH27bvdwVNfrzTf5DY1pNzHuMM,2530
48
+ tgp_ui/templates/publication.html,sha256=TXCHCE2198cHFmN4_l4JNmq0nQoy8GwnESePjRfjliU,9357
49
+ tgp_ui/templates/tab_final_upload.html,sha256=Nn1pwxI_Y_XBB81N5l2G8P_pSsVOyS2AQLsp8IE9a4s,1663
50
+ tg_prepare-2.1.0b1.dist-info/METADATA,sha256=sZRgS2wsd54fQOGcyShPWkEBMqpj0C08w8xXTzhfLrs,529
51
+ tg_prepare-2.1.0b1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
52
+ tg_prepare-2.1.0b1.dist-info/entry_points.txt,sha256=mbsfiEA8_fQdrFUE_oabDi-Syw8jjli37Ms7tawDDhs,78
53
+ tg_prepare-2.1.0b1.dist-info/top_level.txt,sha256=ueOyX_KdozreQJD_HRs6kAsvGNO4vfM9B_QqqhdyRPI,19
54
+ tg_prepare-2.1.0b1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1 @@
1
+ "94c9d50a8949b627be9608827a66d0025919e013d89236b092de90dd0b79fd12"
tgp_backend/__init__.py CHANGED
@@ -4,15 +4,14 @@
4
4
 
5
5
  import logging
6
6
 
7
- from .util import config
7
+ from .config import LOG_LEVEL
8
8
 
9
9
  log = logging.getLogger(__name__)
10
10
 
11
- log_level = config("log", "level", default="INFO")
12
11
  log_config = dict(
13
- level=log_level,
12
+ level=LOG_LEVEL,
14
13
  format="%(asctime)s %(name)-10s %(levelname)-4s %(message)s",
15
14
  )
16
15
 
17
16
  logging.basicConfig(**log_config)
18
- logging.getLogger("").setLevel(log_level)
17
+ logging.getLogger("").setLevel(LOG_LEVEL)
tgp_backend/config.py ADDED
@@ -0,0 +1,31 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (C) 2023-2024 TU-Dresden (ZIH)
3
+ # ralf.klammer@tu-dresden.de
4
+ import logging
5
+
6
+ from configparser import ConfigParser
7
+
8
+ log = logging.getLogger(__name__)
9
+
10
+
11
+ def get_config_value(section, parameter, default=None):
12
+ _config = ConfigParser()
13
+ _config.read("config.ini")
14
+
15
+ if section not in _config:
16
+ log.warn("Section: %s not in *.ini -> using default" % section)
17
+ return default
18
+ config_val = _config[section].get(parameter)
19
+ if not config_val:
20
+ log.info(
21
+ "Parameter: %s not in section: (%s) of *.ini -> using default: %s"
22
+ % (parameter, section, default)
23
+ )
24
+ return default
25
+ else:
26
+ return config_val
27
+
28
+
29
+ LOG_LEVEL = get_config_value("log", "level", default="INFO")
30
+ MAIN_PATH = get_config_value("main", "path", default="./projects")
31
+ MAX_FILENAME_LENGTH = get_config_value("main", "maxFileNameLength", default=15)
@@ -6,13 +6,11 @@ import logging
6
6
  import os
7
7
  import re
8
8
 
9
- from tgp_backend.util import config, get_file_extension
10
-
9
+ from tgp_backend.config import MAX_FILENAME_LENGTH
10
+ from tgp_backend.util import get_file_extension
11
11
 
12
12
  log = logging.getLogger(__name__)
13
13
 
14
- maxFileNameLength = config("main", "maxFileNameLength", default=15)
15
-
16
14
 
17
15
  def generateList(path=None):
18
16
  # path = path if path else config("main", "path", default="/tmp")
@@ -38,12 +36,12 @@ def generateList(path=None):
38
36
  curDir = path
39
37
 
40
38
  for d in dList:
41
- if len(d["name"]) > maxFileNameLength:
39
+ if len(d["name"]) > MAX_FILENAME_LENGTH:
42
40
  dots = "..."
43
41
  else:
44
42
  dots = ""
45
43
  temp_dir = {}
46
- temp_dir["f"] = d["name"][0:maxFileNameLength] + dots
44
+ temp_dir["f"] = d["name"][0:MAX_FILENAME_LENGTH] + dots
47
45
  temp_dir["name"] = d["name"]
48
46
  temp_dir["f_url"] = re.sub("#", "|HASHTAG|", d["fullpath"])
49
47
  temp_dir["currentDir"] = curDir
@@ -61,12 +59,12 @@ def generateList(path=None):
61
59
  pass
62
60
  if not image:
63
61
  image = "files_icon/unknown-icon.png"
64
- if len(f["name"]) > maxFileNameLength:
62
+ if len(f["name"]) > MAX_FILENAME_LENGTH:
65
63
  dots = "..."
66
64
  else:
67
65
  dots = ""
68
66
  temp_file = {}
69
- temp_file["f"] = f["name"][0:maxFileNameLength] + dots
67
+ temp_file["f"] = f["name"][0:MAX_FILENAME_LENGTH] + dots
70
68
  temp_file["name"] = f["name"]
71
69
  temp_file["name_stripped"] = f["name"].replace(f".{tp}", "")
72
70
  temp_file["f_url"] = re.sub("#", "|HASHTAG|", f["fullpath"])
tgp_backend/nextcloud.py CHANGED
@@ -4,7 +4,6 @@
4
4
  # moritz.wilhelm@tu-dresden.de
5
5
 
6
6
  import logging
7
-
8
7
  import nextcloud
9
8
  import os
10
9
 
@@ -14,21 +13,21 @@ log = logging.getLogger(__name__)
14
13
  class Nextcloud:
15
14
  def __init__(
16
15
  self,
17
- nextcloud_url=None,
16
+ url=None,
18
17
  username=None,
19
18
  password=None,
20
- root_dir=None,
19
+ folder=None,
21
20
  **kwargs,
22
21
  ):
23
22
  self._nxc = None
24
23
  self._root = None
25
- self.nextcloud_url = nextcloud_url
24
+ self.url = url
26
25
  self.username = username
27
26
  self.password = password
28
- self.root_dir = root_dir
27
+ self.root_dir = folder
29
28
 
30
29
  def test_connection(self):
31
- if all([self.nextcloud_url, self.username, self.password]):
30
+ if all([self.url, self.username, self.password]):
32
31
  return str(self.nxc.list_folders().status_code).startswith("2")
33
32
  return False
34
33
 
@@ -36,7 +35,7 @@ class Nextcloud:
36
35
  def nxc(self):
37
36
  if self._nxc is None:
38
37
  self._nxc = nextcloud.NextCloud(
39
- endpoint=self.nextcloud_url,
38
+ endpoint=self.url,
40
39
  user=self.username,
41
40
  password=self.password,
42
41
  )
@@ -51,29 +50,51 @@ class Nextcloud:
51
50
  raise "Could not find the Root Directory"
52
51
  return self._root
53
52
 
54
- def nxc_list_files_and_folders(self):
53
+ def nxc_list_files_and_folders(self, get_selectable=False):
54
+
55
+ selectable_folders = []
55
56
 
56
- def _list_rec(item):
57
+ def recursive_list(item, depth=0):
57
58
  items = []
58
59
  for d in item.list():
59
60
  if d.isdir():
60
- children = _list_rec(d)
61
+ children = recursive_list(d, depth=depth + 1)
62
+ contains_xml = any(
63
+ f.get("name", "").endswith(".xml") for f in children
64
+ )
65
+ item = {
66
+ "type": "folder",
67
+ "name": d.basename(),
68
+ "depth": depth,
69
+ "path": d.get_relative_path(),
70
+ "contains_xml": contains_xml,
71
+ "children": {
72
+ "count": len(children),
73
+ "list": children,
74
+ },
75
+ }
76
+ items.append(item)
77
+ if contains_xml:
78
+ selectable_folders.append(item)
79
+ else:
61
80
  items.append(
62
81
  {
63
- "type": "folder",
82
+ "type": "file",
64
83
  "name": d.basename(),
65
- "path": d.get_relative_path(),
66
- "children": {
67
- "count": len(children),
68
- "list": children,
69
- },
84
+ "depth": depth,
70
85
  }
71
86
  )
72
- else:
73
- items.append({"type": "file", "name": d.basename()})
74
87
  return items
75
88
 
76
- return _list_rec(self.root)
89
+ result = recursive_list(self.root)
90
+ if get_selectable:
91
+ return selectable_folders
92
+ else:
93
+ return result
94
+
95
+ def get_selectable_folders(self):
96
+
97
+ return self.nxc_list_files_and_folders(get_selectable=True)
77
98
 
78
99
  def download_nxc_files(self, file_paths, projectname):
79
100
  for path in file_paths:
tgp_backend/project.py CHANGED
@@ -3,10 +3,15 @@
3
3
  # ralf.klammer@tu-dresden.de
4
4
  # moritz.wilhelm@tu-dresden.de
5
5
  import logging
6
+
7
+ import json
6
8
  import os
7
9
  import subprocess
8
10
 
11
+ from datetime import datetime
12
+
9
13
  from tg_model.collection import CollectionModeler # type: ignore
14
+ from tg_model.project import Project as ProjectHandler # type: ignore
10
15
  from tg_model.tei import TEIParser # type: ignore
11
16
 
12
17
  from tg_model.yaml import ( # type: ignore
@@ -16,93 +21,172 @@ from tg_model.yaml import ( # type: ignore
16
21
  ProjectConfig,
17
22
  )
18
23
 
19
- from tg_model.project import Project as TGMProject
20
-
24
+ from tgp_backend.config import MAIN_PATH
21
25
  from tgp_backend.tgclient import TGclient
22
- from tgp_backend.util import config, list_files_and_folders
26
+ from tgp_backend.util import (
27
+ list_files_and_folders,
28
+ get_selectable_folders,
29
+ )
23
30
 
24
31
  log = logging.getLogger(__name__)
25
32
 
26
- MAIN_PATH = config("main", "path", default="./projects")
27
-
28
33
 
29
34
  class Project(object):
30
- def __init__(self, projectname):
31
- self.name = projectname
32
- self.fullpath = f"{MAIN_PATH}/{projectname}"
33
- self.datapath = f"{self.fullpath}/data"
34
- self.metadatapath = f"{self.fullpath}/metadata"
35
- self._main_config = None
35
+ def __init__(self, path):
36
+ self.path = path
37
+ self._project_config = None
36
38
  self._collections = None
37
39
  self._nextcloud = None
38
40
  self.tgm_project = TGMProject(self.metadatapath)
39
41
 
42
+ @property
43
+ def name(self):
44
+ log.warning("Project.name is deprecated, use Project.path instead")
45
+ return self.path
46
+
47
+ @property
48
+ def fullpath(self):
49
+ return f"{MAIN_PATH}/{self.path}"
50
+
51
+ @property
52
+ def title(self):
53
+ return self.project_config.title
54
+
55
+ @property
56
+ def description(self):
57
+ return self.project_config.description
58
+
59
+ @property
60
+ def collectors(self):
61
+ return self.project_config.collectors
62
+
63
+ @property
64
+ def path_data(self):
65
+ return os.path.join(self.fullpath, "data")
66
+
67
+ @property
68
+ def path_metadata(self):
69
+ return os.path.join(self.fullpath, "metadata")
70
+
71
+ @property
72
+ def path_otherfiles(self):
73
+ return os.path.join(self.path_metadata, "other_files")
74
+
40
75
  def create(self):
41
76
  if not os.path.exists(self.fullpath):
42
77
  os.makedirs(self.fullpath)
43
- os.makedirs(self.datapath)
44
- os.makedirs(self.metadatapath)
78
+ os.makedirs(self.path_data)
79
+ os.makedirs(self.path_metadata)
45
80
  else:
46
81
  log.warning("Project already exists!")
47
82
 
83
+ @property
84
+ def avatar(self):
85
+ if self.project_config.avatar:
86
+ return os.path.join(
87
+ self.path_otherfiles, self.project_config.avatar
88
+ )
89
+
90
+ @property
91
+ def xslt(self):
92
+ if self.project_config.xslt:
93
+ return os.path.join(self.path_otherfiles, self.project_config.xslt)
94
+
95
+ def save_avatar(self, file):
96
+ if file.filename and file.filename != "":
97
+ file.save(os.path.join(self.path_otherfiles, file.filename))
98
+ self.project_config.avatar = file.filename
99
+
100
+ def save_xslt(self, file):
101
+ if file.filename and file.filename != "":
102
+ file.save(os.path.join(self.path_otherfiles, file.filename))
103
+ self.project_config.xslt = file.filename
104
+
105
+ def clear_xslt(self):
106
+ self.project_config.xslt = None
107
+ self.project_config.save()
108
+
109
+ def update(self, title, description, collectors, avatar, xslt):
110
+ self.project_config.title = title
111
+ self.project_config.description = description
112
+ self.project_config.collectors = collectors
113
+
114
+ if avatar:
115
+ self.save_avatar(avatar)
116
+ self.save_xslt(xslt)
117
+
118
+ self.project_config.save()
119
+
48
120
  def clone_git_project(self, url):
49
121
  repo_name = url.split("/")[-1].replace(".git", "")
50
- repo_path = os.path.join(self.datapath, repo_name)
122
+ repo_path = os.path.join(self.path_data, repo_name)
51
123
 
52
124
  if not os.path.exists(repo_path):
53
125
  subprocess.run(["git", "clone", url, repo_path])
54
126
  else:
55
- log.warning("Repository already exists!")
127
+ log.info("Repository already exists!")
56
128
 
57
129
  def file_upload(self, files):
58
130
  for file in files:
59
131
  if file:
60
132
  filename = file.filename
61
- filepath = os.path.join(self.datapath, filename)
133
+ filepath = os.path.join(self.path_data, filename)
62
134
  directory = os.path.dirname(filepath)
63
135
  if not os.path.exists(directory):
64
136
  os.makedirs(directory)
65
137
  file.save(filepath)
66
138
 
67
139
  def list_files_and_folders(self):
68
- return list_files_and_folders(self.datapath)
140
+ return list_files_and_folders(self.path_data)
141
+
142
+ def get_selectable_folders(self):
143
+ selectable_folders = get_selectable_folders(self.path_data)
144
+ for folder in selectable_folders:
145
+ folder["relative_path"] = folder["path"].replace(
146
+ self.path_data, ""
147
+ )
148
+ return selectable_folders
69
149
 
70
150
  @property
71
151
  def main_config(self):
72
- if not self._main_config:
73
- self._main_config = ProjectConfig(projectpath=self.metadatapath)
74
- return self._main_config
75
-
76
- @main_config.setter
77
- def main_config(self, tei_directories):
78
- self._main_config = ProjectConfigTemplate(self.metadatapath).render(
79
- tei_directories=tei_directories
152
+ log.warning(
153
+ "Project.main_config is deprecated, use Project.project_config instead"
80
154
  )
155
+ return self.project_config
81
156
 
82
- def init_configs(self, tei_directories):
83
- if not self.main_config.exists():
84
- self.main_config = tei_directories
85
- else:
86
- self.main_config.update(tei_directories=tei_directories)
87
- # for tei_dir in tei_directories:z
88
- # # if not self.main_config.get_subproject(inpath=tei_dir):
89
- # break
90
- self._set_collections()
157
+ @property
158
+ def project_config(self):
159
+ if not self._project_config:
160
+ self._project_config = ProjectConfig(
161
+ projectpath=self.path_metadata
162
+ )
163
+ if not self._project_config.exists():
164
+ self._project_config = ProjectConfigTemplate(
165
+ self.path_metadata
166
+ ).render()
167
+ self._project_config.title = self.path
168
+ self._project_config.save()
169
+
170
+ return self._project_config
171
+
172
+ @property
173
+ def project_handler(self):
174
+ return ProjectHandler(self.path_metadata)
91
175
 
92
176
  def get_subproject_inpath(self, name):
93
- for subproject in self.main_config.content["subprojects"]:
177
+ for subproject in self.project_config.content["subprojects"]:
94
178
  if subproject["name"] == name:
95
179
  return subproject["inpath"]
96
180
  return None
97
181
 
98
182
  def _set_collections(self):
99
183
  self._collections = {}
100
- if self.main_config.exists():
101
- for subproject in self.main_config.content["subprojects"]:
184
+ if self.project_config.exists():
185
+ for subproject in self.project_config.content["subprojects"]:
102
186
  collection_config = CollectionConfig(subproject["basepath"])
103
187
  if not collection_config.exists():
104
188
  collection_config = CollectionConfigTemplate(
105
- projectpath=self.metadatapath,
189
+ projectpath=self.path_metadata,
106
190
  subproject=subproject,
107
191
  files=[
108
192
  TEIParser(fullpath=file)
@@ -115,7 +199,7 @@ class Project(object):
115
199
  "paths": subproject,
116
200
  "parser": CollectionParser(collection_config),
117
201
  "modeler": CollectionModeler(
118
- subproject, self.metadatapath
202
+ subproject, self.path_metadata
119
203
  ),
120
204
  }
121
205
 
@@ -134,6 +218,49 @@ class Project(object):
134
218
  def get_tgp(self, instance):
135
219
  return TGProject(self, instance)
136
220
 
221
+ def _save_validation_results(self, results, filepath):
222
+ """
223
+ Saves the validation results to a JSON file.
224
+ Adds the date of the last execution.
225
+ """
226
+ results["last_run"] = datetime.now().strftime("%Y-%m-%d %H:%M")
227
+
228
+ with open(filepath, "w", encoding="utf-8") as f:
229
+ json.dump(results, f, indent=4, ensure_ascii=False)
230
+
231
+ log.info(f"Validation results saved to {filepath}")
232
+
233
+ def _run_validation(self):
234
+ validation_results = self.project_handler.validate()
235
+ self._save_validation_results(validation_results, self.validation_file)
236
+ return validation_results
237
+
238
+ @property
239
+ def validation_file(self):
240
+ """
241
+ Returns the path to the validation results file.
242
+ """
243
+ return os.path.join(self.path_metadata, "validation_results.json")
244
+
245
+ def get_validation_results(self, refresh=False):
246
+ """
247
+ Returns stored validation results.
248
+ If no results are saved or `refresh=True`, a new validation will
249
+ be performed.
250
+ """
251
+ # If `refresh` is set or file doesn't exist, perform new validation
252
+ if refresh or not os.path.exists(self.validation_file):
253
+ validation_results = self._run_validation()
254
+ else:
255
+ # Load results from file
256
+ with open(self.validation_file, "r", encoding="utf-8") as f:
257
+ validation_results = json.load(f)
258
+
259
+ if "ready_for_publication" not in validation_results:
260
+ validation_results = self._run_validation()
261
+
262
+ return validation_results
263
+
137
264
 
138
265
  class TGProject(object):
139
266
 
@@ -143,7 +270,7 @@ class TGProject(object):
143
270
  else:
144
271
  self.project = Project(project)
145
272
  self.instance = instance
146
- self.main_config = self.project.main_config
273
+ self.project_config = self.project.project_config
147
274
  self.collections = self.project.collections
148
275
  self._tg_client = None
149
276
  self._tg_session_id = None
@@ -152,7 +279,7 @@ class TGProject(object):
152
279
  @property
153
280
  def tg_session_id(self):
154
281
  if not self._tg_session_id:
155
- self._tg_session_id = self.main_config.get_tg_session_id(
282
+ self._tg_session_id = self.project_config.get_tg_session_id(
156
283
  self.instance
157
284
  )
158
285
  return self._tg_session_id
@@ -160,12 +287,12 @@ class TGProject(object):
160
287
  @tg_session_id.setter
161
288
  def tg_session_id(self, session_id):
162
289
  self._tg_session_id = session_id
163
- self.main_config.set_tg_session_id(session_id, self.instance)
290
+ self.project_config.set_tg_session_id(session_id, self.instance)
164
291
 
165
292
  @property
166
293
  def tg_project_id(self):
167
294
  if not self._tg_project_id:
168
- self._tg_project_id = self.main_config.get_tg_project_id(
295
+ self._tg_project_id = self.project_config.get_tg_project_id(
169
296
  self.instance
170
297
  )
171
298
  return self._tg_project_id
@@ -173,7 +300,7 @@ class TGProject(object):
173
300
  @tg_project_id.setter
174
301
  def tg_project_id(self, project_id):
175
302
  self._tg_project_id = project_id
176
- self.main_config.set_tg_project_id(project_id, self.instance)
303
+ self.project_config.set_tg_project_id(project_id, self.instance)
177
304
 
178
305
  @property
179
306
  def tg_client(self):
@@ -208,7 +335,7 @@ class TGProject(object):
208
335
  if self.tg_session_id:
209
336
  return self.tg_client.get_project_content(project_id).hits
210
337
 
211
- def publish_tg_project(self, instance="test"):
338
+ def upload_tg_project(self, instance="test"):
212
339
  if not self.tg_session_id:
213
340
  return False
214
341
 
@@ -0,0 +1,47 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (C) 2023-2025 TU-Dresden (ZIH)
3
+ # ralf.klammer@tu-dresden.de
4
+ # moritz.wilhelm@tu-dresden.de
5
+
6
+ import logging
7
+
8
+ log = logging.getLogger(__name__)
9
+
10
+
11
+ class SessionManager:
12
+ def __init__(self, session):
13
+ self.session = session
14
+
15
+
16
+ class SessionManagerNextcloud(SessionManager):
17
+
18
+ def save_credentials(self, form_data):
19
+ log.info("Saving credentials to session")
20
+ self.session["nextcloud"] = {
21
+ "username": form_data.get("nextcloud_user"),
22
+ "password": form_data.get("nextcloud_password"),
23
+ "url": form_data.get("nextcloud_url"),
24
+ "folder": form_data.get("nextcloud_folder", ""),
25
+ }
26
+
27
+ def get_credentials(self):
28
+ log.info("Retrieving credentials from session")
29
+ return self.session.get("nextcloud", {})
30
+
31
+ def delete_nextcloud_credentials(self):
32
+ log.info("Deleting credentials from session")
33
+ self.session.pop("nextcloud", None)
34
+
35
+ def request_has_valid_credentials(self, request):
36
+ """
37
+ Check if the request contains valid Nextcloud credentials.
38
+ """
39
+ relevant_form_keys = [
40
+ "nextcloud_url",
41
+ "nextcloud_user",
42
+ "nextcloud_password",
43
+ ]
44
+ relevant_form_values = [
45
+ request.form.get(key) for key in relevant_form_keys
46
+ ]
47
+ return all(relevant_form_values)