Flask — Website, is the former master source of Flask, made by Flask, including template rendering, database operation, openID authentication, full text retrieval and other functions. Flask is a great way to learn how to build a complete Web site using Flask.

The project structure

Flask-website has been archived, and we use the last version 8B08, which includes the following modules:

The module describe
run.py The startup script
websiteconfig.py Setup script
update-doc-searchindex.py Update index script
database.py Database module
docs.py Index document module
openid_auth.py Request certification
search.py Search module
utils.py Utility class
listings Some display bars
views Blueprint module, including community, extension, mailing list, code snippet, etc
static Static resources for the site
templates Template resources for the site

Flask-website’s project structure, which can be used as flask’s scaffolding to build its own site following this directory plan:

. ├ ─ ─ LICENSE ├ ─ ─ a Makefile ├ ─ ─ the README ├ ─ ─ flask_website │ ├ ─ ─ just set py │ ├ ─ ─ database. Py │ ├ ─ ─ docs. Py │ ├ ─ ─ │ ├── Search Views ├─ ├─ run.py ├─ update-doc-searchindexCopy the code
  • Run.py acts as the startup entry for the project
  • Requirements. TXT describes the dependency packages for the project
  • Flask_website is the main module of the project. It contains: static directory for storing static resources; The templates directory where the template files are stored; Views module that holds blueprints that are used to build different pages of your site.

Site entry

The code for run.py is very simple. Import the app and run it:

from flask_website import app
app.run(debug=True)
Copy the code

The APP is flask based and initialized using the configuration in Websiteconfig

app = Flask(__name__)
app.config.from_object('websiteconfig')
Copy the code

Some global implementations are set in app, such as 404 page definition, global user, closing DB connection, and template time:

@app.errorhandler(404) def not_found(error): return render_template('404.html'), 404 @app.before_request def load_current_user(): g.user = User.query.filter_by(openid=session['openid']).first() \ if 'openid' in session else None @app.teardown_request  def remove_db_session(exception): db_session.remove() @app.context_processor def current_year(): return {'current_year': datetime.utcnow().year}Copy the code

There are two ways to load the View section. The first is to use Flask’s add_url_rule function, which sets up the document search implementation, and these urls execute the docs module:

app.add_url_rule('/docs/', endpoint='docs.index', build_only=True)
app.add_url_rule('/docs/<path:page>/', endpoint='docs.show',
                 build_only=True)
app.add_url_rule('/docs/<version>/.latex/Flask.pdf', endpoint='docs.pdf',
                 build_only=True)
Copy the code

The second is to use Flask’s blueprint function:

from flask_website.views import general
from flask_website.views import community
from flask_website.views import mailinglist
from flask_website.views import snippets
from flask_website.views import extensions
app.register_blueprint(general.mod)
app.register_blueprint(community.mod)
app.register_blueprint(mailinglist.mod)
app.register_blueprint(snippets.mod)
app.register_blueprint(extensions.mod)
Copy the code

Finally, the app defines some jinja template utility functions:

app.jinja_env.filters['datetimeformat'] = utils.format_datetime
app.jinja_env.filters['dateformat'] = utils.format_date
app.jinja_env.filters['timedeltaformat'] = utils.format_timedelta
app.jinja_env.filters['displayopenid'] = utils.display_openid
Copy the code

Template rendering

Now the mainstream of the site is the use of the front end of the separation of the structure, the back end to provide the storage API, the front end using VUE and other construction. This structure for building a small site, will be more complex, there is a feeling of killing chickens. For individual developers, there is more to learn about the front end. Building pages using back-end template rendering is a more traditional approach that is more practical for small sites.

This project is built using a template, in the General blueprint:

mod = Blueprint('general', __name__) @mod.route('/') def index(): if request_wants_json(): return jsonify(releases=[r.to_json() for r in releases]) return render_template( 'general/index.html', latest_release=releases[-1], # pdf link does not redirect, needs version # docs version only includes major.minor docs_pdf_version='.'.join(releases[-1].version.split('.', 2) 2] [:))Copy the code

You can see that the home page has two output methods, one is json output, the other is HTML output, we focus on the second method. Render_template passes the template path, latest_RELEASE, and docs_PDF_version.

Templates are also modular and generally based on page layout. A template for a layout definition, such as a left or right column or a top or bottom column, is called a layout. For example, the template of this project is defined as the following five pieces from top to bottom:

  • Head generally defines HTML page titles (browser bar), CSS styles/jS-script loading on demand, etc
  • Body_title defines the title of the page
  • Message defines a set of unified notifications that inform the class’s presentation space
  • The body part of the page
  • Footer Unified footer

Use layout template definition to unify the display style of the website. Each page can inherit and expand. Here are the definition details for the head fast and message blocks:

<! doctype html> {% block head %} <title>{% block title %}Welcome{% endblock %} | Flask (A Python Microframework)</title> <meta charset=utf-8> <link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}"> <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}"> <script type=text/javascript SRC = "http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" > < / script > {% endblock %} < div class = box >... <p class=nav> <a href="{{ url_for('general.index') }}">overview</a> // <a href="{{ url_for('docs.index') }}">docs</a> //  <a href="{{ url_for('community.index') }}">community</a> // <a href="{{ url_for('extensions.index') }}">extensions</a> // <a href="https://psfmember.org/civicrm/contribute/transact?reset=1&id=20">donate</a> {% for message in get_flashed_messages() %} <p class=message>{{ message }} {% endfor %} ...Copy the code

The general/index of the homepage of this project inherits from the global layout and overwrites the body part of it, using its own configuration:

{% extends "layout.html" %}
    ....
{% block body %}
  <ul>
    <li><a href="{{ latest_release.detail_url }}">Download latest release</a> ({{ latest_release.version }})
    <li><a href="{{ url_for('docs.index') }}">Read the documentation</a>
    <li><a href="{{ url_for('mailinglist.index') }}">Join the mailinglist</a>
    <li><a href=https://github.com/pallets/flask>Fork it on github</a>
    <li><a href=https://github.com/pallets/flask/issues>Add issues and feature requests</a>
  </ul>
  ...
Copy the code
  • This list mainly uses the latest_RELEASE variable passed in the blueprint to show the URL of the latest document (PDF)

Database operations

Web sites are interactive and must persist data. The SQLite database used in this project is relatively lightweight. The database is implemented using ORM encapsulated by SQLAlchemy. The following code shows how to create a comment:

@mod.route('/comments/<int:id>/', methods=['GET', 'POST'])
@requires_admin
def edit_comment(id):
    comment = Comment.query.get(id)
    snippet = comment.snippet
    form = dict(title=comment.title, text=comment.text)
    if request.method == 'POST':
        ...
        form['title'] = request.form['title']
        form['text'] = request.form['text']
        ..
        comment.title = form['title']
        comment.text = form['text']
        db_session.commit()
        flash(u'Comment was updated.')
        return redirect(snippet.url)
    ...
Copy the code
  • Create comment object
  • Get the user-submitted title and text from the HTML form form
  • Assign and commit the COMMENT object
  • A prompt to refresh the page (shown in the Message section of the template)
  • Return to the new URL

With SQLAlchemy, the data model’s manipulation API is straightforward. To use a database, you need to create a database connection, build a model, etc., mainly in the Database module:

DATABASE_URI = 'sqlite:///' + os.path.join(_basedir, 'flask-website.db') # create_engine(app.config['DATABASE_URI'], convert_unicode=True, **app.config['DATABASE_CONNECT_OPTIONS']) # db_session = scoped_session(sessionmaker(autocommit=False, Autoflush =False, bind=engine) def init_db(): Model = declarative_base(name='Model') model.query = db_session.query_property()Copy the code

Comment Data model definition:

class Comment(Model):
    __tablename__ = 'comments'
    id = Column('comment_id', Integer, primary_key=True)
    snippet_id = Column(Integer, ForeignKey('snippets.snippet_id'))
    author_id = Column(Integer, ForeignKey('users.user_id'))
    title = Column(String(200))
    text = Column(String)
    pub_date = Column(DateTime)

    snippet = relation(Snippet, backref=backref('comments', lazy=True))
    author = relation(User, backref=backref('comments', lazy='dynamic'))

    def __init__(self, snippet, author, title, text):
        self.snippet = snippet
        self.author = author
        self.title = title
        self.text = text
        self.pub_date = datetime.utcnow()

    def to_json(self):
        return dict(author=self.author.to_json(),
                    title=self.title,
                    pub_date=http_date(self.pub_date),
                    text=unicode(self.rendered_text))

    @property
    def rendered_text(self):
        from flask_website.utils import format_creole
        return format_creole(self.text)
Copy the code

The Comment model defines the table name, 6 fields, 2 association relations and json and textual presentation methods in a structured way.

The use of SQLAlchemy was covered in previous articles and will not be covered in this article.

OpenID authentication

A niche website, the construction of their own account is troublesome and unsafe, the use of a third-party user system will be more appropriate. This project uses optnID login authentication provided by the flask-OpenID library.

When a user logs in, the system switches to the corresponding website for authentication based on the selected three-party login site:

@mod.route('/login/', methods=['GET', 'POST'])
@oid.loginhandler
def login():
    ..
    openid = request.values.get('openid')
    if not openid:
        openid = COMMON_PROVIDERS.get(request.args.get('provider'))
    if openid:
        return oid.try_login(openid, ask_for=['fullname', 'nickname'])
    ..
Copy the code

This process is easier to understand from the corresponding template, you can see the default support for AOL/Google/Yahoo account system authentication:

{% block body %}
  <form action="" method=post>
    <p>
      For some of the features on this site (such as creating snippets
      or adding comments) you have to be signed in.  You don't need to
      create an account on this website, just sign in with an existing
      <a href=http://openid.net/>OpenID</a> account.
    <p>
      OpenID URL:
      <input type=text name=openid class=openid size=30>
      <input type=hidden name=next value="{{ next }}">
      <input type=submit value=Login>
    <p>
      Alternatively you can directly sign in by clicking on one of
      the providers here in case you don't know the identity URL:
    <ul>
      <li><a href=?provider=aol>AOL</a>
      <li><a href=?provider=google>Google</a>
      <li><a href=?provider=yahoo>Yahoo</a>
    </ul>
  </form>
{% endblock %}
Copy the code

After the three-party site authentication is completed, the binding relationship between the users of the site and OpenID will be established:

@mod.route('/first-login/', methods=['GET', 'POST'])
def first_login():
    ...
        db_session.add(User(request.form['name'], session['openid']))
        db_session.commit()
        flash(u'Successfully created profile and logged in')
    ...
Copy the code
  • The OpenID in the session is written to the session after the third-party login succeeds

The logical process of three-party login is as follows: Log in to the three-party platform and associate the account with the local site. The specific implementation mainly relies on the flask-OpenID module, which we can roughly understand.

The full text retrieval

Full text search is very important for a site to help users quickly find suitable content on the site. This project demonstrates how to use Whoosh, a full-text search tool implemented in pure Python, to build web site content searches, unlike using a large search library like ElasticSearch. In short, this project uses small tools, pure Python implementations.

Full text retrieval is accessed from the /search/ entry:

@mod.route('/search/')
def search():
    q = request.args.get('q') or ''
    page = request.args.get('page', type=int) or 1
    results = None
    if q:
        results = perform_search(q, page=page)
        if results is None:
            abort(404)
    return render_template('general/search.html', results=results, q=q)
Copy the code
  • Q is the keyword of the search, and page is the number of pages turned
  • The index is queried using the perform_search method
  • If the content display cannot be found 404; If the content is found, show the results

The search method is provided in the search module, and the perform_search function called earlier is its alias:

def search(query, page=1, per_page=20): with index.searcher() as s: qp = qparser.MultifieldParser(['title', 'content'], index.schema) q = qp.parse(unicode(query)) try: result_page = s.search_page(q, page, pagelen=per_page) except ValueError: if page == 1: return SearchResultPage(None, page) return None results = result_page.results results.highlighter.fragmenter.maxchars = 512 results.highlighter.fragmenter.surround = 40 results.highlighter.formatter = highlight.HtmlFormatter('em', Classname ='search-match', termclass='search-term', between=u'<span class=ellipsis>... </span>') return SearchResultPage(result_page, page)Copy the code
  • Search ttile and Content for the keyword q
  • Set to use Unicode encoding
  • Encapsulate the search results as a SearchResultPage

Focus on the index.searcher() index, which is built using the following method:

from whoosh import highlight, analysis, qparser from whoosh.support.charset import accent_map ... def open_index(): from whoosh import index, fields as f if os.path.isdir(app.config['WHOOSH_INDEX']): return index.open_dir(app.config['WHOOSH_INDEX']) os.mkdir(app.config['WHOOSH_INDEX']) analyzer = analysis.StemmingAnalyzer() | analysis.CharsetFilter(accent_map) schema = f.Schema( url=f.ID(stored=True, unique=True), Id =f.ID(stored=True), title= f.text (stored=True, field_boost=2.0, Analyzer = Analyzer), type= F.ID (stored=True), keywords=f.KEYWORD(commas=True), content=f.TEXT(analyzer=analyzer) ) return index.create_in(app.config['WHOOSH_INDEX'], schema) index = open_index()Copy the code
  • Whoosh creates the local index file
  • Whoosh builds the data structure of the search, including url, title, keyword, and content
  • Keyword and content participation retrieval

Indexes need to be built and refreshed:

def update_documentation_index():
    from flask_website.docs import DocumentationPage
    writer = index.writer()
    for page in DocumentationPage.iter_pages():
        page.remove_from_search_index(writer)
        page.add_to_search_index(writer)
    writer.commit()
Copy the code

The document index is built into the Docs module:

DOCUMENTATION_PATH = os.path.join(_basedir, '.. /flask/docs/_build/dirhtml') WHOOSH_INDEX = os.path.join(_basedir, 'flask-website.whoosh') class DocumentationPage(Indexable): search_document_kind = 'documentation' def __init__(self, slug): self.slug = slug fn = os.path.join(app.config['DOCUMENTATION_PATH'], slug, 'index.html') with open(fn) as f: contents = f.read().decode('utf-8') title, Text = _doc_body_re.search(contents).groups() self.title = Markup(title).striptags().split(u' -- ')[0].strip() self.text = Markup(text).striptags().strip().replace(u'¶', u') @classmethod def iter_pages(CLS): base_folder = os.path.abspath(app.config['DOCUMENTATION_PATH']) for dirpath, dirnames, filenames in os.walk(base_folder): if 'index.html' in filenames: slug = dirpath[len(base_folder) + 1:] # skip the index page. useless if slug: yield DocumentationPage(slug)Copy the code
  • Document reads source files in DOCUMENTATION_PATH (project documents)
  • Read the title and text of the file and build the index file

summary

Here we take a quick look at Flask-View, flask’s former main station. Although we don’t go into too much detail, we know the implementation of the four functions of template rendering, database operation, OpenID authentication and full text retrieval, and establish an index of related technologies. If we need to build our own small Web project, such as a blog, we can use that project as a base and modify the implementation.

After a few weeks of tweaking, let’s move on to one of Python’s most influential projects: Django. Stay tuned.

tip

This project provides two very useful tips. The first is jSON-formatted and HTMl-formatted output, so the user can choose the output method and the site can also build a pure API interface. This functionality is provided using the request_wants_json function below:

def request_wants_json():
    # we only accept json if the quality of json is greater than the
    # quality of text/html because text/html is preferred to support
    # browsers that accept on */*
    best = request.accept_mimetypes \
        .best_match(['application/json', 'text/html'])
    return best == 'application/json' and \
       request.accept_mimetypes[best] > request.accept_mimetypes['text/html']
Copy the code

The request_wants_json function determines the MIME type of the header and determines whether it is application/ JSON or text/ HTML.

The second tip is the authentication decorator. The first one is login authentication and the second one is super administrative authentication:

def requires_login(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if g.user is None:
            flash(u'You need to be signed in for this page.')
            return redirect(url_for('general.login', next=request.path))
        return f(*args, **kwargs)
    return decorated_function

def requires_admin(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if not g.user.is_admin:
            abort(401)
        return f(*args, **kwargs)
    return requires_login(decorated_function)
Copy the code

These two decorators, used on the VIEW API, require login to edit snippets and administrator permissions to comment:

@mod.route('/edit/<int:id>/', methods=['GET', 'POST'])
@requires_login
def edit(id):
    ...

@mod.route('/comments/<int:id>/', methods=['GET', 'POST'])
@requires_admin
def edit_comment(id):
    ...
Copy the code

Refer to the link

  • Github.com/pallets/fla…