libreqda 1.2.0__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.
Files changed (255) hide show
  1. CHANGELOG.md +481 -0
  2. libreqda-1.2.0.dist-info/LICENSE.txt +28 -0
  3. libreqda-1.2.0.dist-info/METADATA +131 -0
  4. libreqda-1.2.0.dist-info/RECORD +255 -0
  5. libreqda-1.2.0.dist-info/WHEEL +4 -0
  6. libreqda-1.2.0.dist-info/entry_points.txt +4 -0
  7. scripts/babelrc +8 -0
  8. scripts/dist/LICENSE.txt +33 -0
  9. scripts/dist/RELEASE.txt +23 -0
  10. scripts/dist/macos/libreqda.icns +0 -0
  11. scripts/dist/macos/macos.spec +43 -0
  12. scripts/dist/macos/plist.patch +10 -0
  13. scripts/dist/macos/taguette.icns +0 -0
  14. scripts/dist/macos/taguette_console_wrapper +4 -0
  15. scripts/dist/macos.sh +32 -0
  16. scripts/dist/pyinstaller_entrypoint.py +19 -0
  17. scripts/dist/windows/windows-installer.iss +51 -0
  18. scripts/dist/windows/windows.spec +39 -0
  19. scripts/edugain/worker_update_edugain_idps.py +89 -0
  20. scripts/edugain/worker_update_edugain_idps.sh +9 -0
  21. scripts/make_password_reset_link.py +56 -0
  22. scripts/make_theme.py +99 -0
  23. scripts/new_db_revision.sh +8 -0
  24. scripts/register_user.py +76 -0
  25. scripts/update_pot.sh +33 -0
  26. scripts/update_translations.sh +17 -0
  27. scripts/validate.py +140 -0
  28. scripts/wipe_and_disable_user.py +90 -0
  29. taguette/__init__.py +13 -0
  30. taguette/access_control.py +145 -0
  31. taguette/convert.py +623 -0
  32. taguette/database/__init__.py +177 -0
  33. taguette/database/base.py +9 -0
  34. taguette/database/copy.py +433 -0
  35. taguette/database/models.py +920 -0
  36. taguette/demodata.py +688 -0
  37. taguette/export.py +500 -0
  38. taguette/extract.py +287 -0
  39. taguette/import_codebook.py +110 -0
  40. taguette/l10n/de_DE/LC_MESSAGES/taguette_javascript.mo +0 -0
  41. taguette/l10n/de_DE/LC_MESSAGES/taguette_main.mo +0 -0
  42. taguette/l10n/el_EL/LC_MESSAGES/taguette_javascript.mo +0 -0
  43. taguette/l10n/el_GR/LC_MESSAGES/taguette_javascript.mo +0 -0
  44. taguette/l10n/el_GR/LC_MESSAGES/taguette_main.mo +0 -0
  45. taguette/l10n/en_US/LC_MESSAGES/taguette_javascript.mo +0 -0
  46. taguette/l10n/en_US/LC_MESSAGES/taguette_main.mo +0 -0
  47. taguette/l10n/es_ES/LC_MESSAGES/taguette_javascript.mo +0 -0
  48. taguette/l10n/es_ES/LC_MESSAGES/taguette_main.mo +0 -0
  49. taguette/l10n/fr_FR/LC_MESSAGES/taguette_javascript.mo +0 -0
  50. taguette/l10n/fr_FR/LC_MESSAGES/taguette_main.mo +0 -0
  51. taguette/l10n/id_ID/LC_MESSAGES/taguette_javascript.mo +0 -0
  52. taguette/l10n/id_ID/LC_MESSAGES/taguette_main.mo +0 -0
  53. taguette/l10n/it_IT/LC_MESSAGES/taguette_javascript.mo +0 -0
  54. taguette/l10n/it_IT/LC_MESSAGES/taguette_main.mo +0 -0
  55. taguette/l10n/nl_NL/LC_MESSAGES/taguette_javascript.mo +0 -0
  56. taguette/l10n/nl_NL/LC_MESSAGES/taguette_main.mo +0 -0
  57. taguette/l10n/pt_PT/LC_MESSAGES/taguette_javascript.mo +0 -0
  58. taguette/l10n/pt_PT/LC_MESSAGES/taguette_main.mo +0 -0
  59. taguette/l10n/sv_SE/LC_MESSAGES/taguette_javascript.mo +0 -0
  60. taguette/l10n/sv_SE/LC_MESSAGES/taguette_main.mo +0 -0
  61. taguette/loki_handler.py +107 -0
  62. taguette/main.py +649 -0
  63. taguette/migrations/env.py +72 -0
  64. taguette/migrations/script.py.mako +24 -0
  65. taguette/migrations/versions/09c662cd9483_make_documents_filename_not_null.py +39 -0
  66. taguette/migrations/versions/1b29e68900c1_add_user_roles.py +55 -0
  67. taguette/migrations/versions/1b96a606551d_update_user_to_have_a_display_name.py +34 -0
  68. taguette/migrations/versions/2467bab602b7_add_color_column_to_tags_table.py +33 -0
  69. taguette/migrations/versions/382db36f20f4_add_user_disabled.py +66 -0
  70. taguette/migrations/versions/43d6c240309d_remove_document_groups.py +81 -0
  71. taguette/migrations/versions/447d636f72c5_cascades.py +40 -0
  72. taguette/migrations/versions/491de2dc7cd7_add_indexes.py +51 -0
  73. taguette/migrations/versions/4c55b3b3ff30_user_login_length.py +58 -0
  74. taguette/migrations/versions/61a4abcc8769_user_with_parent_for_permissions.py +40 -0
  75. taguette/migrations/versions/6489b5f9cfb5_increase_password_hash_size.py +34 -0
  76. taguette/migrations/versions/679f625e6e6a_fix_unique_tags.py +59 -0
  77. taguette/migrations/versions/77c4543a5b9b_add_parent_id_column_to_collection_table.py +41 -0
  78. taguette/migrations/versions/7c94b7a31e69_add_is_archived_column_to_projets.py +39 -0
  79. taguette/migrations/versions/7f56de97e3c8_change_edugain_displayname_length.py +42 -0
  80. taguette/migrations/versions/807c0cc0ecf6_remove_invalid_hltags.py +43 -0
  81. taguette/migrations/versions/80b1cc9d4c22_add_user_email.py +35 -0
  82. taguette/migrations/versions/91ade71ccf4d_add_user_language.py +28 -0
  83. taguette/migrations/versions/955db2e523d1_add_collections_tables_and_associations_.py +60 -0
  84. taguette/migrations/versions/a1b2c3d4e5f6_add_user_hide_code_counts.py +28 -0
  85. taguette/migrations/versions/b23f3b7a638e_add_user_password_change_date.py +36 -0
  86. taguette/migrations/versions/b7c1e2d3f4a5_add_project_compact_layout.py +28 -0
  87. taguette/migrations/versions/bc8e0e0677e9_rename_command_fields.py +126 -0
  88. taguette/migrations/versions/bce44849c2f2_lowercase_logins.py +96 -0
  89. taguette/migrations/versions/ca076140922f_update_user_for_external_logins.py +32 -0
  90. taguette/migrations/versions/d86c45d7aa8a_add_document_direction.py +127 -0
  91. taguette/migrations/versions/db5e31a0233d_remove_blank_highlights.py +88 -0
  92. taguette/migrations/versions/dd37d9027a24_unique_tags.py +68 -0
  93. taguette/migrations/versions/de678cef2a94_add_project_id_to_collections_and_.py +41 -0
  94. taguette/migrations/versions/e459991d960e_add_table_edugainidps.py +39 -0
  95. taguette/migrations/versions/e4cf92942271_add_index.py +29 -0
  96. taguette/migrations/versions/e4e090a5b511_remove_commands_document_id_fk.py +38 -0
  97. taguette/migrations/versions/e9a9f1c77971_add_favorite_column_to_document.py +32 -0
  98. taguette/migrations/versions/ecb4065de575_don_t_use_server_default_now_timezone_.py +44 -0
  99. taguette/migrations/versions/fbdc29566f62_edugain_fix_field_type.py +42 -0
  100. taguette/migrations/versions/fdc6eb54b034_add_parent_id_column_to_tags_table.py +31 -0
  101. taguette/static/README.md +34 -0
  102. taguette/static/alt_templates/.gitkeep +0 -0
  103. taguette/static/assets/test/logos/monsters-university-logo.png +0 -0
  104. taguette/static/css/custom.css +1696 -0
  105. taguette/static/css/fa-all.min.css +5 -0
  106. taguette/static/css/themed.css +3 -0
  107. taguette/static/css/themed.css.map +25 -0
  108. taguette/static/css/vendor/bootstrap-treeview.min.css +1 -0
  109. taguette/static/css/vendor/bootstrap.min.css +6 -0
  110. taguette/static/css/vendor/font/context-menu-icons.eot +0 -0
  111. taguette/static/css/vendor/font/context-menu-icons.ttf +0 -0
  112. taguette/static/css/vendor/font/context-menu-icons.woff +0 -0
  113. taguette/static/css/vendor/font/context-menu-icons.woff2 +0 -0
  114. taguette/static/css/vendor/hierarchy-select.min.css +1 -0
  115. taguette/static/css/vendor/introjs.min.css +2 -0
  116. taguette/static/css/vendor/jquery.contextMenu.min.css +16 -0
  117. taguette/static/favicon.ico +0 -0
  118. taguette/static/js/codebook-viz.js +428 -0
  119. taguette/static/js/collection/context-menu.js +180 -0
  120. taguette/static/js/collection/hierarchy-select.js +71 -0
  121. taguette/static/js/collection/manager.js +217 -0
  122. taguette/static/js/document/context-menu.js +92 -0
  123. taguette/static/js/document/custom-select.js +82 -0
  124. taguette/static/js/document/manager.js +943 -0
  125. taguette/static/js/document/treeview.js +593 -0
  126. taguette/static/js/highlight/README.md +18 -0
  127. taguette/static/js/highlight/crud.js +119 -0
  128. taguette/static/js/highlight/extracts.js +497 -0
  129. taguette/static/js/highlight/loader.js +109 -0
  130. taguette/static/js/highlight/renderer.js +405 -0
  131. taguette/static/js/login.js +15 -0
  132. taguette/static/js/permissions.js +153 -0
  133. taguette/static/js/polling-event.js +145 -0
  134. taguette/static/js/project/intro.js +524 -0
  135. taguette/static/js/project/manager.js +0 -0
  136. taguette/static/js/project/search.js +124 -0
  137. taguette/static/js/tag/context-menu.js +133 -0
  138. taguette/static/js/tag/explore-codes.js +400 -0
  139. taguette/static/js/tag/hierarchy-select.js +69 -0
  140. taguette/static/js/tag/manager.js +545 -0
  141. taguette/static/js/tag/treeview.js +613 -0
  142. taguette/static/js/taguette.js +1790 -0
  143. taguette/static/js/utilities.js +714 -0
  144. taguette/static/js/vendor/bootstrap-treeview-custum.js +421 -0
  145. taguette/static/js/vendor/bootstrap-treeview-custum.min.js +1 -0
  146. taguette/static/js/vendor/bootstrap-treeview-default.min.js +1 -0
  147. taguette/static/js/vendor/bootstrap.bundle.min.js +7 -0
  148. taguette/static/js/vendor/bootstrap.bundle.min.js.map +1 -0
  149. taguette/static/js/vendor/hierarchy-select.min.js +1 -0
  150. taguette/static/js/vendor/introjs.min.js +17 -0
  151. taguette/static/js/vendor/jquery-3.7.1.js +2 -0
  152. taguette/static/js/vendor/jquery.contextMenu.min.js +2 -0
  153. taguette/static/libreqda.ico +0 -0
  154. taguette/static/logo-h.png +0 -0
  155. taguette/static/logo.png +0 -0
  156. taguette/static/logo_blanc.png +0 -0
  157. taguette/static/missing.png +0 -0
  158. taguette/static/robots.txt +2 -0
  159. taguette/static/scss/components/_badge.scss +42 -0
  160. taguette/static/scss/components/_button.scss +169 -0
  161. taguette/static/scss/components/_checkbox.scss +40 -0
  162. taguette/static/scss/components/_context_menu.scss +28 -0
  163. taguette/static/scss/components/_custom_select.scss +150 -0
  164. taguette/static/scss/components/_dropdown.scss +43 -0
  165. taguette/static/scss/components/_form.scss +54 -0
  166. taguette/static/scss/components/_modal.scss +57 -0
  167. taguette/static/scss/components/_project_card.scss +163 -0
  168. taguette/static/scss/components/_tag.scss +308 -0
  169. taguette/static/scss/components/_treeview.scss +76 -0
  170. taguette/static/scss/custom.scss +809 -0
  171. taguette/static/scss/layout/_credits.scss +62 -0
  172. taguette/static/scss/layout/_footer.scss +6 -0
  173. taguette/static/scss/layout/_navigation.scss +165 -0
  174. taguette/static/scss/layout/_sidebar.scss +37 -0
  175. taguette/static/scss/themes/default/_variables.scss +103 -0
  176. taguette/static/scss/themes/default/theme.css +3 -0
  177. taguette/static/scss/themes/default/theme.css.map +26 -0
  178. taguette/static/scss/themes/default/theme.scss +4 -0
  179. taguette/static/webfonts/BadScript-OFL.txt +94 -0
  180. taguette/static/webfonts/BadScript.ttf +0 -0
  181. taguette/static/webfonts/Montserrat-Light.ttf +0 -0
  182. taguette/static/webfonts/Montserrat-OFL.txt +93 -0
  183. taguette/static/webfonts/fa-brands-400.eot +0 -0
  184. taguette/static/webfonts/fa-brands-400.svg +1184 -0
  185. taguette/static/webfonts/fa-brands-400.ttf +0 -0
  186. taguette/static/webfonts/fa-brands-400.woff +0 -0
  187. taguette/static/webfonts/fa-brands-400.woff2 +0 -0
  188. taguette/static/webfonts/fa-regular-400.eot +0 -0
  189. taguette/static/webfonts/fa-regular-400.svg +467 -0
  190. taguette/static/webfonts/fa-regular-400.ttf +0 -0
  191. taguette/static/webfonts/fa-regular-400.woff +0 -0
  192. taguette/static/webfonts/fa-regular-400.woff2 +0 -0
  193. taguette/static/webfonts/fa-solid-900.eot +0 -0
  194. taguette/static/webfonts/fa-solid-900.svg +2618 -0
  195. taguette/static/webfonts/fa-solid-900.ttf +0 -0
  196. taguette/static/webfonts/fa-solid-900.woff +0 -0
  197. taguette/static/webfonts/fa-solid-900.woff2 +0 -0
  198. taguette/templates/alt_templates/.gitkeep +0 -0
  199. taguette/templates/archived_projects.html +50 -0
  200. taguette/templates/auth/account.html +74 -0
  201. taguette/templates/auth/cilogon.login.html +89 -0
  202. taguette/templates/auth/email_reset_password.html +5 -0
  203. taguette/templates/auth/external_account.html +60 -0
  204. taguette/templates/auth/login.html +144 -0
  205. taguette/templates/auth/reset_password.html +27 -0
  206. taguette/templates/auth/token_needed.html +7 -0
  207. taguette/templates/base.html +209 -0
  208. taguette/templates/bug_report.html +158 -0
  209. taguette/templates/cookies_prompt.html +15 -0
  210. taguette/templates/downloads.html +127 -0
  211. taguette/templates/email_bug_report.txt +9 -0
  212. taguette/templates/email_reset_password.txt +5 -0
  213. taguette/templates/error.html +8 -0
  214. taguette/templates/export_codebook.html +20 -0
  215. taguette/templates/export_document.html +20 -0
  216. taguette/templates/export_highlights.html +47 -0
  217. taguette/templates/index.html +347 -0
  218. taguette/templates/maintenance.html +8 -0
  219. taguette/templates/messages.js +74 -0
  220. taguette/templates/modal/change_document.html +68 -0
  221. taguette/templates/modal/create_collection.html +100 -0
  222. taguette/templates/modal/create_document.html +84 -0
  223. taguette/templates/modal/create_highlight.html +38 -0
  224. taguette/templates/modal/create_tag.html +73 -0
  225. taguette/templates/modal/explore_codes.html +58 -0
  226. taguette/templates/modal/manage_collaborators.html +44 -0
  227. taguette/templates/modal/merge_tag.html +49 -0
  228. taguette/templates/new_password.html +32 -0
  229. taguette/templates/partials/alert.html +16 -0
  230. taguette/templates/partials/codebook_viz_modal.html +34 -0
  231. taguette/templates/partials/confirm.html +17 -0
  232. taguette/templates/partials/document_toolbar.html +37 -0
  233. taguette/templates/partials/hlinfo.html +13 -0
  234. taguette/templates/partials/project_card.html +44 -0
  235. taguette/templates/partials/project_card_archived.html +41 -0
  236. taguette/templates/partials/project_sidebar.html +99 -0
  237. taguette/templates/partials/spinner.html +11 -0
  238. taguette/templates/permissions.html +54 -0
  239. taguette/templates/project.html +288 -0
  240. taguette/templates/project_delete.html +17 -0
  241. taguette/templates/project_import.html +122 -0
  242. taguette/templates/project_import_codebook.html +34 -0
  243. taguette/templates/project_import_codebook_confirm.html +49 -0
  244. taguette/templates/project_new.html +31 -0
  245. taguette/templates/tos.html +8 -0
  246. taguette/templates/trans.js +7 -0
  247. taguette/templates/welcome.html +15 -0
  248. taguette/utils.py +177 -0
  249. taguette/validate.py +141 -0
  250. taguette/version_check.py +102 -0
  251. taguette/web/__init__.py +252 -0
  252. taguette/web/api.py +1835 -0
  253. taguette/web/base.py +864 -0
  254. taguette/web/export.py +338 -0
  255. taguette/web/views.py +1618 -0
@@ -0,0 +1,89 @@
1
+ # imports
2
+ import json
3
+ import datetime
4
+ import os
5
+
6
+ from taguette import database
7
+ from taguette.database.models import EdugainIdps
8
+
9
+ dir_path = os.path.dirname(os.path.realpath(__file__))
10
+ conf_path = os.path.dirname(os.path.dirname(dir_path))
11
+
12
+ # Open edugain idps JSON file
13
+ json_file = f'{dir_path}/edugain_idps.json'
14
+ try:
15
+ f = open(json_file)
16
+ idps = json.load(f)
17
+ except:
18
+ print(f"{json_file} not found")
19
+ exit()
20
+
21
+ config = {}
22
+ config_file = os.path.abspath(f'{conf_path}/taguette.conf')
23
+ with open(config_file) as fp:
24
+ code = compile(fp.read(), config_file, 'exec')
25
+ exec(code, config)
26
+ DBSession = database.connect(config['DATABASE'])
27
+ db = DBSession()
28
+
29
+ idps_added = 0
30
+ idps_removed = 0
31
+ idps_updated = 0
32
+
33
+ if idps[0][0]['id'] is not None:
34
+
35
+ for idp in idps:
36
+ idp_id = (db.query(database.EdugainIdps).
37
+ filter_by(entityid=idp[0]['entityid']).first())
38
+
39
+ if idp_id is None:
40
+ # Add a new entry
41
+ new_idp = EdugainIdps()
42
+ new_idp.id = idp[0]['id']
43
+ new_idp.entityid = idp[0]['entityid']
44
+ new_idp.e_displayname = idp[0]['e_displayname']
45
+ new_idp.regauth = idp[0]['regauth']
46
+ new_idp.entity_cat = idp[0]['entity_cat']
47
+ new_idp.eccs_status = idp[0]['eccs_status']
48
+ new_idp.validator_status = idp[0]['validator_status']
49
+ new_idp.coco_status = idp[0]['coco_status']
50
+ new_idp.coco_id = idp[0]['coco_id']
51
+ new_idp.sirtfi_status = idp[0]['sirtfi_status']
52
+ if new_idp.entityid.lower().find('inrs'):
53
+ new_idp.code = 'CAF'
54
+ else:
55
+ new_idp.code = idp[0]['code']
56
+ new_idp.scopes = idp[0]['scopes']
57
+
58
+ db.add(new_idp)
59
+ idps_added += 1
60
+
61
+ else:
62
+ # Update existing entry
63
+ idp_id.id = idp[0]['id']
64
+ idp_id.entityid = idp[0]['entityid']
65
+ idp_id.e_displayname = idp[0]['e_displayname']
66
+ idp_id.regauth = idp[0]['regauth']
67
+ idp_id.entity_cat = idp[0]['entity_cat']
68
+ idp_id.eccs_status = idp[0]['eccs_status']
69
+ idp_id.validator_status = idp[0]['validator_status']
70
+ idp_id.coco_status = idp[0]['coco_status']
71
+ idp_id.coco_id = idp[0]['coco_id']
72
+ idp_id.sirtfi_status = idp[0]['sirtfi_status']
73
+ if idp_id.entityid.lower().find('inrs'):
74
+ idp_id.code = 'CAF'
75
+ else:
76
+ idp_id.code = idp[0]['code']
77
+ idp_id.scopes = idp[0]['scopes']
78
+ idps_updated += 1
79
+
80
+ db.commit()
81
+
82
+
83
+ if idps_added != 0 or idps_removed != 0 or idps_updated != 0:
84
+
85
+ ct = "["+str(datetime.datetime.now())+"]"
86
+ print( f"{ct} UPDATE EDUGAIN IDPs => IDPs added: {idps_added}, updated {idps_updated}, removed {idps_removed}" )
87
+
88
+ # Close File
89
+ f.close()
@@ -0,0 +1,9 @@
1
+ #!/bin/bash
2
+ SCRIPTPATH="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
3
+
4
+ curl --fail --silent https://technical.edugain.org/api.php?action=list_entities -H "Accept: application/json" --output "${SCRIPTPATH}/edugain_idps.json"
5
+
6
+ # Update edugain IDPs local database table (edugain_idps)
7
+ cd "${SCRIPTPATH}/../../"
8
+ poetry run python "${SCRIPTPATH}/worker_update_edugain_idps.py" &>> "${SCRIPTPATH}/worker_update_edugain_idps.log"
9
+
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import argparse
4
+ import os
5
+ import sys
6
+ import time
7
+ from tornado.web import create_signed_value
8
+
9
+ from taguette import database
10
+
11
+
12
+ def main():
13
+ parser = argparse.ArgumentParser(
14
+ description="Generate a password reset link for manual delivery",
15
+ )
16
+ parser.add_argument('config_file', help="Configuration file")
17
+ parser.add_argument('user_login')
18
+
19
+ args = parser.parse_args()
20
+
21
+ config = {}
22
+ config_file = os.path.abspath(args.config_file)
23
+ with open(config_file) as fp:
24
+ code = compile(fp.read(), config_file, 'exec')
25
+ exec(code, config)
26
+
27
+ DBSession = database.connect(config['DATABASE'])
28
+ db = DBSession()
29
+
30
+ user = db.query(database.User).get(args.user_login)
31
+ if user is None:
32
+ print("No such user: %r" % args.user_login, file=sys.stderr)
33
+ sys.exit(1)
34
+
35
+ if user.disabled:
36
+ print("WARNING: USER IS DISABLED")
37
+
38
+ # Generate a signed token
39
+ reset_token = '%s|%s|%s' % (
40
+ int(time.time()),
41
+ user.login,
42
+ user.email,
43
+ )
44
+ reset_token = create_signed_value(
45
+ config['SECRET_KEY'],
46
+ 'reset_token',
47
+ reset_token,
48
+ )
49
+ reset_token = reset_token.decode('utf-8')
50
+ reset_link = '/new_password?reset_token=' + reset_token
51
+
52
+ print(reset_link)
53
+
54
+
55
+ if __name__ == '__main__':
56
+ main()
scripts/make_theme.py ADDED
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import os
4
+ import shutil
5
+ import sys
6
+ from pathlib import Path
7
+ import sass
8
+
9
+ script_dir = Path(__file__).parent
10
+ project_root = script_dir.parent
11
+ static_dir = project_root / "taguette" / "static"
12
+ templates_dir = project_root / "taguette" / "templates"
13
+
14
+
15
+ def get_theme_dir(theme_name):
16
+ return static_dir / "scss" / "themes" / theme_name
17
+
18
+
19
+ def get_template_dir(theme_name):
20
+ return templates_dir / "alt_templates" / theme_name
21
+
22
+
23
+ def get_assets_dir(theme_name):
24
+ return static_dir / "alt_templates" / theme_name
25
+
26
+
27
+ def compile_scss(theme_name):
28
+ theme_dir = get_theme_dir(theme_name)
29
+ scss_input = theme_dir / "theme.scss"
30
+ css_output = theme_dir / "theme.css"
31
+
32
+ if not scss_input.exists():
33
+ print(f"Error: input SCSS not found: {scss_input}")
34
+ sys.exit(1)
35
+
36
+ css_output.parent.mkdir(exist_ok=True)
37
+
38
+ try:
39
+ css, src_map = sass.compile(
40
+ filename=str(scss_input),
41
+ output_style="compressed",
42
+ source_map_filename=str(css_output) + ".map"
43
+ )
44
+
45
+ with open(css_output, "w") as f:
46
+ f.write(css)
47
+
48
+ with open(str(css_output) + ".map", "w") as f:
49
+ f.write(src_map)
50
+
51
+ print(f"Successfully compiled {scss_input} to {css_output}")
52
+
53
+ except sass.CompileError as e:
54
+ print(f"SCSS compilation error: {e}")
55
+ sys.exit(1)
56
+
57
+ except Exception as e:
58
+ print(f"SCSS can't write {e}")
59
+
60
+
61
+ def copy_default_theme(destination_theme_name):
62
+ from_theme_dir = get_theme_dir("default")
63
+ to_theme_dir = get_theme_dir(destination_theme_name)
64
+ shutil.copytree(from_theme_dir, to_theme_dir, dirs_exist_ok=True)
65
+ return destination_theme_name
66
+
67
+
68
+ def create_directories(theme_name):
69
+ theme_dir = get_theme_dir(theme_name)
70
+ template_dir = get_template_dir(theme_name)
71
+ assets_dir = get_assets_dir(theme_name)
72
+ if not os.path.exists(theme_dir):
73
+ os.makedirs(theme_dir, exist_ok=True)
74
+ copy_default_theme(theme_name)
75
+ print(f"Theme directory created : {theme_dir}")
76
+ else:
77
+ print(f"Theme directory already exists : {theme_dir}")
78
+
79
+ if not os.path.exists(template_dir):
80
+ os.makedirs(template_dir, exist_ok=True)
81
+ print(f"Templates directory created : {template_dir}")
82
+ else:
83
+ print(f"Templates directory already exists : {template_dir}")
84
+
85
+ if not os.path.exists(assets_dir):
86
+ os.makedirs(assets_dir, exist_ok=True)
87
+ print(f"Assets directory created : {assets_dir}")
88
+ else:
89
+ print(f"Assets directory already exists : {assets_dir}")
90
+
91
+
92
+ def main():
93
+ theme = sys.argv[1] if len(sys.argv) > 1 else "default"
94
+ create_directories(theme)
95
+ compile_scss(theme)
96
+
97
+
98
+ if __name__ == "__main__":
99
+ main()
@@ -0,0 +1,8 @@
1
+ #!/bin/bash
2
+
3
+ if [ "$#" != 1 ]; then
4
+ echo "Usage: new_db_revision.sh \"message\"" >&2
5
+ exit 2
6
+ fi
7
+
8
+ alembic -x db=sqlite:///$HOME/.local/share/taguette/taguette.sqlite3 revision --autogenerate -m "${1}"
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import argparse
4
+ import asyncio
5
+ import os
6
+ import random
7
+ import string
8
+ import sys
9
+
10
+ from taguette import database
11
+ from taguette import validate
12
+
13
+
14
+ PASSWORD_CHARS = string.ascii_letters + string.digits + '#%&-.?~'
15
+ PASSWORD_LENGTH = 14
16
+
17
+
18
+ async def main():
19
+ parser = argparse.ArgumentParser(
20
+ description="Register a new user",
21
+ )
22
+ parser.add_argument('config_file', help="Configuration file")
23
+ parser.add_argument('login')
24
+ parser.add_argument(
25
+ '--email',
26
+ help="User's email address (optional)",
27
+ action='store',
28
+ )
29
+
30
+ args = parser.parse_args()
31
+
32
+ config = {}
33
+ config_file = os.path.abspath(args.config_file)
34
+ with open(config_file) as fp:
35
+ code = compile(fp.read(), config_file, 'exec')
36
+ exec(code, config)
37
+
38
+ DBSession = database.connect(config['DATABASE'])
39
+ db = DBSession()
40
+
41
+ user = db.query(database.User).get(args.login)
42
+ if user is not None:
43
+ print("User exists: %s" % args.login, file=sys.stderr)
44
+ sys.exit(1)
45
+
46
+ # Validate input
47
+ login = validate.fix_user_login(args.login, new=True)
48
+ if args.email:
49
+ email = args.email
50
+ validate.user_email(email)
51
+ else:
52
+ email = None
53
+ print("email: %r" % email)
54
+
55
+ # Generate password
56
+ password = ''.join(random.choices(PASSWORD_CHARS, k=PASSWORD_LENGTH))
57
+
58
+ # Create user
59
+ user = database.User(
60
+ login=login,
61
+ email=email,
62
+ )
63
+ await user.set_password(password)
64
+ db.add(user)
65
+ db.commit()
66
+
67
+ print(
68
+ (
69
+ "New user created:\n login: {login}\n"
70
+ + " password: {password}"
71
+ ).format(login=login, password=password)
72
+ )
73
+
74
+
75
+ if __name__ == '__main__':
76
+ asyncio.run(main())
scripts/update_pot.sh ADDED
@@ -0,0 +1,33 @@
1
+ #!/bin/sh
2
+
3
+ set -eu
4
+
5
+ cd "$(dirname "$(dirname "$0")")"
6
+ pybabel extract -F scripts/babelrc -k _f -c TRANSLATORS: -s -o po/main.pot $(find taguette -name '*.py' -o -name '*.html')
7
+ pybabel extract -F scripts/babelrc -c TRANSLATORS: -s -o po/javascript.pot $(find taguette -name '*.js')
8
+
9
+ LOCALES=$(ls po/main_*.po 2>/dev/null | sed 's/.*main_\(.*\)\.po/\1/' | sort -u || echo "")
10
+
11
+ if [ -z "$LOCALES" ]; then
12
+ echo "No existing .po files found. Skipping update."
13
+ exit 0
14
+ fi
15
+
16
+ echo ""
17
+ echo "Found locales: $LOCALES"
18
+
19
+ # Update main catalogue
20
+ echo ""
21
+ echo "Updating main catalogue..."
22
+ for locale in $LOCALES; do
23
+ echo " - $locale"
24
+ pybabel update -i po/main.pot -l $locale -o po/main_${locale}.po --no-fuzzy-matching
25
+ done
26
+
27
+ # Update javascript catalogue
28
+ echo ""
29
+ echo "Updating javascript catalogue..."
30
+ for locale in $LOCALES; do
31
+ echo " - $locale"
32
+ pybabel update -i po/javascript.pot -l $locale -o po/javascript_${locale}.po --no-fuzzy-matching
33
+ done
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+
3
+ set -eu
4
+
5
+ cd "$(dirname "$(dirname "$0")")"
6
+ find po -name 'main_*.po' | while read fin; do
7
+ loc=$(echo $fin | sed 's/^po\/main_//' | sed 's/\.po$//')
8
+ dir=taguette/l10n/$loc/LC_MESSAGES
9
+ mkdir -p $dir
10
+ pybabel compile -i $fin -o $dir/taguette_main.mo
11
+ done
12
+ find po -name 'javascript_*.po' | while read fin; do
13
+ loc=$(echo $fin | sed 's/^po\/javascript_//' | sed 's/\.po$//')
14
+ dir=taguette/l10n/$loc/LC_MESSAGES
15
+ mkdir -p $dir
16
+ pybabel compile -i $fin -o $dir/taguette_javascript.mo
17
+ done
scripts/validate.py ADDED
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import argparse
4
+ import logging
5
+ import os
6
+ import pkg_resources
7
+ from sqlalchemy.orm.exc import ObjectDeletedError
8
+ import sys
9
+ import tornado.locale
10
+
11
+ from taguette import database, validate, convert
12
+
13
+
14
+ def main():
15
+ logging.basicConfig(level=logging.INFO)
16
+
17
+ parser = argparse.ArgumentParser(
18
+ description="Run validation on an existing database",
19
+ )
20
+ parser.add_argument('config_file', help="Configuration file")
21
+ parser.add_argument('--users', action='store_true',
22
+ help="Validate users")
23
+ parser.add_argument('--projects', action='store_true',
24
+ help="Validate project metadata")
25
+ parser.add_argument('--documents', action='store_true',
26
+ help="Validate documents")
27
+ parser.add_argument('--tags', action='store_true',
28
+ help="Validate tags")
29
+
30
+ args = parser.parse_args()
31
+
32
+ config = {}
33
+ config_file = os.path.abspath(args.config_file)
34
+ with open(config_file) as fp:
35
+ code = compile(fp.read(), config_file, 'exec')
36
+ exec(code, config)
37
+
38
+ DBSession = database.connect(config['DATABASE'])
39
+ db = DBSession()
40
+
41
+ errors = 0
42
+
43
+ def check(func, *args, **kwargs):
44
+ try:
45
+ return func(*args, **kwargs)
46
+ except validate.InvalidFormat:
47
+ return False
48
+
49
+ if args.users:
50
+ d = pkg_resources.resource_filename('taguette', 'l10n')
51
+ tornado.locale.load_gettext_translations(d, 'taguette_main')
52
+
53
+ for user in db.query(database.User):
54
+ if not check(validate.user_login, user.login):
55
+ errors += 1
56
+ print("Invalid user login: %r" % user.login)
57
+ if user.email and not check(validate.user_email, user.email):
58
+ errors += 1
59
+ print("Invalid user email for %r: %r" % (
60
+ user.login,
61
+ user.email,
62
+ ))
63
+ if (
64
+ user.language
65
+ and user.language not in tornado.locale.get_supported_locales()
66
+ ):
67
+ errors += 1
68
+ print("User locale unsupported for %r: %r" % (
69
+ user.login,
70
+ user.language,
71
+ ))
72
+
73
+ if args.projects:
74
+ for project in db.query(database.Project):
75
+ if not check(validate.project_name, project.name):
76
+ errors += 1
77
+ print("Invalid project name: %r" % project.name)
78
+ if not check(validate.description, project.description):
79
+ errors += 1
80
+ print("Invalid project description for %d (%r): %r" % (
81
+ project.id,
82
+ project.name,
83
+ project.description,
84
+ ))
85
+
86
+ if args.documents:
87
+ for document in db.query(database.Document):
88
+ if not check(validate.document_name, document.name):
89
+ errors += 1
90
+ print("Invalid document name for %r: %r" % (
91
+ document.id,
92
+ document.name,
93
+ ))
94
+ if not check(validate.description, document.description):
95
+ errors += 1
96
+ print("Invalid document description for %d (%r): %r" % (
97
+ document.id,
98
+ document.name,
99
+ document.description,
100
+ ))
101
+ if not check(validate.filename, document.filename):
102
+ errors += 1
103
+ print("Invalid document filename for %d (%r): %r" % (
104
+ document.id,
105
+ document.name,
106
+ document.filename,
107
+ ))
108
+ try:
109
+ contents = document.contents
110
+ except ObjectDeletedError:
111
+ # The document was deleted since we started iterating
112
+ pass
113
+ else:
114
+ if not convert.is_html_safe(contents):
115
+ errors += 1
116
+ print("Invalid document contents for %d (%r)" % (
117
+ document.id,
118
+ document.name,
119
+ ))
120
+
121
+ if args.tags:
122
+ for tag in db.query(database.Tag):
123
+ if not check(validate.tag_path, tag.path):
124
+ errors += 1
125
+ print("Invalid tag path for %d: %r" % (tag.id, tag.path))
126
+ if not check(validate.description, tag.description):
127
+ errors += 1
128
+ print("Invalid tag description for %d (%r): %r" % (
129
+ tag.id,
130
+ tag.path,
131
+ tag.description
132
+ ))
133
+
134
+ print("%d errors found" % errors)
135
+ if errors:
136
+ sys.exit(1)
137
+
138
+
139
+ if __name__ == '__main__':
140
+ main()
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import argparse
4
+ import os
5
+ from sqlalchemy.orm import joinedload
6
+ import sys
7
+
8
+ from taguette import database
9
+
10
+
11
+ def main():
12
+ parser = argparse.ArgumentParser(
13
+ description="Wipe user data and disable it",
14
+ )
15
+ parser.add_argument('config_file', help="Configuration file")
16
+ parser.add_argument('user_login')
17
+ parser.add_argument(
18
+ '--delete-projects',
19
+ action='store_true',
20
+ help="If projects exist, delete them rather than bailing out",
21
+ )
22
+
23
+ args = parser.parse_args()
24
+
25
+ config = {}
26
+ config_file = os.path.abspath(args.config_file)
27
+ with open(config_file) as fp:
28
+ code = compile(fp.read(), config_file, 'exec')
29
+ exec(code, config)
30
+
31
+ DBSession = database.connect(config['DATABASE'])
32
+ db = DBSession()
33
+
34
+ user = (
35
+ db.query(database.User)
36
+ .options(
37
+ joinedload(database.User.project_memberships)
38
+ .options(joinedload(database.ProjectMember.project))
39
+ )
40
+ .get(args.user_login)
41
+ )
42
+ if user is None:
43
+ print("No such user: %r" % args.user_login, file=sys.stderr)
44
+ sys.exit(1)
45
+
46
+ if user.disabled:
47
+ print("User is already disabled")
48
+ sys.exit(3)
49
+
50
+ if not args.delete_projects:
51
+ sole_admin = False
52
+
53
+ for membership in user.project_memberships:
54
+ project = membership.project
55
+
56
+ # Check if that project has other members
57
+ if not any(
58
+ member.user_login != user.login
59
+ for member in project.members
60
+ ):
61
+ sole_admin = True
62
+ print("User is sole member of project %d %r" % (
63
+ project.id, project.name,
64
+ ))
65
+
66
+ if sole_admin:
67
+ sys.exit(1)
68
+
69
+ else:
70
+ for membership in user.project_memberships:
71
+ project = membership.project
72
+
73
+ # Check if that project has other members
74
+ if not any(
75
+ member.user_login != user.login
76
+ for member in project.members
77
+ ):
78
+ print("Deleting project %d %r" % (project.id, project.name))
79
+ db.delete(project)
80
+
81
+ user.project_memberships.clear()
82
+ user.hashed_password = ''
83
+ user.language = None
84
+ user.email = None
85
+ user.disabled = True
86
+ db.commit()
87
+
88
+
89
+ if __name__ == '__main__':
90
+ main()
taguette/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ __version__ = '1.1.3'
2
+
3
+
4
+ from gettext import NullTranslations
5
+ trans = NullTranslations()
6
+ del NullTranslations
7
+
8
+
9
+ _exact_version = __version__
10
+
11
+
12
+ def exact_version():
13
+ return _exact_version