Coverage for crateweb/config/urls.py: 78%
23 statements
« prev ^ index » next coverage.py v7.8.0, created at 2026-01-06 10:22 -0600
« prev ^ index » next coverage.py v7.8.0, created at 2026-01-06 10:22 -0600
1"""
2crate_anon/crateweb/config/urls.py
4===============================================================================
6 Copyright (C) 2015, University of Cambridge, Department of Psychiatry.
7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
9 This file is part of CRATE.
11 CRATE is free software: you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation, either version 3 of the License, or
14 (at your option) any later version.
16 CRATE is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with CRATE. If not, see <https://www.gnu.org/licenses/>.
24===============================================================================
26**crateweb Django URL configuration**
28The `urlpatterns` list routes URLs to views. For more information please see:
29https://docs.djangoproject.com/en/3.2/topics/http/urls/
31Examples:
33Function views
35 1. Add an import: ``from my_app import views``
36 2. Add a URL to urlpatterns: ``re_path(r'^$', views.home, name='home')``
38Class-based views
40 1. Add an import: ``from other_app.views import Home``
41 2. Add a URL to urlpatterns: ``re_path(r'^$', Home.as_view(), name='home')``
43Including another URLconf
45 1. Add an import: ``from blog import urls as blog_urls``
46 2. Add a URL to urlpatterns: ``re_path(r'^blog/', include(blog_urls))``
48""" # noqa: E501
50import logging
51import os
53import debug_toolbar
54from django.conf import settings
55from django.urls import include, re_path
56from django.contrib.staticfiles.urls import staticfiles_urlpatterns
58from crate_anon.common.constants import EnvVar
59from crate_anon.crateweb.config.constants import (
60 DOWNLOAD_PRIVATESTORAGE_URL_STEM,
61 UrlNames,
62)
63from crate_anon.crateweb.core.admin import (
64 mgr_admin_site,
65 dev_admin_site,
66 res_admin_site,
67)
68import crate_anon.crateweb.core.auth_views as core_auth_views
69import crate_anon.crateweb.core.views as core_views
70import crate_anon.crateweb.consent.views as consent_views
71import crate_anon.crateweb.research.views as research_views
72import crate_anon.crateweb.userprofile.views as userprofile_views
74# This is the place for one-time startup code.
75# https://stackoverflow.com/questions/6791911/execute-code-when-django-starts-once-only # noqa: E501
76# So we cache things here that we don't want the user to have to wait for:
77if (
78 EnvVar.GENERATING_CRATE_DOCS not in os.environ
79 and EnvVar.RUNNING_TESTS not in os.environ
80):
81 from crate_anon.crateweb.research.research_db_info import (
82 get_research_db_info,
83 )
85 research_database_info = get_research_db_info()
86 research_database_info.get_colinfolist()
88log = logging.getLogger(__name__)
91urlpatterns = [
92 # -------------------------------------------------------------------------
93 # Login, other authentication/password stuff
94 # -------------------------------------------------------------------------
95 re_path(r"^login/", core_auth_views.login_view, name=UrlNames.LOGIN),
96 re_path(r"^logout/", core_auth_views.logout_view, name=UrlNames.LOGOUT),
97 re_path(
98 r"^password_change/",
99 core_auth_views.password_change,
100 name=UrlNames.PASSWORD_CHANGE,
101 ),
102 # -------------------------------------------------------------------------
103 # Home, About
104 # -------------------------------------------------------------------------
105 re_path(r"^$", core_views.home, name=UrlNames.HOME),
106 re_path(r"^about/$", core_views.about, name=UrlNames.ABOUT),
107 # -------------------------------------------------------------------------
108 # Admin sites
109 # -------------------------------------------------------------------------
110 # ... obfuscate: p351 of Greenfeld_2015.
111 re_path(r"^mgr_admin/", mgr_admin_site.urls),
112 re_path(r"^dev_admin/", dev_admin_site.urls),
113 re_path(r"^res_admin/", res_admin_site.urls),
114 # ... namespace is defined in call to AdminSite(); see core/admin.py
115 # -------------------------------------------------------------------------
116 # Anonymisation API
117 # -------------------------------------------------------------------------
118 re_path(r"^anon_api/", include("crate_anon.crateweb.anonymise_api.urls")),
119 # -------------------------------------------------------------------------
120 # Main query views
121 # -------------------------------------------------------------------------
122 re_path(
123 r"^build_query/$",
124 research_views.query_build,
125 name=UrlNames.BUILD_QUERY,
126 ),
127 re_path(
128 r"^query/$", research_views.query_edit_select, name=UrlNames.QUERY
129 ),
130 re_path(
131 r"^activate_query/(?P<query_id>[0-9]+)/$",
132 research_views.query_activate,
133 name=UrlNames.ACTIVATE_QUERY,
134 ),
135 re_path(
136 r"^delete_query/(?P<query_id>[0-9]+)/$",
137 research_views.query_delete,
138 name=UrlNames.DELETE_QUERY,
139 ),
140 re_path(
141 r"^highlight/$",
142 research_views.highlight_edit_select,
143 name=UrlNames.HIGHLIGHT,
144 ),
145 re_path(
146 r"^activate_highlight/(?P<highlight_id>[0-9]+)/$",
147 research_views.highlight_activate,
148 name=UrlNames.ACTIVATE_HIGHLIGHT,
149 ),
150 re_path(
151 r"^deactivate_highlight/(?P<highlight_id>[0-9]+)/$",
152 research_views.highlight_deactivate,
153 name=UrlNames.DEACTIVATE_HIGHLIGHT,
154 ),
155 re_path(
156 r"^delete_highlight/(?P<highlight_id>[0-9]+)/$",
157 research_views.highlight_delete,
158 name=UrlNames.DELETE_HIGHLIGHT,
159 ),
160 re_path(
161 r"^count/(?P<query_id>[0-9]+)/$",
162 research_views.query_count,
163 name=UrlNames.COUNT,
164 ),
165 re_path(
166 r"^results/(?P<query_id>[0-9]+)/$",
167 research_views.query_results,
168 name=UrlNames.RESULTS,
169 ),
170 re_path(
171 r"^results_recordwise/(?P<query_id>[0-9]+)/$",
172 research_views.query_results_recordwise,
173 name=UrlNames.RESULTS_RECORDWISE,
174 ),
175 re_path(
176 r"^tsv/(?P<query_id>[0-9]+)/$",
177 research_views.query_tsv,
178 name=UrlNames.TSV,
179 ),
180 re_path(
181 r"^query_excel/(?P<query_id>[0-9]+)/$",
182 research_views.query_excel,
183 name=UrlNames.QUERY_EXCEL,
184 ),
185 re_path(
186 r"^sitewide_queries/$",
187 research_views.query_add_sitewide,
188 name=UrlNames.SITEWIDE_QUERIES,
189 ),
190 re_path(
191 r"^delete_sitewide_query/(?P<query_id>[0-9]+)/$",
192 research_views.sitewide_query_delete,
193 name=UrlNames.DELETE_SITEWIDE_QUERY,
194 ),
195 re_path(
196 r"^standard_queries/$",
197 research_views.show_sitewide_queries,
198 name=UrlNames.STANDARD_QUERIES,
199 ),
200 re_path(
201 r"^process_standard_query/(?P<query_id>[0-9]+)/$",
202 research_views.sitewide_query_process,
203 name=UrlNames.PROCESS_STANDARD_QUERY,
204 ),
205 re_path(
206 r"^edit_display/(?P<query_id>[0-9]+)/$",
207 research_views.edit_display,
208 name=UrlNames.EDIT_DISPLAY,
209 ),
210 re_path(
211 r"^save_display/(?P<query_id>[0-9]+)/$",
212 research_views.save_display,
213 name=UrlNames.SAVE_DISPLAY,
214 ),
215 re_path(
216 r"^show_query/(?P<query_id>[0-9]+)/$",
217 research_views.show_query,
218 name=UrlNames.SHOW_QUERY,
219 ),
220 re_path(
221 r"^source_information/(?P<srcdb>.+)/(?P<srctable>.+)/(?P<srcfield>.+)/"
222 r"(?P<srcpkfield>.+)/(?P<srcpkval>.+)/(?P<srcpkstr>.+)/$",
223 research_views.source_info,
224 name=UrlNames.SRCINFO,
225 ),
226 # -------------------------------------------------------------------------
227 # Patient Explorer views
228 # -------------------------------------------------------------------------
229 re_path(r"^pe_build/$", research_views.pe_build, name=UrlNames.PE_BUILD),
230 re_path(
231 r"^pe_choose/$", research_views.pe_choose, name=UrlNames.PE_CHOOSE
232 ),
233 re_path(
234 r"^pe_activate/(?P<pe_id>[0-9]+)/$",
235 research_views.pe_activate,
236 name=UrlNames.PE_ACTIVATE,
237 ),
238 re_path(
239 r"^pe_edit/(?P<pe_id>[0-9]+)/$",
240 research_views.pe_edit,
241 name=UrlNames.PE_EDIT,
242 ),
243 re_path(
244 r"^pe_delete/(?P<pe_id>[0-9]+)/$",
245 research_views.pe_delete,
246 name=UrlNames.PE_DELETE,
247 ),
248 re_path(
249 r"^pe_results/(?P<pe_id>[0-9]+)/$",
250 research_views.pe_results,
251 name=UrlNames.PE_RESULTS,
252 ),
253 # re_path(r'^pe_tsv_zip/(?P<pe_id>[0-9]+)/$',
254 # research_views.patient_explorer_tsv_zip, name='pe_tsv_zip'),
255 re_path(
256 r"^pe_excel/(?P<pe_id>[0-9]+)/$",
257 research_views.pe_excel,
258 name=UrlNames.PE_EXCEL,
259 ),
260 re_path(
261 r"^pe_df_results/(?P<pe_id>[0-9]+)/$",
262 research_views.pe_data_finder_results,
263 name=UrlNames.PE_DF_RESULTS,
264 ),
265 re_path(
266 r"^pe_df_excel/(?P<pe_id>[0-9]+)/$",
267 research_views.pe_data_finder_excel,
268 name=UrlNames.PE_DF_EXCEL,
269 ),
270 re_path(
271 r"^pe_monster_results/(?P<pe_id>[0-9]+)/$",
272 research_views.pe_monster_results,
273 name=UrlNames.PE_MONSTER_RESULTS,
274 ),
275 # We don't offer the monster view in Excel; it'd be huge.
276 re_path(
277 r"^pe_table_browser/(?P<pe_id>[0-9]+)/$",
278 research_views.pe_table_browser,
279 name=UrlNames.PE_TABLE_BROWSER,
280 ),
281 re_path(
282 r"^pe_one_table/(?P<pe_id>[0-9]+)/(?P<db>.*)/(?P<schema>.+)/(?P<table>.+)/$", # noqa: E501
283 research_views.pe_one_table,
284 name=UrlNames.PE_ONE_TABLE,
285 ),
286 re_path(
287 r"^pe_one_table/(?P<pe_id>[0-9]+)/(?P<schema>.+)/(?P<table>.+)/$",
288 research_views.pe_one_table,
289 name=UrlNames.PE_ONE_TABLE,
290 ),
291 # -------------------------------------------------------------------------
292 # Research database structure
293 # -------------------------------------------------------------------------
294 re_path(
295 r"^structure_table_long/$",
296 research_views.structure_table_long,
297 name=UrlNames.STRUCTURE_TABLE_LONG,
298 ),
299 re_path(
300 r"^structure_table_paginated/$",
301 research_views.structure_table_paginated,
302 name=UrlNames.STRUCTURE_TABLE_PAGINATED,
303 ),
304 re_path(
305 r"^structure_tree/$",
306 research_views.structure_tree,
307 name=UrlNames.STRUCTURE_TREE,
308 ),
309 re_path(
310 r"^structure_tsv/$",
311 research_views.structure_tsv,
312 name=UrlNames.STRUCTURE_TSV,
313 ),
314 re_path(
315 r"^structure_excel/$",
316 research_views.structure_excel,
317 name=UrlNames.STRUCTURE_EXCEL,
318 ),
319 re_path(
320 r"^structure_help/$",
321 research_views.local_structure_help,
322 name=UrlNames.STRUCTURE_HELP,
323 ),
324 # -------------------------------------------------------------------------
325 # SQL helpers
326 # -------------------------------------------------------------------------
327 re_path(
328 r"^sqlhelper_text_anywhere/$",
329 research_views.sqlhelper_text_anywhere,
330 name=UrlNames.SQLHELPER_TEXT_ANYWHERE,
331 ),
332 re_path(
333 r"^sqlhelper_text_anywhere_with_db/(?P<dbname>[a-zA-Z0-9_]+)/$",
334 research_views.sqlhelper_text_anywhere_with_db,
335 name=UrlNames.SQLHELPER_TEXT_ANYWHERE_WITH_DB,
336 ),
337 re_path(
338 r"^sqlhelper_drug_type/$",
339 research_views.sqlhelper_drug_type,
340 name=UrlNames.SQLHELPER_DRUG_TYPE,
341 ),
342 re_path(
343 r"^sqlhelper_drug_type_with_db/(?P<dbname>[a-zA-Z0-9_]+)/$",
344 research_views.sqlhelper_drug_type_with_db,
345 name=UrlNames.SQLHELPER_DRUG_TYPE_WITH_DB,
346 ),
347 # -------------------------------------------------------------------------
348 # Researcher consent functions
349 # -------------------------------------------------------------------------
350 re_path(
351 r"^submit_contact_request/$",
352 consent_views.submit_contact_request,
353 name=UrlNames.SUBMIT_CONTACT_REQUEST,
354 ),
355 # -------------------------------------------------------------------------
356 # Clinician views
357 # -------------------------------------------------------------------------
358 re_path(
359 r"^all_text_from_pid/$",
360 research_views.all_text_from_pid,
361 name=UrlNames.ALL_TEXT_FROM_PID,
362 ),
363 re_path(
364 r"^all_text_from_pid_with_db/(?P<dbname>[a-zA-Z0-9_]+)/$",
365 research_views.all_text_from_pid_with_db,
366 name=UrlNames.ALL_TEXT_FROM_PID_WITH_DB,
367 ),
368 re_path(
369 r"^clinician_contact_request/$",
370 consent_views.clinician_initiated_contact_request,
371 name=UrlNames.CLINICIAN_CONTACT_REQUEST,
372 ),
373 # -------------------------------------------------------------------------
374 # Archive views
375 # -------------------------------------------------------------------------
376 re_path(
377 r"^launch_archive/$",
378 research_views.launch_archive,
379 name=UrlNames.LAUNCH_ARCHIVE,
380 ),
381 re_path(
382 r"^archive/$",
383 research_views.archive_template,
384 name=UrlNames.ARCHIVE_TEMPLATE,
385 ),
386 re_path(
387 r"^archive_attachment/$",
388 research_views.archive_attachment,
389 name=UrlNames.ARCHIVE_ATTACHMENT,
390 ),
391 re_path(
392 r"^archive_static/$",
393 research_views.archive_static,
394 name=UrlNames.ARCHIVE_STATIC,
395 ),
396 # -------------------------------------------------------------------------
397 # Look up PID/RID
398 # -------------------------------------------------------------------------
399 re_path(
400 r"^pidlookup/$", research_views.pidlookup, name=UrlNames.PIDLOOKUP
401 ),
402 re_path(
403 r"^pidlookup_with_db/(?P<dbname>[a-zA-Z0-9_]+)/$",
404 research_views.pidlookup_with_db,
405 name=UrlNames.PIDLOOKUP_WITH_DB,
406 ),
407 re_path(
408 r"^ridlookup/$", research_views.ridlookup, name=UrlNames.RIDLOOKUP
409 ),
410 re_path(
411 r"^ridlookup_with_db/(?P<dbname>[a-zA-Z0-9_]+)/$",
412 research_views.ridlookup_with_db,
413 name=UrlNames.RIDLOOKUP_WITH_DB,
414 ),
415 # -------------------------------------------------------------------------
416 # User profile
417 # -------------------------------------------------------------------------
418 re_path(
419 r"^edit_profile/$",
420 userprofile_views.edit_profile,
421 name=UrlNames.EDIT_PROFILE,
422 ),
423 # -------------------------------------------------------------------------
424 # Superuser access only
425 # -------------------------------------------------------------------------
426 # ... NB hard-coded reference to this in consent/storage.py;
427 # can't use reverse
428 re_path(
429 rf"^{DOWNLOAD_PRIVATESTORAGE_URL_STEM}/(?P<filename>.+)$",
430 consent_views.download_privatestorage,
431 name=UrlNames.DOWNLOAD_PRIVATESTORAGE,
432 ),
433 re_path(
434 r"^charity_report/$",
435 consent_views.charity_report,
436 name=UrlNames.CHARITY_REPORT,
437 ),
438 re_path(
439 r"^exclusion_report/$",
440 consent_views.exclusion_report,
441 name=UrlNames.EXCLUSION_REPORT,
442 ),
443 re_path(
444 r"^test_email_rdbm/$",
445 consent_views.test_email_rdbm,
446 name=UrlNames.TEST_EMAIL_RDBM,
447 ),
448 # -------------------------------------------------------------------------
449 # Public views
450 # -------------------------------------------------------------------------
451 re_path(
452 r"^study_details/(?P<study_id>-?[0-9]+)/$",
453 consent_views.study_details,
454 name=UrlNames.STUDY_DETAILS,
455 ),
456 re_path(
457 r"^study_form/(?P<study_id>[0-9]+)/$",
458 consent_views.study_form,
459 name=UrlNames.STUDY_FORM,
460 ),
461 re_path(
462 r"^study_pack/(?P<study_id>[0-9]+)/$",
463 consent_views.study_pack,
464 name=UrlNames.STUDY_PACK,
465 ),
466 re_path(
467 r"^leaflet/(?P<leaflet_name>[a-zA-Z0-9_]+)/$",
468 consent_views.view_leaflet,
469 name=UrlNames.LEAFLET,
470 ),
471 # -------------------------------------------------------------------------
472 # Restricted C4C views (token-based); clinicians
473 # -------------------------------------------------------------------------
474 # note the -? : allows viewing (and URL-reversing within) an e-mail
475 # having a dummy ID of -1.
476 re_path(
477 r"^clinician_response/(?P<clinician_response_id>-?[0-9]+)/$",
478 consent_views.clinician_response_view,
479 name=UrlNames.CLINICIAN_RESPONSE,
480 ),
481 re_path(
482 r"^clinician_pack/(?P<clinician_response_id>-?[0-9]+)/(?P<token>[a-zA-Z0-9]+)/$", # noqa: E501
483 consent_views.clinician_pack,
484 name=UrlNames.CLINICIAN_PACK,
485 ),
486 # -------------------------------------------------------------------------
487 # Restricted views; superuser + researchers
488 # -------------------------------------------------------------------------
489 re_path(
490 r"^view_email_html/(?P<email_id>[0-9]+)/$",
491 consent_views.view_email_html,
492 name=UrlNames.VIEW_EMAIL_HTML,
493 ),
494 re_path(
495 r"^view_email_attachment/(?P<attachment_id>[0-9]+)/$",
496 consent_views.view_email_attachment,
497 name=UrlNames.VIEW_EMAIL_ATTACHMENT,
498 ),
499 re_path(
500 r"^letter/(?P<letter_id>[0-9]+)/$",
501 consent_views.view_letter,
502 name=UrlNames.LETTER,
503 ),
504 # -------------------------------------------------------------------------
505 # Developer functions and test views
506 # -------------------------------------------------------------------------
507 re_path(
508 r"^generate_random_nhs/$",
509 consent_views.generate_random_nhs,
510 name=UrlNames.GENERATE_RANDOM_NHS,
511 ),
512 re_path(
513 r"^test_patient_lookup/$",
514 consent_views.test_patient_lookup,
515 name=UrlNames.TEST_PATIENT_LOOKUP,
516 ),
517 re_path(
518 r"^test_consent_lookup/$",
519 consent_views.test_consent_lookup,
520 name=UrlNames.TEST_CONSENT_LOOKUP,
521 ),
522 re_path(
523 r"^draft_clinician_email/(?P<contact_request_id>-?[0-9]+)/$",
524 consent_views.draft_clinician_email,
525 name=UrlNames.DRAFT_CLINICIAN_EMAIL,
526 ),
527 re_path(
528 r"^draft_approval_email/(?P<contact_request_id>-?[0-9]+)/$",
529 consent_views.draft_approval_email,
530 name=UrlNames.DRAFT_APPROVAL_EMAIL,
531 ),
532 re_path(
533 r"^draft_withdrawal_email/(?P<contact_request_id>-?[0-9]+)/$",
534 consent_views.draft_withdrawal_email,
535 name=UrlNames.DRAFT_WITHDRAWAL_EMAIL,
536 ),
537 re_path(
538 r"^draft_approval_letter/"
539 r"(?P<contact_request_id>-?[0-9]+)/(?P<viewtype>pdf|html)/$",
540 consent_views.draft_approval_letter,
541 name=UrlNames.DRAFT_APPROVAL_LETTER,
542 ),
543 re_path(
544 r"^draft_withdrawal_letter/"
545 r"(?P<contact_request_id>-?[0-9]+)/(?P<viewtype>pdf|html)/$",
546 consent_views.draft_withdrawal_letter,
547 name=UrlNames.DRAFT_WITHDRAWAL_LETTER,
548 ),
549 re_path(
550 r"^draft_first_traffic_light_letter/"
551 r"(?P<patient_lookup_id>-?[0-9]+)/(?P<viewtype>pdf|html)/$",
552 consent_views.draft_first_traffic_light_letter,
553 name=UrlNames.DRAFT_FIRST_TRAFFIC_LIGHT_LETTER,
554 ),
555 re_path(
556 r"^draft_letter_clinician_to_pt_re_study/"
557 r"(?P<contact_request_id>-?[0-9]+)/(?P<viewtype>pdf|html)/$",
558 consent_views.draft_letter_clinician_to_pt_re_study,
559 name=UrlNames.DRAFT_LETTER_CLINICIAN_TO_PT_RE_STUDY,
560 ),
561 re_path(
562 r"^decision_form_to_pt_re_study/"
563 r"(?P<contact_request_id>-?[0-9]+)/(?P<viewtype>pdf|html)/$",
564 consent_views.decision_form_to_pt_re_study,
565 name=UrlNames.DECISION_FORM_TO_PT_RE_STUDY,
566 ),
567 re_path(
568 r"^draft_confirm_traffic_light_letter/"
569 r"(?P<consent_mode_id>-?[0-9]+)/(?P<viewtype>pdf|html)/$",
570 consent_views.draft_confirm_traffic_light_letter,
571 name=UrlNames.DRAFT_CONFIRM_TRAFFIC_LIGHT_LETTER,
572 ),
573 re_path(
574 r"^draft_traffic_light_decision_form/"
575 r"(?P<patient_lookup_id>-?[0-9]+)/(?P<viewtype>pdf|html)/$",
576 consent_views.draft_traffic_light_decision_form,
577 name=UrlNames.DRAFT_TRAFFIC_LIGHT_DECISION_FORM,
578 ),
579 re_path(
580 r"^draft_traffic_light_decision_form_generic/(?P<viewtype>pdf|html)/$",
581 consent_views.draft_traffic_light_decision_form_generic,
582 name=UrlNames.DRAFT_TRAFFIC_LIGHT_DECISION_FORM_GENERIC,
583 ),
584 re_path(
585 r"^draft_researcher_cover_letter/(?P<viewtype>pdf|html)/$",
586 consent_views.draft_researcher_cover_letter,
587 name=UrlNames.DRAFT_RESEARCHER_COVER_LETTER,
588 ),
589 # -------------------------------------------------------------------------
590 # Other test views
591 # -------------------------------------------------------------------------
592 # re_path(r'^404/$', django.views.defaults.page_not_found, ),
593]
596if settings.DEBUG:
597 # Debug toolbar
598 # - https://github.com/jazzband/django-debug-toolbar/issues/529
599 # - https://stackoverflow.com/questions/32111203/what-is-the-benefit-of-using-django-conf-urls-patterns-versus-a-list-of-url-in-d # noqa: E501
600 urlpatterns += [
601 re_path(r"^__debug__/", include(debug_toolbar.urls)),
602 ]
604 # Silk
605 #
606 # urlpatterns += patterns('', re_path(r'^silk/',
607 # include('silk.urls', namespace='silk')))
609 # Serve static files for development
610 urlpatterns += staticfiles_urlpatterns()