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,127 @@
1
+ """add document direction
2
+
3
+ Revision ID: d86c45d7aa8a
4
+ Revises: 43d6c240309d
5
+ Create Date: 2021-06-09 13:17:10.397016
6
+
7
+ """
8
+ from alembic import op
9
+ import json
10
+ import sqlalchemy as sa
11
+ from sqlalchemy.dialects import postgresql
12
+
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision = 'd86c45d7aa8a'
16
+ down_revision = '43d6c240309d'
17
+ branch_labels = None
18
+ depends_on = None
19
+
20
+
21
+ meta = sa.MetaData(naming_convention={
22
+ "ix": "ix_%(column_0_label)s",
23
+ "uq": "uq_%(table_name)s_%(column_0_name)s",
24
+ "ck": "ck_%(table_name)s_%(constraint_name)s",
25
+ "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
26
+ "pk": "pk_%(table_name)s",
27
+ })
28
+
29
+ commands = sa.Table(
30
+ 'commands',
31
+ meta,
32
+ sa.Column('id', sa.Integer, primary_key=True),
33
+ sa.Column('date', sa.DateTime, nullable=False),
34
+ sa.Column('user_login', sa.String(30), nullable=False),
35
+ sa.Column('project_id', sa.Integer, nullable=False),
36
+ sa.Column('document_id', sa.Integer, nullable=True),
37
+ sa.Column('payload', sa.Text, nullable=False),
38
+ )
39
+
40
+
41
+ def _do_map(map_func):
42
+ """Map JSON payloads through the given function.
43
+ """
44
+ connection = op.get_bind()
45
+ assert connection is not None
46
+
47
+ payloads = connection.execute('''\
48
+ SELECT id, payload FROM commands;
49
+ ''')
50
+ update = []
51
+ for id, payload in payloads:
52
+ payload = json.loads(payload)
53
+ payload = map_func(payload)
54
+ if payload is not None:
55
+ payload = json.dumps(payload, sort_keys=True)
56
+ update.append({'cmd_id': id, 'cmd_payload': payload})
57
+
58
+ if update:
59
+ with connection.begin():
60
+ connection.execute(
61
+ (
62
+ commands.update()
63
+ .where(commands.c.id == sa.bindparam('cmd_id'))
64
+ .values(payload=sa.bindparam('cmd_payload'))
65
+ ),
66
+ update,
67
+ )
68
+
69
+
70
+ def upgrade():
71
+ # Add textdirection enum type
72
+ connection = op.get_bind()
73
+ if connection.dialect.name == 'postgresql':
74
+ direction_enum = postgresql.ENUM(
75
+ 'LEFT_TO_RIGHT', 'RIGHT_TO_LEFT',
76
+ name='textdirection',
77
+ create_type=False,
78
+ )
79
+ direction_enum.create(connection)
80
+ else:
81
+ direction_enum = sa.Enum(
82
+ 'LEFT_TO_RIGHT', 'RIGHT_TO_LEFT',
83
+ name='textdirection',
84
+ )
85
+
86
+ # Add column to 'documents'
87
+ op.add_column(
88
+ 'documents',
89
+ sa.Column(
90
+ 'text_direction',
91
+ direction_enum,
92
+ nullable=True,
93
+ ),
94
+ )
95
+ op.execute(
96
+ '''\
97
+ UPDATE documents SET text_direction = 'LEFT_TO_RIGHT';
98
+ ''',
99
+ )
100
+ with op.batch_alter_table('documents', schema=None) as batch_op:
101
+ batch_op.alter_column('text_direction', nullable=False)
102
+
103
+ # Add field to commands
104
+ def add_field(payload):
105
+ if payload['type'] == 'document_add':
106
+ payload['text_direction'] = 'LEFT_TO_RIGHT'
107
+ return payload
108
+
109
+ _do_map(add_field)
110
+
111
+
112
+ def downgrade():
113
+ # Remove field from commands
114
+ def remove_field(payload):
115
+ payload.pop('text_direction', None)
116
+ return payload
117
+
118
+ _do_map(remove_field)
119
+
120
+ # Remove column from 'documents'
121
+ with op.batch_alter_table('documents', schema=None) as batch_op:
122
+ batch_op.drop_column('text_direction')
123
+
124
+ # Remove textdirection enum type
125
+ connection = op.get_bind()
126
+ if connection.dialect.name == 'postgresql':
127
+ op.execute("DROP TYPE textdirection;")
@@ -0,0 +1,88 @@
1
+ """remove blank highlights
2
+
3
+ Revision ID: db5e31a0233d
4
+ Revises: 382db36f20f4
5
+ Create Date: 2022-11-18 15:03:03.154401
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+ import sys
11
+
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = 'db5e31a0233d'
15
+ down_revision = '382db36f20f4'
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+
20
+ meta = sa.MetaData(naming_convention={
21
+ "ix": "ix_%(column_0_label)s",
22
+ "uq": "uq_%(table_name)s_%(column_0_name)s",
23
+ "ck": "ck_%(table_name)s_%(constraint_name)s",
24
+ "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
25
+ "pk": "pk_%(table_name)s",
26
+ })
27
+
28
+ highlights = sa.Table(
29
+ 'highlights',
30
+ meta,
31
+ sa.Column('id', sa.Integer, primary_key=True),
32
+ sa.Column('document_id', sa.Integer, nullable=False),
33
+ sa.Column('start_offset', sa.Integer, nullable=False),
34
+ sa.Column('end_offset', sa.Integer, nullable=False),
35
+ sa.Column(
36
+ 'snippet', sa.Text,
37
+ sa.ForeignKey('documents.id', ondelete='CASCADE'),
38
+ nullable=False,
39
+ ),
40
+ )
41
+
42
+ highlight_tags = sa.Table(
43
+ 'highlight_tags',
44
+ meta,
45
+ sa.Column(
46
+ 'highlight_id', sa.Integer,
47
+ sa.ForeignKey('highlights.id', ondelete='CASCADE'),
48
+ primary_key=True,
49
+ ),
50
+ sa.Column(
51
+ 'tag_id', sa.Integer,
52
+ sa.ForeignKey('tags.id', ondelete='CASCADE'),
53
+ primary_key=True,
54
+ ),
55
+ )
56
+
57
+
58
+ def upgrade():
59
+ bind = op.get_bind()
60
+
61
+ # Find blank highlights
62
+ rows = bind.execute(
63
+ sa.select([highlights.c.id, highlights.c.snippet])
64
+ .select_from(highlights)
65
+ )
66
+ to_delete = []
67
+ for id, snippet in rows:
68
+ if all(c in '\r\n\t' for c in snippet):
69
+ to_delete.append(id)
70
+
71
+ # Delete them
72
+ if to_delete:
73
+ print(
74
+ "%d blank highlights to delete" % len(to_delete),
75
+ file=sys.stderr,
76
+ )
77
+ bind.execute(
78
+ highlight_tags.delete()
79
+ .where(highlight_tags.c.highlight_id.in_(to_delete))
80
+ )
81
+ bind.execute(
82
+ highlights.delete()
83
+ .where(highlights.c.id.in_(to_delete))
84
+ )
85
+
86
+
87
+ def downgrade():
88
+ pass
@@ -0,0 +1,68 @@
1
+ """unique_tags
2
+
3
+ Revision ID: dd37d9027a24
4
+ Revises: e4e090a5b511
5
+ Create Date: 2018-11-10 21:45:13.844011
6
+
7
+ """
8
+ from alembic import op
9
+ from sqlalchemy.orm import Session
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = 'dd37d9027a24'
14
+ down_revision = 'e4e090a5b511'
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade():
20
+ # Change the paths of tags that collide, before adding the unique index
21
+ bind = op.get_bind()
22
+ session = Session(bind=bind)
23
+ # https://stackoverflow.com/a/7416624
24
+ session.execute(
25
+ '''\
26
+ UPDATE tags
27
+ SET path = path || ' ' || id
28
+ WHERE
29
+ id NOT IN (
30
+ SELECT MIN(t3.id)
31
+ FROM tags t3
32
+ GROUP BY project_id, path
33
+ HAVING count(t3.id) > 1
34
+ ) AND id IN (
35
+ SELECT t1.id
36
+ FROM tags AS t1
37
+ INNER JOIN tags AS t2
38
+ ON t1.path = t2.path AND
39
+ t1.project_id = t2.project_id AND
40
+ t1.id <> t2.id
41
+ );
42
+ ''',
43
+ )
44
+ session.commit()
45
+
46
+ # Add the indexes
47
+ with op.batch_alter_table('tags') as batch_op:
48
+ batch_op.create_index(
49
+ batch_op.f('ix_tags_path'),
50
+ ['path'],
51
+ unique=True,
52
+ )
53
+
54
+ # We assume no one created those, table is unused so far
55
+ with op.batch_alter_table('groups') as batch_op:
56
+ batch_op.create_index(
57
+ batch_op.f('ix_groups_path'),
58
+ ['path'],
59
+ unique=True,
60
+ )
61
+
62
+
63
+ def downgrade():
64
+ with op.batch_alter_table('groups') as batch_op:
65
+ batch_op.drop_index(batch_op.f('ix_groups_path'))
66
+
67
+ with op.batch_alter_table('tags') as batch_op:
68
+ batch_op.drop_index(batch_op.f('ix_tags_path'))
@@ -0,0 +1,41 @@
1
+ """Add project_id to collections and relation to Project
2
+
3
+ Revision ID: de678cef2a94
4
+ Revises: 955db2e523d1
5
+ Create Date: 2025-07-08 14:22:33.272231
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = 'de678cef2a94'
14
+ down_revision = '955db2e523d1'
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade():
20
+ # ### commands auto generated by Alembic - please adjust! ###
21
+ with op.batch_alter_table('collections', schema=None) as batch_op:
22
+ batch_op.add_column(
23
+ sa.Column('project_id', sa.Integer(), nullable=False))
24
+ batch_op.create_index(batch_op.f('ix_collections_project_id'), [
25
+ 'project_id'], unique=False)
26
+ batch_op.create_foreign_key(
27
+ batch_op.f('fk_collections_project_id_projects'),
28
+ 'projects', ['project_id'], ['id'], ondelete='CASCADE')
29
+
30
+ # ### end Alembic commands ###
31
+
32
+
33
+ def downgrade():
34
+ # ### commands auto generated by Alembic - please adjust! ###
35
+ with op.batch_alter_table('collections', schema=None) as batch_op:
36
+ batch_op.drop_constraint(batch_op.f(
37
+ 'fk_collections_project_id_projects'), type_='foreignkey')
38
+ batch_op.drop_index(batch_op.f('ix_collections_project_id'))
39
+ batch_op.drop_column('project_id')
40
+
41
+ # ### end Alembic commands ###
@@ -0,0 +1,39 @@
1
+ """add table edugainidps
2
+
3
+ Revision ID: e459991d960e
4
+ Revises: 4c55b3b3ff30
5
+ Create Date: 2024-03-11 10:16:16.804208
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = 'e459991d960e'
14
+ down_revision = '4c55b3b3ff30'
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade():
20
+ op.create_table(
21
+ 'edigainidps',
22
+ sa.Column('id', sa.Integer(), nullable=False),
23
+ sa.Column('entityid', sa.String(length=255), nullable=True),
24
+ sa.Column('e_displayname', sa.String(length=255), nullable=True),
25
+ sa.Column('regauth', sa.String(length=255), nullable=True),
26
+ sa.Column('entity_cat', sa.String(length=255), nullable=True),
27
+ sa.Column('eccs_status', sa.Integer(), nullable=True),
28
+ sa.Column('validator_status', sa.BigInteger(), nullable=True),
29
+ sa.Column('coco_status', sa.String(length=255), nullable=True),
30
+ sa.Column('coco_id', sa.Integer(), nullable=True),
31
+ sa.Column('sirtfi_status', sa.Integer(), nullable=True),
32
+ sa.Column('code', sa.String(length=255), nullable=True),
33
+ sa.Column('scopes', sa.Text(), nullable=True),
34
+ sa.PrimaryKeyConstraint('id', name=op.f('pk_edigainidps'))
35
+ )
36
+
37
+
38
+ def downgrade():
39
+ op.drop_table('edigainidps')
@@ -0,0 +1,29 @@
1
+ """add index
2
+
3
+ Revision ID: e4cf92942271
4
+ Revises: 491de2dc7cd7
5
+ Create Date: 2021-11-18 10:38:47.124630
6
+
7
+ """
8
+ from alembic import op
9
+
10
+
11
+ # revision identifiers, used by Alembic.
12
+ revision = 'e4cf92942271'
13
+ down_revision = '491de2dc7cd7'
14
+ branch_labels = None
15
+ depends_on = None
16
+
17
+
18
+ def upgrade():
19
+ with op.batch_alter_table('commands', schema=None) as batch_op:
20
+ batch_op.create_index(
21
+ 'idx_project_id',
22
+ ['project_id', 'id'],
23
+ unique=False,
24
+ )
25
+
26
+
27
+ def downgrade():
28
+ with op.batch_alter_table('commands', schema=None) as batch_op:
29
+ batch_op.drop_index('idx_project_id')
@@ -0,0 +1,38 @@
1
+ """remove commands.document_id fk
2
+
3
+ Revision ID: e4e090a5b511
4
+ Revises:
5
+ Create Date: 2018-10-28 22:54:11.833009
6
+
7
+ """
8
+ from alembic import op
9
+
10
+
11
+ # revision identifiers, used by Alembic.
12
+ revision = 'e4e090a5b511'
13
+ down_revision = None
14
+ branch_labels = None
15
+ depends_on = None
16
+
17
+
18
+ def upgrade():
19
+ naming_convention = {
20
+ "fk":
21
+ "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
22
+ }
23
+ with op.batch_alter_table(
24
+ 'commands',
25
+ naming_convention=naming_convention,
26
+ ) as batch_op:
27
+ batch_op.drop_constraint(
28
+ 'fk_commands_document_id_documents',
29
+ type_='foreignkey',
30
+ )
31
+
32
+
33
+ def downgrade():
34
+ with op.batch_alter_table('commands') as batch_op:
35
+ batch_op.create_foreign_key(
36
+ None,
37
+ 'documents', ['document_id'], ['id'],
38
+ )
@@ -0,0 +1,32 @@
1
+ """Add favorite column to document
2
+
3
+ Revision ID: e9a9f1c77971
4
+ Revises: 7f56de97e3c8
5
+ Create Date: 2024-04-08 19:29:37.036133
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = 'e9a9f1c77971'
14
+ down_revision = '7f56de97e3c8'
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade():
20
+ # ### commands auto generated by Alembic - please adjust! ###
21
+ with op.batch_alter_table('documents', schema=None) as batch_op:
22
+ batch_op.add_column(sa.Column('favorite', sa.Boolean(), nullable=True))
23
+
24
+ # ### end Alembic commands ###
25
+
26
+
27
+ def downgrade():
28
+ # ### commands auto generated by Alembic - please adjust! ###
29
+ with op.batch_alter_table('documents', schema=None) as batch_op:
30
+ batch_op.drop_column('favorite')
31
+
32
+ # ### end Alembic commands ###
@@ -0,0 +1,44 @@
1
+ """Don't use server_default=now(), timezone issues
2
+
3
+ Revision ID: ecb4065de575
4
+ Revises: 807c0cc0ecf6
5
+ Create Date: 2021-02-27 11:12:36.221364
6
+
7
+ """
8
+ from alembic import op
9
+ from sqlalchemy.sql import functions
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = 'ecb4065de575'
14
+ down_revision = '807c0cc0ecf6'
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade():
20
+ with op.batch_alter_table('users') as batch_op:
21
+ batch_op.alter_column('created', server_default=None)
22
+
23
+ with op.batch_alter_table('projects') as batch_op:
24
+ batch_op.alter_column('created', server_default=None)
25
+
26
+ with op.batch_alter_table('documents') as batch_op:
27
+ batch_op.alter_column('created', server_default=None)
28
+
29
+ with op.batch_alter_table('commands') as batch_op:
30
+ batch_op.alter_column('date', server_default=None)
31
+
32
+
33
+ def downgrade():
34
+ with op.batch_alter_table('users') as batch_op:
35
+ batch_op.alter_column('created', server_default=functions.now())
36
+
37
+ with op.batch_alter_table('projects') as batch_op:
38
+ batch_op.alter_column('created', server_default=functions.now())
39
+
40
+ with op.batch_alter_table('documents') as batch_op:
41
+ batch_op.alter_column('created', server_default=functions.now())
42
+
43
+ with op.batch_alter_table('commands') as batch_op:
44
+ batch_op.alter_column('date', server_default=functions.now())
@@ -0,0 +1,42 @@
1
+ """edugain : fix field type
2
+
3
+ Revision ID: fbdc29566f62
4
+ Revises: e459991d960e
5
+ Create Date: 2024-03-26 11:25:38.289515
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+ import logging
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision = 'fbdc29566f62'
16
+ down_revision = 'e459991d960e'
17
+ branch_labels = None
18
+ depends_on = None
19
+
20
+
21
+ def upgrade():
22
+ db_url = op.get_context().config.get_section('alembic')['sqlalchemy.url']
23
+ if db_url.startswith('sqlite:'):
24
+ # SQLite doesn't mind field length
25
+ logger.info(f"Skipping upgrade {revision} for SQLite")
26
+ else:
27
+ op.alter_column('edigainidps', 'scopes',
28
+ existing_type=sa.VARCHAR(length=255),
29
+ type_=sa.Text(),
30
+ existing_nullable=True)
31
+
32
+
33
+ def downgrade():
34
+ db_url = op.get_context().config.get_section('alembic')['sqlalchemy.url']
35
+ if db_url.startswith('sqlite:'):
36
+ # SQLite doesn't mind field length
37
+ logger.info(f"Skipping downgrade {down_revision} for SQLite")
38
+ else:
39
+ op.alter_column('edigainidps', 'scopes',
40
+ existing_type=sa.Text(),
41
+ type_=sa.VARCHAR(length=255),
42
+ existing_nullable=True)
@@ -0,0 +1,31 @@
1
+ """Add parent_id column to tags table
2
+
3
+ Revision ID: fdc6eb54b034
4
+ Revises: db5e31a0233d
5
+ Create Date: 2024-01-09 13:46:17.464940
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = 'fdc6eb54b034'
14
+ down_revision = 'db5e31a0233d'
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade():
20
+ # ### commands auto generated by Alembic - please adjust! ###
21
+ with op.batch_alter_table('tags', schema=None) as batch_op:
22
+ batch_op.add_column(sa.Column('parent_id', sa.Integer(),
23
+ nullable=True))
24
+ batch_op.create_index(batch_op.f('ix_tags_parent_id'),
25
+ ['parent_id'],
26
+ unique=False)
27
+ batch_op.create_foreign_key(batch_op.f('fk_tags_parent_id_tags'),
28
+ 'tags', ['parent_id'], ['id'],
29
+ ondelete='CASCADE')
30
+
31
+ # ### end Alembic commands ###
@@ -0,0 +1,34 @@
1
+ # Customizing styles
2
+ LibreQDA uses SCSS instead of plain CSS for better modularity and theme management.
3
+
4
+ ## Theme creation
5
+ Themes are stored under `taguette/static/scss/themes/<theme_name>.
6
+
7
+ You can copy the `default` or `example` theme and customize the main variables in `_variables.scss`. They are used throughout the partials in `components/` and `layout/`.
8
+
9
+ Note: do not edit `themed.css` directly — it’s overwritten on each build.
10
+
11
+ ## Compilation
12
+ To compile a theme, run:
13
+
14
+ ``` console
15
+ poetry run make-theme <theme_name>
16
+ ```
17
+
18
+ ## Customizing the logo
19
+
20
+ Default LibreQDA logos are stored at the following path: `taguette/static/`.
21
+
22
+ To customize:
23
+
24
+ 1. Add a new logo image under `taguette/static/assets/<theme_name>/`.
25
+ You need to create a directory for your theme inside `assets/`.
26
+
27
+ 2. In your custom `_variables.scss`, define a new variable `$logo-main` and set it to the path for your new logo:
28
+
29
+ ```scss
30
+ $logo-main: "/static/assets/<theme_name>/<your-logo.png>";
31
+
32
+ 3. If the `$logo-main` variable is defined in your `_variables.scss`, it will overwrite the default logo. Otherwise, the default logo will be displayed.
33
+
34
+ 4. You can repeat the same steps for the horizontal version of the logo using the `$logo-horizontal` variable.
File without changes