Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Flask By Example - PyCon 2014

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
Tweet

More Decks by Miguel Grinberg

Other Decks in Programming

Transcript

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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
  49. 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
  50. 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
  51. 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
  52. 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
  53. 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
  54. 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
  55. 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
  56. 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
  57. 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
  58. 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
  59. 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
  60. 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
  61. 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
  62. 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
  63. 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
  64. 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
  65. 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
  66. 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
  67. 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
  68. 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
  69. 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
  70. 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
  71. 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
  72. 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
  73. 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
  74. 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
  75. 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
  76. 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
  77. 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
  78. 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
  79. 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
  80. 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
  81. 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
  82. 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
  83. 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
  84. 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
  85. 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
  86. 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
  87. 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
  88. 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
  89. 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
  90. 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
  91. 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
  92. 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