Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a153d02

Browse files
committed
major refactor to modularize the app
1 parent 50cdc83 commit a153d02

File tree

11 files changed

+676
-313
lines changed

11 files changed

+676
-313
lines changed

activities/activity4/app.py

Lines changed: 22 additions & 312 deletions
Original file line numberDiff line numberDiff line change
@@ -1,330 +1,40 @@
1-
import datetime
2-
import os
3-
import pprint
4-
import json
1+
from flask import Flask, render_template
52

6-
from tempfile import mkdtemp
7-
from flask import Flask, jsonify, request, render_template, url_for, session, abort, send_from_directory
8-
9-
from flask_caching import Cache
10-
from werkzeug.exceptions import Forbidden
11-
from pylti1p3.contrib.flask import FlaskOIDCLogin, FlaskMessageLaunch, FlaskRequest, FlaskCacheDataStorage
12-
from pylti1p3.deep_link_resource import DeepLinkResource
13-
from pylti1p3.grade import Grade
14-
from pylti1p3.lineitem import LineItem
15-
from pylti1p3.tool_config import ToolConfJsonFile
16-
from pylti1p3.registration import Registration
173
from flask_session import Session
4+
from utils import ReverseProxied, initialize_cache
5+
from modules.lti import register as register_lti
6+
from modules.home import register as register_home
7+
from modules.nrps import register as register_nrps
8+
from modules.dl import register as register_dl
9+
from modules.ags import register as register_ags
1810

19-
class ReverseProxied:
20-
def __init__(self, app):
21-
self.app = app
11+
# from modules.deeplink.routes import deeplink_response
2212

23-
def __call__(self, environ, start_response):
24-
scheme = environ.get('HTTP_X_FORWARDED_PROTO')
25-
if scheme:
26-
environ['wsgi.url_scheme'] = scheme
27-
return self.app(environ, start_response)
2813

2914
app = Flask(__name__)
3015
app.wsgi_app = ReverseProxied(app.wsgi_app)
3116

17+
app.config.from_object('config.Config')
18+
initialize_cache(app)
3219

33-
34-
config = {
35-
"DEBUG": True,
36-
"ENV": "development",
37-
"CACHE_TYPE": "simple",
38-
"CACHE_DEFAULT_TIMEOUT": 600,
39-
"SECRET_KEY": "replace-me",
40-
"SESSION_TYPE": "filesystem",
41-
"SESSION_FILE_DIR": mkdtemp(),
42-
"SESSION_COOKIE_NAME": "lti1p3session-id",
43-
"SESSION_COOKIE_HTTPONLY": True,
44-
"SESSION_COOKIE_SECURE": True, # should be True in case of HTTPS usage (production)
45-
"SESSION_COOKIE_SAMESITE": "None", # should be 'None' in case of HTTPS usage (production)
46-
"SESSION_COOKIE_PARTITIONED ": True,
47-
"SESSION_PERMANENT": True,
48-
"PERMANENT_SESSION_LIFETIME": datetime.timedelta(minutes=60),
49-
"DEBUG_TB_INTERCEPT_REDIRECTS": False
50-
}
51-
app.config.from_mapping(config)
52-
cache = Cache(app)
53-
# Initialize session extension
5420
Session(app)
5521

56-
def get_lti_config_path():
57-
return os.path.join(app.root_path, '..', 'configs', 'registrations.json')
58-
59-
60-
def get_launch_data_storage():
61-
return FlaskCacheDataStorage(cache)
62-
63-
64-
def get_jwk_from_public_key(key_name):
65-
key_path = os.path.join(app.root_path, '..', 'configs', key_name)
66-
f = open(key_path, 'r')
67-
key_content = f.read()
68-
jwk = Registration.get_jwk(key_content)
69-
f.close()
70-
return jwk
71-
72-
@app.route("/home")
73-
def home():
74-
# Get launch data from session
75-
launch_data = session.get('launch_data', {})
76-
launch_id = session.get('launch_id', '')
77-
78-
79-
tpl_kwargs = {
80-
'page_title': "Home",
81-
'launch_id': launch_id,
82-
'target_link_uri': launch_data.get('https://purl.imsglobal.org/spec/lti/claim/target_link_uri', ''),
83-
'user_data': {
84-
'sub': launch_data.get('sub', ''),
85-
'name': launch_data.get('name', ''),
86-
'family_name': launch_data.get('family_name', ''),
87-
'given_name': launch_data.get('given_name', ''),
88-
'email': launch_data.get('email', ''),
89-
'sourced_id': launch_data.get('https://purl.imsglobal.org/spec/lti/claim/lis', '{}').get('person_sourcedid', ''),
90-
},
91-
'context_data': {
92-
**launch_data.get('https://purl.imsglobal.org/spec/lti/claim/context', {}),
93-
'sourced_id': launch_data.get('https://purl.imsglobal.org/spec/lti/claim/lis', {}).get('course_section_sourcedid', '')
94-
},
95-
'user_roles': launch_data.get('https://purl.imsglobal.org/spec/lti/claim/roles', []),
96-
'resource_link': launch_data.get('https://purl.imsglobal.org/spec/lti/claim/resource_link', {})
97-
}
98-
return render_template("home.html", **tpl_kwargs)
99-
100-
# Load the courses from the JSON file
101-
def load_courses():
102-
courses_path = os.path.join(app.root_path, '..', 'configs', 'resources.json')
103-
with open(courses_path) as f:
104-
courses = json.load(f)
105-
return courses
106-
107-
108-
def deeplink():
109-
# Get launch data from session
110-
launch_data = session.get('launch_data', {})
111-
launch_id = session.get('launch_id', '')
112-
113-
courses = load_courses()
114-
115-
tpl_kwargs = {
116-
'page_title': "Deeplinking",
117-
"courses": courses,
118-
'launch_id': launch_id,
119-
'target_link_uri': launch_data.get('https://purl.imsglobal.org/spec/lti/claim/target_link_uri', ''),
120-
'user_data': {
121-
'sub': launch_data.get('sub', ''),
122-
'name': launch_data.get('name', ''),
123-
'family_name': launch_data.get('family_name', ''),
124-
'given_name': launch_data.get('given_name', ''),
125-
'email': launch_data.get('email', ''),
126-
'sourced_id': launch_data.get('https://purl.imsglobal.org/spec/lti/claim/lis', '{}').get('person_sourcedid', ''),
127-
},
128-
'context_data': {
129-
**launch_data.get('https://purl.imsglobal.org/spec/lti/claim/context', {}),
130-
'sourced_id': launch_data.get('https://purl.imsglobal.org/spec/lti/claim/lis', {}).get('course_section_sourcedid', '')
131-
},
132-
'user_roles': launch_data.get('https://purl.imsglobal.org/spec/lti/claim/roles', []),
133-
'resource_link': launch_data.get('https://purl.imsglobal.org/spec/lti/claim/resource_link', {})
134-
}
135-
return render_template("deeplink.html", **tpl_kwargs)
136-
137-
@app.route("/")
138-
def basic():
139-
return render_template("basic.html")
140-
141-
@app.route('/jwks/', methods=['GET'])
142-
def get_jwks():
143-
tool_conf = ToolConfJsonFile(get_lti_config_path())
144-
return jsonify(tool_conf.get_jwks())
145-
146-
147-
@app.route("/nrps")
148-
def nrps():
149-
return render_template("nrps.html")
150-
151-
@app.route("/ags")
152-
def ags():
153-
launch_id = session.get('launch_id', '')
154-
155-
tool_conf = ToolConfJsonFile(get_lti_config_path())
156-
flask_request = FlaskRequest()
157-
launch_data_storage = get_launch_data_storage()
158-
message_launch = FlaskMessageLaunch.from_cache(launch_id, flask_request, tool_conf,
159-
launch_data_storage=launch_data_storage)
160-
161-
if not message_launch.has_ags():
162-
raise Forbidden('AGS not enabled!')
163-
164-
ags_service = message_launch.get_ags()
165-
166-
# Fetch members and line items
167-
members = get_nrps_members()
168-
lineitems = ags_service.get_lineitems()
169-
170-
# Create a dictionary to store grades by user and lineitem id for fast lookup
171-
grade_lookup = {}
172-
for item in lineitems:
173-
lineitem = ags_service.find_lineitem_by_id(item['id'])
174-
grades = ags_service.get_grades(lineitem)
175-
if grades:
176-
for grade in grades:
177-
grade_lookup[(grade['userId'], item['id'])] = grade['resultScore']
178-
179-
# Create gradebook data
180-
gradebook = []
181-
for member in members:
182-
row = [member['user_id'], member['name']]
183-
for item in lineitems:
184-
# Use the grade_lookup dictionary for fast access to grades
185-
grade = grade_lookup.get((member['user_id'], item['id']), '')
186-
row.append(grade)
187-
gradebook.append(row)
188-
189-
# Pretty print the gradebook for debugging
190-
pprint.pprint(gradebook)
191-
192-
# Render the template with gradebook data
193-
tpl_kwargs = {
194-
'page_title': "Assignments and Grades",
195-
'lineitems': lineitems,
196-
'gradebook': gradebook
197-
}
198-
199-
return render_template("ags.html", **tpl_kwargs)
200-
201-
202-
# @app.route("/assignments_grades")
203-
# def assignments_grades():
204-
# return render_template("assignments_grades.html")
205-
206-
@app.route("/id_token")
207-
def id_token():
208-
# Get launch data from session
209-
launch_data = session.get('launch_data', {})
210-
launch_id = session.get('launch_id', '')
211-
212-
tpl_kwargs = {
213-
'page_title': "ID Token",
214-
'launch_id': launch_id,
215-
'launch_data': launch_data
216-
}
217-
218-
return render_template("id_token.html", **tpl_kwargs)
219-
220-
221-
@app.route('/login/', methods=['GET', 'POST'])
222-
def login():
223-
tool_conf = ToolConfJsonFile(get_lti_config_path())
224-
launch_data_storage = get_launch_data_storage()
225-
226-
flask_request = FlaskRequest()
227-
launch_url = url_for('launch', _external=True)
228-
target_link_uri = flask_request.get_param('target_link_uri')
229-
if not target_link_uri:
230-
raise Exception('Missing "target_link_uri" param')
231-
232-
oidc_login = FlaskOIDCLogin(flask_request, tool_conf, launch_data_storage=launch_data_storage)
233-
return oidc_login\
234-
.enable_check_cookies()\
235-
.redirect(launch_url)
236-
237-
238-
@app.route('/launch/', methods=['POST'])
239-
def launch():
240-
tool_conf = ToolConfJsonFile(get_lti_config_path())
241-
flask_request = FlaskRequest()
242-
launch_data_storage = get_launch_data_storage()
243-
message_launch = FlaskMessageLaunch(flask_request, tool_conf, launch_data_storage=launch_data_storage)
244-
message_launch_data = message_launch.get_launch_data()
245-
# pprint.pprint(message_launch_data)
246-
247-
# Store the launch data in the session
248-
session['launch_data'] = message_launch_data
249-
session['launch_id'] = message_launch.get_launch_id()
250-
251-
if message_launch.is_deep_link_launch():
252-
return deeplink()
253-
return home()
254-
255-
256-
257-
@app.route('/api/nrps/members', methods=['GET'])
258-
def get_nrps_members():
259-
launch_id = session.get('launch_id', '')
260-
261-
tool_conf = ToolConfJsonFile(get_lti_config_path())
262-
flask_request = FlaskRequest()
263-
launch_data_storage = get_launch_data_storage()
264-
message_launch = FlaskMessageLaunch.from_cache(launch_id, flask_request, tool_conf,
265-
launch_data_storage=launch_data_storage)
266-
if not message_launch.has_nrps():
267-
raise Forbidden('NRPS not enabled!')
268-
269-
members = message_launch.get_nrps().get_members()
270-
return members
271-
272-
@app.route('/dl/<resource_id>/', methods=['GET', 'POST'])
273-
def deeplink_response(resource_id):
274-
275-
276-
launch_id = session.get('launch_id', '')
277-
tool_conf = ToolConfJsonFile(get_lti_config_path())
278-
flask_request = FlaskRequest()
279-
launch_data_storage = get_launch_data_storage()
280-
message_launch = FlaskMessageLaunch.from_cache(launch_id, flask_request, tool_conf,
281-
launch_data_storage=launch_data_storage)
282-
283-
if not message_launch.is_deep_link_launch():
284-
raise Forbidden('Must be a deep link!')
285-
286-
courses = load_courses()
287-
course = next((course for course in courses if course['id'] == int(resource_id)), None)
288-
289-
if not course:
290-
abort(404, description=f"Course with ID {resource_id} not found")
291-
292-
293-
launch_url = url_for('home', _external=True) + '/resource/' + resource_id + '/'
294-
295-
resource = DeepLinkResource()
296-
resource.set_url(launch_url) \
297-
.set_title(course.get("title", "Resource " + resource_id))
298-
299-
# Get the optional 'model_id' from query parameters
300-
model_id = request.args.get('model_id', 'default')
301-
302-
if model_id:
303-
resource.set_custom_params({'model_id': model_id})
304-
305-
306-
html = message_launch.get_deep_link().output_response_form([resource])
307-
return html
308-
22+
register_home(app)
23+
register_lti(app)
24+
register_nrps(app)
25+
register_dl(app)
26+
register_ags(app)
30927

310-
# @app.route('/api/ags/gradebook', methods=['GET'])
311-
# def get_gradebook():
312-
# launch_id = session.get('launch_id', '')
28+
# @app.route("/home")
29+
# def home():
30+
# return home_route()
31331

314-
# tool_conf = ToolConfJsonFile(get_lti_config_path())
315-
# flask_request = FlaskRequest()
316-
# launch_data_storage = get_launch_data_storage()
317-
# message_launch = FlaskMessageLaunch.from_cache(launch_id, flask_request, tool_conf,
318-
# launch_data_storage=launch_data_storage)
319-
# if not message_launch.has_ags():
320-
# raise Forbidden('AGS not enabled!')
321-
# ags_service = message_launch.get_ags()
32232

323-
# lineitems = ags_service.get_lineitems()
324-
325-
# return lineitems
32633

32734

35+
# @app.route('/dl/<resource_id>/', methods=['GET', 'POST'])
36+
# def deeplink(resource_id):
37+
# return deeplink_response(resource_id)
32838

329-
if __name__ == '__main__':
39+
if __name__ == "__main__":
33040
app.run(host='0.0.0.0', port=3000, debug=True)

activities/activity4/config.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# config.py
2+
import os
3+
import datetime
4+
from tempfile import mkdtemp
5+
6+
class Config:
7+
DEBUG = True
8+
ENV = "development"
9+
CACHE_TYPE = "simple"
10+
CACHE_DEFAULT_TIMEOUT = 600
11+
SECRET_KEY = "replace-me"
12+
SESSION_TYPE = "filesystem"
13+
SESSION_FILE_DIR = mkdtemp()
14+
SESSION_COOKIE_NAME = "lti1p3session-id"
15+
SESSION_COOKIE_HTTPONLY = True
16+
SESSION_COOKIE_SECURE = True # Set True for production HTTPS
17+
SESSION_COOKIE_SAMESITE = "None" # Set 'None' for production HTTPS
18+
SESSION_COOKIE_PARTITIONED = True
19+
SESSION_PERMANENT = True
20+
PERMANENT_SESSION_LIFETIME = datetime.timedelta(minutes=60)
21+
DEBUG_TB_INTERCEPT_REDIRECTS = False

0 commit comments

Comments
 (0)