Tetsuya Morimoto avatar Tetsuya Morimoto committed a61d2a7

added css and base template for web design
added to display error messages
added insert_or_update_object() context manager
refactored login form to redirect with "next" parameter

Comments (0)

Files changed (11)

src/raido/forms.py

     Form Module
 """
 
-from flaskext.wtf import Form, TextField, Required
+from flaskext.wtf import Form, HiddenField, SubmitField, TextField, Required
 
 class LoginForm(Form):
     username = TextField("username", validators=[Required()])
     birth_date = TextField("birth date")  # for no validate
+    next_url = HiddenField("next_url")
+    submit_btn = SubmitField("login")
 
 class ConsumerForm(Form):
     client_id = TextField("client_id", validators=[Required()])
     client_secret = TextField("client_secret", validators=[Required()])
     redirect_uri = TextField("redirect_uri", validators=[Required()])
     app_name = TextField("app_name")
+    submit_btn = SubmitField("register")

src/raido/provider.py

 from raido.forms import LoginForm, ConsumerForm
 from raido.models import *
 from raido.utils.contextmanagers import (
-        get_object_or_none, query_with_join_session)
+        get_object_or_none, insert_or_update_object, query_with_join_session)
 from raido.utils.misc import (
         convert_datetime_to_str, convert_str_to_datetime,
         get_log_file_handler)
 from raido.views.helper import *
 
 app.add_url_rule("/favicon.ico", "favicon",
-    lambda: redirect("http://tools.ietf.org/images/id.png"))
+        lambda: redirect(app.config["RAIDO_LOGO"]))
 
 @app.route("/", methods=["GET", "POST"])
 def index():
     app.logger.debug("Session: {0}".format(session))
-    params = {"logged_in": False, "url": url_for("login")}
     if g.user:
         app.logger.debug("User: {0}".format(g.user.name))
-        _cnv = convert_datetime_to_str
-        params.update(logged_in=True, url=url_for("logout"), name=g.user.name,
-                      birth_date=_cnv(g.user.birth_date, "%Y-%m-%d"))
-    return render_template("index.html", **params)
+        return render_template("index.html",
+                               user=g.user, url=url_for("logout"))
+    return redirect(url_for("login"))
 
 @app.route("/login", methods=["GET", "POST"])
 def login():
             db.session.commit()
         session["username"] = form.username.data
         app.logger.debug("{0} is logged in".format(u.name))
-        param = get_parameter(request.args, ("next",))
-        app.logger.debug("Parameters: {0}".format(param))
-        url = param["next"] if param.get("next") else url_for("index")
-        return redirect(url)
+        return redirect(form.next_url.data)
     # GET or others
-    return render_template("login.html", form=form)
+    form.next_url.data = request.args.get("next", url_for("index"))
+    return render_template("login.html", user=g.user, form=form)
 
 @app.route("/logout")
 def logout():
     if username:
         with get_object_or_none((User,), commit=True, name=username) as u:
             u.logged_in = False
-            app.logger.debug("{0} is logged in".format(u.name))
+        app.logger.debug("{0} is logged out".format(u.name))
     return redirect(url_for("index"))
 
 @app.route("/consumer/list", methods=["GET", "POST"])
 @app.route("/consumer/register", methods=["GET", "POST"])
 def consumer_register():
     form = ConsumerForm(request.form)
-    if request.method == "POST" and form.validate():
-        _cid = form.client_id.data
-        with get_object_or_none((Consumer,), client_id=_cid) as c:
-            if c:
-                c.client_secret = form.client_secret.data
-                c.redirect_uri = form.redirect_uri.data
-                c.app_name = form.app_name.data
-                c.update_date = datetime.now()
-                app.logger.debug("client_secret and redirect_uri are changed")
-            else:
-                columns = form.data.copy()
-                columns.pop("csrf")
-                c = Consumer(**columns)
-                app.logger.debug("consumer is registered")
-            db.session.add(c)
-            db.session.commit()
+    if request.method == "POST":
         app.logger.debug("consumer form data: {0}".format(form.data))
-        return redirect(url_for("consumer_list"))
-    # GET or others
+        if form.validate():
+            columns = form.data.copy()
+            columns.pop("csrf")
+            columns.pop("submit_btn")
+            with insert_or_update_object(
+                    Consumer, columns, client_id=columns["client_id"]) as c:
+                app.logger.debug("registered: {0}".format(c))
+            return redirect(url_for("consumer_list"))
+        else:
+            flash_form_errors(form)
     return render_template("consumer/register.html", form=form)
 
 @app.route("/oauth/2/auth_code", methods=["GET"])
 def main():
     # app is imported from models module
     set_app_config(app, sys.argv)
+    set_template_filter(app)
     if app.debug:
         print " * Use werkzeug server for debug"
         app.logger.setLevel(app.config["LOG_LEVEL_DEBUG"])

src/raido/settings.py

 CLIENT_SECRET = "SAMPLE_APPLICATION_SECRET"
 REDIRECT_URI = "http://oauth2-consumer.example.com:5100/login/authorized"
 APP_NAME = "Sample Consumer Application"
+
+# for web design
+RAIDO_LOGO = "http://www.logomaker.com/logo-images/c659737cccd24b25.gif"

src/raido/static/style.css

+body{
+background:#FFFFFF;
+margin:9px;
+font: 8pt/14pt 'Lucida Grande', Verdana, Helvetica, sans-serif;
+color:#666666;
+}
+
+A:link{ color:#999999; text-decoration:none; }
+A:hover{ color:#6C8EFF; text-decoration:underline; }
+A:active{ color:#999999; text-decoration:none; }
+A:active:hover{ color:#6C8EFF; text-decoration:underline; }
+A:visited{ color:#999999; text-decoration:none; }
+A:visited:hover{ color:#6C8EFF; text-decoration:underline; }
+
+
+#wrap{
+width:90%;
+margin-left:auto;
+margin-right:auto;
+}
+
+#sidebar{
+float:left;
+text-align:left;
+width:150px;
+}
+
+#container{
+width:auto;
+margin-left:160px;
+border-left:#CCCCCC 1px solid;
+}
+
+#content1{
+padding:20px;
+text-align:justify;
+}
+
+.content2{
+padding:0px 20px 10px;
+text-align:justify;
+}
+
+.flash{
+color:#FF0000;
+font-weight:bold;
+}
+
+h3{
+text-align:right;
+color:#6C8EFF;
+font-size:14pt;
+font-weight:bold;
+}
+
+#title{
+height:120px;
+margin-top:30px;
+border-bottom:#CCCCCC 1px solid;
+text-align:right;
+font-size:10pt;
+letter-spacing:-1px;
+color:#CCCCCC;
+}
+
+#footer{
+text-align:center;
+font-size:10px;
+height:30px;
+margin-top:10px;
+border-top:#CCCCCC 1px solid;
+text-transform:lowercase;
+}
+
+ #navlist
+{
+padding: 0 1px 1px;
+margin-left: 0;
+font: bold 12px Verdana, sans-serif;
+background:#F3F3F3;
+width: 13em;
+}
+
+#navlist li
+{
+list-style: none;
+margin: 0;
+border-top:#CCCCCC 1px solid;
+text-align: right;
+}
+
+#navlist li a
+{
+display: block;
+padding: 0.25em 0.5em 0.25em 0.75em;
+background: #F3F3F3;
+text-decoration: none;
+}
+
+#navlist li a:link { color: #6C8EFF; }
+#navlist li a:visited { color: #999999; }
+
+#navlist li a:hover
+{
+border-color: #FE3;
+color: #666666;
+background: #FFFFFF;
+}
+

src/raido/templates/base.html

+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head>
+  <title>Raido Provider</title>
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+  <meta name="author" content="t2y" />
+  <meta name="description" content="Raido Contents" />
+  <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
+  {% block extra_head %}{% endblock %}
+</head>
+
+<body>
+<div id="wrap">
+  <div id="sidebar">
+    <img src="http://www.logomaker.com/logo-images/c659737cccd24b25.gif" alt="logo design by www.logomaker.com" />
+
+    <div id="navcontainer">
+      <ul id="navlist">
+        <li><a href="/">Home</a></li>
+        <li><a href="/login">Login</a></li>
+        <li><a href="/consumer/list">Consumer List</a></li>
+        <li><a href="/consumer/register">Consumer Register</a></li>
+        <li><a href="/info">OAuth 2 User Info</a></li>
+      </ul>
+    </div>
+  </div>  <!--end sidebar-->
+
+  <div id="container">
+    <div id="title">Raido Simple Provider<br />by t2y</div>
+
+    <div id="content1">
+    {% block content1 %}{% endblock %}
+    </div>
+
+    <div class="content2">
+      {% for message in get_flashed_messages() %}
+      <div class=flash>{{ message }}</div>
+      {% endfor %}
+    </div>
+
+    <div id="footer">  <br />
+      <a href="http://www.dream-logic.com">Web Design by dreamLogic</a> |
+      <a href="#">Privacy Policy</a> | 
+      <a href="#">Terms and Conditions</a>
+    </div>
+
+  </div>  <!-- end container -->
+</div>  <!-- end wrap-->
+
+{% block extra_script %}{% endblock %}
+
+</body>
+</html>

src/raido/templates/consumer/list.html

+{% extends "base.html" %}
+{% block content1 %}
+<h3>Consumers</h3>
+
 <table cellpadding="10">
 <tr>
     <th>Client ID</th>
 <p>move to register page</p>
 <input type="button" value="Register" style="cursor:pointer;"
     onClick='location.href="{{ url }}"' />
+{% endblock %}

src/raido/templates/consumer/register.html

+{% extends "base.html" %}
+{% block content1 %}
+<h3>Consumer Register</h3>
+
 <form method="POST" action="/consumer/register">
 {{ form.csrf }}
 <table>
         <td>{{ form.app_name(size=128) }}</td>
     </tr>
 </table>
-<input type="submit" value="Register">
+{{ form.submit_btn }}
 </form>
+{% endblock %}

src/raido/templates/index.html

-<title>Raido Index</title>
-{% if logged_in %}
-    <p>Logged in as {{ name }} (birth: {{ birth_date }})</p>
+{% extends "base.html" %}
+{% block content1 %}
+<h3>Home</h3>
+
+{% if user %}
+    <p>Logged in as <strong>{{ user.name }}</strong>
+    (birth: {{ user.birth_date|datetime("%Y-%m-%d") }})</p>
     <input type="button" value="logout" style="cursor:pointer;"
         onClick='location.href="{{ url }}"'>
 {% else %}
     <p>You are not logged in</p>
-    <input type="button" value="move to login page" style="cursor:pointer;"
-        onClick='location.href="{{ url }}"'>
 {% endif %}
+{% endblock %}

src/raido/templates/login.html

+{% extends "base.html" %}
+{% block content1 %}
+<h3>Login</h3>
+
+{% if user %}
+    <p>Already logged in as <strong>{{ user.name }}</strong></p>
+{% endif %}
+
 <form method="POST" action="/login">
 {{ form.csrf }}
+{{ form.next_url }}
 {{ form.username.label }} {{ form.username(size=32) }}<br />
 {% if form.username.errors %}
     <ul class="errors">
 <hr>
 there are extra user information<br />
 {{ form.birth_date.label }} {{ form.birth_date }} (yyyy-mm-dd)<br />
-<input type="submit" value="Login">
+{{ form.submit_btn }}
 </form>
+{% endblock %}

src/raido/utils/contextmanagers.py

         db.session.commit()
 
 @contextmanager
+def insert_or_update_object(entity, columns, **kwargs):
+    obj = db.session.query(entity).filter_by(**kwargs).first()
+    if obj:
+        primary_key = entity.__table__.primary_key.columns._data.keys()[0]
+        columns.pop(primary_key)
+        for column, value in columns.items():
+            setattr(obj, column, value)
+    else:
+        obj = entity(**columns)
+    db.session.add(obj)
+    db.session.commit()
+    yield obj
+
+@contextmanager
 def query_with_join_session(entities, joins, **kwargs):
     q = db.session.query(*entities).join(*joins)
     if kwargs:

src/raido/views/helper.py

 
 from operator import getitem
 from random import randint
+from flask import flash
 
 from raido.models import db, AuthCode, AccessToken
 from raido.utils.contextmanagers import get_object_or_none
             db.session.delete(obj)
             db.session.commit()
 
+def flash_form_errors(form):
+    for field in form.errors:
+        flash("{0}: {1}".format(field, ", ".join(form.errors[field])))
+
 def set_app_config(app, argv):
     """ configure WSGI application via command line argument
 
     if opts.debug:
         app.config["DEBUG"] = True
 
+def set_template_filter(app):
+    from raido.utils.misc import convert_datetime_to_str
+    app.jinja_env.filters["datetime"] = convert_datetime_to_str
 
 from string import digits, letters
 _BASE_TOKEN_STRING = digits + letters
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.