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.
- CHANGELOG.md +481 -0
- libreqda-1.2.0.dist-info/LICENSE.txt +28 -0
- libreqda-1.2.0.dist-info/METADATA +131 -0
- libreqda-1.2.0.dist-info/RECORD +255 -0
- libreqda-1.2.0.dist-info/WHEEL +4 -0
- libreqda-1.2.0.dist-info/entry_points.txt +4 -0
- scripts/babelrc +8 -0
- scripts/dist/LICENSE.txt +33 -0
- scripts/dist/RELEASE.txt +23 -0
- scripts/dist/macos/libreqda.icns +0 -0
- scripts/dist/macos/macos.spec +43 -0
- scripts/dist/macos/plist.patch +10 -0
- scripts/dist/macos/taguette.icns +0 -0
- scripts/dist/macos/taguette_console_wrapper +4 -0
- scripts/dist/macos.sh +32 -0
- scripts/dist/pyinstaller_entrypoint.py +19 -0
- scripts/dist/windows/windows-installer.iss +51 -0
- scripts/dist/windows/windows.spec +39 -0
- scripts/edugain/worker_update_edugain_idps.py +89 -0
- scripts/edugain/worker_update_edugain_idps.sh +9 -0
- scripts/make_password_reset_link.py +56 -0
- scripts/make_theme.py +99 -0
- scripts/new_db_revision.sh +8 -0
- scripts/register_user.py +76 -0
- scripts/update_pot.sh +33 -0
- scripts/update_translations.sh +17 -0
- scripts/validate.py +140 -0
- scripts/wipe_and_disable_user.py +90 -0
- taguette/__init__.py +13 -0
- taguette/access_control.py +145 -0
- taguette/convert.py +623 -0
- taguette/database/__init__.py +177 -0
- taguette/database/base.py +9 -0
- taguette/database/copy.py +433 -0
- taguette/database/models.py +920 -0
- taguette/demodata.py +688 -0
- taguette/export.py +500 -0
- taguette/extract.py +287 -0
- taguette/import_codebook.py +110 -0
- taguette/l10n/de_DE/LC_MESSAGES/taguette_javascript.mo +0 -0
- taguette/l10n/de_DE/LC_MESSAGES/taguette_main.mo +0 -0
- taguette/l10n/el_EL/LC_MESSAGES/taguette_javascript.mo +0 -0
- taguette/l10n/el_GR/LC_MESSAGES/taguette_javascript.mo +0 -0
- taguette/l10n/el_GR/LC_MESSAGES/taguette_main.mo +0 -0
- taguette/l10n/en_US/LC_MESSAGES/taguette_javascript.mo +0 -0
- taguette/l10n/en_US/LC_MESSAGES/taguette_main.mo +0 -0
- taguette/l10n/es_ES/LC_MESSAGES/taguette_javascript.mo +0 -0
- taguette/l10n/es_ES/LC_MESSAGES/taguette_main.mo +0 -0
- taguette/l10n/fr_FR/LC_MESSAGES/taguette_javascript.mo +0 -0
- taguette/l10n/fr_FR/LC_MESSAGES/taguette_main.mo +0 -0
- taguette/l10n/id_ID/LC_MESSAGES/taguette_javascript.mo +0 -0
- taguette/l10n/id_ID/LC_MESSAGES/taguette_main.mo +0 -0
- taguette/l10n/it_IT/LC_MESSAGES/taguette_javascript.mo +0 -0
- taguette/l10n/it_IT/LC_MESSAGES/taguette_main.mo +0 -0
- taguette/l10n/nl_NL/LC_MESSAGES/taguette_javascript.mo +0 -0
- taguette/l10n/nl_NL/LC_MESSAGES/taguette_main.mo +0 -0
- taguette/l10n/pt_PT/LC_MESSAGES/taguette_javascript.mo +0 -0
- taguette/l10n/pt_PT/LC_MESSAGES/taguette_main.mo +0 -0
- taguette/l10n/sv_SE/LC_MESSAGES/taguette_javascript.mo +0 -0
- taguette/l10n/sv_SE/LC_MESSAGES/taguette_main.mo +0 -0
- taguette/loki_handler.py +107 -0
- taguette/main.py +649 -0
- taguette/migrations/env.py +72 -0
- taguette/migrations/script.py.mako +24 -0
- taguette/migrations/versions/09c662cd9483_make_documents_filename_not_null.py +39 -0
- taguette/migrations/versions/1b29e68900c1_add_user_roles.py +55 -0
- taguette/migrations/versions/1b96a606551d_update_user_to_have_a_display_name.py +34 -0
- taguette/migrations/versions/2467bab602b7_add_color_column_to_tags_table.py +33 -0
- taguette/migrations/versions/382db36f20f4_add_user_disabled.py +66 -0
- taguette/migrations/versions/43d6c240309d_remove_document_groups.py +81 -0
- taguette/migrations/versions/447d636f72c5_cascades.py +40 -0
- taguette/migrations/versions/491de2dc7cd7_add_indexes.py +51 -0
- taguette/migrations/versions/4c55b3b3ff30_user_login_length.py +58 -0
- taguette/migrations/versions/61a4abcc8769_user_with_parent_for_permissions.py +40 -0
- taguette/migrations/versions/6489b5f9cfb5_increase_password_hash_size.py +34 -0
- taguette/migrations/versions/679f625e6e6a_fix_unique_tags.py +59 -0
- taguette/migrations/versions/77c4543a5b9b_add_parent_id_column_to_collection_table.py +41 -0
- taguette/migrations/versions/7c94b7a31e69_add_is_archived_column_to_projets.py +39 -0
- taguette/migrations/versions/7f56de97e3c8_change_edugain_displayname_length.py +42 -0
- taguette/migrations/versions/807c0cc0ecf6_remove_invalid_hltags.py +43 -0
- taguette/migrations/versions/80b1cc9d4c22_add_user_email.py +35 -0
- taguette/migrations/versions/91ade71ccf4d_add_user_language.py +28 -0
- taguette/migrations/versions/955db2e523d1_add_collections_tables_and_associations_.py +60 -0
- taguette/migrations/versions/a1b2c3d4e5f6_add_user_hide_code_counts.py +28 -0
- taguette/migrations/versions/b23f3b7a638e_add_user_password_change_date.py +36 -0
- taguette/migrations/versions/b7c1e2d3f4a5_add_project_compact_layout.py +28 -0
- taguette/migrations/versions/bc8e0e0677e9_rename_command_fields.py +126 -0
- taguette/migrations/versions/bce44849c2f2_lowercase_logins.py +96 -0
- taguette/migrations/versions/ca076140922f_update_user_for_external_logins.py +32 -0
- taguette/migrations/versions/d86c45d7aa8a_add_document_direction.py +127 -0
- taguette/migrations/versions/db5e31a0233d_remove_blank_highlights.py +88 -0
- taguette/migrations/versions/dd37d9027a24_unique_tags.py +68 -0
- taguette/migrations/versions/de678cef2a94_add_project_id_to_collections_and_.py +41 -0
- taguette/migrations/versions/e459991d960e_add_table_edugainidps.py +39 -0
- taguette/migrations/versions/e4cf92942271_add_index.py +29 -0
- taguette/migrations/versions/e4e090a5b511_remove_commands_document_id_fk.py +38 -0
- taguette/migrations/versions/e9a9f1c77971_add_favorite_column_to_document.py +32 -0
- taguette/migrations/versions/ecb4065de575_don_t_use_server_default_now_timezone_.py +44 -0
- taguette/migrations/versions/fbdc29566f62_edugain_fix_field_type.py +42 -0
- taguette/migrations/versions/fdc6eb54b034_add_parent_id_column_to_tags_table.py +31 -0
- taguette/static/README.md +34 -0
- taguette/static/alt_templates/.gitkeep +0 -0
- taguette/static/assets/test/logos/monsters-university-logo.png +0 -0
- taguette/static/css/custom.css +1696 -0
- taguette/static/css/fa-all.min.css +5 -0
- taguette/static/css/themed.css +3 -0
- taguette/static/css/themed.css.map +25 -0
- taguette/static/css/vendor/bootstrap-treeview.min.css +1 -0
- taguette/static/css/vendor/bootstrap.min.css +6 -0
- taguette/static/css/vendor/font/context-menu-icons.eot +0 -0
- taguette/static/css/vendor/font/context-menu-icons.ttf +0 -0
- taguette/static/css/vendor/font/context-menu-icons.woff +0 -0
- taguette/static/css/vendor/font/context-menu-icons.woff2 +0 -0
- taguette/static/css/vendor/hierarchy-select.min.css +1 -0
- taguette/static/css/vendor/introjs.min.css +2 -0
- taguette/static/css/vendor/jquery.contextMenu.min.css +16 -0
- taguette/static/favicon.ico +0 -0
- taguette/static/js/codebook-viz.js +428 -0
- taguette/static/js/collection/context-menu.js +180 -0
- taguette/static/js/collection/hierarchy-select.js +71 -0
- taguette/static/js/collection/manager.js +217 -0
- taguette/static/js/document/context-menu.js +92 -0
- taguette/static/js/document/custom-select.js +82 -0
- taguette/static/js/document/manager.js +943 -0
- taguette/static/js/document/treeview.js +593 -0
- taguette/static/js/highlight/README.md +18 -0
- taguette/static/js/highlight/crud.js +119 -0
- taguette/static/js/highlight/extracts.js +497 -0
- taguette/static/js/highlight/loader.js +109 -0
- taguette/static/js/highlight/renderer.js +405 -0
- taguette/static/js/login.js +15 -0
- taguette/static/js/permissions.js +153 -0
- taguette/static/js/polling-event.js +145 -0
- taguette/static/js/project/intro.js +524 -0
- taguette/static/js/project/manager.js +0 -0
- taguette/static/js/project/search.js +124 -0
- taguette/static/js/tag/context-menu.js +133 -0
- taguette/static/js/tag/explore-codes.js +400 -0
- taguette/static/js/tag/hierarchy-select.js +69 -0
- taguette/static/js/tag/manager.js +545 -0
- taguette/static/js/tag/treeview.js +613 -0
- taguette/static/js/taguette.js +1790 -0
- taguette/static/js/utilities.js +714 -0
- taguette/static/js/vendor/bootstrap-treeview-custum.js +421 -0
- taguette/static/js/vendor/bootstrap-treeview-custum.min.js +1 -0
- taguette/static/js/vendor/bootstrap-treeview-default.min.js +1 -0
- taguette/static/js/vendor/bootstrap.bundle.min.js +7 -0
- taguette/static/js/vendor/bootstrap.bundle.min.js.map +1 -0
- taguette/static/js/vendor/hierarchy-select.min.js +1 -0
- taguette/static/js/vendor/introjs.min.js +17 -0
- taguette/static/js/vendor/jquery-3.7.1.js +2 -0
- taguette/static/js/vendor/jquery.contextMenu.min.js +2 -0
- taguette/static/libreqda.ico +0 -0
- taguette/static/logo-h.png +0 -0
- taguette/static/logo.png +0 -0
- taguette/static/logo_blanc.png +0 -0
- taguette/static/missing.png +0 -0
- taguette/static/robots.txt +2 -0
- taguette/static/scss/components/_badge.scss +42 -0
- taguette/static/scss/components/_button.scss +169 -0
- taguette/static/scss/components/_checkbox.scss +40 -0
- taguette/static/scss/components/_context_menu.scss +28 -0
- taguette/static/scss/components/_custom_select.scss +150 -0
- taguette/static/scss/components/_dropdown.scss +43 -0
- taguette/static/scss/components/_form.scss +54 -0
- taguette/static/scss/components/_modal.scss +57 -0
- taguette/static/scss/components/_project_card.scss +163 -0
- taguette/static/scss/components/_tag.scss +308 -0
- taguette/static/scss/components/_treeview.scss +76 -0
- taguette/static/scss/custom.scss +809 -0
- taguette/static/scss/layout/_credits.scss +62 -0
- taguette/static/scss/layout/_footer.scss +6 -0
- taguette/static/scss/layout/_navigation.scss +165 -0
- taguette/static/scss/layout/_sidebar.scss +37 -0
- taguette/static/scss/themes/default/_variables.scss +103 -0
- taguette/static/scss/themes/default/theme.css +3 -0
- taguette/static/scss/themes/default/theme.css.map +26 -0
- taguette/static/scss/themes/default/theme.scss +4 -0
- taguette/static/webfonts/BadScript-OFL.txt +94 -0
- taguette/static/webfonts/BadScript.ttf +0 -0
- taguette/static/webfonts/Montserrat-Light.ttf +0 -0
- taguette/static/webfonts/Montserrat-OFL.txt +93 -0
- taguette/static/webfonts/fa-brands-400.eot +0 -0
- taguette/static/webfonts/fa-brands-400.svg +1184 -0
- taguette/static/webfonts/fa-brands-400.ttf +0 -0
- taguette/static/webfonts/fa-brands-400.woff +0 -0
- taguette/static/webfonts/fa-brands-400.woff2 +0 -0
- taguette/static/webfonts/fa-regular-400.eot +0 -0
- taguette/static/webfonts/fa-regular-400.svg +467 -0
- taguette/static/webfonts/fa-regular-400.ttf +0 -0
- taguette/static/webfonts/fa-regular-400.woff +0 -0
- taguette/static/webfonts/fa-regular-400.woff2 +0 -0
- taguette/static/webfonts/fa-solid-900.eot +0 -0
- taguette/static/webfonts/fa-solid-900.svg +2618 -0
- taguette/static/webfonts/fa-solid-900.ttf +0 -0
- taguette/static/webfonts/fa-solid-900.woff +0 -0
- taguette/static/webfonts/fa-solid-900.woff2 +0 -0
- taguette/templates/alt_templates/.gitkeep +0 -0
- taguette/templates/archived_projects.html +50 -0
- taguette/templates/auth/account.html +74 -0
- taguette/templates/auth/cilogon.login.html +89 -0
- taguette/templates/auth/email_reset_password.html +5 -0
- taguette/templates/auth/external_account.html +60 -0
- taguette/templates/auth/login.html +144 -0
- taguette/templates/auth/reset_password.html +27 -0
- taguette/templates/auth/token_needed.html +7 -0
- taguette/templates/base.html +209 -0
- taguette/templates/bug_report.html +158 -0
- taguette/templates/cookies_prompt.html +15 -0
- taguette/templates/downloads.html +127 -0
- taguette/templates/email_bug_report.txt +9 -0
- taguette/templates/email_reset_password.txt +5 -0
- taguette/templates/error.html +8 -0
- taguette/templates/export_codebook.html +20 -0
- taguette/templates/export_document.html +20 -0
- taguette/templates/export_highlights.html +47 -0
- taguette/templates/index.html +347 -0
- taguette/templates/maintenance.html +8 -0
- taguette/templates/messages.js +74 -0
- taguette/templates/modal/change_document.html +68 -0
- taguette/templates/modal/create_collection.html +100 -0
- taguette/templates/modal/create_document.html +84 -0
- taguette/templates/modal/create_highlight.html +38 -0
- taguette/templates/modal/create_tag.html +73 -0
- taguette/templates/modal/explore_codes.html +58 -0
- taguette/templates/modal/manage_collaborators.html +44 -0
- taguette/templates/modal/merge_tag.html +49 -0
- taguette/templates/new_password.html +32 -0
- taguette/templates/partials/alert.html +16 -0
- taguette/templates/partials/codebook_viz_modal.html +34 -0
- taguette/templates/partials/confirm.html +17 -0
- taguette/templates/partials/document_toolbar.html +37 -0
- taguette/templates/partials/hlinfo.html +13 -0
- taguette/templates/partials/project_card.html +44 -0
- taguette/templates/partials/project_card_archived.html +41 -0
- taguette/templates/partials/project_sidebar.html +99 -0
- taguette/templates/partials/spinner.html +11 -0
- taguette/templates/permissions.html +54 -0
- taguette/templates/project.html +288 -0
- taguette/templates/project_delete.html +17 -0
- taguette/templates/project_import.html +122 -0
- taguette/templates/project_import_codebook.html +34 -0
- taguette/templates/project_import_codebook_confirm.html +49 -0
- taguette/templates/project_new.html +31 -0
- taguette/templates/tos.html +8 -0
- taguette/templates/trans.js +7 -0
- taguette/templates/welcome.html +15 -0
- taguette/utils.py +177 -0
- taguette/validate.py +141 -0
- taguette/version_check.py +102 -0
- taguette/web/__init__.py +252 -0
- taguette/web/api.py +1835 -0
- taguette/web/base.py +864 -0
- taguette/web/export.py +338 -0
- 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()
|
scripts/register_user.py
ADDED
|
@@ -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()
|