From 63b226de989d62b87e64e6738f5d0b259a79edd7 Mon Sep 17 00:00:00 2001 From: Prabhav Chawla Date: Tue, 20 Jun 2017 16:15:08 -0400 Subject: [PATCH 1/8] Basic webapp to make Google search requests --- appengine/standard/hello_world/main.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/appengine/standard/hello_world/main.py b/appengine/standard/hello_world/main.py index c26ad7a80f6..af9cbcecf19 100644 --- a/appengine/standard/hello_world/main.py +++ b/appengine/standard/hello_world/main.py @@ -14,13 +14,20 @@ import webapp2 +form = """ +
+ + +
+ """ -class MainPage(webapp2.RequestHandler): +class MainPage(webapp2.RequestHandler): # It inherits from webapp2.RequestHandler def get(self): - self.response.headers['Content-Type'] = 'text/plain' - self.response.write('Hello, World!') + self.response.headers['Content-Type'] = 'text/html' # self.response is the global response object that the app uses. We set the Content-Type header, the default value of which is text/html + self.response.write(form) # writes this string app = webapp2.WSGIApplication([ ('/', MainPage), -], debug=True) +], debug=True) # URL mapping section. The "/" URL maps to the handler MainPage. MainPage defined in the class. + From 97985f35b5c75added0e205f9206935abecbd703 Mon Sep 17 00:00:00 2001 From: Prabhav Chawla Date: Tue, 20 Jun 2017 16:35:49 -0400 Subject: [PATCH 2/8] Add a path to our app --- appengine/standard/hello_world/main.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/appengine/standard/hello_world/main.py b/appengine/standard/hello_world/main.py index af9cbcecf19..da3623bf90e 100644 --- a/appengine/standard/hello_world/main.py +++ b/appengine/standard/hello_world/main.py @@ -15,7 +15,7 @@ import webapp2 form = """ -
+
@@ -24,10 +24,15 @@ class MainPage(webapp2.RequestHandler): # It inherits from webapp2.RequestHandler def get(self): self.response.headers['Content-Type'] = 'text/html' # self.response is the global response object that the app uses. We set the Content-Type header, the default value of which is text/html - self.response.write(form) # writes this string + self.response.out.write(form) # writes this string +class TestHandler(webapp2.RequestHandler): + def get(self): + q = self.request.get("q") # the request parameter + self.response.out.write(q) app = webapp2.WSGIApplication([ ('/', MainPage), + ('/testform', TestHandler) ], debug=True) # URL mapping section. The "/" URL maps to the handler MainPage. MainPage defined in the class. - +# we need to handle /testform now, need to map the URL From 8b79809ef9a373aad425065b6fec81c67eac02cc Mon Sep 17 00:00:00 2001 From: Prabhav Chawla Date: Thu, 5 Oct 2017 21:48:57 -0400 Subject: [PATCH 3/8] Test form --- appengine/standard/hello_world/main.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/appengine/standard/hello_world/main.py b/appengine/standard/hello_world/main.py index da3623bf90e..2f510265503 100644 --- a/appengine/standard/hello_world/main.py +++ b/appengine/standard/hello_world/main.py @@ -15,21 +15,23 @@ import webapp2 form = """ -
+
""" + # default method is post class MainPage(webapp2.RequestHandler): # It inherits from webapp2.RequestHandler def get(self): self.response.headers['Content-Type'] = 'text/html' # self.response is the global response object that the app uses. We set the Content-Type header, the default value of which is text/html self.response.out.write(form) # writes this string -class TestHandler(webapp2.RequestHandler): - def get(self): +class TestHandler(webapp2.RequestHandler): # needs to handle post requests + def post(self): q = self.request.get("q") # the request parameter self.response.out.write(q) +# Works exactly like it did before, except this time the query parameter is not in our URL but in our HTTP body app = webapp2.WSGIApplication([ ('/', MainPage), From 451cccb368577c10156834dd0cb0c8c8bead6ded Mon Sep 17 00:00:00 2001 From: Prabhav Chawla Date: Sat, 7 Oct 2017 13:51:22 -0400 Subject: [PATCH 4/8] Implement redirects, HTML escaping, data validation --- appengine/standard/hello_world/main.py | 101 +++++++++++++++++++++---- 1 file changed, 87 insertions(+), 14 deletions(-) diff --git a/appengine/standard/hello_world/main.py b/appengine/standard/hello_world/main.py index 2f510265503..d848bf436d9 100644 --- a/appengine/standard/hello_world/main.py +++ b/appengine/standard/hello_world/main.py @@ -13,28 +13,101 @@ # limitations under the License. import webapp2 +import cgi form = """ -
- + + What is your birthday? +
+ + + +
%(error)s
+
+
""" # default method is post +months = ['January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December'] + +def valid_month(month): + if month.title() in months: + return month.title() + return None + +def valid_day(day): + if day.isdigit() and 1 <= int(day) <= 31: + return int(day) + return None + +def valid_year(year): + if year.isdigit() and 1900 <= int(year) <= 2020: + return int(year) + return None + +def escape_html(html_str): + # Use cgi: + return cgi.escape(html_str, quote = True) + # Or + """ + for (char, replacement) in (("&", "&"), (">", ">"), (">", ">"), (">", ">")): # escape html characters + html_str = html_str.replace(char, replacement) + + return html_str""" + + class MainPage(webapp2.RequestHandler): # It inherits from webapp2.RequestHandler - def get(self): + def write_form(self, error="", month="", day="", year=""): + self.response.out.write(form % {"error": escape_html(error), "month": escape_html(month), "day": escape_html(day), "year": escape_html(year)}) # see this string substitution method in python + + def get(self): # note that even though our form has method post, we never handle a post request to this URL, so we will get an error self.response.headers['Content-Type'] = 'text/html' # self.response is the global response object that the app uses. We set the Content-Type header, the default value of which is text/html - self.response.out.write(form) # writes this string + self.write_form() -class TestHandler(webapp2.RequestHandler): # needs to handle post requests def post(self): - q = self.request.get("q") # the request parameter - self.response.out.write(q) -# Works exactly like it did before, except this time the query parameter is not in our URL but in our HTTP body - -app = webapp2.WSGIApplication([ - ('/', MainPage), - ('/testform', TestHandler) -], debug=True) # URL mapping section. The "/" URL maps to the handler MainPage. MainPage defined in the class. -# we need to handle /testform now, need to map the URL + user_month = self.request.get('month') + user_day = self.request.get('day') + user_year = self.request.get('year') + + month = valid_month(user_month) + day = valid_day(user_day) + year = valid_year(user_year) + + print(month, day, year) + + if not month or not day or not year: + self.write_form(error="Invalid data entered.", month=user_month, day=user_day, year=user_year) # form with old inputted values + else: + # self.response.out.write("Valid data received") + # If we reload our success response in the post, we can't share a success URL and when we refresh, + # the browser asks us "confirm resubmission...". We use a redirect to a success page/success html. Also reloading the page in this case would cause + # our server to receive the same form with the same data and evaluate the params again for validity. + self.redirect("/thanks") # redirecting to same domain/host, so just give a path. no need to write https:// .. + +class ThanksHandler(webapp2.RequestHandler): + def get(self): + self.response.out.write('

Valid data received

') + + def post(self): + self.redirect("/") + +app = webapp2.WSGIApplication([('/', MainPage), ('/thanks', ThanksHandler)], debug=True) # URL mapping section. The "/" URL maps to the handler MainPage. MainPage defined in the class. From 911dc46f20bad6cad8b50a4f4acf58e03176d97a Mon Sep 17 00:00:00 2001 From: Prabhav Chawla Date: Thu, 4 Jan 2018 00:19:16 +0530 Subject: [PATCH 5/8] HW-2 Part 1 --- appengine/standard/hello_world/main.py | 122 +++++++------------------ 1 file changed, 31 insertions(+), 91 deletions(-) diff --git a/appengine/standard/hello_world/main.py b/appengine/standard/hello_world/main.py index d848bf436d9..5b909539bbb 100644 --- a/appengine/standard/hello_world/main.py +++ b/appengine/standard/hello_world/main.py @@ -16,98 +16,38 @@ import cgi form = """ -
- What is your birthday? -
- - - -
%(error)s
-
-
- -
- """ - # default method is post - -months = ['January', - 'February', - 'March', - 'April', - 'May', - 'June', - 'July', - 'August', - 'September', - 'October', - 'November', - 'December'] - -def valid_month(month): - if month.title() in months: - return month.title() - return None - -def valid_day(day): - if day.isdigit() and 1 <= int(day) <= 31: - return int(day) - return None - -def valid_year(year): - if year.isdigit() and 1900 <= int(year) <= 2020: - return int(year) - return None - -def escape_html(html_str): - # Use cgi: - return cgi.escape(html_str, quote = True) - # Or - """ - for (char, replacement) in (("&", "&"), (">", ">"), (">", ">"), (">", ">")): # escape html characters - html_str = html_str.replace(char, replacement) - - return html_str""" - - -class MainPage(webapp2.RequestHandler): # It inherits from webapp2.RequestHandler - def write_form(self, error="", month="", day="", year=""): - self.response.out.write(form % {"error": escape_html(error), "month": escape_html(month), "day": escape_html(day), "year": escape_html(year)}) # see this string substitution method in python - - def get(self): # note that even though our form has method post, we never handle a post request to this URL, so we will get an error - self.response.headers['Content-Type'] = 'text/html' # self.response is the global response object that the app uses. We set the Content-Type header, the default value of which is text/html - self.write_form() - - def post(self): - user_month = self.request.get('month') - user_day = self.request.get('day') - user_year = self.request.get('year') - - month = valid_month(user_month) - day = valid_day(user_day) - year = valid_year(user_year) - - print(month, day, year) - - if not month or not day or not year: - self.write_form(error="Invalid data entered.", month=user_month, day=user_day, year=user_year) # form with old inputted values - else: - # self.response.out.write("Valid data received") - # If we reload our success response in the post, we can't share a success URL and when we refresh, - # the browser asks us "confirm resubmission...". We use a redirect to a success page/success html. Also reloading the page in this case would cause - # our server to receive the same form with the same data and evaluate the params again for validity. - self.redirect("/thanks") # redirecting to same domain/host, so just give a path. no need to write https:// .. - -class ThanksHandler(webapp2.RequestHandler): + + + +Udacity Web Dev ROT13 + + +

Enter some text to ROT13:

+
+ +
+ +
+ + +""" + +def get_rot13(char): + if char.isalpha(): + temp_char = ord(char) + 13 + if (char.islower() and not chr(temp_char).islower()) or (char.isupper() and not chr(temp_char).isupper()): + temp_char += ord('a') - ord('z') - 1 + return chr(temp_char) + return char + +class MainPage(webapp2.RequestHandler): def get(self): - self.response.out.write('

Valid data received

') + self.response.headers['Content-Type'] = 'text/html' + self.response.out.write(form % "") def post(self): - self.redirect("/") + self.response.headers['Content-Type'] = 'text/html' + rot_text = "".join([get_rot13(char) for char in self.request.get('text')]) + self.response.out.write(form % cgi.escape(rot_text, quote = True)) -app = webapp2.WSGIApplication([('/', MainPage), ('/thanks', ThanksHandler)], debug=True) # URL mapping section. The "/" URL maps to the handler MainPage. MainPage defined in the class. +app = webapp2.WSGIApplication([('/', MainPage)], debug=True) From 4e5eb504921af8ec959257e5add4dd3505159162 Mon Sep 17 00:00:00 2001 From: Prabhav Chawla Date: Thu, 4 Jan 2018 23:14:13 +0530 Subject: [PATCH 6/8] HW2 user sign up --- appengine/standard/hello_world/main.py | 86 +++++++++++++++++++++----- 1 file changed, 71 insertions(+), 15 deletions(-) diff --git a/appengine/standard/hello_world/main.py b/appengine/standard/hello_world/main.py index 5b909539bbb..66316e6f019 100644 --- a/appengine/standard/hello_world/main.py +++ b/appengine/standard/hello_world/main.py @@ -14,40 +14,96 @@ import webapp2 import cgi +import re form = """ -Udacity Web Dev ROT13 +Udacity Web Dev Sign-up -

Enter some text to ROT13:

+

Sign-up


- -
+ + + %(username_error)s +
+ + %(pass_error)s +
+ + %(verify_error)s +
+ + %(email_error)s +
""" -def get_rot13(char): - if char.isalpha(): - temp_char = ord(char) + 13 - if (char.islower() and not chr(temp_char).islower()) or (char.isupper() and not chr(temp_char).isupper()): - temp_char += ord('a') - ord('z') - 1 - return chr(temp_char) - return char +USER_RE = re.compile(r"^[a-zA-Z0-9_-]{3,20}$") +def valid_username(username): + return USER_RE.match(username) + +PASSWORD_RE = re.compile(r"^.{3,20}$") +def valid_password(password): + return PASSWORD_RE.match(password) + +EMAIL_RE = re.compile(r"^[\S]+@[\S]+.[\S]+$") +def valid_email(email): + if not email: + return True + return EMAIL_RE.match(email) + +def verify_password(str1, str2): + return str1 == str2 class MainPage(webapp2.RequestHandler): + def write_form(self, username_error="", pass_error="", verify_error="", email_error="", username="", email=""): + self.response.out.write(form % {'username_error': username_error, 'pass_error': pass_error, 'verify_error': verify_error, 'email_error': email_error, 'username': username, 'email': email}) + def get(self): self.response.headers['Content-Type'] = 'text/html' - self.response.out.write(form % "") + self.write_form() def post(self): self.response.headers['Content-Type'] = 'text/html' - rot_text = "".join([get_rot13(char) for char in self.request.get('text')]) - self.response.out.write(form % cgi.escape(rot_text, quote = True)) -app = webapp2.WSGIApplication([('/', MainPage)], debug=True) + username = self.request.get('username') + password = self.request.get('password') + verify = self.request.get('verify') + email = self.request.get('email') + + username_error = valid_username(username) + password_error = valid_password(password) + email_error = valid_email(email) + verify_error = verify_password(password, verify) + + if not username_error or not password_error or not email_error or not verify_error: + self.write_form(username_error="Invalid username" if not username_error else "", pass_error="Invalid password" if not password_error else "", + verify_error="Passwords don't match" if not verify_error else "", email_error="Invalid email" if not email_error else "", username=username, email=email) + else: + self.redirect('/welcome?username=%s' % username) + +class WelcomePage(webapp2.RequestHandler): + def get(self): + self.response.headers['Content-Type'] = 'text/html' + self.response.out.write("

Welcome, %s!

" % self.request.get('username')) + + +app = webapp2.WSGIApplication([('/', MainPage), ('/welcome', WelcomePage)], debug=True) From ed3bbd39daf45e8ac4f52125ee68c84fe2a77990 Mon Sep 17 00:00:00 2001 From: Prabhav Chawla Date: Wed, 23 May 2018 01:34:33 +0530 Subject: [PATCH 7/8] ugly shopping list app --- appengine/standard/hello_world/main.py | 118 ++++++++----------------- 1 file changed, 38 insertions(+), 80 deletions(-) diff --git a/appengine/standard/hello_world/main.py b/appengine/standard/hello_world/main.py index 66316e6f019..14d3378daff 100644 --- a/appengine/standard/hello_world/main.py +++ b/appengine/standard/hello_world/main.py @@ -14,96 +14,54 @@ import webapp2 import cgi -import re -form = """ - - - -Udacity Web Dev Sign-up - - -

Sign-up

-
-
- - %(username_error)s -
- - %(pass_error)s -
- - %(verify_error)s -
- - %(email_error)s -
- +form_html = """ + +

Add Food

+ +%s +
- - """ -USER_RE = re.compile(r"^[a-zA-Z0-9_-]{3,20}$") -def valid_username(username): - return USER_RE.match(username) - -PASSWORD_RE = re.compile(r"^.{3,20}$") -def valid_password(password): - return PASSWORD_RE.match(password) - -EMAIL_RE = re.compile(r"^[\S]+@[\S]+.[\S]+$") -def valid_email(email): - if not email: - return True - return EMAIL_RE.match(email) +hidden_html= """ + +""" +# hidden: include values in our query that the user can't see or interact with. This is different from type=password. Note that multiple query items can have +# the same name, so if you have a hidden input with name food and a text input with name food: ?food=val1&food=val2 is possible +shopping_html = """ +
+
+

Shopping List

+
    +%s +
+""" -def verify_password(str1, str2): - return str1 == str2 +item_html = "
  • %s
  • " -class MainPage(webapp2.RequestHandler): - def write_form(self, username_error="", pass_error="", verify_error="", email_error="", username="", email=""): - self.response.out.write(form % {'username_error': username_error, 'pass_error': pass_error, 'verify_error': verify_error, 'email_error': email_error, 'username': username, 'email': email}) +class Handler(webapp2.RequestHandler): + def write(self, *a, **ka): + self.response.out.write(*a, **ka) + +class MainPage(Handler): def get(self): - self.response.headers['Content-Type'] = 'text/html' - self.write_form() - - def post(self): - self.response.headers['Content-Type'] = 'text/html' + output = form_html + output_hidden = "" + output_items = "" - username = self.request.get('username') - password = self.request.get('password') - verify = self.request.get('verify') - email = self.request.get('email') + items = self.request.get_all('food') # get all params with name food + if items: - username_error = valid_username(username) - password_error = valid_password(password) - email_error = valid_email(email) - verify_error = verify_password(password, verify) + for item in items: + output_hidden += hidden_html % item + output_items += item_html % item - if not username_error or not password_error or not email_error or not verify_error: - self.write_form(username_error="Invalid username" if not username_error else "", pass_error="Invalid password" if not password_error else "", - verify_error="Passwords don't match" if not verify_error else "", email_error="Invalid email" if not email_error else "", username=username, email=email) - else: - self.redirect('/welcome?username=%s' % username) - -class WelcomePage(webapp2.RequestHandler): - def get(self): - self.response.headers['Content-Type'] = 'text/html' - self.response.out.write("

    Welcome, %s!

    " % self.request.get('username')) + output_shopping = shopping_html % output_items + output += output_shopping + output = output % output_hidden + self.write(output) -app = webapp2.WSGIApplication([('/', MainPage), ('/welcome', WelcomePage)], debug=True) +app = webapp2.WSGIApplication([('/', MainPage)], debug=True) From 17b390de242f0e442b5433b6bdd408f774edc4de Mon Sep 17 00:00:00 2001 From: Prabhav Chawla Date: Thu, 24 May 2018 01:39:09 +0530 Subject: [PATCH 8/8] Complete shopping list with template --- appengine/standard/hello_world/app.yaml | 4 ++ appengine/standard/hello_world/main.py | 68 ++++--------------- .../standard/hello_world/templates/base.html | 11 +++ .../hello_world/templates/shopping_list.html | 24 +++++++ 4 files changed, 54 insertions(+), 53 deletions(-) create mode 100644 appengine/standard/hello_world/templates/base.html create mode 100644 appengine/standard/hello_world/templates/shopping_list.html diff --git a/appengine/standard/hello_world/app.yaml b/appengine/standard/hello_world/app.yaml index f041d384c05..ef5a5697bac 100644 --- a/appengine/standard/hello_world/app.yaml +++ b/appengine/standard/hello_world/app.yaml @@ -5,3 +5,7 @@ threadsafe: true handlers: - url: /.* script: main.app + +libraries: +- name: jinja2 + version: latest diff --git a/appengine/standard/hello_world/main.py b/appengine/standard/hello_world/main.py index 14d3378daff..811ea565f2e 100644 --- a/appengine/standard/hello_world/main.py +++ b/appengine/standard/hello_world/main.py @@ -1,67 +1,29 @@ -# Copyright 2016 Google Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - import webapp2 import cgi +import jinja2 +import os -form_html = """ -
    -

    Add Food

    - -%s - -
    -""" - -hidden_html= """ - -""" -# hidden: include values in our query that the user can't see or interact with. This is different from type=password. Note that multiple query items can have +# type hidden: include values in our query that the user can't see or interact with. This is different from type=password. Note that multiple query items can have # the same name, so if you have a hidden input with name food and a text input with name food: ?food=val1&food=val2 is possible -shopping_html = """ -
    -
    -

    Shopping List

    -
      -%s -
    -""" -item_html = "
  • %s
  • " +template_dir = os.path.join(os.path.dirname(__file__), 'templates') # os.path.dirname(__file__) is location of this file +jinja_env = jinja2.Environment(loader = jinja2.FileSystemLoader(template_dir), autoescape=True) # when we render templates, jinja will look for them in this directory class Handler(webapp2.RequestHandler): - def write(self, *a, **ka): - self.response.out.write(*a, **ka) + def write(self, *a, **kw): + self.response.out.write(*a, **kw) + + def render_str(self, template, **params): + t = jinja_env.get_template(template) # create a jinja template from the file argument template + return t.render(params) # see example + + def render(self, template, **kw): + self.write(self.render_str(template, **kw)) # send to browser class MainPage(Handler): def get(self): - output = form_html - output_hidden = "" - output_items = "" - items = self.request.get_all('food') # get all params with name food - if items: - - for item in items: - output_hidden += hidden_html % item - output_items += item_html % item - - output_shopping = shopping_html % output_items - output += output_shopping - - output = output % output_hidden - self.write(output) + self.render("shopping_list.html", items=items) # again, we are dealing with user input. Consider data validation and HTML escaping app = webapp2.WSGIApplication([('/', MainPage)], debug=True) diff --git a/appengine/standard/hello_world/templates/base.html b/appengine/standard/hello_world/templates/base.html new file mode 100644 index 00000000000..638c10feaf8 --- /dev/null +++ b/appengine/standard/hello_world/templates/base.html @@ -0,0 +1,11 @@ + + + + Udacity Templates! + + +

    YOLO

    + {% block content %} + {% endblock %} + + \ No newline at end of file diff --git a/appengine/standard/hello_world/templates/shopping_list.html b/appengine/standard/hello_world/templates/shopping_list.html new file mode 100644 index 00000000000..560d1d9ed39 --- /dev/null +++ b/appengine/standard/hello_world/templates/shopping_list.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} + +{% block content %} +
    +

    Add Food

    + + {% if items %} + {% for item in items %} + + {% endfor %} + {% endif %} + + {% if items %} +
    +
    +

    Shopping List

    +
      + {% for item in items %} +
    • {{item}}
    • + {% endfor %} +
    + {% endif %} +
    +{% endblock %}