Slide 1

Slide 1 text

Flask By Example Miguel Grinberg PyCon 2014

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

About This Tutorial

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Talks The Example Application

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

Flask from Scratch

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Flask Has Awesome Documentation Flask from Scratch ········• Read it! · 21/123

Slide 22

Slide 22 text

Version 0.1 Basic Project Structure

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

Version 0.2 Twitter Bootstrap Integration

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

Version 0.3 Advanced Template Inheritance

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Version 0.4 Links

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

Version 0.5 Database

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

Version 0.6 Password Hashing

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

Version 0.7 Authentication Blueprint

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

Version 0.8 User Registration

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

Version 0.9 Login Form

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

Version 0.10 Logging In

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

Version 0.11 Logging Out

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

Version 0.12 User Avatars

Slide 69

Slide 69 text

Gravatar Mini-Reference Example avatar markup: 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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

Version 0.13 User Profile Editor

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

Version 0.14 Adding Talks

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

Version 0.15 Timelines

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

Version 0.16 Date Handling

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

Version 0.17 Talk Page

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

Version 0.18 Talk Editor

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

Version 0.19 User Comments

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

Version 0.20 Comment Moderation

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

Version 0.21 Pagination

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

Version 0.22 Bonus Feature: Emails

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

Version 0.23 Unit Tests

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

Version 0.24 Production Mode

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

We are done! Questions?

Slide 123

Slide 123 text

Thank You! twitter @miguelgrinberg www miguelgrinberg.com github github.com/miguelgrinberg