Skip to content

Commit 9f365dd

Browse files
committed
Update Walkthrough 2 code to use the revised classroom-addon routing.
Change-Id: I23c739987e7dd5d4dd44b5fd5c0a48749757ff03
1 parent 5c952cc commit 9f365dd

File tree

15 files changed

+224
-92
lines changed

15 files changed

+224
-92
lines changed

README.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,21 +45,21 @@ Enable the following in the API Library:
4545

4646
1. Install [Python 3.7+](https://www.python.org/downloads/) and ensure that `pip` is available:
4747

48-
```shell
48+
```posix-terminal
4949
python -m ensurepip --upgrade
5050
```
5151

5252
1. Clone this repository and `cd` into the root project directory:
5353

54-
```shell
54+
```posix-terminal
5555
git clone https://github.com/<org>/<repo>/
5656
cd <repo>
5757
```
5858

5959
1. *(Optional, but recommended!)* Set up and activate a new Python virtual environment in
6060
the <repo> directory:
6161

62-
```shell
62+
```posix-terminal
6363
python3 -m venv .classroom-addon-env
6464
source .classroom-addon-env/bin/activate
6565
```
@@ -68,13 +68,13 @@ the <repo> directory:
6868

6969
1. `cd` into an example directory:
7070

71-
```shell
71+
```posix-terminal
7272
cd flask/01-basic-app
7373
```
7474

7575
1. Install the required libraries using `pip`:
7676

77-
```shell
77+
```posix-terminal
7878
pip install -r requirements.txt
7979
```
8080

@@ -90,6 +90,12 @@ example, to run the web app on `localhost`:
9090
app.run(debug=True)
9191
```
9292
93+
1. Launch the server by running the `main.py` file:
94+
95+
```posix-terminal
96+
python main.py
97+
```
98+
9399
1. To load your app, either open the app in your browser or click the **Add-ons** button when creating an Assignment in [Google Classroom](https://classroom.google.com).
94100
95101
Useful Resources

flask/01-basic-app/main.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,28 @@
2525

2626
@app.route("/")
2727
def index():
28-
return flask.render_template("index.html", message="You've reached the index page.")
28+
"""
29+
Render the index page from the "index.html" template. This is meant to act
30+
as a facsimile of a company's home page.
31+
The Add-on Discovery URL should be set to the /classroom-addon route below.
32+
"""
33+
34+
return flask.render_template("index.html",
35+
message="You've reached the index page.")
2936

3037

3138
@app.route("/classroom-addon")
3239
def classroom_addon():
33-
return flask.render_template("addon-discovery.html", message="You've reached the addon discovery page.")
40+
"""
41+
Renders the addon discovery page from the "addon-discovery.html" template.
42+
This is meant to be the landing page when opening the web app in the
43+
Classroom add-on iframe.
44+
"""
45+
46+
return flask.render_template(
47+
"addon-discovery.html",
48+
message="You've reached the addon discovery page.")
49+
3450

3551
if __name__ == "__main__":
3652
# You have several options for running the web server.

flask/01-basic-app/templates/addon-discovery.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
{% include "navbar.html" %}
1919
{% block content %}
2020
<div class="container">
21-
<h3>Welcome to the home page!</h3>
21+
<h3>Welcome to the add-on page!</h3>
2222
<strong>{{message}}</strong>
2323
</div>
2424

flask/01-basic-app/templates/base.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
<head>
2121
<meta charset="utf-8">
22-
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
2322

2423
<title>Classroom Add-on Example</title>
2524

flask/01-basic-app/templates/navbar.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<div class="topnav">
1818
<ul>
1919
<li>
20-
<a href="{{url_for('index')}}">Company Homepage</a>
20+
<a href="{{url_for('classroom_addon')}}">Home</a>
2121
</li>
2222

2323
<li style="float: right">

flask/02-sign-in/main.py

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -46,31 +46,37 @@
4646
# OAuth Consent Screen: https://console.cloud.google.com/apis/credentials/consent
4747
SCOPES = [
4848
"https://www.googleapis.com/auth/userinfo.profile",
49-
"https://www.googleapis.com/auth/userinfo.email",
50-
"https://www.googleapis.com/auth/classroom.courses.readonly",
51-
"https://www.googleapis.com/auth/classroom.addons.student",
52-
"https://www.googleapis.com/auth/classroom.addons.teacher",
49+
"https://www.googleapis.com/auth/userinfo.email"
5350
]
54-
CLASSROOM_API_SERVICE_NAME = "classroom"
55-
CLASSROOM_API_VERSION = "v1"
56-
57-
# An API key in your GCP project's credentials:
58-
# https://console.cloud.google.com/apis/credentials.
59-
GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
6051

6152

6253
@app.route("/")
6354
def index():
6455
"""
65-
Checks if a user is signed in. If so, renders the main page from the "index.html" template.
56+
Render the index page from the "index.html" template. This is meant to act
57+
as a facsimile of a company's home page.
58+
The Add-on Discovery URL should be set to the /classroom-addon route below.
59+
"""
60+
61+
return flask.render_template("index.html",
62+
message="You've reached the index page.")
63+
64+
65+
@app.route("/classroom-addon")
66+
def classroom_addon():
67+
"""
68+
Checks if a user is signed in. If so, renders the addon discovery page from
69+
the "addon-discovery.html" template. This is meant to be the landing page
70+
when opening the web app in the Classroom add-on iframe.
6671
Otherwise, renders the "authorization.html" template.
6772
"""
6873

6974
if "credentials" not in flask.session:
7075
return flask.render_template("authorization.html")
7176

72-
return flask.render_template("index.html",
73-
message="This is the index page.")
77+
return flask.render_template(
78+
"addon-discovery.html",
79+
message="You've reached the addon discovery page.")
7480

7581

7682
@app.route("/test/<request_type>")
@@ -79,7 +85,7 @@ def test_api_request(request_type="username"):
7985
Tests an API request, rendering the result in the "show-api-query-result.html" template.
8086
8187
Args:
82-
request_type: The type of API request to test. Can be "username" or "courses".
88+
request_type: The type of API request to test. Currently only "username" is supported.
8389
"""
8490

8591
if "credentials" not in flask.session:
@@ -102,18 +108,6 @@ def test_api_request(request_type="username"):
102108

103109
fetched_data = flask.session.get("username")
104110

105-
elif request_type == "courses":
106-
classroom_service = googleapiclient.discovery.build(
107-
CLASSROOM_API_SERVICE_NAME,
108-
CLASSROOM_API_VERSION,
109-
credentials=credentials,
110-
discoveryServiceUrl=
111-
f"https://classroom.googleapis.com/$discovery/rest?labels=ADD_ONS_ALPHA&key={GOOGLE_API_KEY}"
112-
)
113-
114-
fetched_data = classroom_service.courses().list(
115-
pageSize=10).execute().get("courses", [])
116-
117111
# Save credentials back to session in case access token was refreshed.
118112
# ACTION ITEM: In a production app, you likely want to save these
119113
# credentials in a persistent database instead.
@@ -163,7 +157,7 @@ def callback():
163157
the user's credentials, including the access token, refresh token, and allowed scopes.
164158
"""
165159

166-
# Specify the state when creating the flow in the callback so that it can
160+
# Specify the state when creating the flow in the callback so that it can be
167161
# verified in the authorization server response.
168162
state = flask.session["state"]
169163

@@ -198,7 +192,7 @@ def revoke():
198192
"""
199193

200194
if "credentials" not in flask.session:
201-
return flask.render_template("index.html",
195+
return flask.render_template("addon-discovery.html",
202196
message="You need to authorize before " +
203197
"attempting to revoke credentials.")
204198

@@ -228,9 +222,7 @@ def clear_credentials():
228222

229223
clear_credentials_in_session()
230224

231-
return flask.render_template(
232-
"index.html",
233-
message="Your credentials have been cleared; you are logged out.")
225+
return flask.render_template("signed-out.html")
234226

235227

236228
def clear_credentials_in_session():

flask/02-sign-in/static/base-style.css

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,32 @@ body {
3939
.topnav a:hover {
4040
background-color: #ddd;
4141
color: black;
42+
}
43+
44+
a.rounded-button {
45+
display: inline-block;
46+
padding: 0.3em 1.2em;
47+
margin: 0 0.1em 0.1em 0;
48+
border: 0.16em solid rgba(255, 255, 255, 0);
49+
border-radius: 2em;
50+
box-sizing: border-box;
51+
text-decoration: none;
52+
font-family: 'Roboto', sans-serif;
53+
font-weight: 300;
54+
color: #FFFFFF;
55+
text-shadow: 0 0.04em 0.04em rgba(0, 0, 0, 0.35);
56+
text-align: center;
57+
transition: all 0.2s;
58+
background-color: #4285F4;
59+
}
60+
61+
a.rounded-button:hover {
62+
border-color: rgba(255, 255, 255, 1);
63+
}
64+
65+
@media all and (max-width:30em) {
66+
a.rounded-button {
67+
display: block;
68+
margin: 0.2em auto;
69+
}
4270
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* Copyright 2021 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Opens a given destination route in a new window. This function uses
19+
* window.open() so as to force window.opener to retain a reference to the
20+
* iframe from which it was called.
21+
* @param {string=} destinationURL The endpoint to open, or "/" if none is
22+
* provided.
23+
*/
24+
function openWebsiteInNewTab(destinationURL = '/') {
25+
window.open(destinationURL, '_blank');
26+
}
27+
28+
/**
29+
* Close the iframe by calling postMessage() in the host Classroom page. This
30+
* function can be called direcly when in a Classroom add-on iframe.
31+
*
32+
* Alternatively, it can be used to close an add-on iframe in another window.
33+
* For example, if an add-on iframe in Window 1 opens a link in a new Window 2
34+
* using the openWebsiteInNewTab function above, you can call
35+
* window.opener.closeIframe() from Window 2 to close the iframe in Window 1.
36+
*/
37+
function closeAddonIframe() {
38+
window.parent.postMessage(
39+
{
40+
type: 'Classroom',
41+
action: 'closeIframe',
42+
},
43+
'*');
44+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<!--
2+
Copyright 2021 Google LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
-->
16+
17+
{% extends "base.html" %}
18+
{% include "navbar.html" %}
19+
{% block content %}
20+
<div class="container">
21+
<h3>Welcome to the home page!</h3>
22+
<strong>{{message}}</strong>
23+
24+
<hr>
25+
26+
<a class="rounded-button" href="#" onclick="openWebsiteInNewTab()">Visit our website</a>
27+
<a class="rounded-button" href="#" onclick="closeAddonIframe()">Close add-on iframe</a>
28+
29+
30+
From here you can:
31+
<ul>
32+
<li>
33+
<a href="{{url_for('test_api_request', request_type='courses')}}">Test a Classroom API request</a>
34+
<br>This will submit an API request and display a JSON response.
35+
<br>You should go through the authorization flow if there are no stored credentials.</li>
36+
<li><a href="{{ url_for('revoke') }}">Revoke credentials</a>
37+
<br>This will force users to allow your app to access their account again via the OAuth consent
38+
screen.</li>
39+
<li><a href="{{ url_for('clear_credentials') }}">Clear session credentials</a>
40+
<br>This will clear credentials from the current session.
41+
<br>You will have to reauthorize to issue any API requests.</li>
42+
</ul>
43+
</div>
44+
{% endblock %}

flask/02-sign-in/templates/authorization.html

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,19 @@
1919
<div class="container">
2020
<h2>Authorization Required</h2>
2121
<hr>
22-
<!-- Developer note: if you have *never* seen the user before, they'll need to authorize your app.
23-
It's most reliable to do so in a popup due to add-ons being presented in an iframe. -->
24-
<a target="popup" id="auth-user" href="{{ url_for('authorize') }}">
22+
<!--
23+
Developer note: if you have *never* seen the user before, they'll need to
24+
authorize your app. It's most reliable to do so in a popup due to add-ons
25+
being presented in an iframe.
26+
Note that this link also calls a function to close the iframe; since we
27+
won't know when the user has finished the authorization flow, we won't
28+
be able to reload the addon with their correct credentials. -->
29+
<a target="popup" onclick="closeAddonIframe()" href="{{ url_for('authorize') }}">
2530
<img src="../static/btn_google_signin_dark_normal_web.png" alt="Authorize the user">
2631
</a>
2732
<br>This will immediately start the authorization flow in a popup.
2833
<br>This should only be necessary once per user, unless your app has changed scopes or they
2934
have revoked their credentials.
3035
<br>Once complete, close the popup and use the other links below.
3136
</div>
32-
33-
<script>
34-
document.querySelector('#auth-user')
35-
.addEventListener('click', () => {
36-
window.parent.postMessage({
37-
type: 'Classroom',
38-
action: 'closeIframe',
39-
}, '*');
40-
});
41-
</script>
4237
{% endblock %}

0 commit comments

Comments
 (0)