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,41 @@
1
+ """Add parent_id column to collection table
2
+
3
+ Revision ID: 77c4543a5b9b
4
+ Revises: de678cef2a94
5
+ Create Date: 2025-07-17 10:28:57.783573
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = '77c4543a5b9b'
14
+ down_revision = 'de678cef2a94'
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('parent_id', sa.Integer(), nullable=True))
24
+ batch_op.create_index(batch_op.f('ix_collections_parent_id'), [
25
+ 'parent_id'], unique=False)
26
+ batch_op.create_foreign_key(
27
+ batch_op.f('fk_collections_parent_id_collections'),
28
+ 'collections', ['parent_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_parent_id_collections'), type_='foreignkey')
38
+ batch_op.drop_index(batch_op.f('ix_collections_parent_id'))
39
+ batch_op.drop_column('parent_id')
40
+
41
+ # ### end Alembic commands ###
@@ -0,0 +1,39 @@
1
+ """Add is_archived column to projets
2
+
3
+ Revision ID: 7c94b7a31e69
4
+ Revises: 77c4543a5b9b
5
+ Create Date: 2026-01-20 15:45:37.747588
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = '7c94b7a31e69'
14
+ down_revision = '77c4543a5b9b'
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('projects', schema=None) as batch_op:
22
+ batch_op.add_column(sa.Column(
23
+ 'is_archived', sa.Boolean(),
24
+ nullable=False, server_default='false'
25
+ ))
26
+
27
+ op.execute(
28
+ 'UPDATE projects SET is_archived = false'
29
+ ' WHERE is_archived != false'
30
+ )
31
+ # ### end Alembic commands ###
32
+
33
+
34
+ def downgrade():
35
+ # ### commands auto generated by Alembic - please adjust! ###
36
+ with op.batch_alter_table('projects', schema=None) as batch_op:
37
+ batch_op.drop_column('is_archived')
38
+
39
+ # ### end Alembic commands ###
@@ -0,0 +1,42 @@
1
+ """change edugain displayname length
2
+
3
+ Revision ID: 7f56de97e3c8
4
+ Revises: fbdc29566f62
5
+ Create Date: 2024-04-04 21:56:27.801558
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 = '7f56de97e3c8'
16
+ down_revision = 'fbdc29566f62'
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', 'e_displayname',
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', 'e_displayname',
40
+ existing_type=sa.Text(),
41
+ type_=sa.VARCHAR(length=255),
42
+ existing_nullable=True)
@@ -0,0 +1,43 @@
1
+ """remove invalid hltags
2
+
3
+ Revision ID: 807c0cc0ecf6
4
+ Revises: b23f3b7a638e
5
+ Create Date: 2021-02-17 13:04:13.809606
6
+
7
+ """
8
+ from alembic import op
9
+
10
+
11
+ # revision identifiers, used by Alembic.
12
+ revision = '807c0cc0ecf6'
13
+ down_revision = 'b23f3b7a638e'
14
+ branch_labels = None
15
+ depends_on = None
16
+
17
+
18
+ def upgrade():
19
+ op.execute(
20
+ '''\
21
+ DELETE FROM highlight_tags
22
+ WHERE
23
+ -- Project of the highlight
24
+ (
25
+ SELECT documents.project_id
26
+ FROM highlights
27
+ INNER JOIN documents
28
+ ON highlights.document_id = documents.id
29
+ WHERE highlights.id = highlight_tags.highlight_id
30
+ )
31
+ NOT IN
32
+ -- Project of the tag
33
+ (
34
+ SELECT tags.project_id
35
+ FROM tags
36
+ WHERE tags.id = highlight_tags.tag_id
37
+ );
38
+ ''',
39
+ )
40
+
41
+
42
+ def downgrade():
43
+ pass
@@ -0,0 +1,35 @@
1
+ """add User.email
2
+
3
+ Revision ID: 80b1cc9d4c22
4
+ Revises: 679f625e6e6a
5
+ Create Date: 2018-11-17 11:46:29.572383
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = '80b1cc9d4c22'
14
+ down_revision = '679f625e6e6a'
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade():
20
+ op.add_column(
21
+ 'users',
22
+ sa.Column('email', sa.String(), nullable=True),
23
+ )
24
+ op.add_column(
25
+ 'users',
26
+ sa.Column('email_sent', sa.DateTime(), nullable=True),
27
+ )
28
+ op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
29
+
30
+
31
+ def downgrade():
32
+ with op.batch_alter_table('users') as batch_op:
33
+ batch_op.drop_index(batch_op.f('ix_users_email'))
34
+ batch_op.drop_column('email_sent')
35
+ batch_op.drop_column('email')
@@ -0,0 +1,28 @@
1
+ """add User.language
2
+
3
+ Revision ID: 91ade71ccf4d
4
+ Revises: bce44849c2f2
5
+ Create Date: 2019-05-09 14:02:38.185065
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = '91ade71ccf4d'
14
+ down_revision = 'bce44849c2f2'
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade():
20
+ op.add_column(
21
+ 'users',
22
+ sa.Column('language', sa.String(), nullable=True),
23
+ )
24
+
25
+
26
+ def downgrade():
27
+ with op.batch_alter_table('users') as batch_op:
28
+ batch_op.drop_column('language')
@@ -0,0 +1,60 @@
1
+ """Add collections tables and associations with documents
2
+
3
+ Revision ID: 955db2e523d1
4
+ Revises: 61a4abcc8769
5
+ Create Date: 2025-07-08 11:16:54.546173
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = '955db2e523d1'
14
+ down_revision = '61a4abcc8769'
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade():
20
+ # ### commands auto generated by Alembic - please adjust! ###
21
+ op.create_table('collections',
22
+ sa.Column('id', sa.Integer(), nullable=False),
23
+ sa.Column('name', sa.String(length=200), nullable=False),
24
+ sa.Column('description', sa.Text(), nullable=True),
25
+ sa.Column('created', sa.DateTime(), nullable=False),
26
+ sa.Column('owner_login', sa.String(length=255),
27
+ nullable=True),
28
+ sa.ForeignKeyConstraint(
29
+ ['owner_login'], ['users.login'],
30
+ name=op.f('fk_collections_owner_login_users')),
31
+ sa.PrimaryKeyConstraint('id', name=op.f('pk_collections')),
32
+ sqlite_autoincrement=True
33
+ )
34
+ op.create_table('collection_documents',
35
+ sa.Column('collection_id', sa.Integer(), nullable=False),
36
+ sa.Column('document_id', sa.Integer(), nullable=False),
37
+ sa.ForeignKeyConstraint(
38
+ ['collection_id'], ['collections.id'],
39
+ name=op.f(
40
+ 'fk_collection_documents_collection_id_'
41
+ 'collections'),
42
+ ondelete='CASCADE'),
43
+ sa.ForeignKeyConstraint(
44
+ ['document_id'], ['documents.id'],
45
+ name=op.f(
46
+ 'fk_collection_documents_document_id_'
47
+ 'documents'),
48
+ ondelete='CASCADE'),
49
+ sa.PrimaryKeyConstraint(
50
+ 'collection_id', 'document_id',
51
+ name=op.f('pk_collection_documents'))
52
+ )
53
+ # ### end Alembic commands ###
54
+
55
+
56
+ def downgrade():
57
+ # ### commands auto generated by Alembic - please adjust! ###
58
+ op.drop_table('collection_documents')
59
+ op.drop_table('collections')
60
+ # ### end Alembic commands ###
@@ -0,0 +1,28 @@
1
+ """add user hide_code_counts
2
+
3
+ Revision ID: a1b2c3d4e5f6
4
+ Revises: 7c94b7a31e69
5
+ Create Date: 2026-07-10 00:00:00.000000
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = 'a1b2c3d4e5f6'
14
+ down_revision = '7c94b7a31e69'
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade():
20
+ with op.batch_alter_table('users', schema=None) as batch_op:
21
+ batch_op.add_column(
22
+ sa.Column('hide_code_counts', sa.Boolean(), nullable=True),
23
+ )
24
+
25
+
26
+ def downgrade():
27
+ with op.batch_alter_table('users', schema=None) as batch_op:
28
+ batch_op.drop_column('hide_code_counts')
@@ -0,0 +1,36 @@
1
+ """add password change date
2
+
3
+ Revision ID: b23f3b7a638e
4
+ Revises: 91ade71ccf4d
5
+ Create Date: 2020-04-03 15:54:14.870029
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = 'b23f3b7a638e'
14
+ down_revision = '91ade71ccf4d'
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade():
20
+ op.add_column(
21
+ 'users',
22
+ sa.Column('password_set_date', sa.DateTime(), nullable=True),
23
+ )
24
+
25
+ # Set the password set time to now if a password is set
26
+ op.execute(
27
+ '''\
28
+ UPDATE users SET password_set_date=DATETIME()
29
+ WHERE hashed_password IS NOT NULL;
30
+ ''',
31
+ )
32
+
33
+
34
+ def downgrade():
35
+ with op.batch_alter_table('users', schema=None) as batch_op:
36
+ batch_op.drop_column('password_set_date')
@@ -0,0 +1,28 @@
1
+ """add project compact_layout
2
+
3
+ Revision ID: b7c1e2d3f4a5
4
+ Revises: a1b2c3d4e5f6
5
+ Create Date: 2026-08-12 13:15:00.000000
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = 'b7c1e2d3f4a5'
14
+ down_revision = 'a1b2c3d4e5f6'
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade():
20
+ with op.batch_alter_table('projects', schema=None) as batch_op:
21
+ batch_op.add_column(
22
+ sa.Column('compact_layout', sa.Boolean(), nullable=True),
23
+ )
24
+
25
+
26
+ def downgrade():
27
+ with op.batch_alter_table('projects', schema=None) as batch_op:
28
+ batch_op.drop_column('compact_layout')
@@ -0,0 +1,126 @@
1
+ """rename fields in Command JSON payload
2
+
3
+ Revision ID: bc8e0e0677e9
4
+ Revises: ecb4065de575
5
+ Create Date: 2021-06-05 18:24:28.756510
6
+
7
+ """
8
+ from alembic import op
9
+ import json
10
+ import sqlalchemy as sa
11
+
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = 'bc8e0e0677e9'
15
+ down_revision = 'ecb4065de575'
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
+ commands = sa.Table(
29
+ 'commands',
30
+ meta,
31
+ sa.Column('id', sa.Integer, primary_key=True),
32
+ sa.Column('date', sa.DateTime, nullable=False),
33
+ sa.Column('user_login', sa.String(30), nullable=False),
34
+ sa.Column('project_id', sa.Integer, nullable=False),
35
+ sa.Column('document_id', sa.Integer, nullable=True),
36
+ sa.Column('payload', sa.Text, nullable=False),
37
+ )
38
+
39
+
40
+ def _do_map(map_func):
41
+ """Map JSON payloads through the given function.
42
+ """
43
+ connection = op.get_bind()
44
+ assert connection is not None
45
+
46
+ payloads = connection.execute('''\
47
+ SELECT id, payload FROM commands;
48
+ ''')
49
+ update = []
50
+ for id, payload in payloads:
51
+ payload = json.loads(payload)
52
+ payload = map_func(payload)
53
+ if payload is not None:
54
+ payload = json.dumps(payload, sort_keys=True)
55
+ update.append({'cmd_id': id, 'cmd_payload': payload})
56
+
57
+ if update:
58
+ with connection.begin():
59
+ connection.execute(
60
+ (
61
+ commands.update()
62
+ .where(commands.c.id == sa.bindparam('cmd_id'))
63
+ .values(payload=sa.bindparam('cmd_payload'))
64
+ ),
65
+ update,
66
+ )
67
+
68
+
69
+ def upgrade():
70
+ def _map_forward(payload):
71
+ if payload['type'] == 'project_meta':
72
+ payload['project_name'] = payload.pop('name')
73
+ elif payload['type'] == 'document_add':
74
+ payload['document_name'] = payload.pop('name')
75
+ elif payload['type'] in ('highlight_add', 'highlight_delete'):
76
+ payload['highlight_id'] = payload.pop('id')
77
+ elif payload['type'] in ('tag_add', 'tag_delete'):
78
+ if payload['type'] == 'tag_add':
79
+ payload['tag_path'] = payload.pop('path')
80
+ payload['tag_id'] = payload.pop('id')
81
+ elif payload['type'] == 'tag_merge':
82
+ payload['src_tag_id'] = payload.pop('src')
83
+ payload['dest_tag_id'] = payload.pop('dest')
84
+ elif 'id' in payload:
85
+ raise ValueError(
86
+ "Unknown command type with 'id' field: %r" % payload['type']
87
+ )
88
+ elif 'name' in payload:
89
+ raise ValueError(
90
+ "Unknown command type with 'name' field: %r" % payload['type']
91
+ )
92
+ else:
93
+ return None
94
+ return payload
95
+
96
+ _do_map(_map_forward)
97
+
98
+
99
+ def downgrade():
100
+ def _map_back(payload):
101
+ if 'id' in payload:
102
+ raise ValueError(
103
+ "Command type with 'id' field: %r" % payload['type']
104
+ )
105
+ elif 'name' in payload:
106
+ raise ValueError(
107
+ "Command type with 'name' field: %r" % payload['type']
108
+ )
109
+ elif payload['type'] == 'project_meta':
110
+ payload['name'] = payload.pop('project_name')
111
+ elif payload['type'] == 'document_add':
112
+ payload['name'] = payload.pop('document_name')
113
+ elif payload['type'] in ('highlight_add', 'highlight_delete'):
114
+ payload['id'] = payload.pop('highlight_id')
115
+ elif payload['type'] in ('tag_add', 'tag_delete'):
116
+ if payload['type'] == 'tag_add':
117
+ payload['path'] = payload.pop('tag_path')
118
+ payload['id'] = payload.pop('tag_id')
119
+ elif payload['type'] == 'tag_merge':
120
+ payload['src'] = payload.pop('src_tag_id')
121
+ payload['dest'] = payload.pop('dest_tag_id')
122
+ else:
123
+ return None
124
+ return payload
125
+
126
+ _do_map(_map_back)
@@ -0,0 +1,96 @@
1
+ """lowercase logins
2
+
3
+ Revision ID: bce44849c2f2
4
+ Revises: 447d636f72c5
5
+ Create Date: 2019-04-04 21:50:36.046753
6
+
7
+ """
8
+ from alembic import op
9
+ from sqlalchemy.orm import Session
10
+ import sys
11
+
12
+ from taguette import validate
13
+
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision = 'bce44849c2f2'
17
+ down_revision = '447d636f72c5'
18
+ branch_labels = None
19
+ depends_on = None
20
+
21
+
22
+ def upgrade():
23
+ # Add ON UPDATE CASCADE to foreign keys
24
+ with op.batch_alter_table('project_members') as batch_op:
25
+ batch_op.drop_constraint(
26
+ 'fk_project_members_user_login_users',
27
+ type_='foreignkey',
28
+ )
29
+ batch_op.create_foreign_key(
30
+ batch_op.f('fk_project_members_user_login_users'),
31
+ 'users', ['user_login'], ['login'],
32
+ onupdate='CASCADE', ondelete='CASCADE',
33
+ )
34
+ with op.batch_alter_table('commands') as batch_op:
35
+ batch_op.drop_constraint(
36
+ 'fk_commands_user_login_users',
37
+ type_='foreignkey',
38
+ )
39
+ batch_op.create_foreign_key(
40
+ batch_op.f('fk_commands_user_login_users'),
41
+ 'users', ['user_login'], ['login'],
42
+ onupdate='CASCADE',
43
+ )
44
+
45
+ # Update the login
46
+ op.execute('UPDATE users SET login = lower(login);')
47
+ # Should update via the cascade:
48
+ # op.execute('UPDATE project_members SET user_login = lower(user_login);')
49
+ # op.execute('UPDATE commands SET user_login = lower(user_login);')
50
+
51
+ # Check that logins pass new validation requirements
52
+ bind = op.get_bind()
53
+ session = Session(bind=bind)
54
+ logins = session.execute(
55
+ '''\
56
+ SELECT login FROM users;
57
+ ''',
58
+ )
59
+ error = False
60
+ for row in logins:
61
+ login, = row
62
+ try:
63
+ changed = validate.fix_user_login(login)
64
+ except validate.InvalidFormat:
65
+ error = True
66
+ print("User login %r does not abide to new restrictions" % login,
67
+ file=sys.stderr)
68
+ else:
69
+ if changed != login:
70
+ raise ValueError("Login %r is still not canonical after "
71
+ "migration, please report this bug!" % login)
72
+ if error:
73
+ raise ValueError("Some user logins do not pass validation")
74
+ session.close()
75
+
76
+
77
+ def downgrade():
78
+ with op.batch_alter_table('project_members') as batch_op:
79
+ batch_op.drop_constraint(
80
+ batch_op.f('fk_project_members_user_login_users'),
81
+ type_='foreignkey',
82
+ )
83
+ batch_op.create_foreign_key(
84
+ 'fk_project_members_user_login_users',
85
+ 'users', ['user_login'], ['login'],
86
+ ondelete='CASCADE',
87
+ )
88
+ with op.batch_alter_table('commands') as batch_op:
89
+ batch_op.drop_constraint(
90
+ batch_op.f('fk_commands_user_login_users'),
91
+ type_='foreignkey',
92
+ )
93
+ batch_op.create_foreign_key(
94
+ 'fk_commands_user_login_users',
95
+ 'users', ['user_login'], ['login'],
96
+ )
@@ -0,0 +1,32 @@
1
+ """update user for external logins
2
+
3
+ Revision ID: ca076140922f
4
+ Revises: 2467bab602b7
5
+ Create Date: 2024-03-08 08:31:48.228013
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = 'ca076140922f'
14
+ down_revision = '2467bab602b7'
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('users', schema=None) as batch_op:
22
+ batch_op.add_column(sa.Column('external', 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('users', schema=None) as batch_op:
30
+ batch_op.drop_column('external')
31
+
32
+ # ### end Alembic commands ###