Anonymous avatar Anonymous committed bf07a7a

move config into separate class

Comments (0)

Files changed (66)

 *~
 
 fabfile.py
+deploy.py
 producton_settings.py
 
 dist
 docs/output
 
-
+uploads

newsmeme/__init__.py

     __init__.py
     ~~~~~~~~~~~
 
-    Description of the module goes here...
-
     :copyright: (c) 2010 by Dan Jacob.
     :license: BSD, see LICENSE for more details.
 """
-import os
-import logging
 
-from logging.handlers import SMTPHandler, RotatingFileHandler
-
-from flask import Flask, Response, session, request, g, \
-    jsonify, redirect, url_for, flash
-
-from flaskext.babel import Babel, gettext as _
-from flaskext.themes import setup_themes
-from flaskext.principal import Principal, RoleNeed, UserNeed, identity_loaded
-
-from newsmeme import views
-from newsmeme import helpers
-from newsmeme.models import User, Tag
-from newsmeme.helpers import render_template
-from newsmeme.extensions import db, mail, oid, cache
-
-DEBUG = True
-
-# change this in your production settings !!!
-
-SECRET_KEY = "secret"
-
-# keys for localhost. Change as appropriate.
-
-RECAPTCHA_PUBLIC_KEY = '6LeYIbsSAAAAACRPIllxA7wvXjIE411PfdB2gt2J'
-RECAPTCHA_PRIVATE_KEY = '6LeYIbsSAAAAAJezaIq3Ft_hSTo0YtyeFG-JgRtu'
-
-SQLALCHEMY_DATABASE_URI = "sqlite:///newsmeme.db"
-SQLALCHEMY_ECHO = False
-
-MAIL_DEBUG = DEBUG
-
-ADMINS = ()
-
-DEFAULT_MAIL_SENDER = "support@thenewsmeme.com"
-
-ACCEPT_LANGUAGES = ['en', 'fi']
-
-DEBUG_LOG = 'logs/debug.log'
-ERROR_LOG = 'logs/error.log'
-
-THEME = 'newsmeme'
-
-CACHE_TYPE = "simple"
-CACHE_DEFAULT_TIMEOUT = 300
-
-def configure_logging(app):
-    if app.debug:
-        return
-
-    mail_handler = \
-        SMTPHandler(app.config['MAIL_SERVER'],
-                   'error@newsmeme.com',
-                   app.config['ADMINS'], 
-                    'Application error',
-                    (
-                        app.config['MAIL_USERNAME'],
-                        app.config['MAIL_PASSWORD'],
-                    ))
-
-    mail_handler.setLevel(logging.ERROR)
-    app.logger.addHandler(mail_handler)
-
-    formatter = logging.Formatter(
-        '%(asctime)s %(levelname)s: %(message)s '
-        '[in %(pathname)s:%(lineno)d]')
-
-    debug_log = os.path.join(app.root_path, app.config['DEBUG_LOG'])
-
-    debug_file_handler = \
-        RotatingFileHandler(debug_log,
-                            maxBytes=100000,
-                            backupCount=10)
-
-    debug_file_handler.setLevel(logging.DEBUG)
-    debug_file_handler.setFormatter(formatter)
-    app.logger.addHandler(debug_file_handler)
-
-    error_log = os.path.join(app.root_path, app.config['ERROR_LOG'])
-
-    error_file_handler = \
-        RotatingFileHandler(error_log,
-                            maxBytes=100000,
-                            backupCount=10)
-
-    error_file_handler.setLevel(logging.ERROR)
-    error_file_handler.setFormatter(formatter)
-    app.logger.addHandler(error_file_handler)
-
+from newsmeme.application import Application
 
 def create_app(config=None):
 
-    app = Flask(__name__)
-    
-    app.config.from_object(__name__)
+    return Application(__name__, config).configure()
 
-    if config is not None:
-        app.config.from_object(config)
 
-    app.config.from_envvar('APP_CONFIG', silent=True)
-
-    # configure logging
-
-    configure_logging(app)
-    
-    # configure extensions
-
-    mail.init_app(app)
-    db.init_app(app)
-    oid.init_app(app)
-    cache.init_app(app)
-
-    babel = Babel(app)
-    setup_themes(app)
-    principal = Principal(app)
-
-    # identity loading
-
-    @identity_loaded.connect_via(app)
-    def on_identity_loaded(sender, identity):
-        if identity.name and identity.name != 'anon':
-            user = User.query.get(identity.name)
-        else:
-            user = None
-        if user:
-            identity.user = user
-            identity.provides.update(user.provides)
-
-    # filters
-
-    @app.template_filter()
-    def timesince(value):
-        return helpers.timesince(value)
-
-    # error handlers
-
-    if not app.testing:
-        @app.errorhandler(404)
-        def page_not_found(error):
-            if request.is_xhr:
-                return jsonify(error=_('Sorry, page not found'))
-            return render_template("errors/404.html", error=error)
-
-        @app.errorhandler(403)
-        def forbidden(error):
-            if request.is_xhr:
-                return jsonify(error=_('Sorry, not allowed'))
-            return render_template("errors/403.html", error=error)
-
-        @app.errorhandler(500)
-        def server_error(error):
-            if request.is_xhr:
-                return jsonify(error=_('Sorry, an error has occurred'))
-            return render_template("errors/500.html", error=error)
-
-        @app.errorhandler(401)
-        def unauthorized(error):
-            if request.is_xhr:
-                return jsonfiy(error=_("Login required"))
-            flash(_("Please login to see this page"), "error")
-            return redirect(url_for("account.login", next=request.path))
-
-    # before handler
-
-    @app.before_request
-    def authenticate():
-        g.user = getattr(g.identity, 'user', None)
-
-    # babel setup
-
-    @babel.localeselector
-    def get_locale():
-        return 'en_US'
-        accept_languages = app.config.get('ACCEPT_LANGUAGES', ['en_gb'])
-        return request.accept_languages.best_match(accept_languages)
-
-    # context processors
-    
-    @app.context_processor
-    def get_tags():
-        tags = cache.get("tags")
-        if tags is None:
-            tags = Tag.query.order_by(Tag.num_posts.desc()).limit(10).all()
-            cache.set("tags", tags)
-
-        return dict(tags=tags)
-
-    @app.context_processor
-    def config():
-        return dict(config=app.config)
-
-    # add modules
-    
-    app.register_module(views.frontend, url_prefix="")
-    app.register_module(views.post, url_prefix="/post")
-    app.register_module(views.user, url_prefix="/user")
-    app.register_module(views.comment, url_prefix="/comment")
-    app.register_module(views.account, url_prefix="/acct")
-    app.register_module(views.feeds, url_prefix="/feeds")
-    app.register_module(views.openid, url_prefix="/openid")
-    app.register_module(views.api, url_prefix="/api")
-
-    return app

newsmeme/application.py

+# -*- coding: utf-8 -*-
+"""
+    application.py
+    ~~~~~~~~~~~
+
+    Application configuration
+
+    :copyright: (c) 2010 by Dan Jacob.
+    :license: BSD, see LICENSE for more details.
+"""
+import os
+import logging
+
+from logging.handlers import SMTPHandler, RotatingFileHandler
+
+from flask import Flask, Response, request, g, \
+        jsonify, redirect, url_for, flash
+
+from flaskext.babel import Babel, gettext as _
+from flaskext.themes import setup_themes
+from flaskext.principal import Principal, identity_loaded
+
+from newsmeme import views
+from newsmeme import helpers
+from newsmeme.models import User, Tag
+from newsmeme.helpers import render_template
+from newsmeme.extensions import db, mail, oid, cache
+
+
+class Application(object):
+
+    DEBUG = True
+
+    # change this in your production settings !!!
+
+    SECRET_KEY = "secret"
+
+    # keys for localhost. Change as appropriate.
+
+    RECAPTCHA_PUBLIC_KEY = '6LeYIbsSAAAAACRPIllxA7wvXjIE411PfdB2gt2J'
+    RECAPTCHA_PRIVATE_KEY = '6LeYIbsSAAAAAJezaIq3Ft_hSTo0YtyeFG-JgRtu'
+
+    SQLALCHEMY_DATABASE_URI = "sqlite://newsmeme.db"
+
+    SQLALCHEMY_ECHO = False
+
+    MAIL_DEBUG = DEBUG
+
+    ADMINS = ()
+
+    DEFAULT_MAIL_SENDER = "support@thenewsmeme.com"
+
+    ACCEPT_LANGUAGES = ['en', 'fi']
+
+    DEBUG_LOG = 'logs/debug.log'
+    ERROR_LOG = 'logs/error.log'
+
+    THEME = 'newsmeme'
+
+    CACHE_TYPE = "simple"
+    CACHE_DEFAULT_TIMEOUT = 300
+
+    def __init__(self, app_name, config=None):
+
+        self.config = config
+        self.app = Flask(app_name)
+
+    def configure(self):
+
+        self.configure_app()
+        self.configure_logging()
+        self.configure_errorhandlers()
+        self.configure_extensions()
+        self.configure_filters()
+        self.configure_context_processors()
+        self.configure_modules()
+
+        return self.app
+    
+    def configure_app(self):
+        
+        self.app.config.from_object(self)
+
+        if self.config is not None:
+            self.app.config.from_object(self.config)
+
+        self.app.config.from_envvar('APP_CONFIG', silent=True)
+
+
+    def configure_modules(self):
+        
+        self.app.register_module(views.frontend, url_prefix="")
+        self.app.register_module(views.post, url_prefix="/post")
+        self.app.register_module(views.user, url_prefix="/user")
+        self.app.register_module(views.comment, url_prefix="/comment")
+        self.app.register_module(views.account, url_prefix="/acct")
+        self.app.register_module(views.feeds, url_prefix="/feeds")
+        self.app.register_module(views.openid, url_prefix="/openid")
+        self.app.register_module(views.api, url_prefix="/api")
+
+    def configure_filters(self):
+
+        @self.app.template_filter()
+        def timesince(value):
+            return helpers.timesince(value)
+
+        @self.app.before_request
+        def authenticate():
+            g.user = getattr(g.identity, 'user', None)
+
+
+    def configure_context_processors(self):
+
+        @self.app.context_processor
+        def get_tags():
+            tags = cache.get("tags")
+            if tags is None:
+                tags = Tag.query.order_by(Tag.num_posts.desc()).limit(10).all()
+                cache.set("tags", tags)
+
+            return dict(tags=tags)
+
+        @self.app.context_processor
+        def config():
+            return dict(config=self.app.config)
+
+    def configure_extensions(self):
+
+        mail.init_app(self.app)
+        db.init_app(self.app)
+        oid.init_app(self.app)
+        cache.init_app(self.app)
+
+        babel = Babel(self.app)
+        setup_themes(self.app)
+        Principal(self.app)
+
+        # identity loading
+
+        @identity_loaded.connect_via(self.app)
+        def on_identity_loaded(sender, identity):
+            if identity.name and identity.name != 'anon':
+                user = User.query.get(identity.name)
+            else:
+                user = None
+            if user:
+                identity.user = user
+                identity.provides.update(user.provides)
+
+        # babel configuration
+
+        @babel.localeselector
+        def get_locale():
+            accept_languages = self.app.config.get('ACCEPT_LANGUAGES', 
+                                                   ['en_gb'])
+
+            return request.accept_languages.best_match(accept_languages)
+
+    def configure_errorhandlers(self):
+        if self.app.testing:
+            return
+
+        @self.app.errorhandler(404)
+        def page_not_found(error):
+            if request.is_xhr:
+                return jsonify(error=_('Sorry, page not found'))
+            return render_template("errors/404.html", error=error)
+
+        @self.app.errorhandler(403)
+        def forbidden(error):
+            if request.is_xhr:
+                return jsonify(error=_('Sorry, not allowed'))
+            return render_template("errors/403.html", error=error)
+
+        @self.app.errorhandler(500)
+        def server_error(error):
+            if request.is_xhr:
+                return jsonify(error=_('Sorry, an error has occurred'))
+            return render_template("errors/500.html", error=error)
+
+        @self.app.errorhandler(401)
+        def unauthorized(error):
+            if request.is_xhr:
+                return jsonfiy(error=_("Login required"))
+            flash(_("Please login to see this page"), "error")
+            return redirect(url_for("account.login", next=request.path))
+
+
+    def configure_logging(self):
+        if self.app.debug or self.app.testing:
+            return
+
+        mail_handler = \
+            SMTPHandler(self.app.config['MAIL_SERVER'],
+                       'error@newsmeme.com',
+                       self.app.config['ADMINS'], 
+                        'self.application error',
+                        (
+                            self.app.config['MAIL_USERNAME'],
+                            self.app.config['MAIL_PASSWORD'],
+                        ))
+
+        mail_handler.setLevel(logging.ERROR)
+        self.app.logger.addHandler(mail_handler)
+
+        formatter = logging.Formatter(
+            '%(asctime)s %(levelname)s: %(message)s '
+            '[in %(pathname)s:%(lineno)d]')
+
+        debug_log = os.path.join(self.app.root_path, 
+                                 self.app.config['DEBUG_LOG'])
+
+        debug_file_handler = \
+            RotatingFileHandler(debug_log,
+                                maxBytes=100000,
+                                backupCount=10)
+
+        debug_file_handler.setLevel(logging.DEBUG)
+        debug_file_handler.setFormatter(formatter)
+        self.app.logger.addHandler(debug_file_handler)
+
+        error_log = os.path.join(self.app.root_path, 
+                                 self.app.config['ERROR_LOG'])
+
+        error_file_handler = \
+            RotatingFileHandler(error_log,
+                                maxBytes=100000,
+                                backupCount=10)
+
+        error_file_handler.setLevel(logging.ERROR)
+        error_file_handler.setFormatter(formatter)
+        self.app.logger.addHandler(error_file_handler)
+
+
+
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/21/006241ed-7185-49e3-8e59-d79fab278f13.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/01dca927-dc0d-4cef-b02c-9811a121d840.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/0598fe3f-1efd-4ed3-816a-f563bec2997f.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/06fe14a0-3c18-4363-94f7-cb9277500cf9.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/07f09483-3017-4214-b322-775d24fe25b8.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/0a71bbbd-fe07-4c52-b3e7-bceda8240313.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/1294644c-f76f-4161-8d11-5e63f9ff8b7a.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/3357b70d-156a-43b1-bcda-21be79beb58e.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/412ddfbb-9e53-4c5c-ab8b-10b7fd1a2ac7.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/4181408a-b8ef-4caf-b2b2-84d4bfff5878.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/522c6a3f-9eca-4526-8499-7043fea5fcda.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/5758df42-8d6c-4768-bff2-fe33b07630b1.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/58f6f2f6-d8c6-463b-8f82-eb8b3d8a0def.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/6b7cc04f-639a-442f-bd75-722654d7ce8e.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/6c537d32-3c6b-49a1-9f32-fac50d60d97d.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/7db00499-ad60-4ca5-9964-0a3706e9874a.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/7dba4702-5e8a-4c44-8e47-8c879194e468.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/8bc3e7c2-0e21-491d-b02c-b5fba667fbba.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/8c63358a-13e9-4a46-b9a2-9e729c930e03.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/8f2a76cf-79f5-44fa-8191-974242195cbd.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/99542e05-681d-4cc4-b9be-a9b4b6db4186.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/a38d5e82-3fa9-4de9-8068-ab2a068517cc.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/b2004b7f-5ea8-422e-b2fb-f12db0606290.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/b965610f-d05a-4aa3-8d07-4316b5bebd83.png

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/c8bafeb9-abfe-4b56-aa63-0ba077b912f6.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/c8dd2b48-f25b-4e13-a632-e1cca34a44fc.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/cef9d08a-8c3d-402a-90f0-1d757a1b6c4b.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/cfce9a81-8f56-43bf-a12f-81433076f70a.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/d0448cea-d79f-4444-9345-a944275d8f50.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/d9b7674b-2f36-47bc-bc82-50c5d32a6304.png

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/dc56de38-7365-4b61-bdc1-4dcbb18d55fa.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/photos/2010/9/22/e4f70f35-657a-4355-b384-36405e41e54e.png

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/21/tn-006241ed-7185-49e3-8e59-d79fab278f13.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-01dca927-dc0d-4cef-b02c-9811a121d840.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-0598fe3f-1efd-4ed3-816a-f563bec2997f.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-06fe14a0-3c18-4363-94f7-cb9277500cf9.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-1294644c-f76f-4161-8d11-5e63f9ff8b7a.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-3357b70d-156a-43b1-bcda-21be79beb58e.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-412ddfbb-9e53-4c5c-ab8b-10b7fd1a2ac7.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-4181408a-b8ef-4caf-b2b2-84d4bfff5878.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-522c6a3f-9eca-4526-8499-7043fea5fcda.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-5758df42-8d6c-4768-bff2-fe33b07630b1.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-58f6f2f6-d8c6-463b-8f82-eb8b3d8a0def.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-6b7cc04f-639a-442f-bd75-722654d7ce8e.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-6c537d32-3c6b-49a1-9f32-fac50d60d97d.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-7db00499-ad60-4ca5-9964-0a3706e9874a.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-7dba4702-5e8a-4c44-8e47-8c879194e468.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-8bc3e7c2-0e21-491d-b02c-b5fba667fbba.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-8c63358a-13e9-4a46-b9a2-9e729c930e03.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-8f2a76cf-79f5-44fa-8191-974242195cbd.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-99542e05-681d-4cc4-b9be-a9b4b6db4186.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-a38d5e82-3fa9-4de9-8068-ab2a068517cc.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-b2004b7f-5ea8-422e-b2fb-f12db0606290.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-b965610f-d05a-4aa3-8d07-4316b5bebd83.png

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-c8bafeb9-abfe-4b56-aa63-0ba077b912f6.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-c8dd2b48-f25b-4e13-a632-e1cca34a44fc.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-cef9d08a-8c3d-402a-90f0-1d757a1b6c4b.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-cfce9a81-8f56-43bf-a12f-81433076f70a.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-d0448cea-d79f-4444-9345-a944275d8f50.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-d9b7674b-2f36-47bc-bc82-50c5d32a6304.png

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-dc56de38-7365-4b61-bdc1-4dcbb18d55fa.jpg

Added
New image
Add a comment to this file

newsmeme/static/_uploads/thumbs/2010/9/22/tn-e4f70f35-657a-4355-b384-36405e41e54e.png

Added
New image

newsmeme/templates/photos/edit.html

+
+{% extends theme("layout.html") %}
+{% from "macros/_forms.html" import render_errors %}
+{% from "macros/_post.html" import markdown_link %}
+
+{% block content %}
+<h2>{{ _('Edit this photo') }}</h2>
+
+<form method="POST" action="." enctype="multipart/form-data">
+    {{ form.hidden_tag() }}
+    <ul>
+        <li>
+            {{ render_errors(form.title) }}
+            {{ form.title.label }}<br>
+            {{ form.title(size=50) }}
+        </li>
+        <li>
+            {{ render_errors(form.photo) }}
+            {{ form.photo.label }}<br>
+            {{ form.photo }}
+        </li>
+        <li>
+            {{ render_errors(form.link) }}
+            {{ form.link.label }}<br>
+            {{ form.link(size=50) }}
+        </li>
+        <li>
+            {{ render_errors(form.tags) }}
+            {{ form.tags.label }}<br>
+            {{ form.tags(size=50) }}
+            <br><small>{{ _('Separate tags with a comma') }}</small>
+        </li>
+        <li>
+            {{ render_errors(form.description) }}
+            {{ form.description.label }}<br>
+            {{ form.description(rows=6, cols=30) }}
+        </li>
+        <li>
+            {{ render_errors(form.access) }}
+            {{ form.access.label }}<br>
+            {% for option in form.access %}
+            {{ option }} {{ option.label(class_="checkbox") }} 
+            {% endfor %}
+        </li>
+        <li>
+            <input type="submit" value="Update">
+            </li>
+            <li>
+                {{ markdown_link() }}
+            </li>
+ 
+    </ul>
+</form>
+
+{% endblock %}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.