├── .gitignore
├── ConferenceCentral_Complete
├── LICENSE
├── README.md
├── app.yaml
├── conference.py
├── cron.yaml
├── index.yaml
├── main.py
├── models.py
├── settings.py
├── static
│ ├── bootstrap
│ │ └── css
│ │ │ ├── bootstrap-cosmo.css
│ │ │ ├── bootstrap-responsive.css
│ │ │ ├── bootstrap.css
│ │ │ ├── main.css
│ │ │ └── offcanvas.css
│ ├── fonts
│ │ ├── glyphicons-halflings-regular.eot
│ │ ├── glyphicons-halflings-regular.svg
│ │ ├── glyphicons-halflings-regular.ttf
│ │ └── glyphicons-halflings-regular.woff
│ ├── img
│ │ ├── CloudPlatform_logo.png
│ │ ├── ajax-loader.gif
│ │ ├── business1.jpg
│ │ ├── business2.jpg
│ │ ├── business3.jpg
│ │ ├── favicon.ico
│ │ └── meeting-room.jpg
│ ├── js
│ │ ├── app.js
│ │ └── controllers.js
│ └── partials
│ │ ├── conference_detail.html
│ │ ├── create_conferences.html
│ │ ├── home.html
│ │ ├── login.modal.html
│ │ ├── profile.html
│ │ └── show_conferences.html
├── templates
│ └── index.html
└── utils.py
├── LICENSE
├── Lesson_2
├── 000_Hello_Endpoints
│ ├── app.yaml
│ ├── helloworld_api.py
│ └── static
│ │ ├── hello.js
│ │ └── index.html
├── 001_Hello_Endpoints_Solutions
│ ├── app.yaml
│ ├── helloworld_api.py
│ └── static
│ │ ├── hello.js
│ │ └── index.html
└── 00_Conference_Central
│ ├── LICENSE
│ ├── README.md
│ ├── app.yaml
│ ├── conference.py
│ ├── models.py
│ ├── settings.py
│ ├── static
│ ├── bootstrap
│ │ └── css
│ │ │ ├── bootstrap-cosmo.css
│ │ │ ├── bootstrap-responsive.css
│ │ │ ├── bootstrap.css
│ │ │ ├── main.css
│ │ │ └── offcanvas.css
│ ├── fonts
│ │ ├── glyphicons-halflings-regular.eot
│ │ ├── glyphicons-halflings-regular.svg
│ │ ├── glyphicons-halflings-regular.ttf
│ │ └── glyphicons-halflings-regular.woff
│ ├── img
│ │ ├── CloudPlatform_logo.png
│ │ ├── ajax-loader.gif
│ │ ├── business1.jpg
│ │ ├── business2.jpg
│ │ ├── business3.jpg
│ │ ├── favicon.ico
│ │ └── meeting-room.jpg
│ ├── js
│ │ ├── app.js
│ │ └── controllers.js
│ └── partials
│ │ ├── conference_detail.html
│ │ ├── create_conferences.html
│ │ ├── home.html
│ │ ├── login.modal.html
│ │ ├── profile.html
│ │ └── show_conferences.html
│ └── templates
│ └── index.html
├── Lesson_3
├── 00_Conference_Central
│ ├── LICENSE
│ ├── README.md
│ ├── app.yaml
│ ├── conference.py
│ ├── models.py
│ ├── settings.py
│ ├── static
│ │ ├── bootstrap
│ │ │ └── css
│ │ │ │ ├── bootstrap-cosmo.css
│ │ │ │ ├── bootstrap-responsive.css
│ │ │ │ ├── bootstrap.css
│ │ │ │ ├── main.css
│ │ │ │ └── offcanvas.css
│ │ ├── fonts
│ │ │ ├── glyphicons-halflings-regular.eot
│ │ │ ├── glyphicons-halflings-regular.svg
│ │ │ ├── glyphicons-halflings-regular.ttf
│ │ │ └── glyphicons-halflings-regular.woff
│ │ ├── img
│ │ │ ├── CloudPlatform_logo.png
│ │ │ ├── ajax-loader.gif
│ │ │ ├── business1.jpg
│ │ │ ├── business2.jpg
│ │ │ ├── business3.jpg
│ │ │ ├── favicon.ico
│ │ │ └── meeting-room.jpg
│ │ ├── js
│ │ │ ├── app.js
│ │ │ └── controllers.js
│ │ └── partials
│ │ │ ├── conference_detail.html
│ │ │ ├── create_conferences.html
│ │ │ ├── home.html
│ │ │ ├── login.modal.html
│ │ │ ├── profile.html
│ │ │ └── show_conferences.html
│ └── templates
│ │ └── index.html
└── additions
│ └── utils.py
├── Lesson_4
├── 00_Conference_Central
│ ├── LICENSE
│ ├── README.md
│ ├── app.yaml
│ ├── conference.py
│ ├── models.py
│ ├── settings.py
│ ├── static
│ │ ├── bootstrap
│ │ │ └── css
│ │ │ │ ├── bootstrap-cosmo.css
│ │ │ │ ├── bootstrap-responsive.css
│ │ │ │ ├── bootstrap.css
│ │ │ │ ├── main.css
│ │ │ │ └── offcanvas.css
│ │ ├── fonts
│ │ │ ├── glyphicons-halflings-regular.eot
│ │ │ ├── glyphicons-halflings-regular.svg
│ │ │ ├── glyphicons-halflings-regular.ttf
│ │ │ └── glyphicons-halflings-regular.woff
│ │ ├── img
│ │ │ ├── CloudPlatform_logo.png
│ │ │ ├── ajax-loader.gif
│ │ │ ├── business1.jpg
│ │ │ ├── business2.jpg
│ │ │ ├── business3.jpg
│ │ │ ├── favicon.ico
│ │ │ └── meeting-room.jpg
│ │ ├── js
│ │ │ ├── app.js
│ │ │ └── controllers.js
│ │ └── partials
│ │ │ ├── conference_detail.html
│ │ │ ├── create_conferences.html
│ │ │ ├── home.html
│ │ │ ├── login.modal.html
│ │ │ ├── profile.html
│ │ │ └── show_conferences.html
│ ├── templates
│ │ └── index.html
│ └── utils.py
└── Additions
│ ├── TODO_1_conference.py
│ ├── TODO_1_models.py
│ ├── TODO_2_conference.py
│ ├── TODO_2_models.py
│ ├── TODO_3_conference.py
│ ├── TODO_4_conference.py
│ ├── TODO_5_conference.py
│ ├── TODO_6_conference.py
│ ├── TODO_6_models.py
│ ├── TODO_7_conference.py
│ ├── conference_api.partial.txt
│ └── conference_models.partial.txt
├── Lesson_5
├── 00_Conference_Central
│ ├── LICENSE
│ ├── README.md
│ ├── app.yaml
│ ├── conference.py
│ ├── index.yaml
│ ├── main.py
│ ├── models.py
│ ├── settings.py
│ ├── static
│ │ ├── bootstrap
│ │ │ └── css
│ │ │ │ ├── bootstrap-cosmo.css
│ │ │ │ ├── bootstrap-responsive.css
│ │ │ │ ├── bootstrap.css
│ │ │ │ ├── main.css
│ │ │ │ └── offcanvas.css
│ │ ├── fonts
│ │ │ ├── glyphicons-halflings-regular.eot
│ │ │ ├── glyphicons-halflings-regular.svg
│ │ │ ├── glyphicons-halflings-regular.ttf
│ │ │ └── glyphicons-halflings-regular.woff
│ │ ├── img
│ │ │ ├── CloudPlatform_logo.png
│ │ │ ├── ajax-loader.gif
│ │ │ ├── business1.jpg
│ │ │ ├── business2.jpg
│ │ │ ├── business3.jpg
│ │ │ ├── favicon.ico
│ │ │ └── meeting-room.jpg
│ │ ├── js
│ │ │ ├── app.js
│ │ │ └── controllers.js
│ │ └── partials
│ │ │ ├── conference_detail.html
│ │ │ ├── create_conferences.html
│ │ │ ├── home.html
│ │ │ ├── login.modal.html
│ │ │ ├── profile.html
│ │ │ └── show_conferences.html
│ ├── templates
│ │ └── index.html
│ └── utils.py
└── Additions
│ ├── TODO_1_app.yaml
│ ├── TODO_1_conference.py
│ ├── TODO_1_main.py
│ ├── TODO_1_models.py
│ ├── TODO_2_app.yaml
│ ├── TODO_2_conference.py
│ ├── TODO_2_main.py
│ └── TODO_3_cron.yaml
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | *.py[cod]
2 |
3 | # C extensions
4 | *.so
5 |
6 | # Packages
7 | *.egg
8 | *.egg-info
9 | dist
10 | build
11 | eggs
12 | parts
13 | bin
14 | var
15 | sdist
16 | develop-eggs
17 | .installed.cfg
18 | lib
19 | lib64
20 | __pycache__
21 |
22 | # Installer logs
23 | pip-log.txt
24 |
25 | # Unit test / coverage reports
26 | .coverage
27 | .tox
28 | nosetests.xml
29 |
30 | # Translations
31 | *.mo
32 |
33 | # Mr Developer
34 | .mr.developer.cfg
35 | .project
36 | .pydevproject
37 |
38 | # OS X Generated Files #
39 | ########################
40 | .DS_Store
41 | .DS_Store?
42 | ._*
43 | .Spotlight-V100
44 | .Trashes
45 | Icon?
46 | ehthumbs.db
47 | Thumbs.db
48 | .AppleDouble
49 |
50 | *.elc
51 | *~
52 | *.aux
53 | *.log
54 | *.mx1
55 | .com.apple.timemachine.supported
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/README.md:
--------------------------------------------------------------------------------
1 | App Engine application for the Udacity training course.
2 |
3 | ## Products
4 | - [App Engine][1]
5 |
6 | ## Language
7 | - [Python][2]
8 |
9 | ## APIs
10 | - [Google Cloud Endpoints][3]
11 |
12 | ## Setup Instructions
13 | 1. Update the value of `application` in `app.yaml` to the app ID you
14 | have registered in the App Engine admin console and would like to use to host
15 | your instance of this sample.
16 | 1. Update the values at the top of `settings.py` to
17 | reflect the respective client IDs you have registered in the
18 | [Developer Console][4].
19 | 1. Update the value of CLIENT_ID in `static/js/app.js` to the Web client ID
20 | 1. (Optional) Mark the configuration files as unchanged as follows:
21 | `$ git update-index --assume-unchanged app.yaml settings.py static/js/app.js`
22 | 1. Run the app with the devserver using `dev_appserver.py DIR`, and ensure it's running by visiting your local server's address (by default [localhost:8080][5].)
23 | 1. (Optional) Generate your client library(ies) with [the endpoints tool][6].
24 | 1. Deploy your application.
25 |
26 |
27 | [1]: https://developers.google.com/appengine
28 | [2]: http://python.org
29 | [3]: https://developers.google.com/appengine/docs/python/endpoints/
30 | [4]: https://console.developers.google.com/
31 | [5]: https://localhost:8080/
32 | [6]: https://developers.google.com/appengine/docs/python/endpoints/endpoints_tool
33 |
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/app.yaml:
--------------------------------------------------------------------------------
1 | application: your-project-id
2 | version: 1
3 | runtime: python27
4 | api_version: 1
5 | threadsafe: yes
6 |
7 | handlers: # static then dynamic
8 |
9 | - url: /favicon\.ico
10 | static_files: favicon.ico
11 | upload: favicon\.ico
12 |
13 | - url: /js
14 | static_dir: static/js
15 |
16 | - url: /img
17 | static_dir: static/img
18 |
19 | - url: /css
20 | static_dir: static/bootstrap/css
21 |
22 | - url: /fonts
23 | static_dir: static/fonts
24 |
25 | - url: /partials
26 | static_dir: static/partials
27 |
28 | - url: /
29 | static_files: templates/index.html
30 | upload: templates/index\.html
31 | secure: always
32 |
33 | - url: /tasks/send_confirmation_email
34 | script: main.app
35 |
36 | - url: /crons/set_announcement
37 | script: main.app
38 |
39 | - url: /_ah/spi/.*
40 | script: conference.api
41 | secure: always
42 |
43 | libraries:
44 |
45 | - name: webapp2
46 | version: latest
47 |
48 | - name: endpoints
49 | version: latest
50 |
51 | # pycrypto library used for OAuth2 (req'd for authenticated APIs)
52 | - name: pycrypto
53 | version: latest
54 |
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/cron.yaml:
--------------------------------------------------------------------------------
1 | cron:
2 | - description: Repopulate the announcement every 1 hour
3 | url: /crons/set_announcement
4 | schedule: every 1 hours
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/index.yaml:
--------------------------------------------------------------------------------
1 | indexes:
2 |
3 | # AUTOGENERATED
4 |
5 | # This index.yaml is automatically updated whenever the dev_appserver
6 | # detects that a new type of query is run. If you want to manage the
7 | # index.yaml file manually, remove the above marker line (the line
8 | # saying "# AUTOGENERATED"). If you want to manage some indexes
9 | # manually, move them above the marker line. The index.yaml file is
10 | # automatically uploaded to the admin console when you next deploy
11 | # your application using appcfg.py.
12 |
13 | - kind: Conference
14 | properties:
15 | - name: city
16 | - name: maxAttendees
17 | - name: month
18 | - name: name
19 |
20 | - kind: Conference
21 | properties:
22 | - name: city
23 | - name: maxAttendees
24 | - name: month
25 | - name: topics
26 | - name: name
27 |
28 | - kind: Conference
29 | properties:
30 | - name: city
31 | - name: maxAttendees
32 | - name: name
33 |
34 | - kind: Conference
35 | properties:
36 | - name: city
37 | - name: month
38 | - name: name
39 |
40 | - kind: Conference
41 | properties:
42 | - name: city
43 | - name: month
44 | - name: topics
45 | - name: name
46 |
47 | - kind: Conference
48 | properties:
49 | - name: city
50 | - name: name
51 |
52 | - kind: Conference
53 | properties:
54 | - name: city
55 | - name: topics
56 | - name: name
57 |
58 | - kind: Conference
59 | properties:
60 | - name: maxAttendees
61 | - name: month
62 | - name: name
63 |
64 | - kind: Conference
65 | properties:
66 | - name: maxAttendees
67 | - name: month
68 | - name: topics
69 | - name: name
70 |
71 | - kind: Conference
72 | properties:
73 | - name: maxAttendees
74 | - name: name
75 |
76 | - kind: Conference
77 | properties:
78 | - name: maxAttendees
79 | - name: topics
80 | - name: name
81 |
82 | - kind: Conference
83 | properties:
84 | - name: month
85 | - name: name
86 |
87 | - kind: Conference
88 | properties:
89 | - name: month
90 | - name: topics
91 | - name: name
92 |
93 | - kind: Conference
94 | properties:
95 | - name: topics
96 | - name: name
97 |
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """
4 | main.py -- Udacity conference server-side Python App Engine
5 | HTTP controller handlers for memcache & task queue access
6 |
7 | $Id$
8 |
9 | created by wesc on 2014 may 24
10 |
11 | """
12 |
13 | __author__ = 'wesc+api@google.com (Wesley Chun)'
14 |
15 | import webapp2
16 | from google.appengine.api import app_identity
17 | from google.appengine.api import mail
18 | from conference import ConferenceApi
19 |
20 | class SetAnnouncementHandler(webapp2.RequestHandler):
21 | def get(self):
22 | """Set Announcement in Memcache."""
23 | ConferenceApi._cacheAnnouncement()
24 | self.response.set_status(204)
25 |
26 |
27 | class SendConfirmationEmailHandler(webapp2.RequestHandler):
28 | def post(self):
29 | """Send email confirming Conference creation."""
30 | mail.send_mail(
31 | 'noreply@%s.appspotmail.com' % (
32 | app_identity.get_application_id()), # from
33 | self.request.get('email'), # to
34 | 'You created a new Conference!', # subj
35 | 'Hi, you have created a following ' # body
36 | 'conference:\r\n\r\n%s' % self.request.get(
37 | 'conferenceInfo')
38 | )
39 |
40 |
41 | app = webapp2.WSGIApplication([
42 | ('/crons/set_announcement', SetAnnouncementHandler),
43 | ('/tasks/send_confirmation_email', SendConfirmationEmailHandler),
44 | ], debug=True)
45 |
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/models.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """models.py
4 |
5 | Udacity conference server-side Python App Engine data & ProtoRPC models
6 |
7 | $Id: models.py,v 1.1 2014/05/24 22:01:10 wesc Exp $
8 |
9 | created/forked from conferences.py by wesc on 2014 may 24
10 |
11 | """
12 |
13 | __author__ = 'wesc+api@google.com (Wesley Chun)'
14 |
15 | import httplib
16 | import endpoints
17 | from protorpc import messages
18 | from google.appengine.ext import ndb
19 |
20 | class ConflictException(endpoints.ServiceException):
21 | """ConflictException -- exception mapped to HTTP 409 response"""
22 | http_status = httplib.CONFLICT
23 |
24 | class Profile(ndb.Model):
25 | """Profile -- User profile object"""
26 | displayName = ndb.StringProperty()
27 | mainEmail = ndb.StringProperty()
28 | teeShirtSize = ndb.StringProperty(default='NOT_SPECIFIED')
29 | conferenceKeysToAttend = ndb.StringProperty(repeated=True)
30 |
31 | class ProfileMiniForm(messages.Message):
32 | """ProfileMiniForm -- update Profile form message"""
33 | displayName = messages.StringField(1)
34 | teeShirtSize = messages.EnumField('TeeShirtSize', 2)
35 |
36 | class ProfileForm(messages.Message):
37 | """ProfileForm -- Profile outbound form message"""
38 | displayName = messages.StringField(1)
39 | mainEmail = messages.StringField(2)
40 | teeShirtSize = messages.EnumField('TeeShirtSize', 3)
41 | conferenceKeysToAttend = messages.StringField(4, repeated=True)
42 |
43 | class StringMessage(messages.Message):
44 | """StringMessage-- outbound (single) string message"""
45 | data = messages.StringField(1, required=True)
46 |
47 | class BooleanMessage(messages.Message):
48 | """BooleanMessage-- outbound Boolean value message"""
49 | data = messages.BooleanField(1)
50 |
51 | class Conference(ndb.Model):
52 | """Conference -- Conference object"""
53 | name = ndb.StringProperty(required=True)
54 | description = ndb.StringProperty()
55 | organizerUserId = ndb.StringProperty()
56 | topics = ndb.StringProperty(repeated=True)
57 | city = ndb.StringProperty()
58 | startDate = ndb.DateProperty()
59 | month = ndb.IntegerProperty() # TODO: do we need for indexing like Java?
60 | endDate = ndb.DateProperty()
61 | maxAttendees = ndb.IntegerProperty()
62 | seatsAvailable = ndb.IntegerProperty()
63 |
64 | class ConferenceForm(messages.Message):
65 | """ConferenceForm -- Conference outbound form message"""
66 | name = messages.StringField(1)
67 | description = messages.StringField(2)
68 | organizerUserId = messages.StringField(3)
69 | topics = messages.StringField(4, repeated=True)
70 | city = messages.StringField(5)
71 | startDate = messages.StringField(6) #DateTimeField()
72 | month = messages.IntegerField(7, variant=messages.Variant.INT32)
73 | maxAttendees = messages.IntegerField(8, variant=messages.Variant.INT32)
74 | seatsAvailable = messages.IntegerField(9, variant=messages.Variant.INT32)
75 | endDate = messages.StringField(10) #DateTimeField()
76 | websafeKey = messages.StringField(11)
77 | organizerDisplayName = messages.StringField(12)
78 |
79 | class ConferenceForms(messages.Message):
80 | """ConferenceForms -- multiple Conference outbound form message"""
81 | items = messages.MessageField(ConferenceForm, 1, repeated=True)
82 |
83 | class TeeShirtSize(messages.Enum):
84 | """TeeShirtSize -- t-shirt size enumeration value"""
85 | NOT_SPECIFIED = 1
86 | XS_M = 2
87 | XS_W = 3
88 | S_M = 4
89 | S_W = 5
90 | M_M = 6
91 | M_W = 7
92 | L_M = 8
93 | L_W = 9
94 | XL_M = 10
95 | XL_W = 11
96 | XXL_M = 12
97 | XXL_W = 13
98 | XXXL_M = 14
99 | XXXL_W = 15
100 |
101 | class ConferenceQueryForm(messages.Message):
102 | """ConferenceQueryForm -- Conference query inbound form message"""
103 | field = messages.StringField(1)
104 | operator = messages.StringField(2)
105 | value = messages.StringField(3)
106 |
107 | class ConferenceQueryForms(messages.Message):
108 | """ConferenceQueryForms -- multiple ConferenceQueryForm inbound form message"""
109 | filters = messages.MessageField(ConferenceQueryForm, 1, repeated=True)
110 |
111 |
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/settings.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """settings.py
4 |
5 | Udacity conference server-side Python App Engine app user settings
6 |
7 | $Id$
8 |
9 | created/forked from conference.py by wesc on 2014 may 24
10 |
11 | """
12 |
13 | # Replace the following lines with client IDs obtained from the APIs
14 | # Console or Cloud Console.
15 | WEB_CLIENT_ID = 'replace with Web client ID'
16 | ANDROID_CLIENT_ID = 'replace with Android client ID'
17 | IOS_CLIENT_ID = 'replace with iOS client ID'
18 | ANDROID_AUDIENCE = WEB_CLIENT_ID
19 |
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/static/bootstrap/css/main.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | height: 100%;
3 | }
4 |
5 | body {
6 | padding-top: 70px;
7 | }
8 |
9 | #signInLink, #signOutLink {
10 | cursor: pointer;
11 | }
12 |
13 | /* To disable the Google+ Sign In Button, but gapi.signin.render should be invoked to store the credential
14 | if that is stored in cookie. */
15 | #signInButton iframe {
16 | display: none;
17 | }
18 |
19 | .required {
20 | color: red;
21 | }
22 |
23 | .dismiss-messages {
24 | cursor: pointer;
25 | }
26 |
27 | /* In a small screen, make the alert message sticky to the top */
28 | @media (max-width: 768px) {
29 | #messages.alert, #rootMessages.alert {
30 | position: fixed;
31 | left: 0;
32 | right: 0;
33 | top: 65px;
34 | z-index: 1000;
35 | }
36 | }
37 |
38 | .form-group-condensed {
39 | margin-top: 0;
40 | margin-bottom: 5px;
41 | }
42 |
43 | .label-separated {
44 | margin-right: 8px;
45 | }
46 |
47 | .spinner {
48 | position: fixed;
49 | top: 70px;
50 | z-index: 9999;
51 | }
52 |
53 | #signInButton {
54 | cursor: pointer;
55 | vertical-align: middle;
56 | }
57 |
58 | #profile-container {
59 | float: right;
60 | font-size: 85%;
61 | }
62 |
63 | #profile img {
64 | max-height: 35px;
65 | width: auto;
66 | vertical-align: middle;
67 | }
68 |
69 | #show-conferences-tab {
70 | margin-bottom: 20px;
71 | }
72 |
73 | ul#filters {
74 | list-style: none;
75 | padding-left: 0px;
76 | font-size: 85%;
77 | }
78 |
79 | ul#filters span.glyphicon-remove {
80 | font-size: 80%;
81 | }
82 |
83 | ul#conferences-list {
84 | list-style: none;
85 | }
86 |
87 | .intro-header {
88 | padding-top: 50px;
89 | padding-bottom: 50px;
90 | color: #f8f8f8;
91 | text-shadow: black 0.1em 0.1em 0.2em;
92 | background: url(/img/meeting-room.jpg) no-repeat center center;
93 | background-size: cover;
94 | text-align: center;
95 | }
96 |
97 | .intro-message {
98 | position: relative;
99 | padding-top: 5%;
100 | padding-bottom: 5%;
101 | vertical-align: middle;
102 | }
103 |
104 | .section-a {
105 | padding: 50px 0;
106 | }
107 |
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/static/bootstrap/css/offcanvas.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Style tweaks
3 | * --------------------------------------------------
4 | */
5 | html,
6 | body {
7 | overflow-x: hidden; /* Prevent scroll on narrow devices */
8 | }
9 |
10 | footer {
11 | padding: 30px 0;
12 | }
13 |
14 | /*
15 | * Off Canvas
16 | * --------------------------------------------------
17 | */
18 | @media screen and (max-width: 767px) {
19 | .row-offcanvas {
20 | position: relative;
21 | -webkit-transition: all .25s ease-out;
22 | -moz-transition: all .25s ease-out;
23 | transition: all .25s ease-out;
24 | }
25 |
26 | .row-offcanvas-right {
27 | right: 0;
28 | }
29 |
30 | .row-offcanvas-left {
31 | left: 0;
32 | }
33 |
34 | .row-offcanvas-right
35 | .sidebar-offcanvas {
36 | right: -50%; /* 6 columns */
37 | }
38 |
39 | .row-offcanvas-left
40 | .sidebar-offcanvas {
41 | left: -50%; /* 6 columns */
42 | }
43 |
44 | .row-offcanvas-right.active {
45 | right: 50%; /* 6 columns */
46 | }
47 |
48 | .row-offcanvas-left.active {
49 | left: 50%; /* 6 columns */
50 | }
51 |
52 | .sidebar-offcanvas {
53 | position: absolute;
54 | top: 0;
55 | width: 50%; /* 6 columns */
56 | }
57 | }
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/static/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/ConferenceCentral_Complete/static/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/static/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/ConferenceCentral_Complete/static/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/static/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/ConferenceCentral_Complete/static/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/static/img/CloudPlatform_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/ConferenceCentral_Complete/static/img/CloudPlatform_logo.png
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/static/img/ajax-loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/ConferenceCentral_Complete/static/img/ajax-loader.gif
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/static/img/business1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/ConferenceCentral_Complete/static/img/business1.jpg
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/static/img/business2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/ConferenceCentral_Complete/static/img/business2.jpg
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/static/img/business3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/ConferenceCentral_Complete/static/img/business3.jpg
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/ConferenceCentral_Complete/static/img/favicon.ico
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/static/img/meeting-room.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/ConferenceCentral_Complete/static/img/meeting-room.jpg
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/static/js/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @ngdoc object
5 | * @name conferenceApp
6 | * @requires $routeProvider
7 | * @requires conferenceControllers
8 | * @requires ui.bootstrap
9 | *
10 | * @description
11 | * Root app, which routes and specifies the partial html and controller depending on the url requested.
12 | *
13 | */
14 | var app = angular.module('conferenceApp',
15 | ['conferenceControllers', 'ngRoute', 'ui.bootstrap']).
16 | config(['$routeProvider',
17 | function ($routeProvider) {
18 | $routeProvider.
19 | when('/conference', {
20 | templateUrl: '/partials/show_conferences.html',
21 | controller: 'ShowConferenceCtrl'
22 | }).
23 | when('/conference/create', {
24 | templateUrl: '/partials/create_conferences.html',
25 | controller: 'CreateConferenceCtrl'
26 | }).
27 | when('/conference/detail/:websafeConferenceKey', {
28 | templateUrl: '/partials/conference_detail.html',
29 | controller: 'ConferenceDetailCtrl'
30 | }).
31 | when('/profile', {
32 | templateUrl: '/partials/profile.html',
33 | controller: 'MyProfileCtrl'
34 | }).
35 | when('/', {
36 | templateUrl: '/partials/home.html'
37 | }).
38 | otherwise({
39 | redirectTo: '/'
40 | });
41 | }]);
42 |
43 | /**
44 | * @ngdoc filter
45 | * @name startFrom
46 | *
47 | * @description
48 | * A filter that extracts an array from the specific index.
49 | *
50 | */
51 | app.filter('startFrom', function () {
52 | /**
53 | * Extracts an array from the specific index.
54 | *
55 | * @param {Array} data
56 | * @param {Integer} start
57 | * @returns {Array|*}
58 | */
59 | var filter = function (data, start) {
60 | return data.slice(start);
61 | }
62 | return filter;
63 | });
64 |
65 |
66 | /**
67 | * @ngdoc constant
68 | * @name HTTP_ERRORS
69 | *
70 | * @description
71 | * Holds the constants that represent HTTP error codes.
72 | *
73 | */
74 | app.constant('HTTP_ERRORS', {
75 | 'UNAUTHORIZED': 401
76 | });
77 |
78 |
79 | /**
80 | * @ngdoc service
81 | * @name oauth2Provider
82 | *
83 | * @description
84 | * Service that holds the OAuth2 information shared across all the pages.
85 | *
86 | */
87 | app.factory('oauth2Provider', function ($modal) {
88 | var oauth2Provider = {
89 | CLIENT_ID: 'web-client-id',
90 | SCOPES: 'email profile',
91 | signedIn: false
92 | }
93 |
94 | /**
95 | * Calls the OAuth2 authentication method.
96 | */
97 | oauth2Provider.signIn = function (callback) {
98 | gapi.auth.signIn({
99 | 'clientid': oauth2Provider.CLIENT_ID,
100 | 'cookiepolicy': 'single_host_origin',
101 | 'accesstype': 'online',
102 | 'approveprompt': 'auto',
103 | 'scope': oauth2Provider.SCOPES,
104 | 'callback': callback
105 | });
106 | };
107 |
108 | /**
109 | * Logs out the user.
110 | */
111 | oauth2Provider.signOut = function () {
112 | gapi.auth.signOut();
113 | // Explicitly set the invalid access token in order to make the API calls fail.
114 | gapi.auth.setToken({access_token: ''})
115 | oauth2Provider.signedIn = false;
116 | };
117 |
118 | /**
119 | * Shows the modal with Google+ sign in button.
120 | *
121 | * @returns {*|Window}
122 | */
123 | oauth2Provider.showLoginModal = function() {
124 | var modalInstance = $modal.open({
125 | templateUrl: '/partials/login.modal.html',
126 | controller: 'OAuth2LoginModalCtrl'
127 | });
128 | return modalInstance;
129 | };
130 |
131 | return oauth2Provider;
132 | });
133 |
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/static/partials/conference_detail.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
{{conference.name}}
17 |
{{conference.description}}
18 |
19 | Registered/Open:
20 | {{conference.maxAttendees - conference.seatsAvailable}} / {{conference.maxAttendees}}
21 |
22 |
23 | Organizer:
24 | {{conference.organizerDisplayName}}
25 |
26 |
Register
28 |
Unregister
30 |
31 |
32 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/static/partials/create_conferences.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
Create a conference
15 |
16 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/static/partials/home.html:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 |
25 |
26 |
View conferences
27 |
28 |
View by city, topics, date, max attendees.
29 |
View conferences
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
Create new conferences
43 |
44 |
In 10 seconds or less.
45 |
Create a conference
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/static/partials/login.modal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Please sign in to complete this action.
4 |
5 |
8 |
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/static/partials/profile.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
My Profile
15 |
16 |
17 | Display Name
18 | Changed
20 |
22 |
23 |
24 |
25 | Tee shirt size
26 | Changed
28 |
31 |
32 |
33 |
34 | Update profile
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Conference Central
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
67 |
68 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/ConferenceCentral_Complete/utils.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import time
4 | import uuid
5 |
6 | from google.appengine.api import urlfetch
7 | from models import Profile
8 |
9 | def getUserId(user, id_type="email"):
10 | if id_type == "email":
11 | return user.email()
12 |
13 | if id_type == "oauth":
14 | """A workaround implementation for getting userid."""
15 | auth = os.getenv('HTTP_AUTHORIZATION')
16 | bearer, token = auth.split()
17 | token_type = 'id_token'
18 | if 'OAUTH_USER_ID' in os.environ:
19 | token_type = 'access_token'
20 | url = ('https://www.googleapis.com/oauth2/v1/tokeninfo?%s=%s'
21 | % (token_type, token))
22 | user = {}
23 | wait = 1
24 | for i in range(3):
25 | resp = urlfetch.fetch(url)
26 | if resp.status_code == 200:
27 | user = json.loads(resp.content)
28 | break
29 | elif resp.status_code == 400 and 'invalid_token' in resp.content:
30 | url = ('https://www.googleapis.com/oauth2/v1/tokeninfo?%s=%s'
31 | % ('access_token', token))
32 | else:
33 | time.sleep(wait)
34 | wait = wait + i
35 | return user.get('user_id', '')
36 |
37 | if id_type == "custom":
38 | # implement your own user_id creation and getting algorythm
39 | # this is just a sample that queries datastore for an existing profile
40 | # and generates an id if profile does not exist for an email
41 | profile = Conference.query(Conference.mainEmail == user.email())
42 | if profile:
43 | return profile.id()
44 | else:
45 | return str(uuid.uuid1().get_hex())
46 |
--------------------------------------------------------------------------------
/Lesson_2/000_Hello_Endpoints/app.yaml:
--------------------------------------------------------------------------------
1 | # This file specifies your Python application's runtime configuration
2 | # including URL routing, versions, static file uploads, etc. See
3 | # https://developers.google.com/appengine/docs/python/config/appconfig
4 | # for details.
5 |
6 | # TODO: Enter your application id below. If you have signed up
7 | # using cloud.google.com/console use the "project id" for your application
8 | # id.
9 | application: your-project-id
10 | version: 1
11 | runtime: python27
12 | threadsafe: true
13 | api_version: 1
14 |
15 | # Handlers tell App Engine how to route requests to your application.
16 | handlers:
17 |
18 | # The following three handlers route requests to static resources: JS, CSS,
19 | # and HTML.
20 | - url: /static
21 | static_dir: static
22 | - url: /
23 | static_files: static/index.html
24 | upload: static/index\.html
25 | secure: always
26 |
27 | # This handler routes requests to your APIs to your Endpoints code.
28 | # See https://developers.google.com/appengine/docs/python/endpoints/
29 | - url: /_ah/spi/.*
30 | script: helloworld_api.APPLICATION
31 | secure: always
32 |
33 | # Third party libraries that are included in the App Engine SDK must be listed
34 | # here if you want to use them. See
35 | # https://developers.google.com/appengine/docs/python/tools/libraries27 for
36 | # a list of libraries included in the SDK. Third party libs that are *not* part
37 | # of the App Engine SDK don't need to be listed here, instead add them to your
38 | # project directory, either as a git submodule or as a plain subdirectory.
39 | # Note that dependencies must be located in your project directory - packages
40 | # installed in the Python environment are not loaded by the App Engine
41 | # development server or deployment tools.
42 | # TODO: List any other App Engine SDK libs you may need here.
43 | libraries:
44 |
45 | # You must include the Endpoints library if you wish to serve an API.
46 | - name: endpoints
47 | version: 1.0
48 |
49 |
--------------------------------------------------------------------------------
/Lesson_2/000_Hello_Endpoints/helloworld_api.py:
--------------------------------------------------------------------------------
1 | """Hello World API implemented using Google Cloud Endpoints.
2 |
3 | Contains declarations of endpoint, endpoint methods,
4 | as well as the ProtoRPC message class and container required
5 | for endpoint method definition.
6 | """
7 | import endpoints
8 | from protorpc import messages
9 | from protorpc import message_types
10 | from protorpc import remote
11 |
12 |
13 | # If the request contains path or querystring arguments,
14 | # you cannot use a simple Message class.
15 | # Instead, you must use a ResourceContainer class
16 | REQUEST_CONTAINER = endpoints.ResourceContainer(
17 | message_types.VoidMessage,
18 | name=messages.StringField(1),
19 | )
20 |
21 |
22 | package = 'Hello'
23 |
24 |
25 | class Hello(messages.Message):
26 | """String that stores a message."""
27 | greeting = messages.StringField(1)
28 |
29 |
30 | @endpoints.api(name='helloworldendpoints', version='v1')
31 | class HelloWorldApi(remote.Service):
32 | """Helloworld API v1."""
33 |
34 | @endpoints.method(message_types.VoidMessage, Hello,
35 | path = "sayHello", http_method='GET', name = "sayHello")
36 | def say_hello(self, request):
37 | return Hello(greeting="Hello World")
38 |
39 | @endpoints.method(REQUEST_CONTAINER, Hello,
40 | path = "sayHelloByName", http_method='GET', name = "sayHelloByName")
41 | def say_hello_by_name(self, request):
42 | greet = "Hello {}".format(request.name)
43 | return Hello(greeting=greet)
44 |
45 |
46 | APPLICATION = endpoints.api_server([HelloWorldApi])
47 |
--------------------------------------------------------------------------------
/Lesson_2/000_Hello_Endpoints/static/hello.js:
--------------------------------------------------------------------------------
1 | /*
2 | * http://stackoverflow.com/questions/18260815/use-gapi-client-javascript-to-execute-my-custom-google-api
3 | * https://developers.google.com/appengine/docs/java/endpoints/consume_js
4 | * https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiclientload
5 | *
6 | */
7 |
8 | /**
9 | * After the client library has loaded, this init() function is called.
10 | * The init() function loads the helloworldendpoints API.
11 | */
12 |
13 | function init() {
14 |
15 | // You need to pass the root path when you load your API
16 | // otherwise calls to execute the API run into a problem
17 |
18 | // rootpath will evaulate to either of these, depending on where the app is running:
19 | // //localhost:8080/_ah/api
20 | // //your-app-id/_ah/api
21 |
22 | var rootpath = "//" + window.location.host + "/_ah/api";
23 |
24 | // Load the helloworldendpoints API
25 | // If loading completes successfully, call loadCallback function
26 | gapi.client.load('helloworldendpoints', 'v1', loadCallback, rootpath);
27 | }
28 |
29 | /*
30 | * When helloworldendpoints API has loaded, this callback is called.
31 | *
32 | * We need to wait until the helloworldendpoints API has loaded to
33 | * enable the actions for the buttons in index.html,
34 | * because the buttons call functions in the helloworldendpoints API
35 | */
36 | function loadCallback () {
37 | // Enable the button actions
38 | enableButtons ();
39 | }
40 |
41 | function enableButtons () {
42 | // Set the onclick action for the first button
43 | btn = document.getElementById("input_greet_generically");
44 | btn.onclick= function(){greetGenerically();};
45 |
46 | // Update the button label now that the button is active
47 | btn.value="Click me for a generic greeting";
48 |
49 | // Set the onclick action for the second button
50 | btn = document.getElementById("input_greet_by_name");
51 | btn.onclick=function(){greetByName();};
52 |
53 | // Update the button label now that the button is active
54 | btn.value="Click me for a personal greeting";
55 | }
56 |
57 | /*
58 | * Execute a request to the sayHello() endpoints function
59 | */
60 | function greetGenerically () {
61 | // Construct the request for the sayHello() function
62 | var request = gapi.client.helloworldendpoints.sayHello();
63 |
64 | // Execute the request.
65 | // On success, pass the response to sayHelloCallback()
66 | request.execute(sayHelloCallback);
67 | }
68 |
69 | /*
70 | * Execute a request to the sayHelloByName() endpoints function.
71 | * Illustrates calling an endpoints function that takes an argument.
72 | */
73 | function greetByName () {
74 | // Get the name from the name_field element
75 | var name = document.getElementById("name_field").value;
76 |
77 | // Call the sayHelloByName() function.
78 | // It takes one argument "name"
79 | // On success, pass the response to sayHelloCallback()
80 | var request = gapi.client.helloworldendpoints.sayHelloByName({'name': name});
81 | request.execute(sayHelloCallback);
82 | }
83 |
84 | // Process the JSON response
85 | // In this case, just show an alert dialog box
86 | // displaying the value of the message field in the response
87 | function sayHelloCallback (response) {
88 | alert(response.greeting);
89 | }
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/Lesson_2/000_Hello_Endpoints/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hello World Endpoints
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | My First Endpoints App
17 |
18 |
20 |
21 | What is your name?
22 |
23 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Lesson_2/001_Hello_Endpoints_Solutions/app.yaml:
--------------------------------------------------------------------------------
1 | # This file specifies your Python application's runtime configuration
2 | # including URL routing, versions, static file uploads, etc. See
3 | # https://developers.google.com/appengine/docs/python/config/appconfig
4 | # for details.
5 |
6 | # TODO: Enter your application id below. If you have signed up
7 | # using cloud.google.com/console use the "project id" for your application
8 | # id.
9 | application: your-project-id
10 | version: 1
11 | runtime: python27
12 | threadsafe: true
13 | api_version: 1
14 |
15 | # Handlers tell App Engine how to route requests to your application.
16 | handlers:
17 |
18 | # The following three handlers route requests to static resources: JS, CSS,
19 | # and HTML.
20 | - url: /static
21 | static_dir: static
22 | - url: /
23 | static_files: static/index.html
24 | upload: static/index\.html
25 | secure: always
26 |
27 | # This handler routes requests to your APIs to your Endpoints code.
28 | # See https://developers.google.com/appengine/docs/python/endpoints/
29 | - url: /_ah/spi/.*
30 | script: helloworld_api.APPLICATION
31 | secure: always
32 |
33 | # Third party libraries that are included in the App Engine SDK must be listed
34 | # here if you want to use them. See
35 | # https://developers.google.com/appengine/docs/python/tools/libraries27 for
36 | # a list of libraries included in the SDK. Third party libs that are *not* part
37 | # of the App Engine SDK don't need to be listed here, instead add them to your
38 | # project directory, either as a git submodule or as a plain subdirectory.
39 | # Note that dependencies must be located in your project directory - packages
40 | # installed in the Python environment are not loaded by the App Engine
41 | # development server or deployment tools.
42 | # TODO: List any other App Engine SDK libs you may need here.
43 | libraries:
44 |
45 | # You must include the Endpoints library if you wish to serve an API.
46 | - name: endpoints
47 | version: 1.0
48 |
49 |
--------------------------------------------------------------------------------
/Lesson_2/001_Hello_Endpoints_Solutions/helloworld_api.py:
--------------------------------------------------------------------------------
1 | """Hello World API implemented using Google Cloud Endpoints.
2 |
3 | Contains declarations of endpoint, endpoint methods,
4 | as well as the ProtoRPC message class and container required
5 | for endpoint method definition.
6 | """
7 | import endpoints
8 | from protorpc import messages
9 | from protorpc import message_types
10 | from protorpc import remote
11 |
12 |
13 | # If the request contains path or querystring arguments,
14 | # you cannot use a simple Message class.
15 | # Instead, you must use a ResourceContainer class
16 | REQUEST_CONTAINER = endpoints.ResourceContainer(
17 | message_types.VoidMessage,
18 | name=messages.StringField(1),
19 | )
20 |
21 | REQUEST_GREETING_CONTAINER = endpoints.ResourceContainer(
22 | period=messages.StringField(1),
23 | name=messages.StringField(2),
24 | )
25 |
26 | package = 'Hello'
27 |
28 |
29 | class Hello(messages.Message):
30 | """String that stores a message."""
31 | greeting = messages.StringField(1)
32 |
33 |
34 | @endpoints.api(name='helloworldendpoints', version='v1')
35 | class HelloWorldApi(remote.Service):
36 | """Helloworld API v1."""
37 |
38 | @endpoints.method(message_types.VoidMessage, Hello,
39 | path = "sayHello", http_method='GET', name = "sayHello")
40 | def say_hello(self, request):
41 | return Hello(greeting="Hello World")
42 |
43 | @endpoints.method(REQUEST_CONTAINER, Hello,
44 | path = "sayHelloByName", http_method='GET', name = "sayHelloByName")
45 | def say_hello_by_name(self, request):
46 | greet = "Hello {}".format(request.name)
47 | return Hello(greeting=greet)
48 |
49 | @endpoints.method(REQUEST_GREETING_CONTAINER, Hello,
50 | path = "greetByPeriod", http_method='GET', name = "greetByPeriod")
51 | def greet_by_period(self, request):
52 | greet = "Good {} {}".format(request.period, request.name)
53 | return Hello(greeting=greet)
54 |
55 |
56 | APPLICATION = endpoints.api_server([HelloWorldApi])
57 |
--------------------------------------------------------------------------------
/Lesson_2/001_Hello_Endpoints_Solutions/static/hello.js:
--------------------------------------------------------------------------------
1 | /*
2 | * http://stackoverflow.com/questions/18260815/use-gapi-client-javascript-to-execute-my-custom-google-api
3 | * https://developers.google.com/appengine/docs/java/endpoints/consume_js
4 | * https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiclientload
5 | *
6 | */
7 |
8 | /**
9 | * After the client library has loaded, this init() function is called.
10 | * The init() function loads the helloworldendpoints API.
11 | */
12 |
13 | function init() {
14 |
15 | // You need to pass the root path when you load your API
16 | // otherwise calls to execute the API run into a problem
17 |
18 | // rootpath will evaulate to either of these, depending on where the app is running:
19 | // //localhost:8080/_ah/api
20 | // //your-app-id/_ah/api
21 |
22 | var rootpath = "//" + window.location.host + "/_ah/api";
23 |
24 | // Load the helloworldendpoints API
25 | // If loading completes successfully, call loadCallback function
26 | gapi.client.load('helloworldendpoints', 'v1', loadCallback, rootpath);
27 | }
28 |
29 | /*
30 | * When helloworldendpoints API has loaded, this callback is called.
31 | *
32 | * We need to wait until the helloworldendpoints API has loaded to
33 | * enable the actions for the buttons in index.html,
34 | * because the buttons call functions in the helloworldendpoints API
35 | */
36 | function loadCallback () {
37 | // Enable the button actions
38 | enableButtons ();
39 | }
40 |
41 | function enableButtons () {
42 | // Set the onclick action for the first button
43 | btn = document.getElementById("input_greet_generically");
44 | btn.onclick= function(){greetGenerically();};
45 |
46 | // Update the button label now that the button is active
47 | btn.value="Click me for a generic greeting";
48 |
49 | // Set the onclick action for the second button
50 | btn = document.getElementById("input_greet_by_name");
51 | btn.onclick=function(){greetByName();};
52 |
53 | // Update the button label now that the button is active
54 | btn.value="Click me for a personal greeting";
55 | }
56 |
57 | /*
58 | * Execute a request to the sayHello() endpoints function
59 | */
60 | function greetGenerically () {
61 | // Construct the request for the sayHello() function
62 | var request = gapi.client.helloworldendpoints.sayHello();
63 |
64 | // Execute the request.
65 | // On success, pass the response to sayHelloCallback()
66 | request.execute(sayHelloCallback);
67 | }
68 |
69 | /*
70 | * Execute a request to the sayHelloByName() endpoints function.
71 | * Illustrates calling an endpoints function that takes an argument.
72 | */
73 | function greetByName () {
74 | // Get the name from the name_field element
75 | var name = document.getElementById("name_field").value;
76 |
77 | // Call the sayHelloByName() function.
78 | // It takes one argument "name"
79 | // On success, pass the response to sayHelloCallback()
80 | var request = gapi.client.helloworldendpoints.sayHelloByName({'name': name});
81 | request.execute(sayHelloCallback);
82 | }
83 |
84 | // Process the JSON response
85 | // In this case, just show an alert dialog box
86 | // displaying the value of the message field in the response
87 | function sayHelloCallback (response) {
88 | alert(response.greeting);
89 | }
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/Lesson_2/001_Hello_Endpoints_Solutions/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hello World Endpoints
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | My First Endpoints App
17 |
18 |
20 |
21 | What is your name?
22 |
23 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/README.md:
--------------------------------------------------------------------------------
1 | App Engine application for the Udacity training course.
2 |
3 | ## Products
4 | - [App Engine][1]
5 |
6 | ## Language
7 | - [Python][2]
8 |
9 | ## APIs
10 | - [Google Cloud Endpoints][3]
11 |
12 | ## Setup Instructions
13 | 1. Update the value of `application` in `app.yaml` to the app ID you
14 | have registered in the App Engine admin console and would like to use to host
15 | your instance of this sample.
16 | 1. Update the values at the top of `settings.py` to
17 | reflect the respective client IDs you have registered in the
18 | [Developer Console][4].
19 | 1. Update the value of CLIENT_ID in `static/js/app.js` to the Web client ID
20 | 1. (Optional) Mark the configuration files as unchanged as follows:
21 | `$ git update-index --assume-unchanged app.yaml settings.py static/js/app.js`
22 | 1. Run the app with the devserver using `dev_appserver.py DIR`, and ensure it's running by visiting
23 | your local server's address (by default [localhost:8080][5].)
24 | 1. Generate your client library(ies) with [the endpoints tool][6].
25 | 1. Deploy your application.
26 |
27 |
28 | [1]: https://developers.google.com/appengine
29 | [2]: http://python.org
30 | [3]: https://developers.google.com/appengine/docs/python/endpoints/
31 | [4]: https://console.developers.google.com/
32 | [5]: https://localhost:8080/
33 | [6]: https://developers.google.com/appengine/docs/python/endpoints/endpoints_tool
34 |
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/app.yaml:
--------------------------------------------------------------------------------
1 | application: ud858-dev-playground
2 | version: 1
3 | runtime: python27
4 | api_version: 1
5 | threadsafe: yes
6 |
7 | handlers: # static then dynamic
8 |
9 | - url: /favicon\.ico
10 | static_files: favicon.ico
11 | upload: favicon\.ico
12 |
13 | - url: /js
14 | static_dir: static/js
15 |
16 | - url: /img
17 | static_dir: static/img
18 |
19 | - url: /css
20 | static_dir: static/bootstrap/css
21 |
22 | - url: /fonts
23 | static_dir: static/fonts
24 |
25 | - url: /partials
26 | static_dir: static/partials
27 |
28 | - url: /
29 | static_files: templates/index.html
30 | upload: templates/index\.html
31 | secure: always
32 |
33 | - url: /_ah/spi/.*
34 | script: conference.api
35 | secure: always
36 |
37 | libraries:
38 |
39 | - name: endpoints
40 | version: latest
41 |
42 | # pycrypto library used for OAuth2 (req'd for authenticated APIs)
43 | - name: pycrypto
44 | version: latest
45 |
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/conference.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """
4 | conference.py -- Udacity conference server-side Python App Engine API;
5 | uses Google Cloud Endpoints
6 |
7 | $Id: conference.py,v 1.25 2014/05/24 23:42:19 wesc Exp wesc $
8 |
9 | created by wesc on 2014 apr 21
10 |
11 | """
12 |
13 | __author__ = 'wesc+api@google.com (Wesley Chun)'
14 |
15 |
16 | from datetime import datetime
17 | import json
18 | import os
19 | import time
20 |
21 | import endpoints
22 | from protorpc import messages
23 | from protorpc import message_types
24 | from protorpc import remote
25 |
26 | from google.appengine.api import urlfetch
27 | from google.appengine.ext import ndb
28 |
29 | from models import Profile
30 | from models import ProfileMiniForm
31 | from models import ProfileForm
32 | from models import TeeShirtSize
33 |
34 | from settings import WEB_CLIENT_ID
35 |
36 | EMAIL_SCOPE = endpoints.EMAIL_SCOPE
37 | API_EXPLORER_CLIENT_ID = endpoints.API_EXPLORER_CLIENT_ID
38 |
39 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
40 |
41 | @endpoints.api( name='conference',
42 | version='v1',
43 | allowed_client_ids=[WEB_CLIENT_ID, API_EXPLORER_CLIENT_ID],
44 | scopes=[EMAIL_SCOPE])
45 | class ConferenceApi(remote.Service):
46 | """Conference API v0.1"""
47 |
48 | # - - - Profile objects - - - - - - - - - - - - - - - - - - -
49 |
50 | def _copyProfileToForm(self, prof):
51 | """Copy relevant fields from Profile to ProfileForm."""
52 | # copy relevant fields from Profile to ProfileForm
53 | pf = ProfileForm()
54 | for field in pf.all_fields():
55 | if hasattr(prof, field.name):
56 | # convert t-shirt string to Enum; just copy others
57 | if field.name == 'teeShirtSize':
58 | setattr(pf, field.name, getattr(TeeShirtSize, getattr(prof, field.name)))
59 | else:
60 | setattr(pf, field.name, getattr(prof, field.name))
61 | pf.check_initialized()
62 | return pf
63 |
64 |
65 | def _getProfileFromUser(self):
66 | """Return user Profile from datastore, creating new one if non-existent."""
67 | ## TODO 2
68 | ## step 1: make sure user is authed
69 | ## uncomment the following lines:
70 | # user = endpoints.get_current_user()
71 | # if not user:
72 | # raise endpoints.UnauthorizedException('Authorization required')
73 | profile = None
74 | ## step 2: create a new Profile from logged in user data
75 | ## you can use user.nickname() to get displayName
76 | ## and user.email() to get mainEmail
77 | if not profile:
78 | profile = Profile(
79 | userId = None,
80 | key = None,
81 | displayName = "Test",
82 | mainEmail= None,
83 | teeShirtSize = str(TeeShirtSize.NOT_SPECIFIED),
84 | )
85 |
86 | return profile # return Profile
87 |
88 |
89 | def _doProfile(self, save_request=None):
90 | """Get user Profile and return to user, possibly updating it first."""
91 | # get user Profile
92 | prof = self._getProfileFromUser()
93 |
94 | # if saveProfile(), process user-modifyable fields
95 | if save_request:
96 | for field in ('displayName', 'teeShirtSize'):
97 | if hasattr(save_request, field):
98 | val = getattr(save_request, field)
99 | if val:
100 | setattr(prof, field, str(val))
101 |
102 | # return ProfileForm
103 | return self._copyProfileToForm(prof)
104 |
105 |
106 | @endpoints.method(message_types.VoidMessage, ProfileForm,
107 | path='profile', http_method='GET', name='getProfile')
108 | def getProfile(self, request):
109 | """Return user profile."""
110 | return self._doProfile()
111 |
112 | # TODO 1
113 | # 1. change request class
114 | # 2. pass request to _doProfile function
115 | @endpoints.method(message_types.VoidMessage, ProfileForm,
116 | path='profile', http_method='POST', name='saveProfile')
117 | def saveProfile(self, request):
118 | """Update & return user profile."""
119 | return self._doProfile()
120 |
121 |
122 | # registers API
123 | api = endpoints.api_server([ConferenceApi])
124 |
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/models.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """models.py
4 |
5 | Udacity conference server-side Python App Engine data & ProtoRPC models
6 |
7 | $Id: models.py,v 1.1 2014/05/24 22:01:10 wesc Exp $
8 |
9 | created/forked from conferences.py by wesc on 2014 may 24
10 |
11 | """
12 |
13 | __author__ = 'wesc+api@google.com (Wesley Chun)'
14 |
15 | import httplib
16 | import endpoints
17 | from protorpc import messages
18 | from google.appengine.ext import ndb
19 |
20 |
21 | class Profile(ndb.Model):
22 | """Profile -- User profile object"""
23 | userId = ndb.StringProperty()
24 | displayName = ndb.StringProperty()
25 | mainEmail = ndb.StringProperty()
26 | teeShirtSize = ndb.StringProperty(default='NOT_SPECIFIED')
27 |
28 |
29 | class ProfileMiniForm(messages.Message):
30 | """ProfileMiniForm -- update Profile form message"""
31 | displayName = messages.StringField(1)
32 | teeShirtSize = messages.EnumField('TeeShirtSize', 2)
33 |
34 |
35 | class ProfileForm(messages.Message):
36 | """ProfileForm -- Profile outbound form message"""
37 | userId = messages.StringField(1)
38 | displayName = messages.StringField(2)
39 | mainEmail = messages.StringField(3)
40 | teeShirtSize = messages.EnumField('TeeShirtSize', 4)
41 |
42 |
43 | class TeeShirtSize(messages.Enum):
44 | """TeeShirtSize -- t-shirt size enumeration value"""
45 | NOT_SPECIFIED = 1
46 | XS_M = 2
47 | XS_W = 3
48 | S_M = 4
49 | S_W = 5
50 | M_M = 6
51 | M_W = 7
52 | L_M = 8
53 | L_W = 9
54 | XL_M = 10
55 | XL_W = 11
56 | XXL_M = 12
57 | XXL_W = 13
58 | XXXL_M = 14
59 | XXXL_W = 15
60 |
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/settings.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """settings.py
4 |
5 | Udacity conference server-side Python App Engine app user settings
6 |
7 | $Id$
8 |
9 | created/forked from conference.py by wesc on 2014 may 24
10 |
11 | """
12 |
13 | # Replace the following lines with client IDs obtained from the APIs
14 | # Console or Cloud Console.
15 | WEB_CLIENT_ID = 'replace with Web client ID'
16 |
17 |
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/static/bootstrap/css/main.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | height: 100%;
3 | }
4 |
5 | body {
6 | padding-top: 70px;
7 | }
8 |
9 | #signInLink, #signOutLink {
10 | cursor: pointer;
11 | }
12 |
13 | /* To disable the Google+ Sign In Button, but gapi.signin.render should be invoked to store the credential
14 | if that is stored in cookie. */
15 | #signInButton iframe {
16 | display: none;
17 | }
18 |
19 | .required {
20 | color: red;
21 | }
22 |
23 | .dismiss-messages {
24 | cursor: pointer;
25 | }
26 |
27 | /* In a small screen, make the alert message sticky to the top */
28 | @media (max-width: 768px) {
29 | #messages.alert, #rootMessages.alert {
30 | position: fixed;
31 | left: 0;
32 | right: 0;
33 | top: 65px;
34 | z-index: 1000;
35 | }
36 | }
37 |
38 | .form-group-condensed {
39 | margin-top: 0;
40 | margin-bottom: 5px;
41 | }
42 |
43 | .label-separated {
44 | margin-right: 8px;
45 | }
46 |
47 | .spinner {
48 | position: fixed;
49 | top: 70px;
50 | z-index: 9999;
51 | }
52 |
53 | #signInButton {
54 | cursor: pointer;
55 | vertical-align: middle;
56 | }
57 |
58 | #profile-container {
59 | float: right;
60 | font-size: 85%;
61 | }
62 |
63 | #profile img {
64 | max-height: 35px;
65 | width: auto;
66 | vertical-align: middle;
67 | }
68 |
69 | #show-conferences-tab {
70 | margin-bottom: 20px;
71 | }
72 |
73 | ul#filters {
74 | list-style: none;
75 | padding-left: 0px;
76 | font-size: 85%;
77 | }
78 |
79 | ul#filters span.glyphicon-remove {
80 | font-size: 80%;
81 | }
82 |
83 | ul#conferences-list {
84 | list-style: none;
85 | }
86 |
87 | .intro-header {
88 | padding-top: 50px;
89 | padding-bottom: 50px;
90 | color: #f8f8f8;
91 | text-shadow: black 0.1em 0.1em 0.2em;
92 | background: url(/img/meeting-room.jpg) no-repeat center center;
93 | background-size: cover;
94 | text-align: center;
95 | }
96 |
97 | .intro-message {
98 | position: relative;
99 | padding-top: 5%;
100 | padding-bottom: 5%;
101 | vertical-align: middle;
102 | }
103 |
104 | .section-a {
105 | padding: 50px 0;
106 | }
107 |
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/static/bootstrap/css/offcanvas.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Style tweaks
3 | * --------------------------------------------------
4 | */
5 | html,
6 | body {
7 | overflow-x: hidden; /* Prevent scroll on narrow devices */
8 | }
9 |
10 | footer {
11 | padding: 30px 0;
12 | }
13 |
14 | /*
15 | * Off Canvas
16 | * --------------------------------------------------
17 | */
18 | @media screen and (max-width: 767px) {
19 | .row-offcanvas {
20 | position: relative;
21 | -webkit-transition: all .25s ease-out;
22 | -moz-transition: all .25s ease-out;
23 | transition: all .25s ease-out;
24 | }
25 |
26 | .row-offcanvas-right {
27 | right: 0;
28 | }
29 |
30 | .row-offcanvas-left {
31 | left: 0;
32 | }
33 |
34 | .row-offcanvas-right
35 | .sidebar-offcanvas {
36 | right: -50%; /* 6 columns */
37 | }
38 |
39 | .row-offcanvas-left
40 | .sidebar-offcanvas {
41 | left: -50%; /* 6 columns */
42 | }
43 |
44 | .row-offcanvas-right.active {
45 | right: 50%; /* 6 columns */
46 | }
47 |
48 | .row-offcanvas-left.active {
49 | left: 50%; /* 6 columns */
50 | }
51 |
52 | .sidebar-offcanvas {
53 | position: absolute;
54 | top: 0;
55 | width: 50%; /* 6 columns */
56 | }
57 | }
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/static/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_2/00_Conference_Central/static/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/static/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_2/00_Conference_Central/static/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/static/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_2/00_Conference_Central/static/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/static/img/CloudPlatform_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_2/00_Conference_Central/static/img/CloudPlatform_logo.png
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/static/img/ajax-loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_2/00_Conference_Central/static/img/ajax-loader.gif
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/static/img/business1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_2/00_Conference_Central/static/img/business1.jpg
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/static/img/business2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_2/00_Conference_Central/static/img/business2.jpg
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/static/img/business3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_2/00_Conference_Central/static/img/business3.jpg
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_2/00_Conference_Central/static/img/favicon.ico
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/static/img/meeting-room.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_2/00_Conference_Central/static/img/meeting-room.jpg
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/static/js/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @ngdoc object
5 | * @name conferenceApp
6 | * @requires $routeProvider
7 | * @requires conferenceControllers
8 | * @requires ui.bootstrap
9 | *
10 | * @description
11 | * Root app, which routes and specifies the partial html and controller depending on the url requested.
12 | *
13 | */
14 | var app = angular.module('conferenceApp',
15 | ['conferenceControllers', 'ngRoute', 'ui.bootstrap']).
16 | config(['$routeProvider',
17 | function ($routeProvider) {
18 | $routeProvider.
19 | when('/conference', {
20 | templateUrl: '/partials/show_conferences.html',
21 | controller: 'ShowConferenceCtrl'
22 | }).
23 | when('/conference/create', {
24 | templateUrl: '/partials/create_conferences.html',
25 | controller: 'CreateConferenceCtrl'
26 | }).
27 | when('/conference/detail/:websafeConferenceKey', {
28 | templateUrl: '/partials/conference_detail.html',
29 | controller: 'ConferenceDetailCtrl'
30 | }).
31 | when('/profile', {
32 | templateUrl: '/partials/profile.html',
33 | controller: 'MyProfileCtrl'
34 | }).
35 | when('/', {
36 | templateUrl: '/partials/home.html'
37 | }).
38 | otherwise({
39 | redirectTo: '/'
40 | });
41 | }]);
42 |
43 | /**
44 | * @ngdoc filter
45 | * @name startFrom
46 | *
47 | * @description
48 | * A filter that extracts an array from the specific index.
49 | *
50 | */
51 | app.filter('startFrom', function () {
52 | /**
53 | * Extracts an array from the specific index.
54 | *
55 | * @param {Array} data
56 | * @param {Integer} start
57 | * @returns {Array|*}
58 | */
59 | var filter = function (data, start) {
60 | return data.slice(start);
61 | }
62 | return filter;
63 | });
64 |
65 |
66 | /**
67 | * @ngdoc constant
68 | * @name HTTP_ERRORS
69 | *
70 | * @description
71 | * Holds the constants that represent HTTP error codes.
72 | *
73 | */
74 | app.constant('HTTP_ERRORS', {
75 | 'UNAUTHORIZED': 401
76 | });
77 |
78 |
79 | /**
80 | * @ngdoc service
81 | * @name oauth2Provider
82 | *
83 | * @description
84 | * Service that holds the OAuth2 information shared across all the pages.
85 | *
86 | */
87 | app.factory('oauth2Provider', function ($modal) {
88 | var oauth2Provider = {
89 | CLIENT_ID: 'web-client-id',
90 | SCOPES: 'email profile',
91 | signedIn: false
92 | }
93 |
94 | /**
95 | * Calls the OAuth2 authentication method.
96 | */
97 | oauth2Provider.signIn = function (callback) {
98 | gapi.auth.signIn({
99 | 'clientid': oauth2Provider.CLIENT_ID,
100 | 'cookiepolicy': 'single_host_origin',
101 | 'accesstype': 'online',
102 | 'approveprompt': 'auto',
103 | 'scope': oauth2Provider.SCOPES,
104 | 'callback': callback
105 | });
106 | };
107 |
108 | /**
109 | * Logs out the user.
110 | */
111 | oauth2Provider.signOut = function () {
112 | gapi.auth.signOut();
113 | // Explicitly set the invalid access token in order to make the API calls fail.
114 | gapi.auth.setToken({access_token: ''})
115 | oauth2Provider.signedIn = false;
116 | };
117 |
118 | /**
119 | * Shows the modal with Google+ sign in button.
120 | *
121 | * @returns {*|Window}
122 | */
123 | oauth2Provider.showLoginModal = function() {
124 | var modalInstance = $modal.open({
125 | templateUrl: '/partials/login.modal.html',
126 | controller: 'OAuth2LoginModalCtrl'
127 | });
128 | return modalInstance;
129 | };
130 |
131 | return oauth2Provider;
132 | });
133 |
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/static/partials/conference_detail.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
{{conference.name}}
17 |
{{conference.description}}
18 |
19 | Registered/Open:
20 | {{conference.maxAttendees - conference.seatsAvailable}} / {{conference.maxAttendees}}
21 |
22 |
23 | Organizer:
24 | {{conference.organizerDisplayName}}
25 |
26 |
Register
28 |
Unregister
30 |
31 |
32 |
33 |
34 |
35 | City:
36 | {{conference.city}}
37 |
38 |
39 | Topics:
40 |
41 | {{topic}}
42 |
43 |
44 |
45 | Start Date:
46 | {{conference.startDate | date:'dd-MMMM-yyyy'}}
47 |
48 |
49 | End Date:
50 | {{conference.endDate | date:'dd-MMMM-yyyy'}}
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/static/partials/home.html:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 |
25 |
26 |
View conferences
27 |
28 |
View by city, topics, date, max attendees.
29 |
View conferences
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
Create new conferences
43 |
44 |
In 10 seconds or less.
45 |
Create a conference
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/static/partials/login.modal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Please sign in to complete this action.
4 |
5 |
8 |
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/static/partials/profile.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
My Profile
15 |
16 |
17 | Display Name
18 | Changed
20 |
22 |
23 |
24 |
25 | Tee shirt size
26 | Changed
28 |
31 |
32 |
33 |
34 | Update profile
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/Lesson_2/00_Conference_Central/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Conference Central
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
67 |
68 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/README.md:
--------------------------------------------------------------------------------
1 | App Engine application for the Udacity training course.
2 |
3 | ## Products
4 | - [App Engine][1]
5 |
6 | ## Language
7 | - [Python][2]
8 |
9 | ## APIs
10 | - [Google Cloud Endpoints][3]
11 |
12 | ## Setup Instructions
13 | 1. Update the value of `application` in `app.yaml` to the app ID you
14 | have registered in the App Engine admin console and would like to use to host
15 | your instance of this sample.
16 | 1. Update the values at the top of `settings.py` to
17 | reflect the respective client IDs you have registered in the
18 | [Developer Console][4].
19 | 1. Update the value of CLIENT_ID in `static/js/app.js` to the Web client ID
20 | 1. (Optional) Mark the configuration files as unchanged as follows:
21 | `$ git update-index --assume-unchanged app.yaml settings.py static/js/app.js`
22 | 1. Run the app with the devserver using `dev_appserver.py DIR`, and ensure it's running by visiting
23 | your local server's address (by default [localhost:8080][5].)
24 | 1. Generate your client library(ies) with [the endpoints tool][6].
25 | 1. Deploy your application.
26 |
27 |
28 | [1]: https://developers.google.com/appengine
29 | [2]: http://python.org
30 | [3]: https://developers.google.com/appengine/docs/python/endpoints/
31 | [4]: https://console.developers.google.com/
32 | [5]: https://localhost:8080/
33 | [6]: https://developers.google.com/appengine/docs/python/endpoints/endpoints_tool
34 |
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/app.yaml:
--------------------------------------------------------------------------------
1 | application: ud858-dev-playground
2 | version: 1
3 | runtime: python27
4 | api_version: 1
5 | threadsafe: yes
6 |
7 | handlers: # static then dynamic
8 |
9 | - url: /favicon\.ico
10 | static_files: favicon.ico
11 | upload: favicon\.ico
12 |
13 | - url: /js
14 | static_dir: static/js
15 |
16 | - url: /img
17 | static_dir: static/img
18 |
19 | - url: /css
20 | static_dir: static/bootstrap/css
21 |
22 | - url: /fonts
23 | static_dir: static/fonts
24 |
25 | - url: /partials
26 | static_dir: static/partials
27 |
28 | - url: /
29 | static_files: templates/index.html
30 | upload: templates/index\.html
31 | secure: always
32 |
33 | - url: /_ah/spi/.*
34 | script: conference.api
35 | secure: always
36 |
37 | libraries:
38 |
39 | - name: endpoints
40 | version: latest
41 |
42 | # pycrypto library used for OAuth2 (req'd for authenticated APIs)
43 | - name: pycrypto
44 | version: latest
45 |
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/conference.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """
4 | conference.py -- Udacity conference server-side Python App Engine API;
5 | uses Google Cloud Endpoints
6 |
7 | $Id: conference.py,v 1.25 2014/05/24 23:42:19 wesc Exp wesc $
8 |
9 | created by wesc on 2014 apr 21
10 |
11 | """
12 |
13 | __author__ = 'wesc+api@google.com (Wesley Chun)'
14 |
15 |
16 | from datetime import datetime
17 |
18 | import endpoints
19 | from protorpc import messages
20 | from protorpc import message_types
21 | from protorpc import remote
22 |
23 | from google.appengine.ext import ndb
24 |
25 | from models import Profile
26 | from models import ProfileMiniForm
27 | from models import ProfileForm
28 | from models import TeeShirtSize
29 |
30 | from settings import WEB_CLIENT_ID
31 |
32 | EMAIL_SCOPE = endpoints.EMAIL_SCOPE
33 | API_EXPLORER_CLIENT_ID = endpoints.API_EXPLORER_CLIENT_ID
34 |
35 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
36 |
37 |
38 | @endpoints.api( name='conference',
39 | version='v1',
40 | allowed_client_ids=[WEB_CLIENT_ID, API_EXPLORER_CLIENT_ID],
41 | scopes=[EMAIL_SCOPE])
42 | class ConferenceApi(remote.Service):
43 | """Conference API v0.1"""
44 |
45 | # - - - Profile objects - - - - - - - - - - - - - - - - - - -
46 |
47 | def _copyProfileToForm(self, prof):
48 | """Copy relevant fields from Profile to ProfileForm."""
49 | # copy relevant fields from Profile to ProfileForm
50 | pf = ProfileForm()
51 | for field in pf.all_fields():
52 | if hasattr(prof, field.name):
53 | # convert t-shirt string to Enum; just copy others
54 | if field.name == 'teeShirtSize':
55 | setattr(pf, field.name, getattr(TeeShirtSize, getattr(prof, field.name)))
56 | else:
57 | setattr(pf, field.name, getattr(prof, field.name))
58 | pf.check_initialized()
59 | return pf
60 |
61 |
62 | def _getProfileFromUser(self):
63 | """Return user Profile from datastore, creating new one if non-existent."""
64 | user = endpoints.get_current_user()
65 | if not user:
66 | raise endpoints.UnauthorizedException('Authorization required')
67 |
68 | # TODO 1
69 | # step 1. copy utils.py from additions folder to this folder
70 | # and import getUserId from it
71 | # step 2. get user id by calling getUserId(user)
72 | # step 3. create a new key of kind Profile from the id
73 |
74 | # TODO 3
75 | # get the entity from datastore by using get() on the key
76 | profile = None
77 | if not profile:
78 | profile = Profile(
79 | key = None, # TODO 1 step 4. replace with the key from step 3
80 | displayName = user.nickname(),
81 | mainEmail= user.email(),
82 | teeShirtSize = str(TeeShirtSize.NOT_SPECIFIED),
83 | )
84 | # TODO 2
85 | # save the profile to datastore
86 |
87 | return profile # return Profile
88 |
89 |
90 | def _doProfile(self, save_request=None):
91 | """Get user Profile and return to user, possibly updating it first."""
92 | # get user Profile
93 | prof = self._getProfileFromUser()
94 |
95 | # if saveProfile(), process user-modifyable fields
96 | if save_request:
97 | for field in ('displayName', 'teeShirtSize'):
98 | if hasattr(save_request, field):
99 | val = getattr(save_request, field)
100 | if val:
101 | setattr(prof, field, str(val))
102 | # TODO 4
103 | # put the modified profile to datastore
104 |
105 | # return ProfileForm
106 | return self._copyProfileToForm(prof)
107 |
108 |
109 | @endpoints.method(message_types.VoidMessage, ProfileForm,
110 | path='profile', http_method='GET', name='getProfile')
111 | def getProfile(self, request):
112 | """Return user profile."""
113 | return self._doProfile()
114 |
115 |
116 | @endpoints.method(ProfileMiniForm, ProfileForm,
117 | path='profile', http_method='POST', name='saveProfile')
118 | def saveProfile(self, request):
119 | """Update & return user profile."""
120 | return self._doProfile(request)
121 |
122 |
123 | # registers API
124 | api = endpoints.api_server([ConferenceApi])
125 |
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/models.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """models.py
4 |
5 | Udacity conference server-side Python App Engine data & ProtoRPC models
6 |
7 | $Id: models.py,v 1.1 2014/05/24 22:01:10 wesc Exp $
8 |
9 | created/forked from conferences.py by wesc on 2014 may 24
10 |
11 | """
12 |
13 | __author__ = 'wesc+api@google.com (Wesley Chun)'
14 |
15 | import httplib
16 | import endpoints
17 | from protorpc import messages
18 | from google.appengine.ext import ndb
19 |
20 |
21 | class Profile(ndb.Model):
22 | """Profile -- User profile object"""
23 | displayName = ndb.StringProperty()
24 | mainEmail = ndb.StringProperty()
25 | teeShirtSize = ndb.StringProperty(default='NOT_SPECIFIED')
26 |
27 |
28 | class ProfileMiniForm(messages.Message):
29 | """ProfileMiniForm -- update Profile form message"""
30 | displayName = messages.StringField(1)
31 | teeShirtSize = messages.EnumField('TeeShirtSize', 2)
32 |
33 |
34 | class ProfileForm(messages.Message):
35 | """ProfileForm -- Profile outbound form message"""
36 | displayName = messages.StringField(1)
37 | mainEmail = messages.StringField(2)
38 | teeShirtSize = messages.EnumField('TeeShirtSize', 3)
39 |
40 |
41 | class TeeShirtSize(messages.Enum):
42 | """TeeShirtSize -- t-shirt size enumeration value"""
43 | NOT_SPECIFIED = 1
44 | XS_M = 2
45 | XS_W = 3
46 | S_M = 4
47 | S_W = 5
48 | M_M = 6
49 | M_W = 7
50 | L_M = 8
51 | L_W = 9
52 | XL_M = 10
53 | XL_W = 11
54 | XXL_M = 12
55 | XXL_W = 13
56 | XXXL_M = 14
57 | XXXL_W = 15
58 |
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/settings.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """settings.py
4 |
5 | Udacity conference server-side Python App Engine app user settings
6 |
7 | $Id$
8 |
9 | created/forked from conference.py by wesc on 2014 may 24
10 |
11 | """
12 |
13 | # Replace the following lines with client IDs obtained from the APIs
14 | # Console or Cloud Console.
15 | WEB_CLIENT_ID = 'replace with Web client ID'
16 |
17 |
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/static/bootstrap/css/main.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | height: 100%;
3 | }
4 |
5 | body {
6 | padding-top: 70px;
7 | }
8 |
9 | #signInLink, #signOutLink {
10 | cursor: pointer;
11 | }
12 |
13 | /* To disable the Google+ Sign In Button, but gapi.signin.render should be invoked to store the credential
14 | if that is stored in cookie. */
15 | #signInButton iframe {
16 | display: none;
17 | }
18 |
19 | .required {
20 | color: red;
21 | }
22 |
23 | .dismiss-messages {
24 | cursor: pointer;
25 | }
26 |
27 | /* In a small screen, make the alert message sticky to the top */
28 | @media (max-width: 768px) {
29 | #messages.alert, #rootMessages.alert {
30 | position: fixed;
31 | left: 0;
32 | right: 0;
33 | top: 65px;
34 | z-index: 1000;
35 | }
36 | }
37 |
38 | .form-group-condensed {
39 | margin-top: 0;
40 | margin-bottom: 5px;
41 | }
42 |
43 | .label-separated {
44 | margin-right: 8px;
45 | }
46 |
47 | .spinner {
48 | position: fixed;
49 | top: 70px;
50 | z-index: 9999;
51 | }
52 |
53 | #signInButton {
54 | cursor: pointer;
55 | vertical-align: middle;
56 | }
57 |
58 | #profile-container {
59 | float: right;
60 | font-size: 85%;
61 | }
62 |
63 | #profile img {
64 | max-height: 35px;
65 | width: auto;
66 | vertical-align: middle;
67 | }
68 |
69 | #show-conferences-tab {
70 | margin-bottom: 20px;
71 | }
72 |
73 | ul#filters {
74 | list-style: none;
75 | padding-left: 0px;
76 | font-size: 85%;
77 | }
78 |
79 | ul#filters span.glyphicon-remove {
80 | font-size: 80%;
81 | }
82 |
83 | ul#conferences-list {
84 | list-style: none;
85 | }
86 |
87 | .intro-header {
88 | padding-top: 50px;
89 | padding-bottom: 50px;
90 | color: #f8f8f8;
91 | text-shadow: black 0.1em 0.1em 0.2em;
92 | background: url(/img/meeting-room.jpg) no-repeat center center;
93 | background-size: cover;
94 | text-align: center;
95 | }
96 |
97 | .intro-message {
98 | position: relative;
99 | padding-top: 5%;
100 | padding-bottom: 5%;
101 | vertical-align: middle;
102 | }
103 |
104 | .section-a {
105 | padding: 50px 0;
106 | }
107 |
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/static/bootstrap/css/offcanvas.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Style tweaks
3 | * --------------------------------------------------
4 | */
5 | html,
6 | body {
7 | overflow-x: hidden; /* Prevent scroll on narrow devices */
8 | }
9 |
10 | footer {
11 | padding: 30px 0;
12 | }
13 |
14 | /*
15 | * Off Canvas
16 | * --------------------------------------------------
17 | */
18 | @media screen and (max-width: 767px) {
19 | .row-offcanvas {
20 | position: relative;
21 | -webkit-transition: all .25s ease-out;
22 | -moz-transition: all .25s ease-out;
23 | transition: all .25s ease-out;
24 | }
25 |
26 | .row-offcanvas-right {
27 | right: 0;
28 | }
29 |
30 | .row-offcanvas-left {
31 | left: 0;
32 | }
33 |
34 | .row-offcanvas-right
35 | .sidebar-offcanvas {
36 | right: -50%; /* 6 columns */
37 | }
38 |
39 | .row-offcanvas-left
40 | .sidebar-offcanvas {
41 | left: -50%; /* 6 columns */
42 | }
43 |
44 | .row-offcanvas-right.active {
45 | right: 50%; /* 6 columns */
46 | }
47 |
48 | .row-offcanvas-left.active {
49 | left: 50%; /* 6 columns */
50 | }
51 |
52 | .sidebar-offcanvas {
53 | position: absolute;
54 | top: 0;
55 | width: 50%; /* 6 columns */
56 | }
57 | }
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/static/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_3/00_Conference_Central/static/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/static/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_3/00_Conference_Central/static/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/static/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_3/00_Conference_Central/static/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/static/img/CloudPlatform_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_3/00_Conference_Central/static/img/CloudPlatform_logo.png
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/static/img/ajax-loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_3/00_Conference_Central/static/img/ajax-loader.gif
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/static/img/business1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_3/00_Conference_Central/static/img/business1.jpg
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/static/img/business2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_3/00_Conference_Central/static/img/business2.jpg
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/static/img/business3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_3/00_Conference_Central/static/img/business3.jpg
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_3/00_Conference_Central/static/img/favicon.ico
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/static/img/meeting-room.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_3/00_Conference_Central/static/img/meeting-room.jpg
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/static/js/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @ngdoc object
5 | * @name conferenceApp
6 | * @requires $routeProvider
7 | * @requires conferenceControllers
8 | * @requires ui.bootstrap
9 | *
10 | * @description
11 | * Root app, which routes and specifies the partial html and controller depending on the url requested.
12 | *
13 | */
14 | var app = angular.module('conferenceApp',
15 | ['conferenceControllers', 'ngRoute', 'ui.bootstrap']).
16 | config(['$routeProvider',
17 | function ($routeProvider) {
18 | $routeProvider.
19 | when('/conference', {
20 | templateUrl: '/partials/show_conferences.html',
21 | controller: 'ShowConferenceCtrl'
22 | }).
23 | when('/conference/create', {
24 | templateUrl: '/partials/create_conferences.html',
25 | controller: 'CreateConferenceCtrl'
26 | }).
27 | when('/conference/detail/:websafeConferenceKey', {
28 | templateUrl: '/partials/conference_detail.html',
29 | controller: 'ConferenceDetailCtrl'
30 | }).
31 | when('/profile', {
32 | templateUrl: '/partials/profile.html',
33 | controller: 'MyProfileCtrl'
34 | }).
35 | when('/', {
36 | templateUrl: '/partials/home.html'
37 | }).
38 | otherwise({
39 | redirectTo: '/'
40 | });
41 | }]);
42 |
43 | /**
44 | * @ngdoc filter
45 | * @name startFrom
46 | *
47 | * @description
48 | * A filter that extracts an array from the specific index.
49 | *
50 | */
51 | app.filter('startFrom', function () {
52 | /**
53 | * Extracts an array from the specific index.
54 | *
55 | * @param {Array} data
56 | * @param {Integer} start
57 | * @returns {Array|*}
58 | */
59 | var filter = function (data, start) {
60 | return data.slice(start);
61 | }
62 | return filter;
63 | });
64 |
65 |
66 | /**
67 | * @ngdoc constant
68 | * @name HTTP_ERRORS
69 | *
70 | * @description
71 | * Holds the constants that represent HTTP error codes.
72 | *
73 | */
74 | app.constant('HTTP_ERRORS', {
75 | 'UNAUTHORIZED': 401
76 | });
77 |
78 |
79 | /**
80 | * @ngdoc service
81 | * @name oauth2Provider
82 | *
83 | * @description
84 | * Service that holds the OAuth2 information shared across all the pages.
85 | *
86 | */
87 | app.factory('oauth2Provider', function ($modal) {
88 | var oauth2Provider = {
89 | CLIENT_ID: 'web-client-id',
90 | SCOPES: 'email profile',
91 | signedIn: false
92 | }
93 |
94 | /**
95 | * Calls the OAuth2 authentication method.
96 | */
97 | oauth2Provider.signIn = function (callback) {
98 | gapi.auth.signIn({
99 | 'clientid': oauth2Provider.CLIENT_ID,
100 | 'cookiepolicy': 'single_host_origin',
101 | 'accesstype': 'online',
102 | 'approveprompt': 'auto',
103 | 'scope': oauth2Provider.SCOPES,
104 | 'callback': callback
105 | });
106 | };
107 |
108 | /**
109 | * Logs out the user.
110 | */
111 | oauth2Provider.signOut = function () {
112 | gapi.auth.signOut();
113 | // Explicitly set the invalid access token in order to make the API calls fail.
114 | gapi.auth.setToken({access_token: ''})
115 | oauth2Provider.signedIn = false;
116 | };
117 |
118 | /**
119 | * Shows the modal with Google+ sign in button.
120 | *
121 | * @returns {*|Window}
122 | */
123 | oauth2Provider.showLoginModal = function() {
124 | var modalInstance = $modal.open({
125 | templateUrl: '/partials/login.modal.html',
126 | controller: 'OAuth2LoginModalCtrl'
127 | });
128 | return modalInstance;
129 | };
130 |
131 | return oauth2Provider;
132 | });
133 |
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/static/partials/conference_detail.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
{{conference.name}}
17 |
{{conference.description}}
18 |
19 | Registered/Open:
20 | {{conference.maxAttendees - conference.seatsAvailable}} / {{conference.maxAttendees}}
21 |
22 |
23 | Organizer:
24 | {{conference.organizerDisplayName}}
25 |
26 |
Register
28 |
Unregister
30 |
31 |
32 |
33 |
34 |
35 | City:
36 | {{conference.city}}
37 |
38 |
39 | Topics:
40 |
41 | {{topic}}
42 |
43 |
44 |
45 | Start Date:
46 | {{conference.startDate | date:'dd-MMMM-yyyy'}}
47 |
48 |
49 | End Date:
50 | {{conference.endDate | date:'dd-MMMM-yyyy'}}
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/static/partials/home.html:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 |
25 |
26 |
View conferences
27 |
28 |
View by city, topics, date, max attendees.
29 |
View conferences
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
Create new conferences
43 |
44 |
In 10 seconds or less.
45 |
Create a conference
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/static/partials/login.modal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Please sign in to complete this action.
4 |
5 |
8 |
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/static/partials/profile.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
My Profile
15 |
16 |
17 | Display Name
18 | Changed
20 |
22 |
23 |
24 |
25 | Tee shirt size
26 | Changed
28 |
31 |
32 |
33 |
34 | Update profile
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/Lesson_3/00_Conference_Central/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Conference Central
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
67 |
68 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/Lesson_3/additions/utils.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import time
4 | import uuid
5 |
6 | from google.appengine.api import urlfetch
7 | from models import Profile
8 |
9 | def getUserId(user, id_type="email"):
10 | if id_type == "email":
11 | return user.email()
12 |
13 | if id_type == "oauth":
14 | """A workaround implementation for getting userid."""
15 | auth = os.getenv('HTTP_AUTHORIZATION')
16 | bearer, token = auth.split()
17 | token_type = 'id_token'
18 | if 'OAUTH_USER_ID' in os.environ:
19 | token_type = 'access_token'
20 | url = ('https://www.googleapis.com/oauth2/v1/tokeninfo?%s=%s'
21 | % (token_type, token))
22 | user = {}
23 | wait = 1
24 | for i in range(3):
25 | resp = urlfetch.fetch(url)
26 | if resp.status_code == 200:
27 | user = json.loads(resp.content)
28 | break
29 | elif resp.status_code == 400 and 'invalid_token' in resp.content:
30 | url = ('https://www.googleapis.com/oauth2/v1/tokeninfo?%s=%s'
31 | % ('access_token', token))
32 | else:
33 | time.sleep(wait)
34 | wait = wait + i
35 | return user.get('user_id', '')
36 |
37 | if id_type == "custom":
38 | # implement your own user_id creation and getting algorythm
39 | # this is just a sample that queries datastore for an existing profile
40 | # and generates an id if profile does not exist for an email
41 | profile = Conference.query(Conference.mainEmail == user.email())
42 | if profile:
43 | return profile.id()
44 | else:
45 | return str(uuid.uuid1().get_hex())
46 |
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/README.md:
--------------------------------------------------------------------------------
1 | App Engine application for the Udacity training course.
2 |
3 | ## Products
4 | - [App Engine][1]
5 |
6 | ## Language
7 | - [Python][2]
8 |
9 | ## APIs
10 | - [Google Cloud Endpoints][3]
11 |
12 | ## Setup Instructions
13 | 1. Update the value of `application` in `app.yaml` to the app ID you
14 | have registered in the App Engine admin console and would like to use to host
15 | your instance of this sample.
16 | 1. Update the values at the top of `settings.py` to
17 | reflect the respective client IDs you have registered in the
18 | [Developer Console][4].
19 | 1. Update the value of CLIENT_ID in `static/js/app.js` to the Web client ID
20 | 1. (Optional) Mark the configuration files as unchanged as follows:
21 | `$ git update-index --assume-unchanged app.yaml settings.py static/js/app.js`
22 | 1. Run the app with the devserver using `dev_appserver.py DIR`, and ensure it's running by visiting
23 | your local server's address (by default [localhost:8080][5].)
24 | 1. Generate your client library(ies) with [the endpoints tool][6].
25 | 1. Deploy your application.
26 |
27 |
28 | [1]: https://developers.google.com/appengine
29 | [2]: http://python.org
30 | [3]: https://developers.google.com/appengine/docs/python/endpoints/
31 | [4]: https://console.developers.google.com/
32 | [5]: https://localhost:8080/
33 | [6]: https://developers.google.com/appengine/docs/python/endpoints/endpoints_tool
34 |
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/app.yaml:
--------------------------------------------------------------------------------
1 | application: ud858-dev-playground
2 | version: 1
3 | runtime: python27
4 | api_version: 1
5 | threadsafe: yes
6 |
7 | handlers: # static then dynamic
8 |
9 | - url: /favicon\.ico
10 | static_files: favicon.ico
11 | upload: favicon\.ico
12 |
13 | - url: /js
14 | static_dir: static/js
15 |
16 | - url: /img
17 | static_dir: static/img
18 |
19 | - url: /css
20 | static_dir: static/bootstrap/css
21 |
22 | - url: /fonts
23 | static_dir: static/fonts
24 |
25 | - url: /partials
26 | static_dir: static/partials
27 |
28 | - url: /
29 | static_files: templates/index.html
30 | upload: templates/index\.html
31 | secure: always
32 |
33 | - url: /_ah/spi/.*
34 | script: conference.api
35 | secure: always
36 |
37 | libraries:
38 |
39 | - name: endpoints
40 | version: latest
41 |
42 | # pycrypto library used for OAuth2 (req'd for authenticated APIs)
43 | - name: pycrypto
44 | version: latest
45 |
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/conference.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """
4 | conference.py -- Udacity conference server-side Python App Engine API;
5 | uses Google Cloud Endpoints
6 |
7 | $Id: conference.py,v 1.25 2014/05/24 23:42:19 wesc Exp wesc $
8 |
9 | created by wesc on 2014 apr 21
10 |
11 | """
12 |
13 | __author__ = 'wesc+api@google.com (Wesley Chun)'
14 |
15 |
16 | from datetime import datetime
17 |
18 | import endpoints
19 | from protorpc import messages
20 | from protorpc import message_types
21 | from protorpc import remote
22 |
23 | from google.appengine.ext import ndb
24 |
25 | from models import Profile
26 | from models import ProfileMiniForm
27 | from models import ProfileForm
28 | from models import TeeShirtSize
29 |
30 | from utils import getUserId
31 |
32 | from settings import WEB_CLIENT_ID
33 |
34 | EMAIL_SCOPE = endpoints.EMAIL_SCOPE
35 | API_EXPLORER_CLIENT_ID = endpoints.API_EXPLORER_CLIENT_ID
36 |
37 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
38 |
39 |
40 | @endpoints.api( name='conference',
41 | version='v1',
42 | allowed_client_ids=[WEB_CLIENT_ID, API_EXPLORER_CLIENT_ID],
43 | scopes=[EMAIL_SCOPE])
44 | class ConferenceApi(remote.Service):
45 | """Conference API v0.1"""
46 |
47 | # - - - Profile objects - - - - - - - - - - - - - - - - - - -
48 |
49 | def _copyProfileToForm(self, prof):
50 | """Copy relevant fields from Profile to ProfileForm."""
51 | # copy relevant fields from Profile to ProfileForm
52 | pf = ProfileForm()
53 | for field in pf.all_fields():
54 | if hasattr(prof, field.name):
55 | # convert t-shirt string to Enum; just copy others
56 | if field.name == 'teeShirtSize':
57 | setattr(pf, field.name, getattr(TeeShirtSize, getattr(prof, field.name)))
58 | else:
59 | setattr(pf, field.name, getattr(prof, field.name))
60 | pf.check_initialized()
61 | return pf
62 |
63 |
64 | def _getProfileFromUser(self):
65 | """Return user Profile from datastore, creating new one if non-existent."""
66 | user = endpoints.get_current_user()
67 | if not user:
68 | raise endpoints.UnauthorizedException('Authorization required')
69 |
70 | # get Profile from datastore
71 | user_id = getUserId(user)
72 | p_key = ndb.Key(Profile, user_id)
73 | profile = p_key.get()
74 | # create new Profile if not there
75 | if not profile:
76 | profile = Profile(
77 | key = p_key,
78 | displayName = user.nickname(),
79 | mainEmail= user.email(),
80 | teeShirtSize = str(TeeShirtSize.NOT_SPECIFIED),
81 | )
82 | profile.put()
83 |
84 | return profile # return Profile
85 |
86 |
87 | def _doProfile(self, save_request=None):
88 | """Get user Profile and return to user, possibly updating it first."""
89 | # get user Profile
90 | prof = self._getProfileFromUser()
91 |
92 | # if saveProfile(), process user-modifyable fields
93 | if save_request:
94 | for field in ('displayName', 'teeShirtSize'):
95 | if hasattr(save_request, field):
96 | val = getattr(save_request, field)
97 | if val:
98 | setattr(prof, field, str(val))
99 | prof.put()
100 |
101 | # return ProfileForm
102 | return self._copyProfileToForm(prof)
103 |
104 |
105 | @endpoints.method(message_types.VoidMessage, ProfileForm,
106 | path='profile', http_method='GET', name='getProfile')
107 | def getProfile(self, request):
108 | """Return user profile."""
109 | return self._doProfile()
110 |
111 |
112 | @endpoints.method(ProfileMiniForm, ProfileForm,
113 | path='profile', http_method='POST', name='saveProfile')
114 | def saveProfile(self, request):
115 | """Update & return user profile."""
116 | return self._doProfile(request)
117 |
118 |
119 | # - - - Conference objects - - - - - - - - - - - - - - - - - - -
120 |
121 | # TODO
122 |
123 | # registers API
124 | api = endpoints.api_server([ConferenceApi])
125 |
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/models.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """models.py
4 |
5 | Udacity conference server-side Python App Engine data & ProtoRPC models
6 |
7 | $Id: models.py,v 1.1 2014/05/24 22:01:10 wesc Exp $
8 |
9 | created/forked from conferences.py by wesc on 2014 may 24
10 |
11 | """
12 |
13 | __author__ = 'wesc+api@google.com (Wesley Chun)'
14 |
15 | import httplib
16 | import endpoints
17 | from protorpc import messages
18 | from google.appengine.ext import ndb
19 |
20 |
21 | class Profile(ndb.Model):
22 | """Profile -- User profile object"""
23 | displayName = ndb.StringProperty()
24 | mainEmail = ndb.StringProperty()
25 | teeShirtSize = ndb.StringProperty(default='NOT_SPECIFIED')
26 |
27 |
28 | class ProfileMiniForm(messages.Message):
29 | """ProfileMiniForm -- update Profile form message"""
30 | displayName = messages.StringField(1)
31 | teeShirtSize = messages.EnumField('TeeShirtSize', 2)
32 |
33 |
34 | class ProfileForm(messages.Message):
35 | """ProfileForm -- Profile outbound form message"""
36 | displayName = messages.StringField(1)
37 | mainEmail = messages.StringField(2)
38 | teeShirtSize = messages.EnumField('TeeShirtSize', 3)
39 |
40 |
41 | class TeeShirtSize(messages.Enum):
42 | """TeeShirtSize -- t-shirt size enumeration value"""
43 | NOT_SPECIFIED = 1
44 | XS_M = 2
45 | XS_W = 3
46 | S_M = 4
47 | S_W = 5
48 | M_M = 6
49 | M_W = 7
50 | L_M = 8
51 | L_W = 9
52 | XL_M = 10
53 | XL_W = 11
54 | XXL_M = 12
55 | XXL_W = 13
56 | XXXL_M = 14
57 | XXXL_W = 15
58 |
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/settings.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """settings.py
4 |
5 | Udacity conference server-side Python App Engine app user settings
6 |
7 | $Id$
8 |
9 | created/forked from conference.py by wesc on 2014 may 24
10 |
11 | """
12 |
13 | # Replace the following lines with client IDs obtained from the APIs
14 | # Console or Cloud Console.
15 | WEB_CLIENT_ID = 'replace with Web client ID'
16 |
17 |
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/static/bootstrap/css/main.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | height: 100%;
3 | }
4 |
5 | body {
6 | padding-top: 70px;
7 | }
8 |
9 | #signInLink, #signOutLink {
10 | cursor: pointer;
11 | }
12 |
13 | /* To disable the Google+ Sign In Button, but gapi.signin.render should be invoked to store the credential
14 | if that is stored in cookie. */
15 | #signInButton iframe {
16 | display: none;
17 | }
18 |
19 | .required {
20 | color: red;
21 | }
22 |
23 | .dismiss-messages {
24 | cursor: pointer;
25 | }
26 |
27 | /* In a small screen, make the alert message sticky to the top */
28 | @media (max-width: 768px) {
29 | #messages.alert, #rootMessages.alert {
30 | position: fixed;
31 | left: 0;
32 | right: 0;
33 | top: 65px;
34 | z-index: 1000;
35 | }
36 | }
37 |
38 | .form-group-condensed {
39 | margin-top: 0;
40 | margin-bottom: 5px;
41 | }
42 |
43 | .label-separated {
44 | margin-right: 8px;
45 | }
46 |
47 | .spinner {
48 | position: fixed;
49 | top: 70px;
50 | z-index: 9999;
51 | }
52 |
53 | #signInButton {
54 | cursor: pointer;
55 | vertical-align: middle;
56 | }
57 |
58 | #profile-container {
59 | float: right;
60 | font-size: 85%;
61 | }
62 |
63 | #profile img {
64 | max-height: 35px;
65 | width: auto;
66 | vertical-align: middle;
67 | }
68 |
69 | #show-conferences-tab {
70 | margin-bottom: 20px;
71 | }
72 |
73 | ul#filters {
74 | list-style: none;
75 | padding-left: 0px;
76 | font-size: 85%;
77 | }
78 |
79 | ul#filters span.glyphicon-remove {
80 | font-size: 80%;
81 | }
82 |
83 | ul#conferences-list {
84 | list-style: none;
85 | }
86 |
87 | .intro-header {
88 | padding-top: 50px;
89 | padding-bottom: 50px;
90 | color: #f8f8f8;
91 | text-shadow: black 0.1em 0.1em 0.2em;
92 | background: url(/img/meeting-room.jpg) no-repeat center center;
93 | background-size: cover;
94 | text-align: center;
95 | }
96 |
97 | .intro-message {
98 | position: relative;
99 | padding-top: 5%;
100 | padding-bottom: 5%;
101 | vertical-align: middle;
102 | }
103 |
104 | .section-a {
105 | padding: 50px 0;
106 | }
107 |
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/static/bootstrap/css/offcanvas.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Style tweaks
3 | * --------------------------------------------------
4 | */
5 | html,
6 | body {
7 | overflow-x: hidden; /* Prevent scroll on narrow devices */
8 | }
9 |
10 | footer {
11 | padding: 30px 0;
12 | }
13 |
14 | /*
15 | * Off Canvas
16 | * --------------------------------------------------
17 | */
18 | @media screen and (max-width: 767px) {
19 | .row-offcanvas {
20 | position: relative;
21 | -webkit-transition: all .25s ease-out;
22 | -moz-transition: all .25s ease-out;
23 | transition: all .25s ease-out;
24 | }
25 |
26 | .row-offcanvas-right {
27 | right: 0;
28 | }
29 |
30 | .row-offcanvas-left {
31 | left: 0;
32 | }
33 |
34 | .row-offcanvas-right
35 | .sidebar-offcanvas {
36 | right: -50%; /* 6 columns */
37 | }
38 |
39 | .row-offcanvas-left
40 | .sidebar-offcanvas {
41 | left: -50%; /* 6 columns */
42 | }
43 |
44 | .row-offcanvas-right.active {
45 | right: 50%; /* 6 columns */
46 | }
47 |
48 | .row-offcanvas-left.active {
49 | left: 50%; /* 6 columns */
50 | }
51 |
52 | .sidebar-offcanvas {
53 | position: absolute;
54 | top: 0;
55 | width: 50%; /* 6 columns */
56 | }
57 | }
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/static/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_4/00_Conference_Central/static/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/static/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_4/00_Conference_Central/static/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/static/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_4/00_Conference_Central/static/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/static/img/CloudPlatform_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_4/00_Conference_Central/static/img/CloudPlatform_logo.png
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/static/img/ajax-loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_4/00_Conference_Central/static/img/ajax-loader.gif
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/static/img/business1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_4/00_Conference_Central/static/img/business1.jpg
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/static/img/business2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_4/00_Conference_Central/static/img/business2.jpg
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/static/img/business3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_4/00_Conference_Central/static/img/business3.jpg
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_4/00_Conference_Central/static/img/favicon.ico
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/static/img/meeting-room.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_4/00_Conference_Central/static/img/meeting-room.jpg
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/static/js/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @ngdoc object
5 | * @name conferenceApp
6 | * @requires $routeProvider
7 | * @requires conferenceControllers
8 | * @requires ui.bootstrap
9 | *
10 | * @description
11 | * Root app, which routes and specifies the partial html and controller depending on the url requested.
12 | *
13 | */
14 | var app = angular.module('conferenceApp',
15 | ['conferenceControllers', 'ngRoute', 'ui.bootstrap']).
16 | config(['$routeProvider',
17 | function ($routeProvider) {
18 | $routeProvider.
19 | when('/conference', {
20 | templateUrl: '/partials/show_conferences.html',
21 | controller: 'ShowConferenceCtrl'
22 | }).
23 | when('/conference/create', {
24 | templateUrl: '/partials/create_conferences.html',
25 | controller: 'CreateConferenceCtrl'
26 | }).
27 | when('/conference/detail/:websafeConferenceKey', {
28 | templateUrl: '/partials/conference_detail.html',
29 | controller: 'ConferenceDetailCtrl'
30 | }).
31 | when('/profile', {
32 | templateUrl: '/partials/profile.html',
33 | controller: 'MyProfileCtrl'
34 | }).
35 | when('/', {
36 | templateUrl: '/partials/home.html'
37 | }).
38 | otherwise({
39 | redirectTo: '/'
40 | });
41 | }]);
42 |
43 | /**
44 | * @ngdoc filter
45 | * @name startFrom
46 | *
47 | * @description
48 | * A filter that extracts an array from the specific index.
49 | *
50 | */
51 | app.filter('startFrom', function () {
52 | /**
53 | * Extracts an array from the specific index.
54 | *
55 | * @param {Array} data
56 | * @param {Integer} start
57 | * @returns {Array|*}
58 | */
59 | var filter = function (data, start) {
60 | return data.slice(start);
61 | }
62 | return filter;
63 | });
64 |
65 |
66 | /**
67 | * @ngdoc constant
68 | * @name HTTP_ERRORS
69 | *
70 | * @description
71 | * Holds the constants that represent HTTP error codes.
72 | *
73 | */
74 | app.constant('HTTP_ERRORS', {
75 | 'UNAUTHORIZED': 401
76 | });
77 |
78 |
79 | /**
80 | * @ngdoc service
81 | * @name oauth2Provider
82 | *
83 | * @description
84 | * Service that holds the OAuth2 information shared across all the pages.
85 | *
86 | */
87 | app.factory('oauth2Provider', function ($modal) {
88 | var oauth2Provider = {
89 | CLIENT_ID: 'web-client-id',
90 | SCOPES: 'email profile',
91 | signedIn: false
92 | }
93 |
94 | /**
95 | * Calls the OAuth2 authentication method.
96 | */
97 | oauth2Provider.signIn = function (callback) {
98 | gapi.auth.signIn({
99 | 'clientid': oauth2Provider.CLIENT_ID,
100 | 'cookiepolicy': 'single_host_origin',
101 | 'accesstype': 'online',
102 | 'approveprompt': 'auto',
103 | 'scope': oauth2Provider.SCOPES,
104 | 'callback': callback
105 | });
106 | };
107 |
108 | /**
109 | * Logs out the user.
110 | */
111 | oauth2Provider.signOut = function () {
112 | gapi.auth.signOut();
113 | // Explicitly set the invalid access token in order to make the API calls fail.
114 | gapi.auth.setToken({access_token: ''})
115 | oauth2Provider.signedIn = false;
116 | };
117 |
118 | /**
119 | * Shows the modal with Google+ sign in button.
120 | *
121 | * @returns {*|Window}
122 | */
123 | oauth2Provider.showLoginModal = function() {
124 | var modalInstance = $modal.open({
125 | templateUrl: '/partials/login.modal.html',
126 | controller: 'OAuth2LoginModalCtrl'
127 | });
128 | return modalInstance;
129 | };
130 |
131 | return oauth2Provider;
132 | });
133 |
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/static/partials/conference_detail.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
{{conference.name}}
17 |
{{conference.description}}
18 |
19 | Registered/Open:
20 | {{conference.maxAttendees - conference.seatsAvailable}} / {{conference.maxAttendees}}
21 |
22 |
23 | Organizer:
24 | {{conference.organizerDisplayName}}
25 |
26 |
Register
28 |
Unregister
30 |
31 |
32 |
33 |
34 |
35 | City:
36 | {{conference.city}}
37 |
38 |
39 | Topics:
40 |
41 | {{topic}}
42 |
43 |
44 |
45 | Start Date:
46 | {{conference.startDate | date:'dd-MMMM-yyyy'}}
47 |
48 |
49 | End Date:
50 | {{conference.endDate | date:'dd-MMMM-yyyy'}}
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/static/partials/home.html:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 |
25 |
26 |
View conferences
27 |
28 |
View by city, topics, date, max attendees.
29 |
View conferences
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
Create new conferences
43 |
44 |
In 10 seconds or less.
45 |
Create a conference
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/static/partials/login.modal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Please sign in to complete this action.
4 |
5 |
8 |
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/static/partials/profile.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
My Profile
15 |
16 |
17 | Display Name
18 | Changed
20 |
22 |
23 |
24 |
25 | Tee shirt size
26 | Changed
28 |
31 |
32 |
33 |
34 | Update profile
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Conference Central
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
67 |
68 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/Lesson_4/00_Conference_Central/utils.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import time
4 | import uuid
5 |
6 | from google.appengine.api import urlfetch
7 | from models import Profile
8 |
9 | def getUserId(user, id_type="email"):
10 | if id_type == "email":
11 | return user.email()
12 |
13 | if id_type == "oauth":
14 | """A workaround implementation for getting userid."""
15 | auth = os.getenv('HTTP_AUTHORIZATION')
16 | bearer, token = auth.split()
17 | token_type = 'id_token'
18 | if 'OAUTH_USER_ID' in os.environ:
19 | token_type = 'access_token'
20 | url = ('https://www.googleapis.com/oauth2/v1/tokeninfo?%s=%s'
21 | % (token_type, token))
22 | user = {}
23 | wait = 1
24 | for i in range(3):
25 | resp = urlfetch.fetch(url)
26 | if resp.status_code == 200:
27 | user = json.loads(resp.content)
28 | break
29 | elif resp.status_code == 400 and 'invalid_token' in resp.content:
30 | url = ('https://www.googleapis.com/oauth2/v1/tokeninfo?%s=%s'
31 | % ('access_token', token))
32 | else:
33 | time.sleep(wait)
34 | wait = wait + i
35 | return user.get('user_id', '')
36 |
37 | if id_type == "custom":
38 | # implement your own user_id creation and getting algorythm
39 | # this is just a sample that queries datastore for an existing profile
40 | # and generates an id if profile does not exist for an email
41 | profile = Conference.query(Conference.mainEmail == user.email())
42 | if profile:
43 | return profile.id()
44 | else:
45 | return str(uuid.uuid1().get_hex())
46 |
--------------------------------------------------------------------------------
/Lesson_4/Additions/TODO_1_conference.py:
--------------------------------------------------------------------------------
1 | from models import Conference
2 | from models import ConferenceForm
3 |
4 |
5 | DEFAULTS = {
6 | "city": "Default City",
7 | "maxAttendees": 0,
8 | "seatsAvailable": 0,
9 | "topics": [ "Default", "Topic" ],
10 | }
11 |
12 |
13 | # - - - Conference objects - - - - - - - - - - - - - - - - -
14 |
15 | def _copyConferenceToForm(self, conf, displayName):
16 | """Copy relevant fields from Conference to ConferenceForm."""
17 | cf = ConferenceForm()
18 | for field in cf.all_fields():
19 | if hasattr(conf, field.name):
20 | # convert Date to date string; just copy others
21 | if field.name.endswith('Date'):
22 | setattr(cf, field.name, str(getattr(conf, field.name)))
23 | else:
24 | setattr(cf, field.name, getattr(conf, field.name))
25 | elif field.name == "websafeKey":
26 | setattr(cf, field.name, conf.key.urlsafe())
27 | if displayName:
28 | setattr(cf, 'organizerDisplayName', displayName)
29 | cf.check_initialized()
30 | return cf
31 |
32 |
33 | def _createConferenceObject(self, request):
34 | """Create or update Conference object, returning ConferenceForm/request."""
35 | # preload necessary data items
36 | user = endpoints.get_current_user()
37 | if not user:
38 | raise endpoints.UnauthorizedException('Authorization required')
39 | user_id = getUserId(user)
40 |
41 | if not request.name:
42 | raise endpoints.BadRequestException("Conference 'name' field required")
43 |
44 | # copy ConferenceForm/ProtoRPC Message into dict
45 | data = {field.name: getattr(request, field.name) for field in request.all_fields()}
46 | del data['websafeKey']
47 | del data['organizerDisplayName']
48 |
49 | # add default values for those missing (both data model & outbound Message)
50 | for df in DEFAULTS:
51 | if data[df] in (None, []):
52 | data[df] = DEFAULTS[df]
53 | setattr(request, df, DEFAULTS[df])
54 |
55 | # convert dates from strings to Date objects; set month based on start_date
56 | if data['startDate']:
57 | data['startDate'] = datetime.strptime(data['startDate'][:10], "%Y-%m-%d").date()
58 | data['month'] = data['startDate'].month
59 | else:
60 | data['month'] = 0
61 | if data['endDate']:
62 | data['endDate'] = datetime.strptime(data['endDate'][:10], "%Y-%m-%d").date()
63 |
64 | # set seatsAvailable to be same as maxAttendees on creation
65 | # both for data model & outbound Message
66 | if data["maxAttendees"] > 0:
67 | data["seatsAvailable"] = data["maxAttendees"]
68 | setattr(request, "seatsAvailable", data["maxAttendees"])
69 |
70 | # make Profile Key from user ID
71 | p_key = ndb.Key(Profile, user_id)
72 | # allocate new Conference ID with Profile key as parent
73 | c_id = Conference.allocate_ids(size=1, parent=p_key)[0]
74 | # make Conference key from ID
75 | c_key = ndb.Key(Conference, c_id, parent=p_key)
76 | data['key'] = c_key
77 | data['organizerUserId'] = request.organizerUserId = user_id
78 |
79 | # create Conference & return (modified) ConferenceForm
80 | Conference(**data).put()
81 |
82 | return request
83 |
84 |
85 | @endpoints.method(ConferenceForm, ConferenceForm, path='conference',
86 | http_method='POST', name='createConference')
87 | def createConference(self, request):
88 | """Create new conference."""
89 | return self._createConferenceObject(request)
90 |
--------------------------------------------------------------------------------
/Lesson_4/Additions/TODO_1_models.py:
--------------------------------------------------------------------------------
1 | class Conference(ndb.Model):
2 | """Conference -- Conference object"""
3 | name = ndb.StringProperty(required=True)
4 | description = ndb.StringProperty()
5 | organizerUserId = ndb.StringProperty()
6 | topics = ndb.StringProperty(repeated=True)
7 | city = ndb.StringProperty()
8 | startDate = ndb.DateProperty()
9 | month = ndb.IntegerProperty()
10 | endDate = ndb.DateProperty()
11 | maxAttendees = ndb.IntegerProperty()
12 | seatsAvailable = ndb.IntegerProperty()
13 |
14 | class ConferenceForm(messages.Message):
15 | """ConferenceForm -- Conference outbound form message"""
16 | name = messages.StringField(1)
17 | description = messages.StringField(2)
18 | organizerUserId = messages.StringField(3)
19 | topics = messages.StringField(4, repeated=True)
20 | city = messages.StringField(5)
21 | startDate = messages.StringField(6)
22 | month = messages.IntegerField(7, variant=messages.Variant.INT32)
23 | maxAttendees = messages.IntegerField(8, variant=messages.Variant.INT32)
24 | seatsAvailable = messages.IntegerField(9, variant=messages.Variant.INT32)
25 | endDate = messages.StringField(10)
26 | websafeKey = messages.StringField(11)
27 | organizerDisplayName = messages.StringField(12)
--------------------------------------------------------------------------------
/Lesson_4/Additions/TODO_2_conference.py:
--------------------------------------------------------------------------------
1 | from models import ConferenceForms
2 | from models import ConferenceQueryForm
3 | from models import ConferenceQueryForms
4 |
5 |
6 | @endpoints.method(ConferenceQueryForms, ConferenceForms,
7 | path='queryConferences',
8 | http_method='POST',
9 | name='queryConferences')
10 | def queryConferences(self, request):
11 | """Query for conferences."""
12 | conferences = Conference.query()
13 |
14 | # return individual ConferenceForm object per Conference
15 | return ConferenceForms(
16 | items=[self._copyConferenceToForm(conf, "") \
17 | for conf in conferences]
18 | )
19 |
--------------------------------------------------------------------------------
/Lesson_4/Additions/TODO_2_models.py:
--------------------------------------------------------------------------------
1 | class ConferenceForms(messages.Message):
2 | """ConferenceForms -- multiple Conference outbound form message"""
3 | items = messages.MessageField(ConferenceForm, 1, repeated=True)
4 |
5 | class ConferenceQueryForm(messages.Message):
6 | """ConferenceQueryForm -- Conference query inbound form message"""
7 | field = messages.StringField(1)
8 | operator = messages.StringField(2)
9 | value = messages.StringField(3)
10 |
11 | class ConferenceQueryForms(messages.Message):
12 | """ConferenceQueryForms -- multiple ConferenceQueryForm inbound form message"""
13 | filters = messages.MessageField(ConferenceQueryForm, 1, repeated=True)
14 |
--------------------------------------------------------------------------------
/Lesson_4/Additions/TODO_3_conference.py:
--------------------------------------------------------------------------------
1 | @endpoints.method(message_types.VoidMessage, ConferenceForms,
2 | path='getConferencesCreated',
3 | http_method='POST', name='getConferencesCreated')
4 | def getConferencesCreated(self, request):
5 | """Return conferences created by user."""
6 | # make sure user is authed
7 | user = endpoints.get_current_user()
8 | if not user:
9 | raise endpoints.UnauthorizedException('Authorization required')
10 |
11 | # make profile key
12 | p_key = ndb.Key(Profile, getUserId(user))
13 | # create ancestor query for this user
14 | conferences = Conference.query(ancestor=p_key)
15 | # get the user profile and display name
16 | prof = p_key.get()
17 | displayName = getattr(prof, 'displayName')
18 | # return set of ConferenceForm objects per Conference
19 | return ConferenceForms(
20 | items=[self._copyConferenceToForm(conf, displayName) for conf in conferences]
21 | )
22 |
23 |
24 | @endpoints.method(CONF_GET_REQUEST, ConferenceForm,
25 | path='conference/{websafeConferenceKey}',
26 | http_method='GET', name='getConference')
27 | def getConference(self, request):
28 | """Return requested conference (by websafeConferenceKey)."""
29 | # get Conference object from request; bail if not found
30 | conf = ndb.Key(urlsafe=request.websafeConferenceKey).get()
31 | if not conf:
32 | raise endpoints.NotFoundException(
33 | 'No conference found with key: %s' % request.websafeConferenceKey)
34 | prof = conf.key.parent().get()
35 | # return ConferenceForm
36 | return self._copyConferenceToForm(conf, getattr(prof, 'displayName'))
37 |
38 |
--------------------------------------------------------------------------------
/Lesson_4/Additions/TODO_4_conference.py:
--------------------------------------------------------------------------------
1 | @endpoints.method(message_types.VoidMessage, ConferenceForms,
2 | path='filterPlayground',
3 | http_method='GET', name='filterPlayground')
4 | def filterPlayground(self, request):
5 | q = Conference.query()
6 | # simple filter usage:
7 | # q = q.filter(Conference.city == "Paris")
8 |
9 | # advanced filter building and usage
10 | # field = "city"
11 | # operator = "="
12 | # value = "London"
13 | # f = ndb.query.FilterNode(field, operator, value)
14 | # q = q.filter(f)
15 |
16 | # TODO
17 | # add 2 filters:
18 | # 1: city equals to London
19 | # 2: topic equals "Medical Innovations"
20 |
21 | return ConferenceForms(
22 | items=[self._copyConferenceToForm(conf, "") for conf in q]
23 | )
--------------------------------------------------------------------------------
/Lesson_4/Additions/TODO_5_conference.py:
--------------------------------------------------------------------------------
1 | OPERATORS = {
2 | 'EQ': '=',
3 | 'GT': '>',
4 | 'GTEQ': '>=',
5 | 'LT': '<',
6 | 'LTEQ': '<=',
7 | 'NE': '!='
8 | }
9 |
10 | FIELDS = {
11 | 'CITY': 'city',
12 | 'TOPIC': 'topics',
13 | 'MONTH': 'month',
14 | 'MAX_ATTENDEES': 'maxAttendees',
15 | }
16 |
17 |
18 | def _getQuery(self, request):
19 | """Return formatted query from the submitted filters."""
20 | q = Conference.query()
21 | inequality_filter, filters = self._formatFilters(request.filters)
22 |
23 | # If exists, sort on inequality filter first
24 | if not inequality_filter:
25 | q = q.order(Conference.name)
26 | else:
27 | q = q.order(ndb.GenericProperty(inequality_filter))
28 | q = q.order(Conference.name)
29 |
30 | for filtr in filters:
31 | if filtr["field"] in ["month", "maxAttendees"]:
32 | filtr["value"] = int(filtr["value"])
33 | formatted_query = ndb.query.FilterNode(filtr["field"], filtr["operator"], filtr["value"])
34 | q = q.filter(formatted_query)
35 | return q
36 |
37 |
38 | def _formatFilters(self, filters):
39 | """Parse, check validity and format user supplied filters."""
40 | formatted_filters = []
41 | inequality_field = None
42 |
43 | for f in filters:
44 | filtr = {field.name: getattr(f, field.name) for field in f.all_fields()}
45 |
46 | try:
47 | filtr["field"] = FIELDS[filtr["field"]]
48 | filtr["operator"] = OPERATORS[filtr["operator"]]
49 | except KeyError:
50 | raise endpoints.BadRequestException("Filter contains invalid field or operator.")
51 |
52 | # Every operation except "=" is an inequality
53 | if filtr["operator"] != "=":
54 | # check if inequality operation has been used in previous filters
55 | # disallow the filter if inequality was performed on a different field before
56 | # track the field on which the inequality operation is performed
57 | if inequality_field and inequality_field != filtr["field"]:
58 | raise endpoints.BadRequestException("Inequality filter is allowed on only one field.")
59 | else:
60 | inequality_field = filtr["field"]
61 |
62 | formatted_filters.append(filtr)
63 | return (inequality_field, formatted_filters)
64 |
--------------------------------------------------------------------------------
/Lesson_4/Additions/TODO_6_conference.py:
--------------------------------------------------------------------------------
1 | from models import BooleanMessage
2 | from models import ConflictException
3 |
4 | CONF_GET_REQUEST = endpoints.ResourceContainer(
5 | message_types.VoidMessage,
6 | websafeConferenceKey=messages.StringField(1),
7 | )
8 |
9 | # - - - Registration - - - - - - - - - - - - - - - - - - - -
10 |
11 | def _conferenceRegistration(self, request, reg=True):
12 | """Register or unregister user for selected conference."""
13 | retval = None
14 | prof = self._getProfileFromUser() # get user Profile
15 |
16 | # check if conf exists given websafeConfKey
17 | # get conference; check that it exists
18 | wsck = request.websafeConferenceKey
19 | conf = ndb.Key(urlsafe=wsck).get()
20 | if not conf:
21 | raise endpoints.NotFoundException(
22 | 'No conference found with key: %s' % wsck)
23 |
24 | # register
25 | if reg:
26 | # check if user already registered otherwise add
27 | if wsck in prof.conferenceKeysToAttend:
28 | raise ConflictException(
29 | "You have already registered for this conference")
30 |
31 | # check if seats avail
32 | if conf.seatsAvailable <= 0:
33 | raise ConflictException(
34 | "There are no seats available.")
35 |
36 | # register user, take away one seat
37 | prof.conferenceKeysToAttend.append(wsck)
38 | conf.seatsAvailable -= 1
39 | retval = True
40 |
41 | # unregister
42 | else:
43 | # check if user already registered
44 | if wsck in prof.conferenceKeysToAttend:
45 |
46 | # unregister user, add back one seat
47 | prof.conferenceKeysToAttend.remove(wsck)
48 | conf.seatsAvailable += 1
49 | retval = True
50 | else:
51 | retval = False
52 |
53 | # write things back to the datastore & return
54 | prof.put()
55 | conf.put()
56 | return BooleanMessage(data=retval)
57 |
58 |
59 | @endpoints.method(CONF_GET_REQUEST, BooleanMessage,
60 | path='conference/{websafeConferenceKey}',
61 | http_method='POST', name='registerForConference')
62 | def registerForConference(self, request):
63 | """Register user for selected conference."""
64 | return self._conferenceRegistration(request)
65 |
--------------------------------------------------------------------------------
/Lesson_4/Additions/TODO_6_models.py:
--------------------------------------------------------------------------------
1 | # replace your existing Profile class with this
2 | class Profile(ndb.Model):
3 | """Profile -- User profile object"""
4 | displayName = ndb.StringProperty()
5 | mainEmail = ndb.StringProperty()
6 | teeShirtSize = ndb.StringProperty(default='NOT_SPECIFIED')
7 | conferenceKeysToAttend = ndb.StringProperty(repeated=True)
8 |
9 | # needed for conference registration
10 | class BooleanMessage(messages.Message):
11 | """BooleanMessage-- outbound Boolean value message"""
12 | data = messages.BooleanField(1)
13 |
14 | class ConflictException(endpoints.ServiceException):
15 | """ConflictException -- exception mapped to HTTP 409 response"""
16 | http_status = httplib.CONFLICT
--------------------------------------------------------------------------------
/Lesson_4/Additions/TODO_7_conference.py:
--------------------------------------------------------------------------------
1 | @endpoints.method(message_types.VoidMessage, ConferenceForms,
2 | path='conferences/attending',
3 | http_method='GET', name='getConferencesToAttend')
4 | def getConferencesToAttend(self, request):
5 | """Get list of conferences that user has registered for."""
6 | # TODO:
7 | # step 1: get user profile
8 | # step 2: get conferenceKeysToAttend from profile.
9 | # to make a ndb key from websafe key you can use:
10 | # ndb.Key(urlsafe=my_websafe_key_string)
11 | # step 3: fetch conferences from datastore.
12 | # Use get_multi(array_of_keys) to fetch all keys at once.
13 | # Do not fetch them one by one!
14 |
15 | # return set of ConferenceForm objects per Conference
16 | return ConferenceForms(items=[self._copyConferenceToForm(conf, "")\
17 | for conf in conferences]
18 | )
--------------------------------------------------------------------------------
/Lesson_4/Additions/conference_models.partial.txt:
--------------------------------------------------------------------------------
1 | class Conference(ndb.Model):
2 | """Conference -- Conference object"""
3 | name = ndb.StringProperty(required=True)
4 | description = ndb.StringProperty()
5 | organizerUserId = ndb.StringProperty()
6 | topics = ndb.StringProperty(repeated=True)
7 | city = ndb.StringProperty()
8 | startDate = ndb.DateProperty()
9 | month = ndb.IntegerProperty()
10 | endDate = ndb.DateProperty()
11 | maxAttendees = ndb.IntegerProperty()
12 | seatsAvailable = ndb.IntegerProperty()
13 |
14 | class ConferenceForm(messages.Message):
15 | """ConferenceForm -- Conference outbound form message"""
16 | name = messages.StringField(1)
17 | description = messages.StringField(2)
18 | organizerUserId = messages.StringField(3)
19 | topics = messages.StringField(4, repeated=True)
20 | city = messages.StringField(5)
21 | startDate = messages.StringField(6) #DateTimeField()
22 | month = messages.IntegerField(7)
23 | maxAttendees = messages.IntegerField(8)
24 | seatsAvailable = messages.IntegerField(9)
25 | endDate = messages.StringField(10) #DateTimeField()
26 | websafeKey = messages.StringField(11)
27 | organizerDisplayName = messages.StringField(12)
28 |
29 | class ConferenceForms(messages.Message):
30 | """ConferenceForms -- multiple Conference outbound form message"""
31 | items = messages.MessageField(ConferenceForm, 1, repeated=True)
32 |
33 | class ConferenceQueryForm(messages.Message):
34 | """ConferenceQueryForm -- Conference query inbound form message"""
35 | field = messages.StringField(1)
36 | operator = messages.StringField(2)
37 | value = messages.StringField(3)
38 |
39 | class ConferenceQueryForms(messages.Message):
40 | """ConferenceQueryForms -- multiple ConferenceQueryForm inbound form message"""
41 | filters = messages.MessageField(ConferenceQueryForm, 1, repeated=True)
42 |
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/README.md:
--------------------------------------------------------------------------------
1 | App Engine application for the Udacity training course.
2 |
3 | ## Products
4 | - [App Engine][1]
5 |
6 | ## Language
7 | - [Python][2]
8 |
9 | ## APIs
10 | - [Google Cloud Endpoints][3]
11 |
12 | ## Setup Instructions
13 | 1. Update the value of `application` in `app.yaml` to the app ID you
14 | have registered in the App Engine admin console and would like to use to host
15 | your instance of this sample.
16 | 1. Update the values at the top of `settings.py` to
17 | reflect the respective client IDs you have registered in the
18 | [Developer Console][4].
19 | 1. Update the value of CLIENT_ID in `static/js/app.js` to the Web client ID
20 | 1. (Optional) Mark the configuration files as unchanged as follows:
21 | `$ git update-index --assume-unchanged app.yaml settings.py static/js/app.js`
22 | 1. Run the app with the devserver using `dev_appserver.py DIR`, and ensure it's running by visiting your local server's address (by default [localhost:8080][5].)
23 | 1. (Optional) Generate your client library(ies) with [the endpoints tool][6].
24 | 1. Deploy your application.
25 |
26 |
27 | [1]: https://developers.google.com/appengine
28 | [2]: http://python.org
29 | [3]: https://developers.google.com/appengine/docs/python/endpoints/
30 | [4]: https://console.developers.google.com/
31 | [5]: https://localhost:8080/
32 | [6]: https://developers.google.com/appengine/docs/python/endpoints/endpoints_tool
33 |
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/app.yaml:
--------------------------------------------------------------------------------
1 | application: ud858-final-python
2 | version: 1
3 | runtime: python27
4 | api_version: 1
5 | threadsafe: yes
6 |
7 | handlers: # static then dynamic
8 |
9 | - url: /favicon\.ico
10 | static_files: favicon.ico
11 | upload: favicon\.ico
12 |
13 | - url: /js
14 | static_dir: static/js
15 |
16 | - url: /img
17 | static_dir: static/img
18 |
19 | - url: /css
20 | static_dir: static/bootstrap/css
21 |
22 | - url: /fonts
23 | static_dir: static/fonts
24 |
25 | - url: /partials
26 | static_dir: static/partials
27 |
28 | - url: /
29 | static_files: templates/index.html
30 | upload: templates/index\.html
31 | secure: always
32 |
33 | - url: /_ah/spi/.*
34 | script: conference.api
35 | secure: always
36 |
37 | libraries:
38 |
39 | - name: webapp2
40 | version: latest
41 |
42 | - name: endpoints
43 | version: latest
44 |
45 | # pycrypto library used for OAuth2 (req'd for authenticated APIs)
46 | - name: pycrypto
47 | version: latest
48 |
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/index.yaml:
--------------------------------------------------------------------------------
1 | indexes:
2 |
3 | # AUTOGENERATED
4 |
5 | # This index.yaml is automatically updated whenever the dev_appserver
6 | # detects that a new type of query is run. If you want to manage the
7 | # index.yaml file manually, remove the above marker line (the line
8 | # saying "# AUTOGENERATED"). If you want to manage some indexes
9 | # manually, move them above the marker line. The index.yaml file is
10 | # automatically uploaded to the admin console when you next deploy
11 | # your application using appcfg.py.
12 |
13 | - kind: Conference
14 | properties:
15 | - name: city
16 | - name: maxAttendees
17 | - name: month
18 | - name: name
19 |
20 | - kind: Conference
21 | properties:
22 | - name: city
23 | - name: maxAttendees
24 | - name: month
25 | - name: topics
26 | - name: name
27 |
28 | - kind: Conference
29 | properties:
30 | - name: city
31 | - name: maxAttendees
32 | - name: name
33 |
34 | - kind: Conference
35 | properties:
36 | - name: city
37 | - name: month
38 | - name: name
39 |
40 | - kind: Conference
41 | properties:
42 | - name: city
43 | - name: month
44 | - name: topics
45 | - name: name
46 |
47 | - kind: Conference
48 | properties:
49 | - name: city
50 | - name: name
51 |
52 | - kind: Conference
53 | properties:
54 | - name: city
55 | - name: topics
56 | - name: name
57 |
58 | - kind: Conference
59 | properties:
60 | - name: maxAttendees
61 | - name: month
62 | - name: name
63 |
64 | - kind: Conference
65 | properties:
66 | - name: maxAttendees
67 | - name: month
68 | - name: topics
69 | - name: name
70 |
71 | - kind: Conference
72 | properties:
73 | - name: maxAttendees
74 | - name: name
75 |
76 | - kind: Conference
77 | properties:
78 | - name: maxAttendees
79 | - name: topics
80 | - name: name
81 |
82 | - kind: Conference
83 | properties:
84 | - name: month
85 | - name: name
86 |
87 | - kind: Conference
88 | properties:
89 | - name: month
90 | - name: topics
91 | - name: name
92 |
93 | - kind: Conference
94 | properties:
95 | - name: topics
96 | - name: name
97 |
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """
4 | main.py -- Udacity conference server-side Python App Engine
5 | HTTP controller handlers for memcache & task queue access
6 |
7 | $Id$
8 |
9 | created by wesc on 2014 may 24
10 |
11 | """
12 |
13 | __author__ = 'wesc+api@google.com (Wesley Chun)'
14 |
15 | import webapp2
16 | from google.appengine.api import app_identity
17 | from google.appengine.api import mail
18 | from conference import ConferenceApi
19 |
20 | class SetAnnouncementHandler(webapp2.RequestHandler):
21 | def get(self):
22 | """Set Announcement in Memcache."""
23 | # TODO 1
24 |
25 |
26 | class SendConfirmationEmailHandler(webapp2.RequestHandler):
27 | def post(self):
28 | """Send email confirming Conference creation."""
29 | mail.send_mail(
30 | 'noreply@%s.appspotmail.com' % (
31 | app_identity.get_application_id()), # from
32 | self.request.get('email'), # to
33 | 'You created a new Conference!', # subj
34 | 'Hi, you have created a following ' # body
35 | 'conference:\r\n\r\n%s' % self.request.get(
36 | 'conferenceInfo')
37 | )
38 |
39 |
40 | app = webapp2.WSGIApplication([
41 | ('/crons/set_announcement', SetAnnouncementHandler),
42 | ('/tasks/send_confirmation_email', SendConfirmationEmailHandler),
43 | ], debug=True)
44 |
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/models.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """models.py
4 |
5 | Udacity conference server-side Python App Engine data & ProtoRPC models
6 |
7 | $Id: models.py,v 1.1 2014/05/24 22:01:10 wesc Exp $
8 |
9 | created/forked from conferences.py by wesc on 2014 may 24
10 |
11 | """
12 |
13 | __author__ = 'wesc+api@google.com (Wesley Chun)'
14 |
15 | import httplib
16 | import endpoints
17 | from protorpc import messages
18 | from google.appengine.ext import ndb
19 |
20 | class ConflictException(endpoints.ServiceException):
21 | """ConflictException -- exception mapped to HTTP 409 response"""
22 | http_status = httplib.CONFLICT
23 |
24 | class Profile(ndb.Model):
25 | """Profile -- User profile object"""
26 | displayName = ndb.StringProperty()
27 | mainEmail = ndb.StringProperty()
28 | teeShirtSize = ndb.StringProperty(default='NOT_SPECIFIED')
29 | conferenceKeysToAttend = ndb.StringProperty(repeated=True)
30 |
31 | class ProfileMiniForm(messages.Message):
32 | """ProfileMiniForm -- update Profile form message"""
33 | displayName = messages.StringField(1)
34 | teeShirtSize = messages.EnumField('TeeShirtSize', 2)
35 |
36 | class ProfileForm(messages.Message):
37 | """ProfileForm -- Profile outbound form message"""
38 | displayName = messages.StringField(1)
39 | mainEmail = messages.StringField(2)
40 | teeShirtSize = messages.EnumField('TeeShirtSize', 3)
41 | conferenceKeysToAttend = messages.StringField(4, repeated=True)
42 |
43 | class BooleanMessage(messages.Message):
44 | """BooleanMessage-- outbound Boolean value message"""
45 | data = messages.BooleanField(1)
46 |
47 | class Conference(ndb.Model):
48 | """Conference -- Conference object"""
49 | name = ndb.StringProperty(required=True)
50 | description = ndb.StringProperty()
51 | organizerUserId = ndb.StringProperty()
52 | topics = ndb.StringProperty(repeated=True)
53 | city = ndb.StringProperty()
54 | startDate = ndb.DateProperty()
55 | month = ndb.IntegerProperty() # TODO: do we need for indexing like Java?
56 | endDate = ndb.DateProperty()
57 | maxAttendees = ndb.IntegerProperty()
58 | seatsAvailable = ndb.IntegerProperty()
59 |
60 | class ConferenceForm(messages.Message):
61 | """ConferenceForm -- Conference outbound form message"""
62 | name = messages.StringField(1)
63 | description = messages.StringField(2)
64 | organizerUserId = messages.StringField(3)
65 | topics = messages.StringField(4, repeated=True)
66 | city = messages.StringField(5)
67 | startDate = messages.StringField(6) #DateTimeField()
68 | month = messages.IntegerField(7, variant=messages.Variant.INT32)
69 | maxAttendees = messages.IntegerField(8, variant=messages.Variant.INT32)
70 | seatsAvailable = messages.IntegerField(9, variant=messages.Variant.INT32)
71 | endDate = messages.StringField(10) #DateTimeField()
72 | websafeKey = messages.StringField(11)
73 | organizerDisplayName = messages.StringField(12)
74 |
75 | class ConferenceForms(messages.Message):
76 | """ConferenceForms -- multiple Conference outbound form message"""
77 | items = messages.MessageField(ConferenceForm, 1, repeated=True)
78 |
79 | class TeeShirtSize(messages.Enum):
80 | """TeeShirtSize -- t-shirt size enumeration value"""
81 | NOT_SPECIFIED = 1
82 | XS_M = 2
83 | XS_W = 3
84 | S_M = 4
85 | S_W = 5
86 | M_M = 6
87 | M_W = 7
88 | L_M = 8
89 | L_W = 9
90 | XL_M = 10
91 | XL_W = 11
92 | XXL_M = 12
93 | XXL_W = 13
94 | XXXL_M = 14
95 | XXXL_W = 15
96 |
97 | class ConferenceQueryForm(messages.Message):
98 | """ConferenceQueryForm -- Conference query inbound form message"""
99 | field = messages.StringField(1)
100 | operator = messages.StringField(2)
101 | value = messages.StringField(3)
102 |
103 | class ConferenceQueryForms(messages.Message):
104 | """ConferenceQueryForms -- multiple ConferenceQueryForm inbound form message"""
105 | filters = messages.MessageField(ConferenceQueryForm, 1, repeated=True)
106 |
107 |
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/settings.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | """settings.py
4 |
5 | Udacity conference server-side Python App Engine app user settings
6 |
7 | $Id$
8 |
9 | created/forked from conference.py by wesc on 2014 may 24
10 |
11 | """
12 |
13 | # Replace the following lines with client IDs obtained from the APIs
14 | # Console or Cloud Console.
15 | WEB_CLIENT_ID = 'replace with Web client ID'
16 | ANDROID_CLIENT_ID = 'replace with Android client ID'
17 | IOS_CLIENT_ID = 'replace with iOS client ID'
18 | ANDROID_AUDIENCE = WEB_CLIENT_ID
19 |
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/static/bootstrap/css/main.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | height: 100%;
3 | }
4 |
5 | body {
6 | padding-top: 70px;
7 | }
8 |
9 | #signInLink, #signOutLink {
10 | cursor: pointer;
11 | }
12 |
13 | /* To disable the Google+ Sign In Button, but gapi.signin.render should be invoked to store the credential
14 | if that is stored in cookie. */
15 | #signInButton iframe {
16 | display: none;
17 | }
18 |
19 | .required {
20 | color: red;
21 | }
22 |
23 | .dismiss-messages {
24 | cursor: pointer;
25 | }
26 |
27 | /* In a small screen, make the alert message sticky to the top */
28 | @media (max-width: 768px) {
29 | #messages.alert, #rootMessages.alert {
30 | position: fixed;
31 | left: 0;
32 | right: 0;
33 | top: 65px;
34 | z-index: 1000;
35 | }
36 | }
37 |
38 | .form-group-condensed {
39 | margin-top: 0;
40 | margin-bottom: 5px;
41 | }
42 |
43 | .label-separated {
44 | margin-right: 8px;
45 | }
46 |
47 | .spinner {
48 | position: fixed;
49 | top: 70px;
50 | z-index: 9999;
51 | }
52 |
53 | #signInButton {
54 | cursor: pointer;
55 | vertical-align: middle;
56 | }
57 |
58 | #profile-container {
59 | float: right;
60 | font-size: 85%;
61 | }
62 |
63 | #profile img {
64 | max-height: 35px;
65 | width: auto;
66 | vertical-align: middle;
67 | }
68 |
69 | #show-conferences-tab {
70 | margin-bottom: 20px;
71 | }
72 |
73 | ul#filters {
74 | list-style: none;
75 | padding-left: 0px;
76 | font-size: 85%;
77 | }
78 |
79 | ul#filters span.glyphicon-remove {
80 | font-size: 80%;
81 | }
82 |
83 | ul#conferences-list {
84 | list-style: none;
85 | }
86 |
87 | .intro-header {
88 | padding-top: 50px;
89 | padding-bottom: 50px;
90 | color: #f8f8f8;
91 | text-shadow: black 0.1em 0.1em 0.2em;
92 | background: url(/img/meeting-room.jpg) no-repeat center center;
93 | background-size: cover;
94 | text-align: center;
95 | }
96 |
97 | .intro-message {
98 | position: relative;
99 | padding-top: 5%;
100 | padding-bottom: 5%;
101 | vertical-align: middle;
102 | }
103 |
104 | .section-a {
105 | padding: 50px 0;
106 | }
107 |
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/static/bootstrap/css/offcanvas.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Style tweaks
3 | * --------------------------------------------------
4 | */
5 | html,
6 | body {
7 | overflow-x: hidden; /* Prevent scroll on narrow devices */
8 | }
9 |
10 | footer {
11 | padding: 30px 0;
12 | }
13 |
14 | /*
15 | * Off Canvas
16 | * --------------------------------------------------
17 | */
18 | @media screen and (max-width: 767px) {
19 | .row-offcanvas {
20 | position: relative;
21 | -webkit-transition: all .25s ease-out;
22 | -moz-transition: all .25s ease-out;
23 | transition: all .25s ease-out;
24 | }
25 |
26 | .row-offcanvas-right {
27 | right: 0;
28 | }
29 |
30 | .row-offcanvas-left {
31 | left: 0;
32 | }
33 |
34 | .row-offcanvas-right
35 | .sidebar-offcanvas {
36 | right: -50%; /* 6 columns */
37 | }
38 |
39 | .row-offcanvas-left
40 | .sidebar-offcanvas {
41 | left: -50%; /* 6 columns */
42 | }
43 |
44 | .row-offcanvas-right.active {
45 | right: 50%; /* 6 columns */
46 | }
47 |
48 | .row-offcanvas-left.active {
49 | left: 50%; /* 6 columns */
50 | }
51 |
52 | .sidebar-offcanvas {
53 | position: absolute;
54 | top: 0;
55 | width: 50%; /* 6 columns */
56 | }
57 | }
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/static/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_5/00_Conference_Central/static/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/static/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_5/00_Conference_Central/static/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/static/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_5/00_Conference_Central/static/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/static/img/CloudPlatform_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_5/00_Conference_Central/static/img/CloudPlatform_logo.png
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/static/img/ajax-loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_5/00_Conference_Central/static/img/ajax-loader.gif
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/static/img/business1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_5/00_Conference_Central/static/img/business1.jpg
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/static/img/business2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_5/00_Conference_Central/static/img/business2.jpg
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/static/img/business3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_5/00_Conference_Central/static/img/business3.jpg
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_5/00_Conference_Central/static/img/favicon.ico
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/static/img/meeting-room.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/udacity/ud858/bd400cf7caf39fed433d95565441298e6400f9e9/Lesson_5/00_Conference_Central/static/img/meeting-room.jpg
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/static/js/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @ngdoc object
5 | * @name conferenceApp
6 | * @requires $routeProvider
7 | * @requires conferenceControllers
8 | * @requires ui.bootstrap
9 | *
10 | * @description
11 | * Root app, which routes and specifies the partial html and controller depending on the url requested.
12 | *
13 | */
14 | var app = angular.module('conferenceApp',
15 | ['conferenceControllers', 'ngRoute', 'ui.bootstrap']).
16 | config(['$routeProvider',
17 | function ($routeProvider) {
18 | $routeProvider.
19 | when('/conference', {
20 | templateUrl: '/partials/show_conferences.html',
21 | controller: 'ShowConferenceCtrl'
22 | }).
23 | when('/conference/create', {
24 | templateUrl: '/partials/create_conferences.html',
25 | controller: 'CreateConferenceCtrl'
26 | }).
27 | when('/conference/detail/:websafeConferenceKey', {
28 | templateUrl: '/partials/conference_detail.html',
29 | controller: 'ConferenceDetailCtrl'
30 | }).
31 | when('/profile', {
32 | templateUrl: '/partials/profile.html',
33 | controller: 'MyProfileCtrl'
34 | }).
35 | when('/', {
36 | templateUrl: '/partials/home.html'
37 | }).
38 | otherwise({
39 | redirectTo: '/'
40 | });
41 | }]);
42 |
43 | /**
44 | * @ngdoc filter
45 | * @name startFrom
46 | *
47 | * @description
48 | * A filter that extracts an array from the specific index.
49 | *
50 | */
51 | app.filter('startFrom', function () {
52 | /**
53 | * Extracts an array from the specific index.
54 | *
55 | * @param {Array} data
56 | * @param {Integer} start
57 | * @returns {Array|*}
58 | */
59 | var filter = function (data, start) {
60 | return data.slice(start);
61 | }
62 | return filter;
63 | });
64 |
65 |
66 | /**
67 | * @ngdoc constant
68 | * @name HTTP_ERRORS
69 | *
70 | * @description
71 | * Holds the constants that represent HTTP error codes.
72 | *
73 | */
74 | app.constant('HTTP_ERRORS', {
75 | 'UNAUTHORIZED': 401
76 | });
77 |
78 |
79 | /**
80 | * @ngdoc service
81 | * @name oauth2Provider
82 | *
83 | * @description
84 | * Service that holds the OAuth2 information shared across all the pages.
85 | *
86 | */
87 | app.factory('oauth2Provider', function ($modal) {
88 | var oauth2Provider = {
89 | CLIENT_ID: 'web-client-id',
90 | SCOPES: 'email profile',
91 | signedIn: false
92 | }
93 |
94 | /**
95 | * Calls the OAuth2 authentication method.
96 | */
97 | oauth2Provider.signIn = function (callback) {
98 | gapi.auth.signIn({
99 | 'clientid': oauth2Provider.CLIENT_ID,
100 | 'cookiepolicy': 'single_host_origin',
101 | 'accesstype': 'online',
102 | 'approveprompt': 'auto',
103 | 'scope': oauth2Provider.SCOPES,
104 | 'callback': callback
105 | });
106 | };
107 |
108 | /**
109 | * Logs out the user.
110 | */
111 | oauth2Provider.signOut = function () {
112 | gapi.auth.signOut();
113 | // Explicitly set the invalid access token in order to make the API calls fail.
114 | gapi.auth.setToken({access_token: ''})
115 | oauth2Provider.signedIn = false;
116 | };
117 |
118 | /**
119 | * Shows the modal with Google+ sign in button.
120 | *
121 | * @returns {*|Window}
122 | */
123 | oauth2Provider.showLoginModal = function() {
124 | var modalInstance = $modal.open({
125 | templateUrl: '/partials/login.modal.html',
126 | controller: 'OAuth2LoginModalCtrl'
127 | });
128 | return modalInstance;
129 | };
130 |
131 | return oauth2Provider;
132 | });
133 |
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/static/partials/conference_detail.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
{{conference.name}}
17 |
{{conference.description}}
18 |
19 | Registered/Open:
20 | {{conference.maxAttendees - conference.seatsAvailable}} / {{conference.maxAttendees}}
21 |
22 |
23 | Organizer:
24 | {{conference.organizerDisplayName}}
25 |
26 |
Register
28 |
Unregister
30 |
31 |
32 |
33 |
34 |
35 | City:
36 | {{conference.city}}
37 |
38 |
39 | Topics:
40 |
41 | {{topic}}
42 |
43 |
44 |
45 | Start Date:
46 | {{conference.startDate | date:'dd-MMMM-yyyy'}}
47 |
48 |
49 | End Date:
50 | {{conference.endDate | date:'dd-MMMM-yyyy'}}
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/static/partials/home.html:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 |
25 |
26 |
View conferences
27 |
28 |
View by city, topics, date, max attendees.
29 |
View conferences
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
Create new conferences
43 |
44 |
In 10 seconds or less.
45 |
Create a conference
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/static/partials/login.modal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Please sign in to complete this action.
4 |
5 |
8 |
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/static/partials/profile.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
My Profile
15 |
16 |
17 | Display Name
18 | Changed
20 |
22 |
23 |
24 |
25 | Tee shirt size
26 | Changed
28 |
31 |
32 |
33 |
34 | Update profile
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Conference Central
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
67 |
68 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/Lesson_5/00_Conference_Central/utils.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | import time
4 | import uuid
5 |
6 | from google.appengine.api import urlfetch
7 | from models import Profile
8 |
9 | def getUserId(user, id_type="email"):
10 | if id_type == "email":
11 | return user.email()
12 |
13 | if id_type == "oauth":
14 | """A workaround implementation for getting userid."""
15 | auth = os.getenv('HTTP_AUTHORIZATION')
16 | bearer, token = auth.split()
17 | token_type = 'id_token'
18 | if 'OAUTH_USER_ID' in os.environ:
19 | token_type = 'access_token'
20 | url = ('https://www.googleapis.com/oauth2/v1/tokeninfo?%s=%s'
21 | % (token_type, token))
22 | user = {}
23 | wait = 1
24 | for i in range(3):
25 | resp = urlfetch.fetch(url)
26 | if resp.status_code == 200:
27 | user = json.loads(resp.content)
28 | break
29 | elif resp.status_code == 400 and 'invalid_token' in resp.content:
30 | url = ('https://www.googleapis.com/oauth2/v1/tokeninfo?%s=%s'
31 | % ('access_token', token))
32 | else:
33 | time.sleep(wait)
34 | wait = wait + i
35 | return user.get('user_id', '')
36 |
37 | if id_type == "custom":
38 | # implement your own user_id creation and getting algorythm
39 | # this is just a sample that queries datastore for an existing profile
40 | # and generates an id if profile does not exist for an email
41 | profile = Conference.query(Conference.mainEmail == user.email())
42 | if profile:
43 | return profile.id()
44 | else:
45 | return str(uuid.uuid1().get_hex())
46 |
--------------------------------------------------------------------------------
/Lesson_5/Additions/TODO_1_app.yaml:
--------------------------------------------------------------------------------
1 | - url: /crons/set_announcement
2 | script: main.app
3 | login: admin
--------------------------------------------------------------------------------
/Lesson_5/Additions/TODO_1_conference.py:
--------------------------------------------------------------------------------
1 | from google.appengine.api import memcache
2 |
3 | from models import StringMessage
4 |
5 | # - - - Announcements - - - - - - - - - - - - - - - - - - - -
6 |
7 | @staticmethod
8 | def _cacheAnnouncement():
9 | """Create Announcement & assign to memcache; used by
10 | memcache cron job & putAnnouncement().
11 | """
12 | confs = Conference.query(ndb.AND(
13 | Conference.seatsAvailable <= 5,
14 | Conference.seatsAvailable > 0)
15 | ).fetch(projection=[Conference.name])
16 |
17 | if confs:
18 | # If there are almost sold out conferences,
19 | # format announcement and set it in memcache
20 | announcement = '%s %s' % (
21 | 'Last chance to attend! The following conferences '
22 | 'are nearly sold out:',
23 | ', '.join(conf.name for conf in confs))
24 | memcache.set(MEMCACHE_ANNOUNCEMENTS_KEY, announcement)
25 | else:
26 | # If there are no sold out conferences,
27 | # delete the memcache announcements entry
28 | announcement = ""
29 | memcache.delete(MEMCACHE_ANNOUNCEMENTS_KEY)
30 |
31 | return announcement
32 |
33 |
34 | @endpoints.method(message_types.VoidMessage, StringMessage,
35 | path='conference/announcement/get',
36 | http_method='GET', name='getAnnouncement')
37 | def getAnnouncement(self, request):
38 | """Return Announcement from memcache."""
39 | # TODO 1
40 | # return an existing announcement from Memcache or an empty string.
41 | announcement = ""
42 | return StringMessage(data=announcement)
43 |
44 |
45 |
--------------------------------------------------------------------------------
/Lesson_5/Additions/TODO_1_main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import webapp2
3 | from google.appengine.api import app_identity
4 | from google.appengine.api import mail
5 | from conference import ConferenceApi
6 |
7 | class SetAnnouncementHandler(webapp2.RequestHandler):
8 | def get(self):
9 | """Set Announcement in Memcache."""
10 | # TODO 1
11 | # use _cacheAnnouncement() to set announcement in Memcache
12 |
13 | app = webapp2.WSGIApplication([
14 | ('/crons/set_announcement', SetAnnouncementHandler),
15 | ], debug=True)
16 |
--------------------------------------------------------------------------------
/Lesson_5/Additions/TODO_1_models.py:
--------------------------------------------------------------------------------
1 | class StringMessage(messages.Message):
2 | """StringMessage-- outbound (single) string message"""
3 | data = messages.StringField(1, required=True)
--------------------------------------------------------------------------------
/Lesson_5/Additions/TODO_2_app.yaml:
--------------------------------------------------------------------------------
1 | - url: /tasks/send_confirmation_email
2 | script: main.app
3 | login: admin
--------------------------------------------------------------------------------
/Lesson_5/Additions/TODO_2_conference.py:
--------------------------------------------------------------------------------
1 | from google.appengine.api import taskqueue
2 |
3 | # Look for TODO 2
4 | # create Conference, send email to organizer confirming
5 | # creation of Conference & return (modified) ConferenceForm
6 | Conference(**data).put()
7 | taskqueue.add(params={'email': user.email(),
8 | 'conferenceInfo': repr(request)},
9 | url='/tasks/send_confirmation_email'
10 | )
--------------------------------------------------------------------------------
/Lesson_5/Additions/TODO_2_main.py:
--------------------------------------------------------------------------------
1 | from google.appengine.api import app_identity
2 | from google.appengine.api import mail
3 |
4 |
5 | class SendConfirmationEmailHandler(webapp2.RequestHandler):
6 | def post(self):
7 | """Send email confirming Conference creation."""
8 | mail.send_mail(
9 | 'noreply@%s.appspotmail.com' % (
10 | app_identity.get_application_id()), # from
11 | self.request.get('email'), # to
12 | 'You created a new Conference!', # subj
13 | 'Hi, you have created a following ' # body
14 | 'conference:\r\n\r\n%s' % self.request.get(
15 | 'conferenceInfo')
16 | )
17 |
18 |
19 | app = webapp2.WSGIApplication([
20 | ('/crons/set_announcement', SetAnnouncementHandler),
21 | ('/tasks/send_confirmation_email', SendConfirmationEmailHandler),
22 | ], debug=True)
--------------------------------------------------------------------------------
/Lesson_5/Additions/TODO_3_cron.yaml:
--------------------------------------------------------------------------------
1 | cron:
2 | - description: Repopulate the announcement every 1 hour
3 | url: /crons/set_announcement
4 | schedule: every 1 hours
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ud858
2 | =====
3 |
4 | Course code for Building Scalable Apps with Google App Engine in Python class
5 |
6 | # Archival Note
7 | This repository is deprecated; therefore, we are going to archive it. However, learners will be able to fork it to their personal Github account but cannot submit PRs to this repository. If you have any issues or suggestions to make, feel free to:
8 | - Utilize the https://knowledge.udacity.com/ forum to seek help on content-specific issues.
9 | - Submit a support ticket along with the link to your forked repository if (learners are) blocked for other reasons. Here are the links for the [retail consumers](https://udacity.zendesk.com/hc/en-us/requests/new) and [enterprise learners](https://udacityenterprise.zendesk.com/hc/en-us/requests/new?ticket_form_id=360000279131).
--------------------------------------------------------------------------------