Share
Embed
Flask By Example - PyCon 2014
(Presented at PyCon 2014)
Watch here: https://www.youtube.com/watch?v=FGrIyBDQLPg
Flask is a web framework for Python based on Werkzeug, Jinja 2 and good intentions. It is considered a micro-framework, but don't get the "micro" part fool you; Flask can do everything others can do, many times in a simpler, leaner way.
In this tutorial session you will follow me as I write a complete web application in front of your eyes using the core framework and a handful of extensions.
Miguel Grinberg
April 10, 2014
Transcript
123 slides
-
Flask and Me Flask and Me My blog http://blog.miguelgrinberg.com is
powered by Flask. I wrote the Flask Mega-Tutorial 18-part series. I wrote several articles on API development with Flask. My most popular Flask extensions: I wrote the book "Flask Web Development" for O'Reilly, in bookstores in May 2014. · · · · Flask-SocketIO (WebSocket communication) Flask-Migrate (Database migrations with Alembic) Flask-HTTPAuth (RESTful authentication) Flask-PageDown (Live Markdown editor) Flask-Moment (Rendering of dates and times) - - - - - · 2/123 -
About This Tutorial Tutorial Pre-requisites Software Requirements About This Tutorial
•· Previous Python coding experience Basic knowledge of HTML and CSS A bit of JavaScript will definitely not hurt · · · Python 2.7 or 3.3+ on any supported OS virtualenv (or pyvenv on Python 3.4) git Network connection (only to install the application) · · · · 4/123 -
About This Tutorial (cont'd) Your homework: About This Tutorial ·•
Today: Later: · Watch me build an application Ask questions - - · Hack on the example application, take what you want to seed your own project! Complete your knowledge with the documentation Ask questions Show me what you build! - - - - 5/123 -
Talks Features: The Example Application •···· One or more speakers
can publish their talks. For each talk the slides and/or video can be embedded. The home page shows a timeline of talks by all speakers. Each speaker has a profile page with information and a list of talks. Users can write questions or comments using Markdown syntax. Speakers moderate comments written on their talks. Administrators moderate comments written on all talks. Email notifications are sent when new comments are written. Lists of talks and comments are paginated. Check out the live app at http://talks.miguelgrinberg.com. · · · · · · · · · · 7/123 -
Talks (cont'd) Project structure: The Example Application ·•··· Available on
github: https://github.com/miguelgrinberg/flask-pycon2014. There are 24 incremental versions tagged v 0 . 1 to v 0 . 2 4 . You can checkout any version to run or study in detail. Click the commit on github to see a detailed list of changes that went into a particular feature. · · · · 8/123 -
Application Setup Clone the git repository: Create a virtual environment
(Linux/OSX): Create a virtual environment (Windows): The Example Application ··•·· $ g i t c l o n e h t t p s : / / g i t h u b . c o m / m i g u e l g r i n b e r g / f l a s k - p y c o n 2 0 1 4 . g i t $ c d f l a s k - p y c o n 2 0 1 4 s h e l l $ v i r t u a l e n v v e n v $ s o u r c e v e n v / b i n / a c t i v a t e ( v e n v ) $ p i p i n s t a l l - r r e q u i r e m e n t s . t x t s h e l l > v i r t u a l e n v v e n v > v e n v \ S c r i p t s \ a c t i v a t e ( v e n v ) > p i p i n s t a l l - r r e q u i r e m e n t s . t x t s h e l l 9/123 -
Application Setup (cont'd) Register a user: Configure a gmail account
as email server (Linux/OSX): Configure a gmail account as email server (Windows): The Example Application ···•· ( v e n v ) $ p y t h o n m a n a g e . p y a d d u s e r - - a d m i n < y o u r - e m a i l - a d d r e s s > < y o u r - u s e r n a m e > P a s s w o r d : < y o u r - p a s s w o r d > C o n f i r m : < y o u r - p a s s w o r d > U s e r < y o u r - u s e r n a m e > w a s r e g i s t e r e d s u c c e s s f u l l y . s h e l l ( v e n v ) $ e x p o r t M A I L _ U S E R N A M E = < y o u r - g m a i l - u s e r n a m e > ( v e n v ) $ e x p o r t M A I L _ P A S S W O R D = < y o u r - g m a i l - p a s s w o r d > s h e l l ( v e n v ) > s e t M A I L _ U S E R N A M E = < y o u r - g m a i l - u s e r n a m e > ( v e n v ) > s e t M A I L _ P A S S W O R D = < y o u r - g m a i l - p a s s w o r d > s h e l l 10/123 -
Application Setup (cont'd) Start the web server: Type h t
t p : / / l o c a l h o s t : 5 0 0 0 in your browser's address bar! The Example Application ····• ( v e n v ) $ p y t h o n m a n a g e . p y r u n s e r v e r * R u n n i n g o n h t t p : / / 1 2 7 . 0 . 0 . 1 : 5 0 0 0 / * R e s t a r t i n g w i t h r e l o a d e r s h e l l 11/123 -
Up and Running Step 1: Install Flask in a virtual
environment Flask from Scratch •········ $ v i r t u a l e n v v e n v $ s o u r c e v e n v / b i n / a c t i v a t e ( v e n v ) $ p i p i n s t a l l f l a s k s h e l l 13/123 -
Up and Running (cont'd) Step 2: Create an application instance
Flask from Scratch ·•······· f r o m f l a s k i m p o r t F l a s k a p p = F l a s k ( _ _ n a m e _ _ ) h e l l o . p y 14/123 -
Up and Running (cont'd) Step 3: Define routes Flask from
Scratch ··•······ f r o m f l a s k i m p o r t F l a s k a p p = F l a s k ( _ _ n a m e _ _ ) @ a p p . r o u t e ( ' / ' ) d e f i n d e x ( ) : r e t u r n ' < h 1 > H e l l o W o r l d ! < / h 1 > ' @ a p p . r o u t e ( ' / u s e r / < n a m e > ' ) d e f u s e r ( n a m e ) : r e t u r n ' < h 1 > H e l l o , { 0 } ! < / h 1 > ' . f o r m a t ( n a m e ) h e l l o . p y 15/123 -
Up and Running (cont'd) Step 4: Start the development web
server Flask from Scratch ···•····· f r o m f l a s k i m p o r t F l a s k a p p = F l a s k ( _ _ n a m e _ _ ) @ a p p . r o u t e ( ' / ' ) d e f i n d e x ( ) : r e t u r n ' < h 1 > H e l l o W o r l d ! < / h 1 > ' @ a p p . r o u t e ( ' / u s e r / < n a m e > ' ) d e f u s e r ( n a m e ) : r e t u r n ' < h 1 > H e l l o , { 0 } ! < / h 1 > ' . f o r m a t ( n a m e ) i f _ _ n a m e _ _ = = ' _ _ m a i n _ _ ' : a p p . r u n ( d e b u g = T r u e ) h e l l o . p y 16/123 -
Up and Running (cont'd) Step 5: Run as a normal
Python script! Flask from Scratch ····•···· ( v e n v ) $ p y t h o n h e l l o . p y * R u n n i n g o n h t t p : / / 1 2 7 . 0 . 0 . 1 : 5 0 0 0 / * R e s t a r t i n g w i t h r e l o a d e r s h e l l 17/123 -
Decorators Flask from Scratch ·····•··· Decorators are used extensively by
the framework and extensions to register application provided functions as callbacks. Useful decorators: Many Flask extensions define their own decorators as well. · · r o u t e registers functions to handle routes. b e f o r e _ r e q u e s t registers a function to run before request handlers. b e f o r e _ f i r s t _ r e q u e s t is similar, but only once at the start. a f t e r _ r e q u e s t registers a function to run after request handlers run. t e a r d o w n _ r e q u e s t registers a function to run after request handlers run, even if they throw an exception. e r r o r h a n d l e r defines a custom error handler. - - - - - - · 18/123 -
Context Globals Flask from Scratch ······•·· Context globals avoid the
need to pass important variables to request handlers. Flask's application context defines the following context globals: Flask's request context defines the following context globals: Some Flask extensions define their own context globals as well. · · c u r r e n t _ a p p is the application instance. g is a global dictionary for request data storage. - - · r e q u e s t is the request being processed by the thread. s e s s i o n is the user session storage. - - · 19/123 -
Helper functions Flask from Scratch ·······•· Flask provides several auxiliary
functions for , among them: · u r l _ f o r ( ) generates links to routes or static files. r e n d e r _ t e m p l a t e ( ) renders Jinja2 templates. r e d i r e c t ( ) generates a redirect response. j s o n i f y ( ) generates a JSON response. a b o r t ( ) generates an error response (throws an exception). f l a s h ( ) registers a message to display to the user. - - - - - - 20/123 -
Structure for Larger Projects This structure is not set in
stone. Customize as you see fit! v0.1 •······· | - t a l k s < - - p r o j e c t f o l d e r | - a p p / < - - a p p l i c a t i o n p a c k a g e | - t e s t s / < - - u n i t t e s t s p a c k a g e | - v e n v / < - - v i r t u a l e n v i r o n m e n t | - r e q u i r e m e n t s . t x t < - - d e p e n d e n c i e s | - c o n f i g . p y < - - c o n f i g u r a t i o n s | - m a n a g e . p y < - - l a u n c h s c r i p t 23/123 -
The Application Package v0.1 ·•······ Templates, static files get each
a dedicated folder. Blueprints are implemented in sub-packages, and also get a sub-folder inside templates. Common functionality such as models are implemented as modules. · · · | - a p p / < - - a p p l i c a t i o n p a c k a g e | - t e m p l a t e s / < - - b a s e t e m p l a t e f o l d e r | - t a l k s / < - - m a i n b l u e p r i n t t e m p l a t e s | - s t a t i c / < - - s t a t i c f i l e s | - t a l k s / < - - a p p l i c a t i o n b l u e p r i n t | - _ _ i n i t _ _ . p y < - - a p p l i c a t i o n f a c t o r y | - m o d e l s . p y < - - d a t a b a s e m o d e l s 24/123 -
Configuration The base configuration class holds common settings, overloaded in
subclasses as necessary. v0.1 ··•····· i m p o r t o s c l a s s C o n f i g : S E C R E T _ K E Y = o s . e n v i r o n . g e t ( ' S E C R E T _ K E Y ' ) c l a s s D e v e l o p m e n t C o n f i g ( C o n f i g ) : D E B U G = T r u e S E C R E T _ K E Y = o s . e n v i r o n . g e t ( ' S E C R E T _ K E Y ' ) o r ' t 0 p s 3 c r 3 t ' c l a s s T e s t i n g C o n f i g ( C o n f i g ) : T E S T I N G = T r u e c l a s s P r o d u c t i o n C o n f i g ( C o n f i g ) : p a s s c o n f i g = { ' d e v e l o p m e n t ' : D e v e l o p m e n t C o n f i g , ' t e s t i n g ' : T e s t i n g C o n f i g , ' p r o d u c t i o n ' : P r o d u c t i o n C o n f i g , ' d e f a u l t ' : D e v e l o p m e n t C o n f i g } c o n f i g . p y 25/123 -
Application Factory Pattern v0.1 ···•···· The application instance is created
and configured at run-time. Routes are imported from a blueprint. · · f r o m f l a s k i m p o r t F l a s k f r o m c o n f i g i m p o r t c o n f i g d e f c r e a t e _ a p p ( c o n f i g _ n a m e ) : a p p = F l a s k ( _ _ n a m e _ _ ) a p p . c o n f i g . f r o m _ o b j e c t ( c o n f i g [ c o n f i g _ n a m e ] ) f r o m . t a l k s i m p o r t t a l k s a s t a l k s _ b l u e p r i n t a p p . r e g i s t e r _ b l u e p r i n t ( t a l k s _ b l u e p r i n t ) r e t u r n a p p a p p / _ _ i n i t _ _ . p y 26/123 -
Blueprints v0.1 ····•··· Blueprints are containers for routes, static files
and/or templates. A blueprint becomes part of the application when it is registered with it. · · f r o m f l a s k i m p o r t B l u e p r i n t t a l k s = B l u e p r i n t ( ' t a l k s ' , _ _ n a m e _ _ ) f r o m . i m p o r t r o u t e s a p p / t a l k s / _ _ i n i t _ _ . p y f r o m f l a s k i m p o r t r e n d e r _ t e m p l a t e f r o m . i m p o r t t a l k s @ t a l k s . r o u t e ( ' / ' ) d e f i n d e x ( ) : r e t u r n r e n d e r _ t e m p l a t e ( ' t a l k s / i n d e x . h t m l ' ) @ t a l k s . r o u t e ( ' / u s e r / < u s e r n a m e > ' ) d e f u s e r ( u s e r n a m e ) : r e t u r n r e n d e r _ t e m p l a t e ( ' t a l k s / u s e r . h t m l ' , u s e r n a m e = u s e r n a m e ) a p p / t a l k s / r o u t e s . p y 27/123 -
Templates v0.1 ·····•·· Templates help separate logic and presentation. The
default template engine for Flask is Jinja2, by the same developer. Dynamic parts are specified with placeholder variables. A wide array of control directives are available in templates. · · · · < h 1 > H e l l o , W o r l d ! < / h 1 > a p p / t e m p l a t e s / t a l k s / i n d e x . h t m l < h 1 > H e l l o , { { u s e r n a m e } } ! < / h 1 > a p p / t e m p l a t e s / t a l k s / u s e r . h t m l 28/123 -
Launch script Flask-Script is an extension that adds command line
options to Flask. v0.1 ······•· ( v e n v ) $ p i p i n s t a l l f l a s k - s c r i p t s h e l l # ! / u s r / b i n / e n v p y t h o n i m p o r t o s f r o m a p p i m p o r t c r e a t e _ a p p f r o m f l a s k . e x t . s c r i p t i m p o r t M a n a g e r a p p = c r e a t e _ a p p ( o s . g e t e n v ( ' F L A S K _ C O N F I G ' ) o r ' d e f a u l t ' ) m a n a g e r = M a n a g e r ( a p p ) i f _ _ n a m e _ _ = = ' _ _ m a i n _ _ ' : m a n a g e r . r u n ( ) m a n a g e . p y ( v e n v ) $ p y t h o n m a n a g e . p y r u n s e r v e r s h e l l 29/123 -
Avoiding Circular Dependencies v0.1 ·······• Frequent problem with Flask applications
that are split in modules. Most common instance: Two tricks that help avoid this problem: · · Blueprint instance is created in _ _ i n i t _ _ . p y Routes are defined in r o u t e s . p y and need to import blueprint instance to get the r o u t e decorator. _ _ i n i t _ _ . p y needs to import the routes to register them with the blueprint. - - - · If routes are imported at the bottom of _ _ i n i t _ _ . p y then the circular dependency remains, but it does not cause errors. Importing symbols inside the function that needs them can sometimes avoid circular dependencies. - - 30/123 -
Flask-Bootstrap v0.2 •· Flask extension that provides the base HTML
document with Bootstrap libraries imported. The official Bootstrap documentation has lots of copy/paste ready examples: http://getbootstrap.com. · · ( v e n v ) $ p i p i n s t a l l f l a s k - b o o t s t r a p s h e l l f r o m f l a s k . e x t . b o o t s t r a p i m p o r t B o o t s t r a p b o o t s t r a p = B o o t s t r a p ( ) d e f c r e a t e _ a p p ( c o n f i g _ n a m e ) : # . . . b o o t s t r a p . i n i t _ a p p ( a p p ) # . . . r e t u r n a p p a p p / _ _ i n i t _ _ . p y 32/123 -
Flask-Bootstrap (cont'd) Jinja2's template inheritance feature is used to greatly
simplify the integration with the Bootstrap libraries. v0.2 ·• { % e x t e n d s " b o o t s t r a p / b a s e . h t m l " % } { % b l o c k t i t l e % } T a l k s { % e n d b l o c k % } { % b l o c k n a v b a r % } . . . { % e n d b l o c k % } { % b l o c k c o n t e n t % } < d i v c l a s s = " c o n t a i n e r " > < h 1 > H e l l o , W o r l d ! < / h 1 > < / d i v > { % e n d b l o c k % } a p p / t e m p l a t e s / t a l k s / i n d e x . h t m l 33/123 -
Template Inheritance An additional level of template inheritance is used
to eliminate duplication of markup. v0.3 •· { % e x t e n d s " b o o t s t r a p / b a s e . h t m l " % } { % b l o c k t i t l e % } T a l k s { % e n d b l o c k % } { % b l o c k n a v b a r % } . . . { % e n d b l o c k % } { % b l o c k c o n t e n t % } < d i v c l a s s = " c o n t a i n e r " > { % b l o c k p a g e _ c o n t e n t % } { % e n d b l o c k % } < / d i v > { % e n d b l o c k % } a p p / t e m p l a t e s / b a s e . h t m l 35/123 -
Template Inheritance (cont'd) The application's templates inherit from the new
base template. v0.3 ·• { % e x t e n d s " b a s e . h t m l " % } { % b l o c k p a g e _ c o n t e n t % } < h 1 > H e l l o , W o r l d ! < / h 1 > { % e n d b l o c k % } a p p / t e m p l a t e s / t a l k s / i n d e x . h t m l { % e x t e n d s " b a s e . h t m l " % } { % b l o c k p a g e _ c o n t e n t % } < d i v c l a s s = " p a g e - h e a d e r " > < h 1 > H e l l o , { { u s e r n a m e } } ! < / h 1 > < / d i v > { % e n d b l o c k % } a p p / t e m p l a t e s / t a l k s / u s e r . h t m l 36/123 -
Links Flask's u r l _ f o r (
) function is used to generate application links. v0.4 • . . . < a c l a s s = " n a v b a r - b r a n d " h r e f = " { { u r l _ f o r ( ' t a l k s . i n d e x ' ) } } " > T a l k s < / a > . . . < u l c l a s s = " n a v n a v b a r - n a v " > < l i > < a h r e f = " { { u r l _ f o r ( ' t a l k s . i n d e x ' ) } } " > H o m e < / a > < / l i > < l i > < a h r e f = " { { u r l _ f o r ( ' t a l k s . u s e r ' , u s e r n a m e = ' m i g u e l ' ) } } " > P r o f i l e < / a > < / l i > < / u l > . . . a p p / t e m p l a t e s / b a s e . h t m l For routes created with the a p p . r o u t e decorator the function name is used. For blueprint routes the blueprint name and the function name are separated by a dot. If the blueprint name is not given it is taken from the running context. Static files can be referenced with endpoint name ' s t a t i c ' and a f i l e n a m e argument. · Example: u r l _ f o r ( ' i n d e x ' ) - · Example: u r l _ f o r ( ' t a l k s . i n d e x ' ) - · Example: u r l _ f o r ( ' . i n d e x ' ) - · Example: u r l _ f o r ( ' s t a t i c ' , f i l e n a m e = ' s t y l e s . c s s ' ) - 38/123 -
Flask-SQLAlchemy v0.5 •··· Flask-SQLAlchemy nicely integrates SQLAlchemy with Flask applications.
Documentation links: · http://pythonhosted.org/Flask-SQLAlchemy/ http://docs.sqlalchemy.org/ - - ( v e n v ) $ p i p i n s t a l l f l a s k - s q l a l c h e m y s h e l l b a s e d i r = o s . p a t h . a b s p a t h ( o s . p a t h . d i r n a m e ( _ _ f i l e _ _ ) ) c l a s s D e v e l o p m e n t C o n f i g ( C o n f i g ) : # . . . S Q L A L C H E M Y _ D A T A B A S E _ U R I = o s . e n v i r o n . g e t ( ' D E V _ D A T A B A S E _ U R L ' ) o r \ ' s q l i t e : / / / ' + o s . p a t h . j o i n ( b a s e d i r , ' d a t a - d e v . s q l i t e ' ) c l a s s T e s t i n g C o n f i g ( C o n f i g ) : # . . . S Q L A L C H E M Y _ D A T A B A S E _ U R I = o s . e n v i r o n . g e t ( ' T E S T _ D A T A B A S E _ U R L ' ) o r \ ' s q l i t e : / / / ' + o s . p a t h . j o i n ( b a s e d i r , ' d a t a - t e s t . s q l i t e ' ) c l a s s P r o d u c t i o n C o n f i g ( C o n f i g ) : # . . . S Q L A L C H E M Y _ D A T A B A S E _ U R I = o s . e n v i r o n . g e t ( ' D A T A B A S E _ U R L ' ) o r \ ' s q l i t e : / / / ' + o s . p a t h . j o i n ( b a s e d i r , ' d a t a . s q l i t e ' ) c o n f i g . p y 40/123 -
Flask-SQLAlchemy (cont'd) v0.5 ·•·· f r o m f l
a s k . e x t . s q l a l c h e m y i m p o r t S Q L A l c h e m y d b = S Q L A l c h e m y ( ) d e f c r e a t e _ a p p ( c o n f i g _ n a m e ) : # . . . d b . i n i t _ a p p ( a p p ) # . . . r e t u r n a p p a p p / _ _ i n i t _ _ . p y 41/123 -
Model Definition Models are defined as Python classes. v0.5 ··•·
f r o m d a t e t i m e i m p o r t d a t e t i m e f r o m . i m p o r t d b c l a s s U s e r ( d b . M o d e l ) : _ _ t a b l e n a m e _ _ = ' u s e r s ' i d = d b . C o l u m n ( d b . I n t e g e r , p r i m a r y _ k e y = T r u e ) e m a i l = d b . C o l u m n ( d b . S t r i n g ( 6 4 ) , n u l l a b l e = F a l s e , u n i q u e = T r u e , i n d e x = T r u e ) u s e r n a m e = d b . C o l u m n ( d b . S t r i n g ( 6 4 ) , n u l l a b l e = F a l s e , u n i q u e = T r u e , i n d e x = T r u e ) i s _ a d m i n = d b . C o l u m n ( d b . B o o l e a n ) p a s s w o r d _ h a s h = d b . C o l u m n ( d b . S t r i n g ( 1 2 8 ) ) n a m e = d b . C o l u m n ( d b . S t r i n g ( 6 4 ) ) l o c a t i o n = d b . C o l u m n ( d b . S t r i n g ( 6 4 ) ) b i o = d b . C o l u m n ( d b . T e x t ( ) ) m e m b e r _ s i n c e = d b . C o l u m n ( d b . D a t e T i m e ( ) , d e f a u l t = d a t e t i m e . u t c n o w ) a v a t a r _ h a s h = d b . C o l u m n ( d b . S t r i n g ( 3 2 ) ) a p p / m o d e l s . p y 42/123 -
Database creation v0.5 ···• The database can be created and
destroyed from a Python shell. · ( v e n v ) $ p y t h o n m a n a g e . p y s h e l l > > > f r o m a p p i m p o r t d b > > > d b . c r e a t e _ a l l ( ) > > > d b . d r o p _ a l l ( ) s h e l l For medium to large applications it is strongly recommended to use a schema migration framework such as Flask-Migrate (Alembic). · 43/123 -
Password Hashing v0.6 • Password hashing is very hard to
get right if you do it on your own! Werkzeug provides secure password hashing and verification functions that use a unique random salt per password and PBKDF2 key derivation. · · f r o m w e r k z e u g . s e c u r i t y i m p o r t g e n e r a t e _ p a s s w o r d _ h a s h , c h e c k _ p a s s w o r d _ h a s h c l a s s U s e r ( d b . M o d e l ) : # . . . @ p r o p e r t y d e f p a s s w o r d ( s e l f ) : r a i s e A t t r i b u t e E r r o r ( ' p a s s w o r d i s n o t a r e a d a b l e a t t r i b u t e ' ) @ p a s s w o r d . s e t t e r d e f p a s s w o r d ( s e l f , p a s s w o r d ) : s e l f . p a s s w o r d _ h a s h = g e n e r a t e _ p a s s w o r d _ h a s h ( p a s s w o r d ) d e f v e r i f y _ p a s s w o r d ( s e l f , p a s s w o r d ) : r e t u r n c h e c k _ p a s s w o r d _ h a s h ( s e l f . p a s s w o r d _ h a s h , p a s s w o r d ) a p p / m o d e l s . p y 45/123 -
More on Blueprints Applications can have multiple blueprints. v0.7 •·
f r o m f l a s k i m p o r t B l u e p r i n t a u t h = B l u e p r i n t ( ' a u t h ' , _ _ n a m e _ _ ) f r o m . i m p o r t r o u t e s a p p / a u t h / _ _ i n i t _ _ . p y f r o m f l a s k i m p o r t r e n d e r _ t e m p l a t e f r o m . i m p o r t a u t h @ a u t h . r o u t e ( ' / l o g i n ' ) d e f l o g i n ( ) : r e t u r n r e n d e r _ t e m p l a t e ( ' a u t h / l o g i n . h t m l ' ) a p p / a u t h / r o u t e s . p y 47/123 -
More on Blueprints (cont'd) Blueprints can be registered with a
URL prefix. v0.7 ·• d e f c r e a t e _ a p p ( c o n f i g _ n a m e ) : # . . . f r o m . a u t h i m p o r t a u t h a s a u t h _ b l u e p r i n t a p p . r e g i s t e r _ b l u e p r i n t ( a u t h _ b l u e p r i n t , u r l _ p r e f i x = ' / a u t h ' ) # . . . r e t u r n a p p a p p / _ _ i n i t _ _ . p y 48/123 -
User Registration In this application users are registered from the
command line with a custom Flask-Script command. v0.8 •· f r o m a p p i m p o r t d b f r o m a p p . m o d e l s i m p o r t U s e r # . . . @ m a n a g e r . c o m m a n d d e f a d d u s e r ( e m a i l , u s e r n a m e , a d m i n = F a l s e ) : " " " R e g i s t e r a n e w u s e r . " " " f r o m g e t p a s s i m p o r t g e t p a s s p a s s w o r d = g e t p a s s ( ) p a s s w o r d 2 = g e t p a s s ( p r o m p t = ' C o n f i r m : ' ) i f p a s s w o r d ! = p a s s w o r d 2 : i m p o r t s y s s y s . e x i t ( ' E r r o r : p a s s w o r d s d o n o t m a t c h . ' ) d b . c r e a t e _ a l l ( ) u s e r = U s e r ( e m a i l = e m a i l , u s e r n a m e = u s e r n a m e , p a s s w o r d = p a s s w o r d , i s _ a d m i n = a d m i n ) d b . s e s s i o n . a d d ( u s e r ) d b . s e s s i o n . c o m m i t ( ) p r i n t ( ' U s e r { 0 } w a s r e g i s t e r e d s u c c e s s f u l l y . ' . f o r m a t ( u s e r n a m e ) ) m a n a g e . p y 50/123 -
Custom Flask-Script Commands Flask-Script uses introspection to generate the command
line help messages. Example usage: v0.8 ·• ( v e n v ) $ p y t h o n m a n a g e . p y a d d u s e r - - h e l p u s a g e : m a n a g e . p y a d d u s e r [ - h ] [ - a ] e m a i l u s e r n a m e R e g i s t e r a n e w u s e r . p o s i t i o n a l a r g u m e n t s : e m a i l u s e r n a m e o p t i o n a l a r g u m e n t s : - h , - - h e l p s h o w t h i s h e l p m e s s a g e a n d e x i t - a , - - a d m i n s h e l l ( v e n v ) $ p y t h o n m a n a g e . p y a d d u s e r j o h n @ e x a m p l e . c o m j o h n P a s s w o r d : < t y p e p a s s w o r d > C o n f i r m : < t y p e p a s s w o r d a g a i n > U s e r j o h n w a s r e g i s t e r e d s u c c e s s f u l l y . s h e l l 51/123 -
Web Forms Typical form workflow: v0.9 •·· The Flask-WTF extension
provides an excellent object-oriented abstraction for working with web forms. · The F o r m class represents a web form. Subclasses of F i e l d represent the form fields. Validators can be applied to form fields. - - - ( v e n v ) $ p i p i n s t a l l f l a s k - w t f s h e l l @ r o u t e ( ' / s o m e - u r l ' , m e t h o d s = [ ' G E T ' , ' P O S T ' ] ) d e f f o o ( ) : f o r m = M y F o r m ( ) i f f o r m . v a l i d a t e _ o n _ s u b m i t ( ) : # p r o c e s s f o r m d a t a # v a l u e s a r e i n f o r m . < f i e l d > . d a t a r e t u r n r e d i r e c t ( u r l _ f o r ( ' . . . ' ) ) # i n i t i a l i z e f o r m f i e l d s h e r e # f o r m . < f i e l d > . d a t a = v a l u e r e t u r n r e n d e r _ t e m p l a t e ( ' t e m p l a t e . h t m l ' , f o r m = f o r m ) p s e u d o - c o d e 53/123 -
Web Forms (cont'd) Form definition: Form usage: v0.9 ·•· f
r o m f l a s k . e x t . w t f i m p o r t F o r m f r o m w t f o r m s i m p o r t S t r i n g F i e l d , P a s s w o r d F i e l d , B o o l e a n F i e l d , S u b m i t F i e l d f r o m w t f o r m s . v a l i d a t o r s i m p o r t R e q u i r e d , L e n g t h , E m a i l c l a s s L o g i n F o r m ( F o r m ) : e m a i l = S t r i n g F i e l d ( ' E m a i l ' , v a l i d a t o r s = [ R e q u i r e d ( ) , L e n g t h ( 1 , 6 4 ) , E m a i l ( ) ] ) p a s s w o r d = P a s s w o r d F i e l d ( ' P a s s w o r d ' , v a l i d a t o r s = [ R e q u i r e d ( ) ] ) r e m e m b e r _ m e = B o o l e a n F i e l d ( ' K e e p m e l o g g e d i n ' ) s u b m i t = S u b m i t F i e l d ( ' L o g I n ' ) a p p / a u t h / f o r m s . p y @ a u t h . r o u t e ( ' / l o g i n ' , m e t h o d s = [ ' G E T ' , ' P O S T ' ] ) d e f l o g i n ( ) : f o r m = L o g i n F o r m ( ) i f f o r m . v a l i d a t e _ o n _ s u b m i t ( ) : p a s s r e t u r n r e n d e r _ t e m p l a t e ( ' a u t h / l o g i n . h t m l ' , f o r m = f o r m ) a p p / a u t h / r o u t e s . p y 54/123 -
Rendering Forms with Flask-Bootstrap Flask-Bootstrap includes Jinja2 macros that simplify
the rendering of Flask-WTF forms. v0.9 ··• { % i m p o r t " b o o t s t r a p / w t f . h t m l " a s w t f % } { % b l o c k p a g e _ c o n t e n t % } . . . { { w t f . q u i c k _ f o r m ( f o r m ) } } { % e n d b l o c k % } a p p / t e m p l a t e s / a u t h / l o g i n . h t m l 55/123 -
Flask-Login v0.10 •········ Flask-Login keeps track of the logged in
user in the user session. It makes no assumptions about how users are represented, stored or logged in. · · ( v e n v ) $ p i p i n s t a l l f l a s k - l o g i n s h e l l f r o m f l a s k . e x t . l o g i n i m p o r t L o g i n M a n a g e r l o g i n _ m a n a g e r = L o g i n M a n a g e r ( ) l o g i n _ m a n a g e r . l o g i n _ v i e w = ' a u t h . l o g i n ' d e f c r e a t e _ a p p ( c o n f i g _ n a m e ) : # . . . l o g i n _ m a n a g e r . i n i t _ a p p ( a p p ) # . . . r e t u r n a p p a p p / _ _ i n i t _ _ . p y 57/123 -
Flask-Login (cont'd) v0.10 ·•······· The user class needs to inherit
from U s e r M i x i n , or else implement the following methods: The application must register a user loader callback. Request handlers can be protected with the l o g i n _ r e q u i r e d decorator. · i s _ a u t h e n t i c a t e d ( ) i s _ a c t i v e ( ) i s _ a n o n y m o u s ( ) g e t _ i d ( ) - - - - · · f r o m f l a s k . e x t . l o g i n i m p o r t U s e r M i x i n f r o m . i m p o r t d b , l o g i n _ m a n a g e r c l a s s U s e r ( U s e r M i x i n , d b . M o d e l ) : # . . . @ l o g i n _ m a n a g e r . u s e r _ l o a d e r d e f l o a d _ u s e r ( u s e r _ i d ) : r e t u r n U s e r . q u e r y . g e t ( i n t ( u s e r _ i d ) ) a p p / m o d e l s . p y 58/123 -
Logging Users In Step 1: Load user by the email
address given in the form. v0.10 ··•······ f r o m f l a s k i m p o r t r e n d e r _ t e m p l a t e , r e d i r e c t , r e q u e s t , u r l _ f o r , f l a s h f r o m f l a s k . e x t . l o g i n i m p o r t l o g i n _ u s e r f r o m . . m o d e l s i m p o r t U s e r @ a u t h . r o u t e ( ' / l o g i n ' , m e t h o d s = [ ' G E T ' , ' P O S T ' ] ) d e f l o g i n ( ) : f o r m = L o g i n F o r m ( ) i f f o r m . v a l i d a t e _ o n _ s u b m i t ( ) : u s e r = U s e r . q u e r y . f i l t e r _ b y ( e m a i l = f o r m . e m a i l . d a t a ) . f i r s t ( ) i f u s e r i s N o n e o r n o t u s e r . v e r i f y _ p a s s w o r d ( f o r m . p a s s w o r d . d a t a ) : f l a s h ( ' I n v a l i d e m a i l o r p a s s w o r d . ' ) r e t u r n r e d i r e c t ( u r l _ f o r ( ' . l o g i n ' ) ) l o g i n _ u s e r ( u s e r , f o r m . r e m e m b e r _ m e . d a t a ) r e t u r n r e d i r e c t ( r e q u e s t . a r g s . g e t ( ' n e x t ' ) o r u r l _ f o r ( ' t a l k s . i n d e x ' ) ) r e t u r n r e n d e r _ t e m p l a t e ( ' a u t h / l o g i n . h t m l ' , f o r m = f o r m ) a p p / m o d e l s . p y 59/123 -
Logging Users In (cont'd) Step 2: Verify that the email
and password given in the form are valid. v0.10 ···•····· f r o m f l a s k i m p o r t r e n d e r _ t e m p l a t e , r e d i r e c t , r e q u e s t , u r l _ f o r , f l a s h f r o m f l a s k . e x t . l o g i n i m p o r t l o g i n _ u s e r f r o m . . m o d e l s i m p o r t U s e r @ a u t h . r o u t e ( ' / l o g i n ' , m e t h o d s = [ ' G E T ' , ' P O S T ' ] ) d e f l o g i n ( ) : f o r m = L o g i n F o r m ( ) i f f o r m . v a l i d a t e _ o n _ s u b m i t ( ) : u s e r = U s e r . q u e r y . f i l t e r _ b y ( e m a i l = f o r m . e m a i l . d a t a ) . f i r s t ( ) i f u s e r i s N o n e o r n o t u s e r . v e r i f y _ p a s s w o r d ( f o r m . p a s s w o r d . d a t a ) : f l a s h ( ' I n v a l i d e m a i l o r p a s s w o r d . ' ) r e t u r n r e d i r e c t ( u r l _ f o r ( ' . l o g i n ' ) ) l o g i n _ u s e r ( u s e r , f o r m . r e m e m b e r _ m e . d a t a ) r e t u r n r e d i r e c t ( r e q u e s t . a r g s . g e t ( ' n e x t ' ) o r u r l _ f o r ( ' t a l k s . i n d e x ' ) ) r e t u r n r e n d e r _ t e m p l a t e ( ' a u t h / l o g i n . h t m l ' , f o r m = f o r m ) a p p / m o d e l s . p y 60/123 -
Logging Users In (cont'd) Step 3: Log the user in
and redirect to the home page. v0.10 ····•···· f r o m f l a s k i m p o r t r e n d e r _ t e m p l a t e , r e d i r e c t , r e q u e s t , u r l _ f o r , f l a s h f r o m f l a s k . e x t . l o g i n i m p o r t l o g i n _ u s e r f r o m . . m o d e l s i m p o r t U s e r @ a u t h . r o u t e ( ' / l o g i n ' , m e t h o d s = [ ' G E T ' , ' P O S T ' ] ) d e f l o g i n ( ) : f o r m = L o g i n F o r m ( ) i f f o r m . v a l i d a t e _ o n _ s u b m i t ( ) : u s e r = U s e r . q u e r y . f i l t e r _ b y ( e m a i l = f o r m . e m a i l . d a t a ) . f i r s t ( ) i f u s e r i s N o n e o r n o t u s e r . v e r i f y _ p a s s w o r d ( f o r m . p a s s w o r d . d a t a ) : f l a s h ( ' I n v a l i d e m a i l o r p a s s w o r d . ' ) r e t u r n r e d i r e c t ( u r l _ f o r ( ' . l o g i n ' ) ) l o g i n _ u s e r ( u s e r , f o r m . r e m e m b e r _ m e . d a t a ) r e t u r n r e d i r e c t ( r e q u e s t . a r g s . g e t ( ' n e x t ' ) o r u r l _ f o r ( ' t a l k s . i n d e x ' ) ) r e t u r n r e n d e r _ t e m p l a t e ( ' a u t h / l o g i n . h t m l ' , f o r m = f o r m ) a p p / m o d e l s . p y 61/123 -
Logging Users In (cont'd) Step 4: Redirect to the "next"
page if available. v0.10 ·····•··· f r o m f l a s k i m p o r t r e n d e r _ t e m p l a t e , r e d i r e c t , r e q u e s t , u r l _ f o r , f l a s h f r o m f l a s k . e x t . l o g i n i m p o r t l o g i n _ u s e r f r o m . . m o d e l s i m p o r t U s e r @ a u t h . r o u t e ( ' / l o g i n ' , m e t h o d s = [ ' G E T ' , ' P O S T ' ] ) d e f l o g i n ( ) : f o r m = L o g i n F o r m ( ) i f f o r m . v a l i d a t e _ o n _ s u b m i t ( ) : u s e r = U s e r . q u e r y . f i l t e r _ b y ( e m a i l = f o r m . e m a i l . d a t a ) . f i r s t ( ) i f u s e r i s N o n e o r n o t u s e r . v e r i f y _ p a s s w o r d ( f o r m . p a s s w o r d . d a t a ) : f l a s h ( ' I n v a l i d e m a i l o r p a s s w o r d . ' ) r e t u r n r e d i r e c t ( u r l _ f o r ( ' . l o g i n ' ) ) l o g i n _ u s e r ( u s e r , f o r m . r e m e m b e r _ m e . d a t a ) r e t u r n r e d i r e c t ( r e q u e s t . a r g s . g e t ( ' n e x t ' ) o r u r l _ f o r ( ' t a l k s . i n d e x ' ) ) r e t u r n r e n d e r _ t e m p l a t e ( ' a u t h / l o g i n . h t m l ' , f o r m = f o r m ) a p p / m o d e l s . p y 62/123 -
Flashed Messages v0.10 ······•·· . . . { % f
o r m e s s a g e i n g e t _ f l a s h e d _ m e s s a g e s ( ) % } < d i v c l a s s = " a l e r t a l e r t - w a r n i n g " > < b u t t o n t y p e = " b u t t o n " c l a s s = " c l o s e " d a t a - d i s m i s s = " a l e r t " > × < / b u t t o n > { { m e s s a g e } } < / d i v > { % e n d f o r % } . . . a p p / t e m p l a t e s / b a s e . h t m l 63/123 -
Access to the logged-in user v0.10 ·······•· The currently logged
in user can be accessed through the c u r r e n t _ u s e r context global. Navigation bar links can be specifically created for the current user: · · A "Profile" link points to the logged-in user's profile page. A "Presenter Login" link is shown if there is no logged-in user. - - { % i f c u r r e n t _ u s e r . i s _ a u t h e n t i c a t e d ( ) % } < l i > < a h r e f = " { { u r l _ f o r ( ' t a l k s . u s e r ' , u s e r n a m e = c u r r e n t _ u s e r . u s e r n a m e ) } } " > P r o f i l e < / a > < / l i > { % e n d i f % } . . . { % i f n o t c u r r e n t _ u s e r . i s _ a u t h e n t i c a t e d ( ) % } < l i > < a h r e f = " { { u r l _ f o r ( ' a u t h . l o g i n ' ) } } " > P r e s e n t e r L o g i n < / a > < / l i > { % e n d i f % } a p p / t e m p l a t e s / b a s e . h t m l 64/123 -
Access to the logged-in user (cont'd) Templates can also access
the logged-in user: User profile route now works with real users: v0.10 ········• { % i f c u r r e n t _ u s e r . i s _ a u t h e n t i c a t e d ( ) % } < h 1 > H e l l o , { { c u r r e n t _ u s e r . u s e r n a m e } } ! < / h 1 > { % e n d i f % } a p p / t e m p l a t e s / t a l k s / i n d e x . h t m l f r o m . . m o d e l s i m p o r t U s e r @ t a l k s . r o u t e ( ' / u s e r / < u s e r n a m e > ' ) d e f u s e r ( u s e r n a m e ) : u s e r = U s e r . q u e r y . f i l t e r _ b y ( u s e r n a m e = u s e r n a m e ) . f i r s t _ o r _ 4 0 4 ( ) r e t u r n r e n d e r _ t e m p l a t e ( ' t a l k s / u s e r . h t m l ' , u s e r = u s e r ) a p p / t a l k s / r o u t e s . p y < h 1 > H e l l o , { { u s e r . u s e r n a m e } } ! < / h 1 > a p p / t e m p l a t e s / t a l k s / u s e r . h t m l 65/123 -
Logging Out The l o g i n _ r
e q u i r e d decorator prevents access to anonymous users. The logged-in state can be used to show log in or out links. v0.11 • f r o m f l a s k . e x t . l o g i n i m p o r t l o g i n _ u s e r , l o g o u t _ u s e r , l o g i n _ r e q u i r e d @ a u t h . r o u t e ( ' / l o g o u t ' ) @ l o g i n _ r e q u i r e d d e f l o g o u t ( ) : l o g o u t _ u s e r ( ) f l a s h ( ' Y o u h a v e b e e n l o g g e d o u t . ' ) r e t u r n r e d i r e c t ( u r l _ f o r ( ' t a l k s . i n d e x ' ) ) a p p / a u t h / r o u t e s . p y . . . { % i f n o t c u r r e n t _ u s e r . i s _ a u t h e n t i c a t e d ( ) % } < l i > < a h r e f = " { { u r l _ f o r ( ' a u t h . l o g i n ' ) } } " > P r e s e n t e r L o g i n < / a > < / l i > { % e l s e % } < l i > < a h r e f = " { { u r l _ f o r ( ' a u t h . l o g o u t ' ) } } " > L o g o u t < / a > < / l i > { % e n d i f % } . . . a p p / t e m p l a t e s / b a s e . h t m l 67/123 -
Gravatar Mini-Reference Example avatar markup: <img src="http://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50?s=128&r=g"> v0.12 •·· The
Gravatar service is the easiest way to include user avatar images. Given H A S H = m d 5 ( e m a i l _ a d d r e s s ) , the URL for the avatar image for the email address is: The query string can include optional arguments: · · http://www.gravatar.com/avatar/HASH (normal version) https://secure.gravatar.com/avatar/HASH (secure version) - - · s = N where N is the size of the image in pixels. d = D where D is the name of an image generator to be used for users that don't have an avatar registered. r = R where R is the image rating (g, pg, etc.) - - - 69/123 -
User Avatars Avatar URL generation is encapsulated in the U
s e r model. v0.12 ·•· c l a s s U s e r ( U s e r M i x i n , d b . M o d e l ) : # . . . d e f _ _ i n i t _ _ ( s e l f , * * k w a r g s ) : s u p e r ( U s e r , s e l f ) . _ _ i n i t _ _ ( * * k w a r g s ) i f s e l f . e m a i l i s n o t N o n e a n d s e l f . a v a t a r _ h a s h i s N o n e : s e l f . a v a t a r _ h a s h = h a s h l i b . m d 5 ( s e l f . e m a i l . e n c o d e ( ' u t f - 8 ' ) ) . h e x d i g e s t ( ) d e f g r a v a t a r ( s e l f , s i z e = 1 0 0 , d e f a u l t = ' i d e n t i c o n ' , r a t i n g = ' g ' ) : i f r e q u e s t . i s _ s e c u r e : u r l = ' h t t p s : / / s e c u r e . g r a v a t a r . c o m / a v a t a r ' e l s e : u r l = ' h t t p : / / w w w . g r a v a t a r . c o m / a v a t a r ' h a s h = s e l f . a v a t a r _ h a s h o r \ h a s h l i b . m d 5 ( s e l f . e m a i l . e n c o d e ( ' u t f - 8 ' ) ) . h e x d i g e s t ( ) r e t u r n ' { u r l } / { h a s h } ? s = { s i z e } & d = { d e f a u l t } & r = { r a t i n g } ' . f o r m a t ( u r l = u r l , h a s h = h a s h , s i z e = s i z e , d e f a u l t = d e f a u l t , r a t i n g = r a t i n g ) a p p / m o d e l s . p y 70/123 -
User Avatars (cont'd) Gravatar URLs can be requested directly from
templates. v0.12 ··• . . . < d i v c l a s s = " p a g e - h e a d e r u s e r - p r o f i l e " > < d i v c l a s s = " u s e r - a v a t a r " > < i m g s r c = " { { u s e r . g r a v a t a r ( 1 2 8 ) } } " > < / d i v > < h 1 > { { u s e r . u s e r n a m e } } < / h 1 > < / d i v > . . . a p p / t e m p l a t e s / t a l k s / u s e r . h t m l 71/123 -
Edit Profile Route v0.13 • The P r o f
i l e F o r m class defines the three user editable fields. The l o g i n _ r e q u i r e d decorator prevents access to regular users. Database session needs the "real" c u r r e n t _ u s e r object, not the context global proxy. · · · @ t a l k s . r o u t e ( ' / p r o f i l e ' , m e t h o d s = [ ' G E T ' , ' P O S T ' ] ) @ l o g i n _ r e q u i r e d d e f p r o f i l e ( ) : f o r m = P r o f i l e F o r m ( ) i f f o r m . v a l i d a t e _ o n _ s u b m i t ( ) : c u r r e n t _ u s e r . n a m e = f o r m . n a m e . d a t a c u r r e n t _ u s e r . l o c a t i o n = f o r m . l o c a t i o n . d a t a c u r r e n t _ u s e r . b i o = f o r m . b i o . d a t a d b . s e s s i o n . a d d ( c u r r e n t _ u s e r . _ g e t _ c u r r e n t _ o b j e c t ( ) ) d b . s e s s i o n . c o m m i t ( ) f l a s h ( ' Y o u r p r o f i l e h a s b e e n u p d a t e d . ' ) r e t u r n r e d i r e c t ( u r l _ f o r ( ' t a l k s . u s e r ' , u s e r n a m e = c u r r e n t _ u s e r . u s e r n a m e ) ) f o r m . n a m e . d a t a = c u r r e n t _ u s e r . n a m e f o r m . l o c a t i o n . d a t a = c u r r e n t _ u s e r . l o c a t i o n f o r m . b i o . d a t a = c u r r e n t _ u s e r . b i o r e t u r n r e n d e r _ t e m p l a t e ( ' t a l k s / p r o f i l e . h t m l ' , f o r m = f o r m ) a p p / t a l k s / r o u t e s . p y 73/123 -
Talk Model SQLAlchemy allows relationships between models to be easily
defined. v0.14 •·· c l a s s T a l k ( d b . M o d e l ) : _ _ t a b l e n a m e _ _ = ' t a l k s ' i d = d b . C o l u m n ( d b . I n t e g e r , p r i m a r y _ k e y = T r u e ) t i t l e = d b . C o l u m n ( d b . S t r i n g ( 1 2 8 ) , n u l l a b l e = F a l s e ) d e s c r i p t i o n = d b . C o l u m n ( d b . T e x t ) s l i d e s = d b . C o l u m n ( d b . T e x t ( ) ) v i d e o = d b . C o l u m n ( d b . T e x t ( ) ) v e n u e = d b . C o l u m n ( d b . S t r i n g ( 1 2 8 ) ) v e n u e _ u r l = d b . C o l u m n ( d b . S t r i n g ( 1 2 8 ) ) d a t e = d b . C o l u m n ( d b . D a t e T i m e ( ) ) u s e r _ i d = d b . C o l u m n ( d b . I n t e g e r , d b . F o r e i g n K e y ( ' u s e r s . i d ' ) ) c l a s s U s e r ( U s e r M i x i n , d b . M o d e l ) : # . . . t a l k s = d b . r e l a t i o n s h i p ( ' T a l k ' , b a c k r e f = ' a u t h o r ' , l a z y = ' d y n a m i c ' ) a p p / m o d e l s . p y 75/123 -
Talk Form The O p t i o n a
l ( ) validator enables validators to work on fields that are allowed to be empty. v0.14 ·•· c l a s s T a l k F o r m ( F o r m ) : t i t l e = S t r i n g F i e l d ( ' T i t l e ' , v a l i d a t o r s = [ R e q u i r e d ( ) , L e n g t h ( 1 , 1 2 8 ) ] ) d e s c r i p t i o n = T e x t A r e a F i e l d ( ' D e s c r i p t i o n ' ) s l i d e s = S t r i n g F i e l d ( ' S l i d e s E m b e d C o d e ( 4 5 0 p i x e l s w i d e ) ' ) v i d e o = S t r i n g F i e l d ( ' V i d e o E m b e d C o d e ( 4 5 0 p i x e l s w i d e ) ' ) v e n u e = S t r i n g F i e l d ( ' V e n u e ' , v a l i d a t o r s = [ R e q u i r e d ( ) , L e n g t h ( 1 , 1 2 8 ) ] ) v e n u e _ u r l = S t r i n g F i e l d ( ' V e n u e U R L ' , v a l i d a t o r s = [ O p t i o n a l ( ) , L e n g t h ( 1 , 1 2 8 ) , U R L ( ) ] ) d a t e = D a t e F i e l d ( ' D a t e ' ) s u b m i t = S u b m i t F i e l d ( ' S u b m i t ' ) a p p / t a l k s / f o r m s . p y 76/123 -
Add Talk Route The relationship handles the foreign keys automatically.
v0.14 ··• @ t a l k s . r o u t e ( ' / n e w ' , m e t h o d s = [ ' G E T ' , ' P O S T ' ] ) @ l o g i n _ r e q u i r e d d e f n e w _ t a l k ( ) : f o r m = T a l k F o r m ( ) i f f o r m . v a l i d a t e _ o n _ s u b m i t ( ) : t a l k = T a l k ( t i t l e = f o r m . t i t l e . d a t a , d e s c r i p t i o n = f o r m . d e s c r i p t i o n . d a t a , s l i d e s = f o r m . s l i d e s . d a t a , v i d e o = f o r m . v i d e o . d a t a , v e n u e = f o r m . v e n u e . d a t a , v e n u e _ u r l = f o r m . v e n u e _ u r l . d a t a , d a t e = f o r m . d a t e . d a t a , a u t h o r = c u r r e n t _ u s e r ) d b . s e s s i o n . a d d ( t a l k ) d b . s e s s i o n . c o m m i t ( ) f l a s h ( ' T h e t a l k w a s a d d e d s u c c e s s f u l l y . ' ) r e t u r n r e d i r e c t ( u r l _ f o r ( ' . i n d e x ' ) ) r e t u r n r e n d e r _ t e m p l a t e ( ' t a l k s / e d i t _ t a l k . h t m l ' , f o r m = f o r m ) a p p / t a l k s / r o u t e s . p y 77/123 -
Database Queries v0.15 •· Queries are used to obtain data
from the database. The one-to-many relationship between users and talks is used to obtain list of talks by a user to show in the profile page. · · @ t a l k s . r o u t e ( ' / ' ) d e f i n d e x ( ) : t a l k _ l i s t = T a l k . q u e r y . o r d e r _ b y ( T a l k . d a t e . d e s c ( ) ) . a l l ( ) r e t u r n r e n d e r _ t e m p l a t e ( ' t a l k s / i n d e x . h t m l ' , t a l k s = t a l k _ l i s t ) @ t a l k s . r o u t e ( ' / u s e r / < u s e r n a m e > ' ) d e f u s e r ( u s e r n a m e ) : u s e r = U s e r . q u e r y . f i l t e r _ b y ( u s e r n a m e = u s e r n a m e ) . f i r s t _ o r _ 4 0 4 ( ) t a l k _ l i s t = u s e r . t a l k s . o r d e r _ b y ( T a l k . d a t e . d e s c ( ) ) . a l l ( ) r e t u r n r e n d e r _ t e m p l a t e ( ' t a l k s / u s e r . h t m l ' , u s e r = u s e r , t a l k s = t a l k _ l i s t ) a p p / t a l k s / r o u t e s . p y 79/123 -
Sub-Templates v0.15 ·• Sub-templates are used to avoid repetition. ·
{ % i n c l u d e " t a l k s / _ t a l k s . h t m l " % } a p p / t e m p l a t e s / t a l k s / { i n d e x | u s e r } . h t m l < u l c l a s s = " t a l k - l i s t " > { % f o r t a l k i n t a l k s % } < l i c l a s s = " t a l k " > { % i n c l u d e " t a l k s / _ t a l k _ h e a d e r . h t m l " % } < / l i > { % e n d f o r % } < / u l > a p p / t e m p l a t e s / t a l k s / _ t a l k s . h t m l < d i v c l a s s = " t a l k - h e a d e r " > < h 2 > { { t a l k . t i t l e } } < / h 2 > < h 3 > { { t a l k . d e s c r i p t i o n } } < / h 3 > < p > < a h r e f = " { { u r l _ f o r ( ' t a l k s . u s e r ' , u s e r n a m e = t a l k . a u t h o r . u s e r n a m e ) } } " > { { t a l k . a u t h o r . u s e r n a m e } } < / a > a t { % i f t a l k . v e n u e _ u r l % } < a h r e f = " { { t a l k . v e n u e _ u r l } } " > { { t a l k . v e n u e } } < / a > { % e l s e % } { { t a l k . v e n u e } } { % e n d i f % } < / p > < / d i v > a p p / t e m p l a t e s / t a l k s / _ t a l k _ h e a d e r . h t m l 80/123 -
Rendering Dates and Times Flask-Moment is a convenient extension that
simplifies the use of to render dates in the browser. v0.16 • ( v e n v ) $ p i p i n s t a l l f l a s k - m o m e n t s h e l l f r o m f l a s k . e x t . m o m e n t i m p o r t M o m e n t m o m e n t = M o m e n t ( ) d e f c r e a t e _ a p p ( c o n f i g _ n a m e ) : # . . . m o m e n t . i n i t _ a p p ( a p p ) # . . . r e t u r n a p p a p p / _ _ i n i t _ _ . p y { % b l o c k s c r i p t s % } { { s u p e r ( ) } } { { m o m e n t . i n c l u d e _ m o m e n t ( ) } } { % e n d b l o c k % } a p p / t e m p l a t e s / b a s e . h t m l . . . o n { { m o m e n t ( t a l k . d a t e , l o c a l = T r u e ) . f o r m a t ( ' L L ' ) } } . a p p / t e m p l a t e s / t a l k s / _ t a l k _ h e a d e r . h t m l 82/123 -
Talk Route v0.17 •·· The route simply loads the requested
talk from the database and passes it to the template for rendering. The g e t _ o r _ 4 0 4 ( ) method of Flask-SQLAlchemy will automatically return a response with 404 status code to the client if the requested talk is not found. · · @ t a l k s . r o u t e ( ' / t a l k / < i n t : i d > ' ) d e f t a l k ( i d ) : t a l k = T a l k . q u e r y . g e t _ o r _ 4 0 4 ( i d ) r e t u r n r e n d e r _ t e m p l a t e ( ' t a l k s / t a l k . h t m l ' , t a l k = t a l k ) a p p / t a l k s / r o u t e s . p y 84/123 -
Talk Templates v0.17 ·•· The talk header template from the
talk timelines is reused here. The s a f e Jinja2 filter suppresses escaping. Only use for trusted content! · · { % e x t e n d s " b a s e . h t m l " % } { % b l o c k p a g e _ c o n t e n t % } < d i v c l a s s = " p a g e - h e a d e r " > { % i n c l u d e " t a l k s / _ t a l k _ h e a d e r . h t m l " % } < / d i v > < d i v c l a s s = " t a l k - b o d y " > { % i f t a l k . v i d e o % } < d i v c l a s s = " t a l k - v i d e o " > { { t a l k . v i d e o | s a f e } } < / d i v > { % e n d i f % } { % i f t a l k . s l i d e s % } < d i v c l a s s = " t a l k - s l i d e s " > { { t a l k . s l i d e s | s a f e } } < / d i v > { % e n d i f % } < / d i v > { % e n d b l o c k % } a p p / t e m p l a t e s / t a l k s / t a l k . h t m l 85/123 -
Talk Templates (cont'd) The talk header template is updated to
display the talk title as a link to the corresponding talk page. v0.17 ··• < h 2 > < a h r e f = " { { u r l _ f o r ( ' t a l k s . t a l k ' , i d = t a l k . i d ) } } " > { { t a l k . t i t l e } } < / a > < / h 2 > < h 3 > { { t a l k . d e s c r i p t i o n } } < / h 3 > a p p / t e m p l a t e s / t a l k s / _ t a l k _ h e a d e r . h t m l 86/123 -
Talk Editor Routes v0.18 •· Additional validation is done to
ensure that only authorized people edit talks. Flask's a b o r t ( ) function is used to return an error response. Data is moved to and from the form using helper methods. · · · @ t a l k s . r o u t e ( ' / e d i t / < i n t : i d > ' , m e t h o d s = [ ' G E T ' , ' P O S T ' ] ) @ l o g i n _ r e q u i r e d d e f e d i t _ t a l k ( i d ) : t a l k = T a l k . q u e r y . g e t _ o r _ 4 0 4 ( i d ) i f n o t c u r r e n t _ u s e r . i s _ a d m i n a n d t a l k . a u t h o r ! = c u r r e n t _ u s e r : a b o r t ( 4 0 3 ) f o r m = T a l k F o r m ( ) i f f o r m . v a l i d a t e _ o n _ s u b m i t ( ) : f o r m . t o _ m o d e l ( t a l k ) d b . s e s s i o n . a d d ( t a l k ) d b . s e s s i o n . c o m m i t ( ) f l a s h ( ' T h e t a l k w a s u p d a t e d s u c c e s s f u l l y . ' ) r e t u r n r e d i r e c t ( u r l _ f o r ( ' . t a l k ' , i d = t a l k . i d ) ) f o r m . f r o m _ m o d e l ( t a l k ) r e t u r n r e n d e r _ t e m p l a t e ( ' t a l k s / e d i t _ t a l k . h t m l ' , f o r m = f o r m ) a p p / t a l k s / r o u t e s . p y 88/123 -
Model/Form Data Exchange v0.18 ·• c l a s s
T a l k F o r m ( F o r m ) : # . . . d e f f r o m _ m o d e l ( s e l f , t a l k ) : s e l f . t i t l e . d a t a = t a l k . t i t l e s e l f . d e s c r i p t i o n . d a t a = t a l k . d e s c r i p t i o n s e l f . s l i d e s . d a t a = t a l k . s l i d e s s e l f . v i d e o . d a t a = t a l k . v i d e o s e l f . v e n u e . d a t a = t a l k . v e n u e s e l f . v e n u e _ u r l . d a t a = t a l k . v e n u e _ u r l s e l f . d a t e . d a t a = t a l k . d a t e d e f t o _ m o d e l ( s e l f , t a l k ) : t a l k . t i t l e = s e l f . t i t l e . d a t a t a l k . d e s c r i p t i o n = s e l f . d e s c r i p t i o n . d a t a t a l k . s l i d e s = s e l f . s l i d e s . d a t a t a l k . v i d e o = s e l f . v i d e o . d a t a t a l k . v e n u e = s e l f . v e n u e . d a t a t a l k . v e n u e _ u r l = s e l f . v e n u e _ u r l . d a t a t a l k . d a t e = s e l f . d a t e . d a t a a p p / t a l k s / f o r m s . p y 89/123 -
Markdown Support v0.19 •········ Comments are entered using Markdown syntax.
Markdown and Bleach are used for Markdown rendering on the server. Flask-PageDown renders Markdown live in the browser. For security reasons the browser only sends Markdown source to the server. · · · · ( v e n v ) $ p i p i n s t a l l f l a s k - p a g e d o w n m a r k d o w n b l e a c h s h e l l f r o m f l a s k . e x t . p a g e d o w n i m p o r t P a g e D o w n p a g e d o w n = P a g e D o w n ( ) d e f c r e a t e _ a p p ( c o n f i g _ n a m e ) : # . . . p a g e d o w n . i n i t _ a p p ( a p p ) # . . . r e t u r n a p p a p p / _ _ i n i t _ _ . p y { % b l o c k s c r i p t s % } { { s u p e r ( ) } } { { p a g e d o w n . i n c l u d e _ p a g e d o w n ( ) } } { % e n d b l o c k % } a p p / t e m p l a t e s / t a l k s / t a l k . h t m l 91/123 -
Comment Model v0.19 ·•······· For each comment the Markdown source
and the rendered HTML are stored. The C o m m e n t model has two one-to-many relationships from U s e r and T a l k . · · c l a s s C o m m e n t ( d b . M o d e l ) : _ _ t a b l e n a m e _ _ = ' c o m m e n t s ' i d = d b . C o l u m n ( d b . I n t e g e r , p r i m a r y _ k e y = T r u e ) b o d y = d b . C o l u m n ( d b . T e x t ) b o d y _ h t m l = d b . C o l u m n ( d b . T e x t ) t i m e s t a m p = d b . C o l u m n ( d b . D a t e T i m e , i n d e x = T r u e , d e f a u l t = d a t e t i m e . u t c n o w ) a u t h o r _ i d = d b . C o l u m n ( d b . I n t e g e r , d b . F o r e i g n K e y ( ' u s e r s . i d ' ) ) a u t h o r _ n a m e = d b . C o l u m n ( d b . S t r i n g ( 6 4 ) ) a u t h o r _ e m a i l = d b . C o l u m n ( d b . S t r i n g ( 6 4 ) ) n o t i f y = d b . C o l u m n ( d b . B o o l e a n , d e f a u l t = T r u e ) a p p r o v e d = d b . C o l u m n ( d b . B o o l e a n , d e f a u l t = F a l s e ) t a l k _ i d = d b . C o l u m n ( d b . I n t e g e r , d b . F o r e i g n K e y ( ' t a l k s . i d ' ) ) c l a s s U s e r ( U s e r M i x i n , d b . M o d e l ) : # . . . c o m m e n t s = d b . r e l a t i o n s h i p ( ' C o m m e n t ' , l a z y = ' d y n a m i c ' , b a c k r e f = ' a u t h o r ' ) c l a s s T a l k ( d b . M o d e l ) : # . . . c o m m e n t s = d b . r e l a t i o n s h i p ( ' C o m m e n t ' , l a z y = ' d y n a m i c ' , b a c k r e f = ' t a l k ' ) a p p / m o d e l s . p y 92/123 -
Server-Side Markdown v0.19 ··•······ A SQLAlchemy change event triggers the
Markdown rendering. The Markdown text is rendered in three steps: · · The text is rendered to HTML. Bleach is used to filter any HTML tags not in the white list. Bleach's l i n k i f y ( ) function is used to convert any plain URLs to links. - - - f r o m m a r k d o w n i m p o r t m a r k d o w n i m p o r t b l e a c h c l a s s C o m m e n t ( d b . M o d e l ) : # . . . @ s t a t i c m e t h o d d e f o n _ c h a n g e d _ b o d y ( t a r g e t , v a l u e , o l d v a l u e , i n i t i a t o r ) : a l l o w e d _ t a g s = [ ' a ' , ' a b b r ' , ' a c r o n y m ' , ' b ' , ' b l o c k q u o t e ' , ' c o d e ' , ' e m ' , ' i ' , ' l i ' , ' o l ' , ' p r e ' , ' s t r o n g ' , ' u l ' , ' h 1 ' , ' h 2 ' , ' h 3 ' , ' p ' ] t a r g e t . b o d y _ h t m l = b l e a c h . l i n k i f y ( b l e a c h . c l e a n ( m a r k d o w n ( v a l u e , o u t p u t _ f o r m a t = ' h t m l ' ) , t a g s = a l l o w e d _ t a g s , s t r i p = T r u e ) ) d b . e v e n t . l i s t e n ( C o m m e n t . b o d y , ' s e t ' , C o m m e n t . o n _ c h a n g e d _ b o d y ) a p p / m o d e l s . p y 93/123 -
Comment Forms v0.19 ···•····· Two forms are used, one for
presenters and administrators, another for regular users. Flask-PageDown's P a g e D o w n F i e l d is used in place of a regular text area field. · · f r o m f l a s k . e x t . p a g e d o w n . f i e l d s i m p o r t P a g e D o w n F i e l d c l a s s P r e s e n t e r C o m m e n t F o r m ( F o r m ) : b o d y = P a g e D o w n F i e l d ( ' C o m m e n t ' , v a l i d a t o r s = [ R e q u i r e d ( ) ] ) s u b m i t = S u b m i t F i e l d ( ' S u b m i t ' ) c l a s s C o m m e n t F o r m ( F o r m ) : n a m e = S t r i n g F i e l d ( ' N a m e ' , v a l i d a t o r s = [ R e q u i r e d ( ) , L e n g t h ( 1 , 6 4 ) ] ) e m a i l = S t r i n g F i e l d ( ' E m a i l ' , v a l i d a t o r s = [ R e q u i r e d ( ) , L e n g t h ( 1 , 6 4 ) , E m a i l ( ) ] ) b o d y = P a g e D o w n F i e l d ( ' C o m m e n t ' , v a l i d a t o r s = [ R e q u i r e d ( ) ] ) n o t i f y = B o o l e a n F i e l d ( ' N o t i f y w h e n n e w c o m m e n t s a r e p o s t e d ' , d e f a u l t = T r u e ) s u b m i t = S u b m i t F i e l d ( ' S u b m i t ' ) a p p / t a l k s / f o r m s . p y 94/123 -
Talk Route with Comment Support v0.19 ····•···· The appropriate comment
form for the current user is used. On form submission a comment is created with its a p p r o v e d field set to F a l s e for regular users. · · @ t a l k s . r o u t e ( ' / t a l k / < i n t : i d > ' , m e t h o d s = [ ' G E T ' , ' P O S T ' ] ) d e f t a l k ( i d ) : t a l k = T a l k . q u e r y . g e t _ o r _ 4 0 4 ( i d ) c o m m e n t = N o n e i f c u r r e n t _ u s e r . i s _ a u t h e n t i c a t e d ( ) : f o r m = P r e s e n t e r C o m m e n t F o r m ( ) i f f o r m . v a l i d a t e _ o n _ s u b m i t ( ) : c o m m e n t = C o m m e n t ( b o d y = f o r m . b o d y . d a t a , t a l k = t a l k , a u t h o r = c u r r e n t _ u s e r , n o t i f y = F a l s e , a p p r o v e d = T r u e ) e l s e : f o r m = C o m m e n t F o r m ( ) i f f o r m . v a l i d a t e _ o n _ s u b m i t ( ) : c o m m e n t = C o m m e n t ( b o d y = f o r m . b o d y . d a t a , t a l k = t a l k , a u t h o r _ n a m e = f o r m . n a m e . d a t a , a u t h o r _ e m a i l = f o r m . e m a i l . d a t a , n o t i f y = f o r m . n o t i f y . d a t a , a p p r o v e d = F a l s e ) # . . . a p p / t a l k s / r o u t e s . p y 95/123 -
Talk Route with Comment Support (cont'd) v0.19 ·····•··· The flashed
message is different for approved or not approved messages. A non-existant fragment is used in the redirect to reset scroll position of the page. The comment list is sorted by date in ascending order and sent to the template. · · · @ t a l k s . r o u t e ( ' / t a l k / < i n t : i d > ' , m e t h o d s = [ ' G E T ' , ' P O S T ' ] ) d e f t a l k ( i d ) : # . . . i f c o m m e n t : d b . s e s s i o n . a d d ( c o m m e n t ) d b . s e s s i o n . c o m m i t ( ) i f c o m m e n t . a p p r o v e d : f l a s h ( ' Y o u r c o m m e n t h a s b e e n p u b l i s h e d . ' ) e l s e : f l a s h ( ' Y o u r c o m m e n t w i l l b e p u b l i s h e d a f t e r i t i s r e v i e w e d b y t h e p r e s e n t e r . ' ) r e t u r n r e d i r e c t ( u r l _ f o r ( ' . t a l k ' , i d = t a l k . i d ) + ' # t o p ' ) c o m m e n t s = t a l k . c o m m e n t s . o r d e r _ b y ( C o m m e n t . t i m e s t a m p . a s c ( ) ) . a l l ( ) r e t u r n r e n d e r _ t e m p l a t e ( ' t a l k s / t a l k . h t m l ' , t a l k = t a l k , f o r m = f o r m , c o m m e n t s = c o m m e n t s ) a p p / t a l k s / r o u t e s . p y 96/123 -
Comment Templates v0.19 ······•·· The list of comments is rendered
below the talk slides and/or video. A sub-template organization similar to the one for tasks is used. A form to enter a new comment is rendered below the comment list. A fragment is given as a form action, so that the scroll position is preserved. · · · · { % i m p o r t " b o o t s t r a p / w t f . h t m l " a s w t f % } . . . { % i f c o m m e n t s % } < h 3 > C o m m e n t s < / h 3 > { % i n c l u d e " t a l k s / _ c o m m e n t s . h t m l " % } { % e n d i f % } < h 3 i d = " c o m m e n t - f o r m " > W r i t e a c o m m e n t < / h 3 > { { w t f . q u i c k _ f o r m ( f o r m , a c t i o n = ' # c o m m e n t - f o r m ' ) } } . . . a p p / t e m p l a t e s / t a l k s / t a l k . h t m l 97/123 -
Comment Templates (cont'd) v0.19 ·······•· Jinja2's s e t directive
is used to pass a variable to the included template. The loop index is available as l o o p . i n d e x · · < u l c l a s s = " c o m m e n t - l i s t " > { % f o r c o m m e n t i n c o m m e n t s % } { % s e t i n d e x = l o o p . i n d e x % } < l i c l a s s = " c o m m e n t " > { % i n c l u d e " t a l k s / _ c o m m e n t . h t m l " % } < / l i > { % e n d f o r % } < / u l > a p p / t e m p l a t e s / t a l k s / _ c o m m e n t s . h t m l 98/123 -
Comment Templates (cont'd) v0.19 ········• The commenter's email address is
shown only to the talk author or administrator. Flask-Moment is used to render the comment timestamp in "time ago" mode. · · < p > { % i f c o m m e n t . a u t h o r % } < s p a n c l a s s = " l a b e l l a b e l - p r i m a r y " > # { { i n d e x } } < / s p a n > < a h r e f = " { { u r l _ f o r ( ' . u s e r ' , u s e r n a m e = c o m m e n t . a u t h o r . u s e r n a m e ) } } " > { { c o m m e n t . a u t h o r . u s e r n a m e } } < / a > { % e l s e % } < s p a n c l a s s = " l a b e l l a b e l - d e f a u l t " > # { { i n d e x } } < / s p a n > < b > { { c o m m e n t . a u t h o r _ n a m e } } < / b > { % i f c u r r e n t _ u s e r . i s _ a u t h e n t i c a t e d ( ) a n d ( t a l k . a u t h o r = = c u r r e n t _ u s e r o r c u r r e n t _ u s e r . i s _ a d m i n ) % } ( < a h r e f = " m a i l t o : { { c o m m e n t . a u t h o r _ e m a i l } } " > { { c o m m e n t . a u t h o r _ e m a i l } } < / a > ) { % e n d i f % } { % e n d i f % } c o m m e n t e d { { m o m e n t ( c o m m e n t . t i m e s t a m p ) . f r o m N o w ( ) } } : < / p > < d i v c l a s s = " c o m m e n t - b o d y " > { { c o m m e n t . b o d y _ h t m l | s a f e } } < / d i v > a p p / t e m p l a t e s / t a l k s / _ c o m m e n t . h t m l 99/123 -
APIs Mini-Reference v0.20 •·········· The REpresentational State Transfer (REST) model
is typically used for Web application APIs due to its close ties to the HTTP protocol. Request URLs name resources, the items of interest in the application's domain. The request method determines the action to carry out: Resources are serialized and sent in the bodies of requests and responses as needed. JSON and XML are common serialization formats. Flask has native support for RESTful APIs through its request routing. · · · P O S T : create a new resource. This is the C in CRUD. G E T : read a resource or collection of resources. This is the R in CRUD. P U T : update a resource. This is the U in CRUD. D E L E T E : delete a resource. This is the D in CRUD. - - - - · · 101/123 -
API Blueprint v0.20 ·•········· The API endpoints are defined in
a separate blueprint. The blueprint is versioned, to leave room for expansion. Each route implements a resource and method combination. This API implements "approve" and "delete" comment moderation operations. · · · · f r o m f l a s k i m p o r t B l u e p r i n t a p i = B l u e p r i n t ( ' a p i ' , _ _ n a m e _ _ ) f r o m . i m p o r t c o m m e n t s , e r r o r s a p p / a p i _ 1 _ 0 / _ _ i n i t _ _ . p y d e f c r e a t e _ a p p ( c o n f i g _ n a m e ) : # . . . f r o m . a p i _ 1 _ 0 i m p o r t a p i a s a p i _ b l u e p r i n t a p p . r e g i s t e r _ b l u e p r i n t ( a p i _ b l u e p r i n t , u r l _ p r e f i x = ' / a p i / 1 . 0 ' ) # . . . r e t u r n a p p a p p / _ _ i n i t _ _ . p y 102/123 -
API endpoints v0.20 ··•········ API routes return a JSON response
using j s o n i f y ( ) . · f r o m f l a s k i m p o r t j s o n i f y f r o m . i m p o r t a p i f r o m . e r r o r s i m p o r t b a d _ r e q u e s t @ a p i . r o u t e ( ' / c o m m e n t s / < i n t : i d > ' , m e t h o d s = [ ' P U T ' ] ) d e f a p p r o v e _ c o m m e n t ( i d ) : c o m m e n t = C o m m e n t . q u e r y . g e t _ o r _ 4 0 4 ( i d ) # T O D O : e n s u r e u s e r h a s p e r m i s s i o n t o a p p r o v e c o m m e n t i f c o m m e n t . a p p r o v e d : r e t u r n b a d _ r e q u e s t ( ' C o m m e n t i s a l r e a d y a p p r o v e d . ' ) c o m m e n t . a p p r o v e d = T r u e d b . s e s s i o n . a d d ( c o m m e n t ) d b . s e s s i o n . c o m m i t ( ) r e t u r n j s o n i f y ( { ' s t a t u s ' : ' o k ' } ) @ a p i . r o u t e ( ' / c o m m e n t s / < i n t : i d > ' , m e t h o d s = [ ' D E L E T E ' ] ) d e f d e l e t e _ c o m m e n t ( i d ) : # . . . a p p / a p i _ 1 _ 0 / c o m m e n t s . p y 103/123 -
Error Handling v0.20 ···•······· Helper functions are implemented for error
responses. All responses return JSON. · · f r o m f l a s k i m p o r t j s o n i f y d e f b a d _ r e q u e s t ( m e s s a g e ) : r e s p o n s e = j s o n i f y ( { ' s t a t u s ' : ' b a d r e q u e s t ' , ' m e s s a g e ' : m e s s a g e } ) r e s p o n s e . s t a t u s _ c o d e = 4 0 0 r e t u r n r e s p o n s e d e f u n a u t h o r i z e d ( m e s s a g e ) : r e s p o n s e = j s o n i f y ( { ' s t a t u s ' : ' u n a u t h o r i z e d ' , ' m e s s a g e ' : m e s s a g e } ) r e s p o n s e . s t a t u s _ c o d e = 4 0 1 r e t u r n r e s p o n s e d e f f o r b i d d e n ( m e s s a g e ) : r e s p o n s e = j s o n i f y ( { ' s t a t u s ' : ' f o r b i d d e n ' , ' m e s s a g e ' : m e s s a g e } ) r e s p o n s e . s t a t u s _ c o d e = 4 0 3 r e t u r n r e s p o n s e d e f n o t _ f o u n d ( m e s s a g e ) : r e s p o n s e = j s o n i f y ( { ' s t a t u s ' : ' n o t f o u n d ' , ' m e s s a g e ' : m e s s a g e } ) r e s p o n s e . s t a t u s _ c o d e = 4 0 4 r e t u r n r e s p o n s e a p p / a p i _ 1 _ 0 / e r r o r s . p y 104/123 -
Error Handling (cont'd) v0.20 ····•······ A custom error handler is
implemented to catch 404 exceptions thrown by Flask-SQLAlchemy. Other exceptions can be handled in the same way. · · # . . . @ a p i . e r r o r h a n d l e r ( 4 0 4 ) d e f n o t _ f o u n d _ h a n d l e r ( e ) : r e t u r n n o t _ f o u n d ( ' r e s o u r c e n o t f o u n d ' ) a p p / a p i _ 1 _ 0 / e r r o r s . p y 105/123 -
Authentication v0.20 ·····•····· All API requests must come with a
valid authentication token. Tokens are generated and validated in the U s e r model. Package itsdangerous is used to generate cryptographically secure tokens. · · · f r o m i t s d a n g e r o u s i m p o r t T i m e d J S O N W e b S i g n a t u r e S e r i a l i z e r a s S e r i a l i z e r c l a s s U s e r ( U s e r M i x i n , d b . M o d e l ) : # . . . d e f g e t _ a p i _ t o k e n ( s e l f , e x p i r a t i o n = 3 0 0 ) : s = S e r i a l i z e r ( c u r r e n t _ a p p . c o n f i g [ ' S E C R E T _ K E Y ' ] , e x p i r a t i o n ) r e t u r n s . d u m p s ( { ' u s e r ' : s e l f . i d } ) . d e c o d e ( ' u t f - 8 ' ) @ s t a t i c m e t h o d d e f v a l i d a t e _ a p i _ t o k e n ( t o k e n ) : s = S e r i a l i z e r ( c u r r e n t _ a p p . c o n f i g [ ' S E C R E T _ K E Y ' ] ) t r y : d a t a = s . l o a d s ( t o k e n ) e x c e p t : r e t u r n N o n e i d = d a t a . g e t ( ' u s e r ' ) i f i d : r e t u r n U s e r . q u e r y . g e t ( i d ) r e t u r n N o n e a p p / m o d e l s . p y 106/123 -
Authentication (cont'd) v0.20 ······•···· Token verification happens in a b
e f o r e _ r e q u e s t handler for the blueprint. The user is obtained from the token and recorded in the g context global. · · f r o m f l a s k i m p o r t r e q u e s t , g f r o m . i m p o r t e r r o r s @ a p i . b e f o r e _ r e q u e s t d e f b e f o r e _ a p i _ r e q u e s t ( ) : i f r e q u e s t . j s o n i s N o n e : r e t u r n e r r o r s . b a d _ r e q u e s t ( ' I n v a l i d J S O N i n b o d y . ' ) t o k e n = r e q u e s t . j s o n . g e t ( ' t o k e n ' ) i f n o t t o k e n : r e t u r n e r r o r s . u n a u t h o r i z e d ( ' A u t h e n t i c a t i o n t o k e n n o t p r o v i d e d . ' ) u s e r = U s e r . v a l i d a t e _ a p i _ t o k e n ( t o k e n ) i f n o t u s e r : r e t u r n e r r o r s . u n a u t h o r i z e d ( ' I n v a l i d a u t h e n t i c a t i o n t o k e n . ' ) g . c u r r e n t _ u s e r = u s e r a p p / a p i _ 1 _ 0 / _ _ i n i t _ _ . p y 107/123 -
Authentication (cont'd) v0.20 ·······•··· API routes access the user making
the requests as g . c u r r e n t _ u s e r . · @ a p i . r o u t e ( ' / c o m m e n t s / < i n t : i d > ' , m e t h o d s = [ ' P U T ' ] ) d e f a p p r o v e _ c o m m e n t ( i d ) : c o m m e n t = C o m m e n t . q u e r y . g e t _ o r _ 4 0 4 ( i d ) i f c o m m e n t . t a l k . a u t h o r ! = g . c u r r e n t _ u s e r a n d \ n o t g . c u r r e n t _ u s e r . i s _ a d m i n : r e t u r n f o r b i d d e n ( ' Y o u c a n n o t m o d i f y t h i s c o m m e n t . ' ) # . . . @ a p i . r o u t e ( ' / c o m m e n t s / < i n t : i d > ' , m e t h o d s = [ ' D E L E T E ' ] ) d e f d e l e t e _ c o m m e n t ( i d ) : c o m m e n t = C o m m e n t . q u e r y . g e t _ o r _ 4 0 4 ( i d ) i f c o m m e n t . t a l k . a u t h o r ! = g . c u r r e n t _ u s e r a n d \ n o t g . c u r r e n t _ u s e r . i s _ a d m i n : r e t u r n f o r b i d d e n ( ' Y o u c a n n o t m o d i f y t h i s c o m m e n t . ' ) # . . . a p p / a p i _ 1 _ 0 / c o m m e n t s . p y 108/123 -
Moderation Queries v0.20 ········•·· The models have helper methods for
common queries needed to perform comment moderation. A database join operation is performed to obtain all the comments to be moderated in all the talks that belong to a speaker. · · c l a s s T a l k ( d b . M o d e l ) : # . . . d e f a p p r o v e d _ c o m m e n t s ( s e l f ) : r e t u r n s e l f . c o m m e n t s . f i l t e r _ b y ( a p p r o v e d = T r u e ) c l a s s C o m m e n t ( d b . M o d e l ) : # . . . @ s t a t i c m e t h o d d e f f o r _ m o d e r a t i o n ( ) : r e t u r n C o m m e n t . q u e r y . f i l t e r ( C o m m e n t . a p p r o v e d = = F a l s e ) c l a s s U s e r ( U s e r M i x i n , d b . M o d e l ) : # . . . d e f f o r _ m o d e r a t i o n ( s e l f , a d m i n = F a l s e ) : i f a d m i n a n d s e l f . i s _ a d m i n : r e t u r n C o m m e n t . f o r _ m o d e r a t i o n ( ) r e t u r n C o m m e n t . q u e r y . j o i n ( T a l k , C o m m e n t . t a l k _ i d = = T a l k . i d ) . \ f i l t e r ( T a l k . a u t h o r = = s e l f ) . f i l t e r ( C o m m e n t . a p p r o v e d = = F a l s e ) a p p / m o d e l s . p y 109/123 -
Moderation Routes v0.20 ·········•· Moderation for speakers and admins are
handled separately. · Speakers can only moderate comments for their talks. Admins can moderate comments for all talks. - - @ t a l k s . r o u t e ( ' / m o d e r a t e ' ) @ l o g i n _ r e q u i r e d d e f m o d e r a t e ( ) : c o m m e n t s = c u r r e n t _ u s e r . f o r _ m o d e r a t i o n ( ) . o r d e r _ b y ( C o m m e n t . t i m e s t a m p . a s c ( ) ) r e t u r n r e n d e r _ t e m p l a t e ( ' t a l k s / m o d e r a t e . h t m l ' , c o m m e n t s = c o m m e n t s ) @ t a l k s . r o u t e ( ' / m o d e r a t e - a d m i n ' ) @ l o g i n _ r e q u i r e d d e f m o d e r a t e _ a d m i n ( ) : i f n o t c u r r e n t _ u s e r . i s _ a d m i n : a b o r t ( 4 0 3 ) c o m m e n t s = C o m m e n t . f o r _ m o d e r a t i o n ( ) . o r d e r _ b y ( C o m m e n t . t i m e s t a m p . a s c ( ) ) r e t u r n r e n d e r _ t e m p l a t e ( ' t a l k s / m o d e r a t e . h t m l ' , c o m m e n t s = c o m m e n t s ) a p p / t a l k s / r o u t e s . p y 110/123 -
Moderation Template v0.20 ··········• The JavaScript API client is included
in the moderation and talk pages so that comments can be moderated in both. The client-side implementation is available on the github repository. · · { % e x t e n d s " b a s e . h t m l " % } { % b l o c k p a g e _ c o n t e n t % } < h 2 > C o m m e n t m o d e r a t i o n < / h 2 > < u l c l a s s = " c o m m e n t - l i s t " > { % f o r c o m m e n t i n c o m m e n t s % } { % s e t t a l k = c o m m e n t . t a l k % } < l i c l a s s = " c o m m e n t " > < p > I n < a h r e f = " { { u r l _ f o r ( ' t a l k s . t a l k ' , i d = t a l k . i d ) } } " > { { t a l k . t i t l e } } < / a > < / p > { % i n c l u d e " t a l k s / _ c o m m e n t . h t m l " % } < / l i > { % e n d f o r % } < / u l > { % e n d b l o c k % } { % b l o c k s c r i p t s % } { { s u p e r ( ) } } { % i n c l u d e " _ a p i _ c l i e n t . h t m l " % } { % e n d b l o c k % } a p p / t e m p l a t e s / t a l k s / m o d e r a t e . h t m l 111/123 -
Pagination v0.21 •· Page sizes are specified as configuration options.
Page number is given as a query string argument. Flask-SQLAlchemy's p a g i n a t e ( ) is applied to database queries. The P a g i n a t i o n object is passed to the template. · · · · c l a s s C o n f i g : # . . . T A L K S _ P E R _ P A G E = 5 0 C O M M E N T S _ P E R _ P A G E = 1 0 0 c o n f i g . p y @ t a l k s . r o u t e ( ' / ' ) d e f i n d e x ( ) : p a g e = r e q u e s t . a r g s . g e t ( ' p a g e ' , 1 , t y p e = i n t ) p a g i n a t i o n = T a l k . q u e r y . o r d e r _ b y ( T a l k . d a t e . d e s c ( ) ) . p a g i n a t e ( p a g e , p e r _ p a g e = c u r r e n t _ a p p . c o n f i g [ ' T A L K S _ P E R _ P A G E ' ] , e r r o r _ o u t = F a l s e ) t a l k _ l i s t = p a g i n a t i o n . i t e m s r e t u r n r e n d e r _ t e m p l a t e ( ' t a l k s / i n d e x . h t m l ' , t a l k s = t a l k _ l i s t , p a g i n a t i o n = p a g i n a t i o n ) a p p / t a l k s / r o u t e s . p y 113/123 -
Pagination (cont'd) v0.21 ·• Bootstrap pager markup is used for
"next" and "previous" links. Flask-SQLAlchemy's pagination object has the previous and next page numbers. · · < u l c l a s s = " p a g e r " > { % i f p a g i n a t i o n . h a s _ p r e v % } < l i c l a s s = " p r e v i o u s " > < a h r e f = " { { u r l _ f o r ( ' t a l k s . i n d e x ' , p a g e = p a g i n a t i o n . p r e v _ n u m ) } } " > ← N e w e r < / a > < / l i > { % e l s e % } < l i c l a s s = " p r e v i o u s d i s a b l e d " > < a h r e f = " # " > ← N e w e r < / a > < / l i > { % e n d i f % } { % i f p a g i n a t i o n . h a s _ n e x t % } < l i c l a s s = " n e x t " > < a h r e f = " { { u r l _ f o r ( ' t a l k s . i n d e x ' , p a g e = p a g i n a t i o n . n e x t _ n u m ) } } " > O l d e r → < / a > < / l i > { % e l s e % } < l i c l a s s = " n e x t d i s a b l e d " > < a h r e f = " # " > O l d e r → < / a > < / l i > { % e n d i f % } < / u l > a p p / t e m p l a t e s / t a l k s / i n d e x . h t m l 114/123 -
Emails v0.22 • The Flask-Mail extension is used to send
emails to users. The default configuration uses a gmail account to send email. This is sufficient for development, but a dedicated email server must be used in production. Two helper functions are added: To avoid sending too many emails a queue collects pending emails. A background thread flushes the email queue at regular intervals. Emails to commenters include an link with an unsubscribe token. When clicked, the comment for that user is flagged so that no new comment notifications are sent to that address. The changes for this feature are on github. · · · s e n d _ a u t h o r _ n o t i f i c a t i o n ( ) sends an email to the author of a talk when new comments require moderation. s e n d _ c o m m e n t _ n o t i f i c a t i o n ( ) sends an email to all the previous commenters in the talk. - - · · · · 116/123 -
Unit Tests A custom Flask-Script command can run all the
tests and generate a coverage report: v0.23 • Business logic should be in models or service classes and tested outside of a running application. Small and focused unit tests are easier to maintain than large end-to-end tests. Write tests that are simple and straightforward, you do not want to have bugs in your tests! Test APIs with the Flask test client. Only use end-to-end testing tools such as Selenium for tests that cannot be implemented with simpler methods. Only test your application code, assume the libraries that you use are well tested. Use code coverage to find out what your tests are missing. · · · · · · · ( v e n v ) $ p i p i n s t a l l n o s e c o v e r a g e s h e l l @ m a n a g e r . c o m m a n d d e f t e s t ( ) : f r o m s u b p r o c e s s i m p o r t c a l l c a l l ( [ ' n o s e t e s t s ' , ' - v ' , ' - - w i t h - c o v e r a g e ' , ' - - c o v e r - p a c k a g e = a p p ' , ' - - c o v e r - b r a n c h e s ' , ' - - c o v e r - e r a s e ' , ' - - c o v e r - h t m l ' , ' - - c o v e r - h t m l - d i r = c o v e r ' ] ) m a n a g e . p y 118/123 -
Logging v0.24 •· Flask's logger does not have any handlers
in production mode. l o g g i n g . S M T P H a n d l e r is added to send application errors by email to a designated administrator. Users see a status 500 error page. l o g g i n g . S y s L o g H a n d l e r is added to send regular logs to syslog (for Unix servers) On Windows N T E v e n t L o g H a n d l e r or F i l e H a n d l e r can be used instead. · · · · 120/123 -
Environment Variables v0.24 ·• Import environment variables from . e
n v file, if present. Due to the sensitive information, this file needs to be created manually for each deployed system, do not put it under version control. Do not use gmail as email server, install sendmail, postfix, etc. or use a third party service. Example: · · · · F L A S K _ C O N F I G = p r o d u c t i o n S E C R E T _ K E Y = y o u - w i l l - n e v e r - g u e s s ! M A I L _ U S E R N A M E = < y o u r - s m t p - u s e r n a m e > M A I L _ P A S S W O R D = < y o u r - s m t p - p a s s w o r d > M A I L _ S E N D E R = a d m i n @ y o u r d o m a i n . c o m M A I L _ E R R O R _ R E C I P I E N T = e r r o r s @ y o u r d o m a i n . c o m D A T A B A S E _ U R L = m y s q l : / / u s e r : p a s s w o r d @ y o u r d o m a i n / t a l k s . e n v 121/123