vim cheatsheet

Write your post here.

Insert mode commands

  • Switch from Insert to Normal mode for one command only: CTRL-o (followed by the normal mode command)
  • Delete back to the beginning of the previous word (or the beginning of the current word if the cursor is already inside a word): CTRL-w
  • Indent current line: CTRL-t (mnemonic: "tab")
  • Dedent current line: CTRL-d (mnemonic: "dedent")
  • Insert a hard tab, even when it would normally be expanded to spaces: CTRL-V <tab>
  • Insert current filename CTRL-r % (for related things that can be inserted with CTRL-r, see :h i_CTRL-R).

Normal mode commands

  • ==: intelligently indent selection (or currently line if no selection); note that what is actually happening here is that we’re using the "filter" functionality with no filter, so Vim just uses the standard builtin "C indenting" functionality (for more information see :h filter)
  • gq{motion}: reformat (rewrap); eg:
    • gqq: reformat (rewrap) current line
  • gu{motion}: lowercase
  • gU{motion}: uppercase
  • g~{motion}: toggle case
  • CTRL-x: increment the number at (the end of) the word under the cursor
  • CTRL-a: decrement the number at (the end of) the word under the cursor
  • gv: reselect last selection
  • g CTRL-G: show current cursor position and total counts (columns, lines, words, bytes)
  • ga: show ASCII info for character under cursor
  • g8: show UTF-8 info for character under cursor
  • CTRL-w f: open the file under the cursor in a split (gf will do this without the split)
Window and tab-related commands
  • CTRL-w =: resize windows to be equal width and height
  • CTRL-W r or CTRL-W CTRL-R: Rotate windows (downwards/rightwards)
  • CTRL-W {H,J,K,L}: Move a window to occupy to an entire edge of the viewport; eg. CTRL-W J pushes the current window to the bottom, occupying the full width (also works: CTRL-W CTRL-{H,J,K,L})
  • CTRL-W T: open current window in a new tab
  • CTRL-W _: make current window as big as possible (cf. 10 CTRL-W _, which makes it 10 lines high); (also works: CTRL-W CTRL-_)
  • CTRL-W b: move to bottom window (also works: CTRL-W CTRL-B)
  • CTRL-W t: move to top window (also works: CTRL-W CTRL-T)
  • gt: next tab
  • gT: previous tab

For more see :h window-moving

Movement

For lots of documentation see :h motion.txt.

  • h, j, k, l: per-character/per-line movement through file (left, down, up, right)
  • gj, gk: per-line movement through buffer, operating on display lines (which may be wrapped) not actual lines in the file (down, up)
  • w: forward a word (mnemonic: "word"); here "word" is an "keyword" (identifier) like "foo9" or "h_go" and the movement is to the beginning of the next word
  • W: forward a "big" word; here a "word" is anything other than whitespace
  • b: back a word (mnemonic: "back"); again the movement is to the beginning of the word
  • B: back a "big" word
  • e: forward to the end of a word (mnemonic: "end")
  • E: forward to the end of a "big" word
  • ge: back to the end of the last word
  • gE: back to the end of the last "big" word
  • f{character}: forward to the next occurrence of character (mnemonic: "find/foward")
  • F{character}: same as f{character}, but search in the opposite direction
  • t{character}: forward til (until) the next occurrence of character (mnemonic: "til/to"); the cursor is place immediately before and not on the character
  • T{character}: same as t{character}, but search in the opposite direction
  • ;: repeat last f, F, t or T operation
  • ,: repeat last f, F, t or T operation, but in the opposite direction
  • CTRL-F: forward a (full) screen (mnemonic: "forward")
  • CTRL-B: back a (full) screen (mnemonic: "back")
  • CTRL-D: down a (half) screen (mnemonic: "down")
  • CTRL-U: up a (half) screen (mnemonic: "up")
  • H: jump to top of screen (mnemonic: "high")
  • M: jump to middle of screen (mnemonic: "middle")
  • L: jump to bottom of screen (mnemonic: "low")
  • gg: jump to top of file (mnemonic: like more or less pagers, except plain g isn’t available so we have to use gg)
  • G: jump to bottom of file (mnemonic: like more or less pagers)
  • 200G: jump to line 200
  • :200: same as 200G
  • {: jump to last blank line
  • }: jump to next blank line
  • [[: jump to previous { in column 0
  • ]]: jump to next { in column 0
  • ][: jump to next } in column 0
  • []: jump to previous } in column 0
  • %: jump to matching brace (with matchit.vim you can also jump to matching HTML tags and the like)
  • ]s: jump to next misspelled word
  • zg: mark a word as good, adding it to the spelling dictionary
Scrolling
  • zz center current cursor line within viewport
  • zt scroll current cursor line to top of viewport
  • zb scroll current cursor line to bottom of viewport
  • CTRL-y scroll down a line
  • CTRL-e scroll up a line
  • CTRL-d scroll down one page
  • CTRL-u scroll up one page
Marks and jumps

When jumping to a mark, there are two variants:

  • with ', the jump is line-wise, to the first non-blank character on the line containing the mark
  • with ```, the jump is character-wise, to the position of the mark within the line

Example commands:

  • m{letter}: create a mark at the current position
  • '{letter}: jump to specified mark (first non-whitespace character on line)
  • `{letter}: jump to specified mark (to column where mark was set)
  • '': jump back to the last line jumped from
  • ````: jump back to the last position jumped from
  • `.: jump to position where last change occurred in current buffer
  • [` and]`: jump to beginning/end of last changed or yanked text
  • <` and>`: jump to beginning/end of last visual selection
  • :jump: show the jump list (places you’ve jumped to with motion commands, specifically those listed at :h jump-motions)
  • CTRL-o: move to older position in jump list (mnemonic: "jump out")
  • CTRL-i: move to newer position in jump list (mnemonic: "jump in")
  • :changes: show the changes list for the current buffer (places you’ve edited)
  • g;: move to older position in change list
  • g,: move to newer position in change list
Search
  • *: find word under cursor
  • #: find word under cursor (reverse direction)
  • g*: like *, but also looks for matches which are substrings of other words
  • g#: like #, but also looks for matches which are substrings of other words
  • /{pattern}: find pattern (mnemonic: like Perl/Ruby/pager etc regex syntax)
  • ?{pattern}: find pattern (reverse direction)
  • n: repeat last search
  • N: repeat last search in opposite direction
  • gn: go to next match and select it visually; can be used as a motion as well (ie. cgn means "change next match")
  • ggn: go to first match in file
  • Gn: go to last match in file
Folding
  • zr: decrement 'foldlevel' A.K.A. "fold less" (mnemonic: reduce)
  • zm: increment 'foldlevel' A.K.A. "fold more" (mnemonic: more)
  • zR: decrement 'foldlevel' to zero A.K.A. "unfold everything" (mnemonic: reduce)
  • zM: increment 'foldlevel' to maximum A.K.A> "fold everything" (mnemonic: more)
  • zo: open current fold
  • zO: open current fold recursively
  • zc: close current fold
  • zC: close current fold recursively
  • zv: view cursor line A.K.A. "open just enough folds to make cursor line visible" (mnemonic: view/visible)
  • zi: toggle value of 'foldenable' (mnemonic: invert)

Visual mode commands

Enter using V:

  • gq: reformat (rewrap) selection
  • o: jump to opposite end of selection

Visual (block) mode commands

Enter using <C-v>:

  • $A{string}<Esc>: append to each line, varying lengths (details).

Command mode commands

  • :nohlsearch (:noh): remove currently visible search highlighting (doesn’t disable highlighting permanently)
  • :enew (:ene): open a new scratch buffer
  • :write: write current buffer to a file
  • :read: read into current buffer; eg:
  • :read !{shell command}: insert output of shell command into current buffer at current cursor location
  • :{range}d: delete lines in {range}
  • :{range}y: yank lines in {range}
  • :g/{pattern}/{action}: perform action on lines matching pattern (eg. :g/a/d would delete all lines matching "a")
  • :v/{pattern}/{action}: inverse of :g (performs action on non-matching lines)

To open the command-line window (showing full command history in a buffer, which you can edit, then use <CR> to run a given line’s command):

  • q:: for commands
  • q/: for searches (or q?)

Funnily, you can easily open this by mistake by typing q: when you mean to type the quite-frequently-used :q instead.

Quickfix/Location list
Commands
  • :cclose (:ccl): close quickfix window (:lclose and :lcl for location list)
  • :colder (:col): show previous quickfix results (:lolder and :lol for location list)
  • :cnewer (:cnew): show subsequent quickfix results (:lnewer and :lnew for location list)
Mappings
  • CTRL-w Enter: open current item in a split.
Mappings available in command mode
  • <C-r>{register}: paste the text from the specified register into the command line (eg. to paste the last-yanked text, do <C-r>")
  • <C-r> w: paste word currently under cursor

Special mappings

  • <C-w> <CR>: open quickfix entry in a horizontal split

Spacevim special

extra pluggin

  • <SPC> D d: search word under current docset
  • <SPC> D D: search word under all docsets

tool chain for email handling

Background

Using email everyday, so the efficiency to handle email defines the efficiency of your day.

Email handling principle

  1. Limit the access or notification of email, but pull the update with specific timeslot per day.
  2. Get rid of redundancies like HTML email, but pure text (or markdown into html automatically if needed)
  3. Filtering, Labeling and "Zero Inbox" is key for email handling

Tool Chain for email

After searching for years, these tools are choosen for efficient email handling:

Email Client: Neomutt together with mbsync, msmtp; Filtering: marina or mu; Editor: vim with muttdown; Calendar: ? TBD with one terminal tool, Contact: abook with khard? ; Misc: urlscan, w3m, gpgme, etc;

Working flow and procedure

Config Email

TBD, suggest follow dotfile procedure.

Recieving Email

Taking your specific time slot for email, than just open terminal and:

mbsync -a 

Sorting/Labeling Email

Write Email

Vim would be the major working force for this task with fancy tools included such as auto complete and spell checking.

Raw Text

Normal working follow editing txt file in vim. TBD for special plugin to handle email more efficiently.

Rich Format

Add muttdown for sending out rich format by using muttdown.

Spellcheck: vim

To move to a misspelled word, use ]s and [s. z=, and Vim will suggest a list of alternatives that it thinks may be correct. zg command and Vim will add it to its dictionary. You can also mark words as incorrect using zw.

;enable spell
set nospell
;disable spell
set spell
;change default lang
set spell spelllang=en_us

Calendar handling

Contact

Abook auto complete integration How to sync with cloud.

Reference

python data visulaization by jupyter and superset

There are plenties of ways to make your data vivid via data visualization.

And python have tools like superset to make the visual report much more easier. And jupyter is a handy tool together with matplotlib and seaborn to try the data lab.

jupyter

For typical jupyter, the intension might not be used as a tool to visualization but a warehouse to tuning, manipulate and sharp the date before reporting. It need several component to make the report more suitable for reporting, such as :

And further digging for seaborn & matplotlib would be continously updated later on.

Superset

The end to end interface for BI as what tableau provided. While, there is pros and cons compared with tableau but for sure the opensource and community eco system is one of the attraictions for non-enterprise users and amateur users.

TBC for usage/configuration of Superset.

Reference

  1. https://superset.incubator.apache.org/
  2. https://www.pervasivecomputing.net/data-analytics/superset-vs-tableau
  3. https://medium.com/airbnb-engineering/caravel-airbnb-s-data-exploration-platform-15a72aa610e5
  4. https://medium.com/airbnb-engineering/superset-scaling-data-access-and-visual-insights-at-airbnb-3ce3e9b88a7f
  5. https://www.xpand-it.com/2018/09/17/apache-superset-open-source-bi/

way to debug vim

Find the right issue submit info, might be some info for getting verbose of runtime debug. Such as SpaceVim Issue Tracker or vim-jedi issue tracker, providing detailed guide for stepwise debugging info

Dis functional shortcut key

Typical scenario would be triggered some error or unexpected behavior after press some key. Then it's essential to get the details by identify what happened firstly after certain key pressed (or what vim thought should be happened)

:verb map K?

Python Plugin

For python plugin, try to find python env issue inside of vim

:python import sys; print sys.path; import jedi

To Be Continue

nikola auto deployment in github pages with custom domain name (CNAME)

While github pages is applicable to using the custom domain name accordingly, which acutally creating a CNAME file with xxx.youdomain.com as contents.

1st trial

It seems currently Nikola did not expected that CNAME as master's content so you have to checkout master manually, and add that CNAME file with the domain name you intend to use, and push into github by youself. * Reminding * the CNAME file created in src branch won't work since nikola github_deploy would ignore and won't copy CNAME into master branch. So if you don't do it manally the auto deploy would be over written again & again even through you settle down the domain name in setting page of github repo or added in "src" branch.

2nd trial

It seems 1st trial works, but after new post build & aut deploymetn by nikola, CNAME disappeared again :( Then, fould this trick, just copy CNAME into files folder in src branch.

One more thing

BTW, the even .gitignore need to double check also in master branch, all these branches would share this simple config file...

Jupyter Extension & Jupyter Lab Config

Jupyter

Enabled extension

First three are used to enable the manager

Nbextensions dashboard tab

Nbextensions edit menu item

contrib_nbextensions_help_item

Below

nbdime

Table of Contents(2)

Zenmode

Comment/Uncomment Hotkey

Snippets Menu

Split Cell Menu

Equation Auto Numbering

Variable Inspector

jupyter-js-widgets/extension

Python markdown

Live Markdown Preview

RICE

Hide input

Latex related (TBC)

Other Configs

Jupyter Lab

Enabled extension

jupyterlab-manager

latex

statusbar

toc

nbdime-jupyterlab

Other Configs

test nicola with markdown

Ch1

Probably all of you learn Flask studies via The Flask Mega-Tutorial. Even though I believe it's typical that starting a tutorial for beginners to start learn by standlone env, a Paas version would be suiable for beginers who are aiming for using Paas as Infrustruction as Paas is "next big thing" and it's popular all over the word even in China. That's enough talk, let's get the work done.

List of tutorials:

  1. getting started and basic configurations in SAE
  2. []

SAE Basic Configuration

Even though sae team claimed that flask only support 0.7 or some other version, I tried latest 0.10.1 in config file it works fine so far.

SVN

Create first version and you would have a init version. detailed useage you could refer SVN Doc for SAE. After checkout, you would have a basic folder name 1 which could be used as a repo for your useage.

Initialize the MySQL

As sooner or later we would adopt MySQL, so we need intiallize it from the control panel.

Local env (optional)

Please refer to SAE python user guide for more details

"Hello World" from Flask in SAE

application folder structure

A recommandated project structure would be like this for now:

1/
    config.yaml
    index.wsgi
    main.py
    app/
        views.py
        __init__.py
        static/
    tmp/

flask modification in SAE

index.wsgi would be frontgate for all request, need a instance to hanle http requests:

import sae

from main import app

application = sae.create_wsgi_app(app)

main.py would be like this:

from app import app
from flask import g, request
import MySQLdb

app.debug = True

from sae.const import (MYSQL_HOST, MYSQL_HOST_S,
    MYSQL_PORT, MYSQL_USER, MYSQL_PASS, MYSQL_DB
)

@app.before_request
def before_request():
    g.db = MySQLdb.connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASS,
                            MYSQL_DB, port=int(MYSQL_PORT))

@app.teardown_request
def teardown_request(exception):
    if hasattr(g, 'db'): g.db.close()

Ready to go

SVN update and visit your application ID, or here to see what is expected.

Ch2

Probably all of you learn Flask studies via The Flask Mega-Tutorial. Even though I believe it's typical that starting a tutorial for beginners to start learn by standlone env, a Paas version would be suiable for beginers who are aiming for using Paas as Infrustruction as Paas is "next big thing" and it's popular all over the word even in China. That's enough talk, let's get the work done.

List of tutorials:

  1. getting started and basic configurations in SAE
  2. [template]

Recap

What we already have now is:

Why we need template?

"Hello World" from Flask in SAE

First impression of template engine in Flask

add index template

create a templates folder in app, the content is

<html>
  <head>
    <title>{{title}} - microblog</title>
  </head>
  <body>
      <h1>Hello, {{user.nickname}}!</h1>
  </body>
</html>

modify view to render from template

view will change into:

from flask import render_template
from app import app

@app.route('/')
@app.route('/index')
def index():
    user = { 'nickname': 'Miguel' } # fake user
    return render_template("index.html",
        title = 'Home',
        user = user)

Then you will find you pass the user name into the template. Jinja2 take care of ...

Higher level templated skills

Let move on for more details

Control

The Jinja2 templates also support control statements, given inside {%...%} blocks. update index template like this:

<html>
  <head>
    {% if title %}
    <title>{{title}} - microblog</title>
    {% else %}
    <title>Welcome to microblog</title>
    {% endif %}
  </head>
  <body>
      <h1>Hello, {{user.nickname}}!</h1>
  </body>
</html>

Then, feel free to remove the title argue in view.py, you will find that logic in index.html will handle missing title situation.

Loop

First of all, you need provide contents(like posts as argue here) in view to show in templates. update view.py as the following:

from app import app
from flask import render_template

@app.route('/')
@app.route('/index')
def index():
    user = {'nickname': "SAE User"} #fake
    posts = [ # fake array of posts
        { 
            'author': { 'nickname': 'John' }, 
            'body': 'Beautiful day in Portland!' 
        },
        { 
            'author': { 'nickname': 'Susan' }, 
            'body': 'The Avengers movie was so cool!' 
        }
    ]
    return render_template("index.html",
        title = 'Home',
        user = user,
        posts = posts)

Then, let us take a look at how for loop works in index.htmljinja2:

<html>
  <head>
    {% if title %}
    <title>{{title}} - microblog</title>
    {% else %}
    <title>microblog</title>
    {% endif %}
  </head>
  <body>
    <h1>Hi, {{user.nickname}}!</h1>
    {% for post in posts %}
    <p>{{post.author.nickname}} says: <b>{{post.body}}</b></p>
    {% endfor %}
  </body>
</html>

Tempalte Inheritance

I could imagine how boring it is to repeat head stuff in htmls everytime when you create tempaltes, while thanks to inheritance of Jinja2, we could make our life easier by creating base.html, in this file we store all the repeating stuff for other siblings of our apps.

<html>
  <head>
    {% if title %}
    <title>{{title}} - microblog</title>
    {% else %}
    <title>microblog</title>
    {% endif %}
  </head>
  <body>
    <div>Microblog: <a href="/index">Home</a></div>
    <hr>
    {% block content %}{% endblock %}
  </body>
</html>

Then, other templates could reuse the generic stuff and do it own specific logic, like index.html:

{% extends "base.html" %}
{% block content %}
<h1>Hi, {{user.nickname}}!</h1>
{% for post in posts %}
<div><p>{{post.author.nickname}} says: <b>{{post.body}}</b></p></div>
{% endfor %}
{% endblock %}

Ready to go

SVN update and visit your application ID in this version to enjoy what you've done already! You could always refer here to see what is expected.

Ch3

Probably all of you learn Flask studies via The Flask Mega-Tutorial. Even though I believe it's typical that starting a tutorial for beginners to start learn by standlone env, a Paas version would be suiable for beginers who are aiming for using Paas as Infrustruction as Paas is "next big thing" and it's popular all over the word even in China. That's enough talk, let's get the work done.

List of tutorials:

  1. getting started and basic configurations in SAE
  2. [template]
  3. [Webform]

Recap

What we already have now is seting up a basic Flask in SAE, and using the template engine in flask. While, web form is always one of the most important way to interact with users. What are we waiting for?

WTF in Flask

about WTF in Flask

In Flask WTF is most widely used form extension for form handling. more details you could get from :

config external packages in SAE

There are several ways to install the packages in SAE:

  • put in the version folder, import them directly
  • use remote API install related packages
  • use virtualenv, then use SAE remote API to install related packages

As there are so many external packages to be installed in SAE, so we preferred 2 or 3 solution in this case. details refer to link or link2, or offical doc.

Anyway, after you installed the nessary package, in our case flask-wtf, you could modify the index.wsgi like this:

import os
import sys
root = os.path.dirname(__file__)
# 两者取其一
sys.path.insert(0, os.path.join(root, 'site-packages')) #if you do not zip the packages
sys.path.insert(0, os.path.join(root, 'site-packages.zip')) # preferred for zipped packages

Then, you could import the packages in your project.

Config flask with the WTF extention

Many extensions require some amount of configuration, fow now we could config our web app with config.py in version 3.

CSRF_ENABLED = True
SECRET_KEY = 'you-will-never-guess'

This is accuary required by WTF, CSRF_ENABLED activates the cross-site request forgery prevention. In most cases you want to have this option enabled as it makes your app more secure.

And SECRET_KEY setting is noly needed when CSRF is enable, and is used to create a cryptographic token that is used to validate a form. WHen you write your own apps, please make sure to set the key into something others nerver to guess.

load config in your flask app

Well, this config file need to imported by our app to use it. Modify the file app/__init__.py as the following:

app.config.from_object('config')

Start using form in Flask

First impression of forms in Flask

Web froms are represented in Flask-WTF as objects, subclassed from class Form. A form subclass simply defines the fields of the form as class variables.

A login form will be created by using OpenID. We don't have to validate passwords.

The OpenID login only requires one string, called OpenID. We will also put a 'remember me' checkbox in the form.

The form would be like this app/form.py:

from flask.ext.wtf import Form
from wtforms import TextField, BooleanField
from wtforms.validators import Required

class LoginForm(Form):
    openid = TextField('openid', validators = [Required()])
    remember_me = BooleanField('remember_me', default = False)

in the file, we imported the Form class, as twoo field classes we needed: TextField and BooleanField. Onething to be highlighted here is the validator in openid field: Required validator simply checks that the field is not submitted empty.

templates for Form

create a templates named login.html for the login form as representative in app/tempaltes, the content is

<!-- extend from base layout -->
{% extends "base.html" %}

{% block content %}
<h1>Sign In</h1>
<form action="" method="post" name="login">
    {{form.hidden_tag()}}
    <p>
        Please enter your OpenID:<br>
        {{form.openid(size=80)}}<br>
    </p>
    <p>{{form.remember_me}} Remember Me</p>
    <p><input type="submit" value="Sign In"></p>
</form>
{% endblock %}

as previous templates intro, inherited from 'base.html' we creat the tempalte for form. While there are several differences which are worth to highlight:

The template expects a form object instantiated from the form class we just defined stored in a template argument named form. We will take care of sending this template argument to the template next, when we write the view function that renders this template.

The form.hidden_tag() template argument will get replaced with a hidden field that implements the CSRF prevention that we enabled in the configuration. This field needs to be in all your forms if you have CSRF enabled.

The actual fields of our form are also rendered by the form object, you just have to refer to a {{form.field_name}} template argument in the place where the field should be inserted. Some fields can take arguments. In our case, we are asking the form to generate our openid field with a width of 80 characters.

Since we have not defined the submit button in the form class we have to define it as a regular field. The submit field does not carry any data so it doesn't need to be defined in the form class.

modify view to render from template

The last step to complete a form is to code a view funtion to handle the specific URL requestion and pass throught the parameteres and render it in the tempaltes.

add contends for form in the app/views.py as following:

from flask import render_template, flash, redirect
from app import app
from forms import LoginForm

#index view function suppressed for brevity

@app.route('/login', methods = ['GET', 'POST'])
def login():
    form = LoginForm()
    return render_template('login.html', 
        title = 'Sign In',
        form = form)

So, we imported the LoginForm Class in view, and sent it to the template. Add in the route decorator, we defined the methods arguments. It tells the flask this view fuction accepts not only GET request, but also POST request..

Not you can see the form in your browser

Receiving data from form submission

How did you handle the data from form? Again, in the view func:

@app.route('/login', methods = ['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        flash('Login requested for OpenID="' + form.openid.data + '", remember_me=' + str(form.remember_me.data))
        return redirect('/index')
    return render_template('login.html', 
        title = 'Sign In',
        form = form)

The validate_on_submit method does all the form processing work. When the form just being presented to the user, then it will return False, which means Flask will just render the template.

If validate_on_submit is called as part of a form submission request, then it will gather all data, run any validators attached to fields, and if everything is all right and it will return True, indicating that the data is valid to proceed next step.

  • if validatio pass through, two more steps are processing after that. flash function is a quick way to show a message on the next page presented to the user. By now, we just show the submitted data to user. of course, we need update the template to show these flash messages. This time we will update in the app/templates/base.html as these flashese would be used as a generic functionalities:
<html>
  <head>
    {% if title %}
    <title>{{title}} - microblog</title>
    {% else %}
    <title>microblog</title>
    {% endif %}
  </head>
  <body>
    <div>Microblog: <a href="/index">Home</a></div>
    <hr>
    {% with messages = get_flashed_messages() %}
    {% if messages %}
    <ul>
    {% for message in messages %}
        <li>{{ message }} </li>
    {% endfor %}
    </ul>
    {% endif %}
    {% endwith %}
    {% block content %}{% endblock %}
  </body>
</html>

Jiajia with will xxxxxxxxxx. the other step is using redirect to tells the client web browser to navigate to a different page(index in this case). noted that the flashed messages will display even if a view function ends in a redirect.

  • if validation teturn Flase, we would see later how to show an error message when vlalidation fails.

This is a great time to start the app and test how the form works. Make sure you try submitting the form with the openid field empty, to see how the Required validator halts the submit process.

improve the field validation

Currently, if validation fails, the page will not submit. While a hint to user which part is missing is helpful to imporvement the usibility. Luckily, Flask-WTF also makes this an easy task.

update app/templates/login.html template like this:

<!-- extend base layout -->
{% extends "base.html" %}

{% block content %}
<h1>Sign In</h1>
<form action="" method="post" name="login">
    {{form.hidden_tag()}}
    <p>
        Please enter your OpenID:<br>
        {{form.openid(size=80)}}<br>
        {% for error in form.errors.openid %}
        <span style="color: red;">[{{error}}]</span>
        {% endfor %}<br>
    </p>
    <p>{{form.remember_me}} Remember Me</p>
    <p><input type="submit" value="Sign In"></p>
</form>
{% endblock %}

The only change we've made is to add a for loop that renders any messages added by the validators below the openid field. As a general rule, any fields that have validators attached will have errors added under form.errors.field_name. In our case we use form.errors.openid. We display these messages in a red style to call the user's attention.

Dealing with OpenID

If you have an account with Google, you have an OpenID with them. Likewise with Yahoo, AOL, Flickr and many other providers.

While we need define which services are supported in our app, so you need update config file(file config.py) like this

CSRF_ENABLED = True
SECRET_KEY = 'you-will-never-guess'

OPENID_PROVIDERS = [
    { 'name': 'Google', 'url': 'https://www.google.com/accounts/o8/id' },
    { 'name': 'Yahoo', 'url': 'https://me.yahoo.com' },
    { 'name': 'AOL', 'url': 'http://openid.aol.com/<username>' },
    { 'name': 'Flickr', 'url': 'http://www.flickr.com/<username>' },
    { 'name': 'MyOpenID', 'url': 'https://www.myopenid.com' }]

Then, let us take a look at how we need update view to handle OpenID:

@app.route('/login', methods = ['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        flash('Login requested for OpenID="' + form.openid.data + '", remember_me=' + str(form.remember_me.data))
        return redirect('/index')
    return render_template('login.html', 
        title = 'Sign In',
        form = form,
        providers = app.config['OPENID_PROVIDERS'])

It just pass through the 'OPENID_PROVIDERS' to tempate.

Now, some modicidations are needed to handle the pass-through OPENID_OROVIDERS, update the app/templates/login.html:

<!-- extend base layout -->
{% extends "base.html" %}

{% block content %}
<script type="text/javascript">
function set_openid(openid, pr)
{
    u = openid.search('<username>')
    if (u != -1) {
        // openid requires username
        user = prompt('Enter your ' + pr + ' username:')
        openid = openid.substr(0, u) + user
    }
    form = document.forms['login'];
    form.elements['openid'].value = openid
}
</script>
<h1>Sign In</h1>
<form action="" method="post" name="login">
    {{form.hidden_tag()}}
    <p>
        Please enter your OpenID, or select one of the providers below:<br>
        {{form.openid(size=80)}}
        {% for error in form.errors.openid %}
        <span style="color: red;">[{{error}}]</span>
        {% endfor %}<br>
        |{% for pr in providers %}
        <a href="javascript:set_openid('{{pr.url}}', '{{pr.name}}');">{{pr.name}}</a> |
        {% endfor %}
    </p>
    <p>{{form.remember_me}} Remember Me</p>
    <p><input type="submit" value="Sign In"></p>
</form>
{% endblock %}

Some OpenIDs include the user's username, so for those we have to have a bit of javascript magic that prompts the user for its username and then composes the OpenID. When the user clicks on an OpenID provider link and (optionally) enters the username, the OpenID for that provider is inserted in the text field.

Ready to go

SVN update and visit your application ID in this version to enjoy what you've done already! You could always refer here to see what is expected.

For now we just finish basic progress regarding forms, there are lots of things to be fixed till we actually do something, such as database setup in next artical.

Ch4

Probably all of you learn Flask studies via The Flask Mega-Tutorial. Even though I believe it's typical that starting a tutorial for beginners to start learn by standlone env, a Paas version would be suiable for beginers who are aiming for using Paas as Infrustruction as Paas is "next big thing" and it's popular all over the word even in China. That's enough talk, let's get the work done.

List of tutorials:

  1. getting started and basic configurations in SAE
  2. [template]
  3. [Webform]
  4. [Database]

Recap

What we already have now is seting up a basic Flask in SAE without anyn database configuration. And as you may already known, database is essential part for web developing and we will start config it in SAE on this artical.

WTF in Flask

about db migration in SAE

There are 3 ways for now I thouht out to migrate db in SAE - Using local db, then update date into remote sae sever. - WTForm Course - flask-wtf

config external packages in SAE

There are several ways to install the packages in SAE:

  • put in the version folder, import them directly
  • use remote API install related packages
  • use virtualenv, then use SAE remote API to install related packages

As there are so many external packages to be installed in SAE, so we preferred 2 or 3 solution in this case. details refer to link or link2, or offical doc.

Anyway, after you installed the nessary package, in our case flask-wtf, you could modify the index.wsgi like this:

import os
import sys
root = os.path.dirname(__file__)
#sys.path.insert(0, os.path.join(root, 'site-packages')) #if you do not zip the packages
sys.path.insert(0, os.path.join(root, 'site-packages.zip')) # preferred for zipped packages

Then, you could import the packages in your project.

Config flask with the WTF extention

Many extensions require some amount of configuration, fow now we could config our web app with config.py in version 3.

CSRF_ENABLED = True
SECRET_KEY = 'you-will-never-guess'

This is accuary required by WTF, CSRF_ENABLED activates the cross-site request forgery prevention. In most cases you want to have this option enabled as it makes your app more secure.

And SECRET_KEY setting is noly needed when CSRF is enable, and is used to create a cryptographic token that is used to validate a form. WHen you write your own apps, please make sure to set the key into something others nerver to guess.

load config in your flask app

Well, this config file need to imported by our app to use it. Modify the file app/__init__.py as the following:

app.config.from_object('config')

Start using form in Flask

First impression of forms in Flask

Web froms are represented in Flask-WTF as objects, subclassed from class Form. A form subclass simply defines the fields of the form as class variables.

A login form will be created by using OpenID. We don't have to validate passwords.

The OpenID login only requires one string, called OpenID. We will also put a 'remember me' checkbox in the form.

The form would be like this app/form.py:

from flask.ext.wtf import Form
from wtforms import TextField, BooleanField
from wtforms.validators import Required

class LoginForm(Form):
    openid = TextField('openid', validators = [Required()])
    remember_me = BooleanField('remember_me', default = False)

in the file, we imported the Form class, as twoo field classes we needed: TextField and BooleanField. Onething to be highlighted here is the validator in openid field: Required validator simply checks that the field is not submitted empty.

templates for Form

create a templates named login.html for the login form as representative in app/tempaltes, the content is

<!-- extend from base layout -->
{% extends "base.html" %}

{% block content %}
<h1>Sign In</h1>
<form action="" method="post" name="login">
    {{form.hidden_tag()}}
    <p>
        Please enter your OpenID:<br>
        {{form.openid(size=80)}}<br>
    </p>
    <p>{{form.remember_me}} Remember Me</p>
    <p><input type="submit" value="Sign In"></p>
</form>
{% endblock %}

as previous templates intro, inherited from 'base.html' we creat the tempalte for form. While there are several differences which are worth to highlight:

The template expects a form object instantiated from the form class we just defined stored in a template argument named form. We will take care of sending this template argument to the template next, when we write the view function that renders this template.

The form.hidden_tag() template argument will get replaced with a hidden field that implements the CSRF prevention that we enabled in the configuration. This field needs to be in all your forms if you have CSRF enabled.

The actual fields of our form are also rendered by the form object, you just have to refer to a {{form.field_name}} template argument in the place where the field should be inserted. Some fields can take arguments. In our case, we are asking the form to generate our openid field with a width of 80 characters.

Since we have not defined the submit button in the form class we have to define it as a regular field. The submit field does not carry any data so it doesn't need to be defined in the form class.

modify view to render from template

The last step to complete a form is to code a view funtion to handle the specific URL requestion and pass throught the parameteres and render it in the tempaltes.

add contends for form in the app/views.py as following:

from flask import render_template, flash, redirect
from app import app
from forms import LoginForm

#index view function suppressed for brevity

@app.route('/login', methods = ['GET', 'POST'])
def login():
    form = LoginForm()
    return render_template('login.html', 
        title = 'Sign In',
        form = form)

So, we imported the LoginForm Class in view, and sent it to the template. Add in the route decorator, we defined the methods arguments. It tells the flask this view fuction accepts not only GET request, but also POST request..

Not you can see the form in your browser

Receiving data from form submission

How did you handle the data from form? Again, in the view func:

@app.route('/login', methods = ['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        flash('Login requested for OpenID="' + form.openid.data + '", remember_me=' + str(form.remember_me.data))
        return redirect('/index')
    return render_template('login.html', 
        title = 'Sign In',
        form = form)

The validate_on_submit method does all the form processing work. When the form just being presented to the user, then it will return False, which means Flask will just render the template.

If validate_on_submit is called as part of a form submission request, then it will gather all data, run any validators attached to fields, and if everything is all right and it will return True, indicating that the data is valid to proceed next step.

  • if validatio pass through, two more steps are processing after that. flash function is a quick way to show a message on the next page presented to the user. By now, we just show the submitted data to user. of course, we need update the template to show these flash messages. This time we will update in the app/templates/base.html as these flashese would be used as a generic functionalities:
<html>
  <head>
    {% if title %}
    <title>{{title}} - microblog</title>
    {% else %}
    <title>microblog</title>
    {% endif %}
  </head>
  <body>
    <div>Microblog: <a href="/index">Home</a></div>
    <hr>
    {% with messages = get_flashed_messages() %}
    {% if messages %}
    <ul>
    {% for message in messages %}
        <li>{{ message }} </li>
    {% endfor %}
    </ul>
    {% endif %}
    {% endwith %}
    {% block content %}{% endblock %}
  </body>
</html>

Jiajia with will xxxxxxxxxx. the other step is using redirect to tells the client web browser to navigate to a different page(index in this case). noted that the flashed messages will display even if a view function ends in a redirect.

  • if validation teturn Flase, we would see later how to show an error message when vlalidation fails.

This is a great time to start the app and test how the form works. Make sure you try submitting the form with the openid field empty, to see how the Required validator halts the submit process.

improve the field validation

Currently, if validation fails, the page will not submit. While a hint to user which part is missing is helpful to imporvement the usibility. Luckily, Flask-WTF also makes this an easy task.

update app/templates/login.html template like this:

<!-- extend base layout -->
{% extends "base.html" %}

{% block content %}
<h1>Sign In</h1>
<form action="" method="post" name="login">
    {{form.hidden_tag()}}
    <p>
        Please enter your OpenID:<br>
        {{form.openid(size=80)}}<br>
        {% for error in form.errors.openid %}
        <span style="color: red;">[{{error}}]</span>
        {% endfor %}<br>
    </p>
    <p>{{form.remember_me}} Remember Me</p>
    <p><input type="submit" value="Sign In"></p>
</form>
{% endblock %}

The only change we've made is to add a for loop that renders any messages added by the validators below the openid field. As a general rule, any fields that have validators attached will have errors added under form.errors.field_name. In our case we use form.errors.openid. We display these messages in a red style to call the user's attention.

Dealing with OpenID

If you have an account with Google, you have an OpenID with them. Likewise with Yahoo, AOL, Flickr and many other providers.

While we need define which services are supported in our app, so you need update config file(file config.py) like this

CSRF_ENABLED = True
SECRET_KEY = 'you-will-never-guess'

OPENID_PROVIDERS = [
    { 'name': 'Google', 'url': 'https://www.google.com/accounts/o8/id' },
    { 'name': 'Yahoo', 'url': 'https://me.yahoo.com' },
    { 'name': 'AOL', 'url': 'http://openid.aol.com/<username>' },
    { 'name': 'Flickr', 'url': 'http://www.flickr.com/<username>' },
    { 'name': 'MyOpenID', 'url': 'https://www.myopenid.com' }]

Then, let us take a look at how we need update view to handle OpenID:

@app.route('/login', methods = ['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        flash('Login requested for OpenID="' + form.openid.data + '", remember_me=' + str(form.remember_me.data))
        return redirect('/index')
    return render_template('login.html', 
        title = 'Sign In',
        form = form,
        providers = app.config['OPENID_PROVIDERS'])

It just pass through the 'OPENID_PROVIDERS' to tempate.

Now, some modicidations are needed to handle the pass-through OPENID_OROVIDERS, update the app/templates/login.html:

<!-- extend base layout -->
{% extends "base.html" %}

{% block content %}
<script type="text/javascript">
function set_openid(openid, pr)
{
    u = openid.search('<username>')
    if (u != -1) {
        // openid requires username
        user = prompt('Enter your ' + pr + ' username:')
        openid = openid.substr(0, u) + user
    }
    form = document.forms['login'];
    form.elements['openid'].value = openid
}
</script>
<h1>Sign In</h1>
<form action="" method="post" name="login">
    {{form.hidden_tag()}}
    <p>
        Please enter your OpenID, or select one of the providers below:<br>
        {{form.openid(size=80)}}
        {% for error in form.errors.openid %}
        <span style="color: red;">[{{error}}]</span>
        {% endfor %}<br>
        |{% for pr in providers %}
        <a href="javascript:set_openid('{{pr.url}}', '{{pr.name}}');">{{pr.name}}</a> |
        {% endfor %}
    </p>
    <p>{{form.remember_me}} Remember Me</p>
    <p><input type="submit" value="Sign In"></p>
</form>
{% endblock %}

Some OpenIDs include the user's username, so for those we have to have a bit of javascript magic that prompts the user for its username and then composes the OpenID. When the user clicks on an OpenID provider link and (optionally) enters the username, the OpenID for that provider is inserted in the text field.

Ready to go

SVN update and visit your application ID in this version to enjoy what you've done already! You could always refer here to see what is expected.

For now we just finish basic progress regarding forms, there are lots of things to be fixed till we actually do something, such as database setup in next artical.

刚做好的pizza

image121792455.jpg蜗居期间,用新的烤箱做的。。。。

还做了比外边任何地方都好吃的烤地瓜

还有绝妙的烤鱼。

终于知道,美味的最高境界就是:

自己动手,创造适合自己口味的美食!!

太惬意了~

PS:用iPhone照的照片,将就留个纪念吧,哈哈。另外吧GAE搭起来把Blogger备份了过来,希望这次不会被封。。。

Mobile Blogging from here.  [Posted with from my iPhone]