Adding a Route
Decide: view or API?
Every new endpoint is either a view (returns HTML) or an API (returns JSON). They live in different files, have different decorators, and have different return conventions.
| View | API | |
|---|---|---|
| File location | orchid/views/ | orchid/api/ |
| Returns | render_template() or redirect() | jsonify() |
| URL prefix | e.g. /admin/, /user/ | e.g. /api/admin/, /api/user/ |
| HTTP methods | Mostly GET | Mostly POST |
Step-by-step
-
Find the right blueprint file
Views are organized by feature. Pick the file that matches your feature:
Feature File Admin case management orchid/views/admin_case_management.pyAdmin contacts orchid/views/admin_contacts.pyAdmin matching orchid/views/admin_matching.pyUser portal orchid/views/user.pyProvider portal orchid/views/provider.pyForms/intake orchid/views/forms.pyReports orchid/views/reports.pyIf none fit, create a new file (see below).
-
Add the route function
@admin_case_management_views.route('/admin/case-management/my-new-page/<int:case_id>')@authcheck(user_types=authgroups.admin)def my_new_page(case_id):case = models.Case.query.filter_by(id=case_id).first_or_404()return render_template('admin/case_management/my_new_page.html',case=case) -
Create the template
Create
templates/admin/case_management/my_new_page.html. See Jinja Macros for available macros to use. -
Test
Navigate to the URL and verify the page renders. Check that:
- The auth decorator correctly blocks unauthorized users
- Template variables are populated
- No 500 errors in the console
-
Find the right blueprint file
orchid/api/admin.py ← admin operations (large — prefer a specific file)orchid/api/user.py ← user operationsorchid/api/profiles.py ← profile operationsorchid/api/matching.py ← matching operationsorchid/api/forms.py ← form operationsorchid/api/workflow.py ← workflow operations -
Add the route function
@admin_api.route('/api/admin/case/<int:case_id>/my-action', methods=['POST'])@authcheck_api(user_types=authgroups.admin)def case_my_action(case_id):case = models.Case.query.filter_by(id=case_id).first_or_404()data = request.get_json()# validate data...# do work...case.some_field = data.get('some_value')Crudler.update(case)Crudler.commit()return jsonify({'success': True, 'case_id': case.id}) -
Return conventions
- Success:
jsonify({'success': True, ...data...}) - Validation error:
jsonify({'error': 'message'}), 400 - Not found: Use
first_or_404()— Flask handles the 404 automatically - Server error: Let the exception propagate —
after_requestwill log it
- Success:
-
Call from the frontend
$.ajax({url: '/api/admin/case/123/my-action',method: 'POST',contentType: 'application/json',data: JSON.stringify({ some_value: 'hello' }),success: function(response) {if (response.success) { /* handle success */ }}});
Auth decorators
Always add an auth decorator to every route. The right decorator depends on the route type:
# For HTML view routes — redirects to login on failure@authcheck(user_types=authgroups.admin)@authcheck(user_types=authgroups.user)@authcheck(user_types=authgroups.all)
# For API routes — returns 403 JSON on failure@authcheck_api(user_types=authgroups.admin)@authcheck_api(user_types=authgroups.user)
# For confidential cases (restricts to assigned team)@confidential_authcheckauthgroups is defined in orchid/utils/auth.py:
authgroups.admin = ('admin', 'admin-master', 'admin-legal', ...)authgroups.user = ('donor', 'surrogate', 'IP', 'IP rep', 'sperm_donor')authgroups.all = (combined)Creating a new blueprint (when necessary)
If no existing blueprint file fits your feature, create a new one. Only do this for genuinely new feature areas — adding routes to existing files is almost always the right call.
- Create
orchid/views/my_feature.py:
from flask import Blueprint, render_templatefrom orchid.utils.auth import authcheck, authgroupsfrom orchid import models
my_feature_views = Blueprint('my_feature_views', __name__)
@my_feature_views.route('/my-feature/')@authcheck(user_types=authgroups.admin)def my_feature_index(): return render_template('my_feature/index.html')- Register it in
flaskapp.py:
from orchid.views.my_feature import my_feature_views# ...app.register_blueprint(my_feature_views)