tg-prepare 1.0.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 (56) hide show
  1. {tg_prepare-1.0.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.0.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 -12
  8. tgp_backend/nextcloud.py +40 -19
  9. tgp_backend/project.py +179 -52
  10. tgp_backend/session_manager.py +47 -0
  11. tgp_backend/tgclient.py +0 -1
  12. tgp_backend/util.py +73 -25
  13. tgp_ui/app.py +43 -337
  14. tgp_ui/routes/__init__.py +0 -0
  15. tgp_ui/routes/collection.py +272 -0
  16. tgp_ui/routes/data.py +228 -0
  17. tgp_ui/routes/project.py +102 -0
  18. tgp_ui/routes/publication.py +129 -0
  19. tgp_ui/routes/tabs.py +34 -0
  20. tgp_ui/routes/views.py +62 -0
  21. tgp_ui/static/css/navbar.css +92 -0
  22. tgp_ui/static/js/collectionManager.js +60 -0
  23. tgp_ui/static/js/fileManager.js +186 -0
  24. tgp_ui/static/js/main.js +32 -450
  25. tgp_ui/static/js/modalManager.js +105 -0
  26. tgp_ui/static/js/navbarManager.js +151 -0
  27. tgp_ui/static/js/projectManager.js +60 -0
  28. tgp_ui/static/js/require.js +5 -0
  29. tgp_ui/static/js/sidebarManager.js +32 -0
  30. tgp_ui/static/js/tabManager.js +79 -0
  31. tgp_ui/templates/layout.html +39 -37
  32. tgp_ui/templates/macros.html +84 -74
  33. tgp_ui/templates/project_main.html +36 -0
  34. tgp_ui/templates/project_navbar.html +81 -0
  35. tgp_ui/templates/{projects.html → projects_main.html} +14 -29
  36. tgp_ui/templates/publication.html +156 -0
  37. tgp_ui/templates/tab_final_upload.html +29 -0
  38. tg_prepare-1.0.0.dist-info/RECORD +0 -46
  39. tgp_ui/static/js/jstree.min.js +0 -3
  40. tgp_ui/templates/collection.html +0 -194
  41. tgp_ui/templates/empty.html +0 -4
  42. tgp_ui/templates/file_upload.html +0 -24
  43. tgp_ui/templates/jsfiler.html +0 -6
  44. tgp_ui/templates/layout2.html +0 -99
  45. tgp_ui/templates/main.html +0 -5
  46. tgp_ui/templates/nxc_file_tree.html +0 -33
  47. tgp_ui/templates/project.html +0 -25
  48. tgp_ui/templates/project_nxc.html +0 -37
  49. tgp_ui/templates/publish.html +0 -120
  50. tgp_ui/templates/storage.html +0 -49
  51. tgp_ui/templates/tei_browser.html +0 -14
  52. tgp_ui/templates/tei_explorer.html +0 -48
  53. tgp_ui/templates/xpath_parser_modal_content.html +0 -37
  54. {tg_prepare-1.0.0.dist-info → tg_prepare-2.1.0b1.dist-info}/entry_points.txt +0 -0
  55. {tg_prepare-1.0.0.dist-info → tg_prepare-2.1.0b1.dist-info}/licenses/LICENSE +0 -0
  56. {tg_prepare-1.0.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.0.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")
@@ -28,7 +26,6 @@ def generateList(path=None):
28
26
  for name in os.listdir(path):
29
27
  fullpath = "%s/%s" % (path, name)
30
28
  if not name.startswith("."):
31
- print(name)
32
29
  if os.path.isdir(fullpath):
33
30
  dList.append({"fullpath": fullpath, "name": name})
34
31
  else:
@@ -39,12 +36,12 @@ def generateList(path=None):
39
36
  curDir = path
40
37
 
41
38
  for d in dList:
42
- if len(d["name"]) > maxFileNameLength:
39
+ if len(d["name"]) > MAX_FILENAME_LENGTH:
43
40
  dots = "..."
44
41
  else:
45
42
  dots = ""
46
43
  temp_dir = {}
47
- temp_dir["f"] = d["name"][0:maxFileNameLength] + dots
44
+ temp_dir["f"] = d["name"][0:MAX_FILENAME_LENGTH] + dots
48
45
  temp_dir["name"] = d["name"]
49
46
  temp_dir["f_url"] = re.sub("#", "|HASHTAG|", d["fullpath"])
50
47
  temp_dir["currentDir"] = curDir
@@ -62,12 +59,12 @@ def generateList(path=None):
62
59
  pass
63
60
  if not image:
64
61
  image = "files_icon/unknown-icon.png"
65
- if len(f["name"]) > maxFileNameLength:
62
+ if len(f["name"]) > MAX_FILENAME_LENGTH:
66
63
  dots = "..."
67
64
  else:
68
65
  dots = ""
69
66
  temp_file = {}
70
- temp_file["f"] = f["name"][0:maxFileNameLength] + dots
67
+ temp_file["f"] = f["name"][0:MAX_FILENAME_LENGTH] + dots
71
68
  temp_file["name"] = f["name"]
72
69
  temp_file["name_stripped"] = f["name"].replace(f".{tp}", "")
73
70
  temp_file["f_url"] = re.sub("#", "|HASHTAG|", f["fullpath"])
@@ -79,7 +76,4 @@ def generateList(path=None):
79
76
 
80
77
  file_list_dict.append(temp_file)
81
78
 
82
- # log.debug("generateList - dir_list_dict: %s" % dir_list_dict)
83
- # print("generateList - file_list_dict: %s" % file_list_dict)
84
- # print(tp_dict)
85
79
  return dir_list_dict, file_list_dict
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,91 +21,172 @@ from tg_model.yaml import ( # type: ignore
16
21
  ProjectConfig,
17
22
  )
18
23
 
24
+ from tgp_backend.config import MAIN_PATH
19
25
  from tgp_backend.tgclient import TGclient
20
- 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
+ )
21
30
 
22
31
  log = logging.getLogger(__name__)
23
32
 
24
- MAIN_PATH = config("main", "path", default="./projects")
25
-
26
33
 
27
34
  class Project(object):
28
- def __init__(self, projectname):
29
- self.name = projectname
30
- self.fullpath = f"{MAIN_PATH}/{projectname}"
31
- self.datapath = f"{self.fullpath}/data"
32
- self.metadatapath = f"{self.fullpath}/metadata"
33
- self._main_config = None
35
+ def __init__(self, path):
36
+ self.path = path
37
+ self._project_config = None
34
38
  self._collections = None
35
39
  self._nextcloud = None
40
+ self.tgm_project = TGMProject(self.metadatapath)
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")
36
74
 
37
75
  def create(self):
38
76
  if not os.path.exists(self.fullpath):
39
77
  os.makedirs(self.fullpath)
40
- os.makedirs(self.datapath)
41
- os.makedirs(self.metadatapath)
78
+ os.makedirs(self.path_data)
79
+ os.makedirs(self.path_metadata)
42
80
  else:
43
81
  log.warning("Project already exists!")
44
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
+
45
120
  def clone_git_project(self, url):
46
121
  repo_name = url.split("/")[-1].replace(".git", "")
47
- repo_path = os.path.join(self.datapath, repo_name)
122
+ repo_path = os.path.join(self.path_data, repo_name)
48
123
 
49
124
  if not os.path.exists(repo_path):
50
125
  subprocess.run(["git", "clone", url, repo_path])
51
126
  else:
52
- log.warning("Repository already exists!")
127
+ log.info("Repository already exists!")
53
128
 
54
129
  def file_upload(self, files):
55
130
  for file in files:
56
131
  if file:
57
132
  filename = file.filename
58
- filepath = os.path.join(self.datapath, filename)
133
+ filepath = os.path.join(self.path_data, filename)
59
134
  directory = os.path.dirname(filepath)
60
135
  if not os.path.exists(directory):
61
136
  os.makedirs(directory)
62
137
  file.save(filepath)
63
138
 
64
139
  def list_files_and_folders(self):
65
- print(self.datapath)
66
- 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
67
149
 
68
150
  @property
69
151
  def main_config(self):
70
- if not self._main_config:
71
- self._main_config = ProjectConfig(projectpath=self.metadatapath)
72
- return self._main_config
73
-
74
- @main_config.setter
75
- def main_config(self, tei_directories):
76
- self._main_config = ProjectConfigTemplate(self.metadatapath).render(
77
- tei_directories=tei_directories
152
+ log.warning(
153
+ "Project.main_config is deprecated, use Project.project_config instead"
78
154
  )
155
+ return self.project_config
79
156
 
80
- def init_configs(self, tei_directories):
81
- if not self.main_config.exists():
82
- self.main_config = tei_directories
83
- else:
84
- self.main_config.update(tei_directories=tei_directories)
85
- # for tei_dir in tei_directories:z
86
- # # if not self.main_config.get_subproject(inpath=tei_dir):
87
- # break
88
- 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)
89
175
 
90
176
  def get_subproject_inpath(self, name):
91
- for subproject in self.main_config.content["subprojects"]:
177
+ for subproject in self.project_config.content["subprojects"]:
92
178
  if subproject["name"] == name:
93
179
  return subproject["inpath"]
94
180
  return None
95
181
 
96
182
  def _set_collections(self):
97
183
  self._collections = {}
98
- if self.main_config.exists():
99
- for subproject in self.main_config.content["subprojects"]:
184
+ if self.project_config.exists():
185
+ for subproject in self.project_config.content["subprojects"]:
100
186
  collection_config = CollectionConfig(subproject["basepath"])
101
187
  if not collection_config.exists():
102
188
  collection_config = CollectionConfigTemplate(
103
- projectpath=self.metadatapath,
189
+ projectpath=self.path_metadata,
104
190
  subproject=subproject,
105
191
  files=[
106
192
  TEIParser(fullpath=file)
@@ -113,7 +199,7 @@ class Project(object):
113
199
  "paths": subproject,
114
200
  "parser": CollectionParser(collection_config),
115
201
  "modeler": CollectionModeler(
116
- subproject, self.metadatapath
202
+ subproject, self.path_metadata
117
203
  ),
118
204
  }
119
205
 
@@ -132,6 +218,49 @@ class Project(object):
132
218
  def get_tgp(self, instance):
133
219
  return TGProject(self, instance)
134
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
+
135
264
 
136
265
  class TGProject(object):
137
266
 
@@ -141,7 +270,7 @@ class TGProject(object):
141
270
  else:
142
271
  self.project = Project(project)
143
272
  self.instance = instance
144
- self.main_config = self.project.main_config
273
+ self.project_config = self.project.project_config
145
274
  self.collections = self.project.collections
146
275
  self._tg_client = None
147
276
  self._tg_session_id = None
@@ -150,7 +279,7 @@ class TGProject(object):
150
279
  @property
151
280
  def tg_session_id(self):
152
281
  if not self._tg_session_id:
153
- self._tg_session_id = self.main_config.get_tg_session_id(
282
+ self._tg_session_id = self.project_config.get_tg_session_id(
154
283
  self.instance
155
284
  )
156
285
  return self._tg_session_id
@@ -158,12 +287,12 @@ class TGProject(object):
158
287
  @tg_session_id.setter
159
288
  def tg_session_id(self, session_id):
160
289
  self._tg_session_id = session_id
161
- self.main_config.set_tg_session_id(session_id, self.instance)
290
+ self.project_config.set_tg_session_id(session_id, self.instance)
162
291
 
163
292
  @property
164
293
  def tg_project_id(self):
165
294
  if not self._tg_project_id:
166
- self._tg_project_id = self.main_config.get_tg_project_id(
295
+ self._tg_project_id = self.project_config.get_tg_project_id(
167
296
  self.instance
168
297
  )
169
298
  return self._tg_project_id
@@ -171,7 +300,7 @@ class TGProject(object):
171
300
  @tg_project_id.setter
172
301
  def tg_project_id(self, project_id):
173
302
  self._tg_project_id = project_id
174
- self.main_config.set_tg_project_id(project_id, self.instance)
303
+ self.project_config.set_tg_project_id(project_id, self.instance)
175
304
 
176
305
  @property
177
306
  def tg_client(self):
@@ -206,22 +335,21 @@ class TGProject(object):
206
335
  if self.tg_session_id:
207
336
  return self.tg_client.get_project_content(project_id).hits
208
337
 
209
- def publish_tg_project(self, instance="test"):
338
+ def upload_tg_project(self, instance="test"):
210
339
  if not self.tg_session_id:
211
340
  return False
212
341
 
213
342
  # step 1: create required metadata
214
- for c_name in self.collections:
215
- collection = self.collections[c_name]
216
- collection["modeler"].render_collection()
343
+ self.project.tgm_project.render_project()
217
344
 
218
345
  # step 2: push project to textgrid server
219
346
  for collection_name in self.collections:
220
347
  collection = self.collections[collection_name]
221
- self.tg_client.put_aggregation(
222
- self.tg_project_id,
223
- collection["modeler"].get_collection_path(),
224
- )
348
+ print(collection["modeler"].get_collection_path())
349
+ # self.tg_client.put_aggregation(
350
+ # self.tg_project_id,
351
+ # collection["modeler"].get_collection_path(),
352
+ # )
225
353
 
226
354
 
227
355
  class CollectionParser(object):
@@ -247,7 +375,6 @@ class CollectionParser(object):
247
375
  results = []
248
376
  for element in self.elements:
249
377
  result = element["tei_parser"].find(xpath)
250
- print(result)
251
378
  if result is not None:
252
379
  results.append(
253
380
  {