Создание блога на Flask и MongoDB

Перевод. Оригинал заметки находится по адресу: http://docs.mongodb.org/ecosystem/tutorial/write-a-tumblelog-application-with-flask-mongoengine/.

Вступление

Это руководство описывает процесс создания веб-приложения блога на популярном фреймворке Python Flask и БД MongoDB.

Блог будет состоять из двух частей:

  1. Публичная часть позволит пользователям просматривать статьи и комментировать их,
  2. Админка сайта позволит добавлять и изменять статьи.

Это руководство предполагает, что вы уже имеете общие представления о Flask и MongoDB и уже установили MongoDB. Это руководство использует MongoEngine как Object Document Mapper (ODM), этот компонент упростит взаимодействие между Flask и MongoDB.

Где получить помощь

Если в процессе изучения данного руководства у вас возникнут проблемы, пожалуйста напишите сообщение mongodb-user (англ., прим. переводчика) или присоединяйтесь к IRC каналу #mongodb на irc.freenode.net (англ., прим. переводчика) для общения с пользователями MongoDB, которые могут оказать вам помощь.

Установка

Начнём с установки пакетов, необходимых для дальнейшей работы.

Зависимости

Это руководство использует pip для установки пакетов и virtualenv для изоляции окружения Python. В целом эти утилиты и конфигурация не требуются, как таковые, они обеспечивают стандартное окружение и рекомендуются к использованию. Выполните следующие команды в системной консоли:

pip install virtualenv
virtualenv myproject

Соответственно эти команды: устанавливают virtualenv (используя pip) и создают изолированное виртуальное окружение Python для проекта (с названием myproject).

Для запуска окружения myproject из системной консоли используйте следующую команду:

source myproject/bin/activate

Установка пакетов

Flask это "микрофреймворк", поскольку предоставляет маленькое ядро для обеспечения функциональности и высокую масштабируемость. Для проекта блога это руководство включает следующие расширения:

  • WTForms обеспечивает простую работу с формами,
  • Flask-MongoEngine обеспечивает интеграцию между MongoEngine, Flask, и WTForms,
  • Flask-Script для простого использования сервера разработки.

Устанавим их следующими командами:

pip install flask
pip install flask-script
pip install WTForms
pip install mongoengine
pip install flask_mongoengine

UPD 14.04.2015 Из-за бага в pymongo 3.0 рекомендую ставить предыдущую версию, иначе будут проблемы соединения с базой: pip install pymongo==2.8, pip install mongoengine==0.8.7.

Начнём создание нашего приложения.

Создание блога

В первую очередь создадим основу прилоожения. Создайте директорию с названием tumblelog, в которой разместите файл __init__.py, содержащий следующий контент:

from flask import Flask
app = Flask(__name__)


if __name__ == '__main__':
    app.run()

Далее создайте файл manage.py#[1] В дальнейшем мы будем использовать этот файл для загрузки дополнительных Flask-скриптов. Flask-скрипты предоставляют сервер разработки и оболочку-шелл:

# -*- coding: utf-8 -*-
# Установка путей
import os, sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from flask.ext.script import Manager, Server
from tumblelog import app

manager = Manager(app)

# Включение по умолчанию отладчика и перезагрузки
manager.add_command("runserver", Server(
    use_debugger = True,
    use_reloader = True,
    host = '0.0.0.0')
)

if __name__ == "__main__":
    manager.run()
Вы можете запустить приложение на тестовом сервере путём выполнения следующей команды в системной консоли:
python manage.py runserver

Ошибок быть не должно и по адресу http://localhost:5000/ ваш браузер отобразит страницу с сообщением "404".

[1] Эта концепция близка разработчикам на Django.

Конфигурирование MongoEngine и Flask

Установим расширение Flask и добавим конфигурацию. Обновите tumblelog/__init__.py так, чтобы он содержал следующее:

from flask import Flask
from flask.ext.mongoengine import MongoEngine

app = Flask(__name__)
app.config["MONGODB_SETTINGS"] = {'DB': "my_tumble_log"}
app.config["SECRET_KEY"] = "KeepThisS3cr3t"

db = MongoEngine(app)

if __name__ == '__main__':
    app.run()

Смотрите так-же:

MongoEngine Settings для дополнительных опций конфигурации.

Определение схемы

Первый шаг в написании блога на Flask это определение "моделей", или в терминологии MongoDB -- документов.

В этом приложении вы определяете статьи и комментарии, так, что каждая Статья (Post) может иметь несколько Комментариев (Comments). Отредактируйте models.py так, чтобы он содержал следующее:

# -*- coding: utf-8 -*-
import datetime
from flask import url_for
from tumblelog import db


class Post(db.Document):
    created_at = db.DateTimeField(default=datetime.datetime.now, required=True)
    title = db.StringField(max_length=255, required=True)
    slug = db.StringField(max_length=255, required=True)
    body = db.StringField(required=True)
    comments = db.ListField(db.EmbeddedDocumentField('Comment'))

    def get_absolute_url(self):
        return url_for('post', kwargs={"slug": self.slug})

    def __unicode__(self):
        return self.title

    meta = {
        'allow_inheritance': True,
        'indexes': ['-created_at', 'slug'],
        'ordering': ['-created_at']
    }


class Comment(db.EmbeddedDocument):
    created_at = db.DateTimeField(default=datetime.datetime.now, required=True)
    body = db.StringField(verbose_name=u"Комментарий", required=True)
    author = db.StringField(verbose_name=u"Имя", max_length=255, required=True)

Выше видно, что синтаксис MongoEngine прост и декларативен. Если вы используете бэкграунд Django, синтаксис может показаться вам знакомым. Этот пример определяет индексы для Post: один для даты created_at, т.к. наша главная страница будет отсортирована по дате и другой для индивидуального адреса статьи.

Добавление данных через шелл

Manage.py обеспечивает интерфейс оболочки шелл для приложения, который вы можете использовать для добавления данных в блог. В первую очередь сконфигурируем адрес и контент для приложения, для этого вы можете использовать интерфейс для взаимодействия с блогом. Начнём с выполнения следующей команды для запуска шелла Python:

python manage.py shell

Начнём с создания первой статьи путём выполнения следующей последовательности команд:

>>> from tumblelog.models import *
>>> post = Post(
... title="Привет, мир!",
... slug="hello-world",
... body="Добро пожаловать на мой блог, работающий на MongoDB, MongoEngine и Flask!"
... )
>>> post.save()

Добавим комментарий, используя следующую последовательность:

>>> post.comments
[]
>>> comment = Comment(
... author="Вася",
... body="Замечательная статья!"
... )
>>> post.comments.append(comment)
>>> post.save()

В завершение просмотрим статью:

>>> post = Post.objects.get()
>>> post
<Post: Привет, мир!>
>>> post.comments
[<Comment: Comment object>]

Добавление представления

Использование основанной на классах системы представлений Flask позволит нам создать представления в виде списка (List) и детального вида (Detail) для отображения статей. Добавьте views.py и создайте blueprint для статей:

from flask import Blueprint, request, redirect, render_template, url_for
from flask.views import MethodView
from tumblelog.models import Post, Comment

posts = Blueprint('posts', __name__, template_folder='templates')


class ListView(MethodView):

    def get(self):
        posts = Post.objects.all()
        return render_template('posts/list.html', posts=posts)


class DetailView(MethodView):

    def get(self, slug):
        post = Post.objects.get_or_404(slug=slug)
        return render_template('posts/detail.html', post=post)


# Register the urls
posts.add_url_rule('/', view_func=ListView.as_view('list'))
posts.add_url_rule('/<slug>/', view_func=DetailView.as_view('detail'))
Теперь в __init__.py зарегистрируйте blueprint, избегая циклическую зависимость в методе. Добавим следующий код в модуль:
def register_blueprints(app):
    # Prevents circular imports
    from tumblelog.views import posts
    app.register_blueprint(posts)

register_blueprints(app)
Добавьте этот метод и метод вызова в тело модуля, до блока main.

Добавление шаблонов

В директорию блога добавьте директории templates и templates/posts для хранения шаблонов блога:

mkdir -p templates/posts

Создадим основной шаблон templates/base.html. Все другие шаблоны будут наследовать его.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Мой блог</title>
    <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet">
    <style>.content {padding-top: 80px;}</style>
  </head>

  <body>

    {%- block topbar -%}
    <nav class="navbar navbar-inverse navbar-fixed-top">
      <div class="container">
        <div class="navbar-header">
          <h2>
              <a href="/" class="brand">Мой блог</a> <small>Работает на Flask, MongoDB и MongoEngine</small>
          </h2>
        </div>
        <div class="navbar-collapse">
        </div><!--/.nav-collapse -->
      </div>
    </nav>
    {%- endblock -%}

    <div class="container">
      <div class="content">
        {% block page_header %}{% endblock %}
        {% block content %}{% endblock %}
      </div>
    </div>
    {% block js_footer %}{% endblock %}
  </body>
</html>

Продолжим созданием лэндинг пейдж, для отображения списка статей блога. Добавим следующее в файл templates/posts/list.html:

{% extends "base.html" %}

{% block content %}
    {% for post in posts %}
      <h2><a href="{{ url_for('posts.detail', slug=post.slug) }}">{{ post.title }}</a></h2>
      <p>{{ post.body|truncate(100) }}</p>
      <p>
        {{ post.created_at.strftime('%H:%M %d-%m-%Y') }} |
        {% with total=post.comments|length %}
            {{ total }} комментари {%- if total %10 in (2,3,4) %}я{%- elif total %10 in (5,6,7,8,9,0) -%}ев{%- else -%}й{%- endif -%}
        {% endwith %}
      </p>
    {% endfor %}
{% endblock %}

В заключение, добавим шаблон templates/posts/detail.html для отображения самой статьи:

{% extends "base.html" %}

{% block page_header %}
  <div class="page-header">
    <h1>{{ post.title }}</h1>
  </div>
{% endblock %}

{% block content %}
  <p>{{ post.body }}<p>
  <p>{{ post.created_at.strftime('%H:%M %d-%m-%Y') }}</p>
  <hr>
  <h2>Комментарии</h2>
  {% if post.comments %}
    {% for comment in post.comments %}
       <p>{{ comment.body }}</p>
       <p><strong>{{ comment.author }}</strong> <small>в {{ comment.created_at.strftime('%H:%M %d-%m-%Y') }}</small></p>
      {{ comment.text }}
    {% endfor %}
  {% endif %}
{% endblock %}

С этого момента вы можете снова выполнить команду python manage.py runserver и увидеть ваш блог. Перейдя по адресу http://localhost:5000 вы увидите нечто похожее:

Добавление комментариев

Следующим шагом мы добавим возможность нашим читателям комментировать статьи. Для обеспечения комментирования мы создадим форму, используя WTForms, для чего обновим шаблон представления.

Добавление комментариев в представление

Начнём с рефакторинга файла views.py, добавив в него форму. Для этого добавим импорт библиотек и класс DetailView в файл:

from flask.ext.mongoengine.wtf import model_form

...

class DetailView(MethodView):

    form = model_form(Comment, exclude=['created_at'])

    def get_context(self, slug):
        post = Post.objects.get_or_404(slug=slug)
        form = self.form(request.form)

        context = {
            "post": post,
            "form": form
        }
        return context

    def get(self, slug):
        context = self.get_context(slug)
        return render_template('posts/detail.html', **context)

    def post(self, slug):
        context = self.get_context(slug)
        form = context.get('form')

        if form.validate():
            comment = Comment()
            form.populate_obj(comment)

            post = context.get('post')
            post.comments.append(comment)
            post.save()

            return redirect(url_for('posts.detail', slug=slug))

        return render_template('posts/detail.html', **context)


Замечание

DetailView расширяет стандартный метод Flask MethodView.  Этот код определяет метод get_context для получения контекста по умолчанию сразу для GET и POST запросов. Для POST, post() происходит проверка комментария: если проверка пройдена, post() добавляет комментарий в статью.

Добавление комментариев в шаблон

В завершение, мы добавим форму в шаблон, что позволит читателям создавать комментарии. Создание макроса для форм в templates/_forms.html позволит нам повторно использовать код формы:

{% macro render(form) -%}
<fieldset>
{% for field in form %}
{% if field.type in ['CSRFTokenField', 'HiddenField'] %}
  {{ field() }}
{% else %}
  <div class="clearfix {% if field.errors %}error{% endif %}">
    {{ field.label }}
    <div class="input">
      {% if field.name == "body" %}
        {{ field(rows=10, cols=40) }}
      {% else %}
        {{ field() }}
      {% endif %}
      {% if field.errors or field.help_text %}
        <span class="help-inline">
        {% if field.errors %}
          {{ field.errors|join(' ') }}
        {% else %}
          {{ field.help_text }}
        {% endif %}
        </span>
      {% endif %}
    </div>
  </div>
{% endif %}
{% endfor %}
</fieldset>
{% endmacro %}

Добавим форму комментариев в шаблон templates/posts/detail.html. Вставим импорт библиотек в начало кода и отображение формы после коментариев:

{% import "_forms.html" as forms %}

...

<hr>
<h2>Добавить комментарий</h2>
<form action="." method="post">
  {{ forms.render(form) }}
<hr>
  <div class="actions">
    <input type="submit" class="btn primary" value="Комментировать">
  </div>
</form>
<hr>

Теперь читатели вашего блога смогут комментировать ваши статьи. Запустите python manage.py runserver чтобы увидеть изменения.

Добавление интерфейса администрирования

Хотя вы можете добавлять статьи используя интерфейс командной строки, как это было показано ранее, следующим шагом мы добавим интерфейс администрирования для  управления сайтом. Чтобы получить интерфейс администрирования нам будет необходимо добавить представление общего плана и аутентификацию. В этом руководстве рассматриваются опции создания и редактирования статей, опция удаления и детектирования коллизий адресов slug рассмотрены не будут.

Добавление общей аутентификации

В рамках данного руководства всё что нам нужно это самая простая форма аутентификации. Следующий пример взят из примера Flask “Auth snippet”. Создадим файл auth.py со следующим содержимым:

from functools import wraps
from flask import request, Response


def check_auth(username, password):
    """This function is called to check if a username /
    password combination is valid.
    """
    return username == 'admin' and password == 'secret'


def authenticate():
    """Sends a 401 response that enables basic auth"""
    return Response(
    'Could not verify your access level for that URL.\n'
    'You have to login with proper credentials', 401,
    {'WWW-Authenticate': 'Basic realm="Login Required"'})


def requires_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth = request.authorization
        if not auth or not check_auth(auth.username, auth.password):
            return authenticate()
        return f(*args, **kwargs)
    return decorated

Замечание

Это создаст декоратор requires_auth: предоставление общей аутентификаци. Декорируйте все представления, нуждающиеся в аутентификации. Пользователь admin и пароль secret.

Создание представления страницы администрирования

Создадим файл представления admin.py. Следующее представление носит общий характер и облегчает настройку.

from flask import Blueprint, request, redirect, render_template, url_for
from flask.views import MethodView

from flask.ext.mongoengine.wtf import model_form

from tumblelog.auth import requires_auth
from tumblelog.models import Post, Comment

admin = Blueprint('admin', __name__, template_folder='templates')


class List(MethodView):
    decorators = [requires_auth]
    cls = Post

    def get(self):
        posts = self.cls.objects.all()
        return render_template('admin/list.html', posts=posts)


class Detail(MethodView):

    decorators = [requires_auth]

    def get_context(self, slug=None):
        form_cls = model_form(Post, exclude=('created_at', 'comments'))

        if slug:
            post = Post.objects.get_or_404(slug=slug)
            if request.method == 'POST':
                form = form_cls(request.form, inital=post._data)
            else:
                form = form_cls(obj=post)
        else:
            post = Post()
            form = form_cls(request.form)

        context = {
            "post": post,
            "form": form,
            "create": slug is None
        }
        return context

    def get(self, slug):
        context = self.get_context(slug)
        return render_template('admin/detail.html', **context)

    def post(self, slug):
        context = self.get_context(slug)
        form = context.get('form')

        if form.validate():
            post = context.get('post')
            form.populate_obj(post)
            post.save()

            return redirect(url_for('admin.index'))
        return render_template('admin/detail.html', **context)


# Register the urls
admin.add_url_rule('/admin/', view_func=List.as_view('index'))
admin.add_url_rule('/admin/create/', defaults={'slug': None}, view_func=Detail.as_view('create'))
admin.add_url_rule('/admin/<slug>/', view_func=Detail.as_view('edit'))

Замечание

Здесь представления List и Detail напоминают одноимённые из фронтэнда сайта, с разницей наличия декораторов requires_auth в обоих представлениях.

Представление "Detail" немного сложнее: для установки контекста, это представление проверяет адрес slug и если он отсутствует, Detail использует представление для создания новой статьи. Если адрес slug существует, Detail использует представление для редактирования существующей статьи.

 

В файле __init__.py обновим метод register_blueprints() для импорта нового blueprint администрирования.

def register_blueprints(app):
    # Prevents circular imports
    from tumblelog.views import posts
    from tumblelog.admin import admin
    app.register_blueprint(posts)
    app.register_blueprint(admin)

Создание шаблонов администрирования

Как и в пользовательской части сайта, секция администрирования нуждается в трёх шаблонах: общий шаблон, представление списка и детальное представление.

Создадим директорию admin для шаблонов. Добавим простую индексную страницу для администрирования в шаблон templates/admin/base.html:

{% extends "base.html" %}

 {%- block topbar -%}
 <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
  <div class="container">
    <div class="navbar-header">
      <h2><a href="{{ url_for('admin.index') }}" class="brand">Админка моего блога</a></h2>
    </div>
    <div class="navbar-collapse">
      <ul class="nav nav-pills navbar-nav navbar-right ">
      <li>
        <a href="{{ url_for("admin.create") }}" class="btn">Добавить статью</a>
      </li>
    </ul>
    </div><!-- /.nav-collapse -->
  </div><!-- /.container -->
</nav>
 {%- endblock -%}

Список всех статей в шаблон templates/admin/list.html:

{% extends "admin/base.html" %}

{% block content %}
  <table  class="table table-bordered table-striped">
    <thead>
      <th>Заголовок</th>
      <th>Дата создания</th>
      <th>Действия</th>
    </thead>
    <tbody>
    {% for post in posts %}
      <tr>
        <th><a href="{{ url_for('admin.edit', slug=post.slug) }}">{{ post.title }}</a></th>
        <td>{{ post.created_at.strftime('%Y-%m-%d') }}</td>
        <td><a href="{{ url_for("admin.edit", slug=post.slug) }}" class="btn primary">Редактировать</a></td>
      </tr>
    {% endfor %}
    </tbody>
  </table>
{% endblock %}

Добавим файл шаблона для создания и редактирования статей templates/admin/detail.html:

{% extends "admin/base.html" %}
{% import "_forms.html" as forms %}

{% block content %}
  <h2>
    {% if create %}
      Добавить статью
    {% else %}
      Редактировать статью
    {% endif %}
  </h2>

  <form action="?{{ request.query_string }}" method="post">
    {{ forms.render(form) }}
    <div class="actions">
      <input type="submit" class="btn primary" value="Сохранить">
      <a href="{{ url_for("admin.index") }}" class="btn secondary">Отмена</a>
    </div>
  </form>
{% endblock %}

Интерфейс администрирования  готов к использованию. Перезапустите тестовый сервер (runserver) и вы сможете зайти на страницу администрирования, расположенную по адресу http://localhost:5000/admin/. (Имя пользователя admin и пароль secret.)

Развитие функционала

На текущий момент, приложение поддерживает только статьи. В этом разделе мы добавим специальные типы статей: Видео, Изображения и Цитаты что более свойственно традиционным блогам. Добавление этих типов не требует дополнительной миграции, т.к.  MongoEngine поддерживает наследование документов.

Начнём с рефакторинга класса Post как с базового и создадим новые классы для новых типов статей. Обновим файл models.py следующим кодом класса Post:

class Post(db.DynamicDocument):
    created_at = db.DateTimeField(default=datetime.datetime.now, required=True)
    title = db.StringField(verbose_name=u"Заголовок", max_length=255, required=True)
    slug = db.StringField(max_length=255, required=True)
    comments = db.ListField(db.EmbeddedDocumentField('Comment'))

    def get_absolute_url(self):
        return url_for('post', kwargs={"slug": self.slug})

    def __unicode__(self):
	return self.title

    @property
    def post_type(self):
        return self.__class__.__name__

    meta = {
        'allow_inheritance': True,
        'indexes': ['-created_at', 'slug'],
        'ordering': ['-created_at']
    }


class BlogPost(Post):
    body = db.StringField(verbose_name=u"Текст",required=True)


class Video(Post):
    embed_code = db.StringField(verbose_name=u"Код для блога",required=True)


class Image(Post):
    image_url = db.StringField(verbose_name=u"URL картинки",required=True, max_length=255)


class Quote(Post):
    body = db.StringField(verbose_name=u"Цитата",required=True)
    author = db.StringField(verbose_name=u"Имя автора", required=True, max_length=255)

class Comment(db.EmbeddedDocument):
    created_at = db.DateTimeField(default=datetime.datetime.now, required=True)
    body = db.StringField(verbose_name=u"Комментарий", required=True)
    author = db.StringField(verbose_name=u"Имя", max_length=255, required=True)

Замечание

В классе Post метод post_type возвращает имя класса, что даёт возможность отображать различные типы статей в шаблонах.

 

Т.к. MongoEngine возвращает корректные классы при выборке объектов Post у нас нет необходимости модифицировать интерфейс логики отображения: только модифицировать шаблоны.

Обновим файл шаблона templates/posts/list.html и изменим формат отображения статей на следующий:

{% if post.body %}
  {% if post.post_type == 'Quote' %}
    <blockquote>{{ post.body|truncate(100) }}</blockquote>
    <p>{{ post.author }}</p>
  {% else %}
    <p>{{ post.body|truncate(100) }}</p>
  {% endif %}
{% endif %}
{% if post.embed_code %}
  {{ post.embed_code|safe() }}
{% endif %}
{% if post.image_url %}
  <p><img src="{{ post.image_url }}" /><p>
{% endif %}

В файле templates/posts/detail.html изменим формат отображения всех статей на следующий:

{% if post.body %}
  {% if post.post_type == 'Quote' %}
    <blockquote>{{ post.body }}</blockquote>
    <p>{{ post.author }}</p>
  {% else %}
    <p>{{ post.body }}</p>
  {% endif %}
{% endif %}
{% if post.embed_code %}
  {{ post.embed_code|safe() }}
{% endif %}
{% if post.image_url %}
  <p><img src="{{ post.image_url }}" /><p>
{% endif %}

Обновление админки

В этом разделе мы обновим интерфейс администрирования для поддержки новых типов статей.

Начнём с обновления файла admin.py с импорта новых моделей документов и обновления get_context() в классе Detail, чтобы динамически создавать правильную форму:

from tumblelog.models import Post, BlogPost, Video, Image, Quote, Comment

# ...

class Detail(MethodView):

    decorators = [requires_auth]
    # Map post types to models
    class_map = {
        'post': BlogPost,
        'video': Video,
        'image': Image,
        'quote': Quote,
    }

    def get_context(self, slug=None):

        if slug:
            post = Post.objects.get_or_404(slug=slug)
            # Handle old posts types as well
            cls = post.__class__ if post.__class__ != Post else BlogPost
            form_cls = model_form(cls,  exclude=('created_at', 'comments'))
            if request.method == 'POST':
                form = form_cls(request.form, inital=post._data)
            else:
                form = form_cls(obj=post)
        else:
            # Determine which post type we need
            cls = self.class_map.get(request.args.get('type', 'post'))
            post = cls()
            form_cls = model_form(cls,  exclude=('created_at', 'comments'))
            form = form_cls(request.form)
        context = {
            "post": post,
            "form": form,
            "create": slug is None
        }
        return context

    # ...

Обновим файл template/admin/base.html для создания выпадающего меню выбора при создании новой статьи:

{% extends "base.html" %}

{%- block topbar -%}
 <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
   <div class="container">
     <div class="navbar-header">
       <h2><a href="{{ url_for('admin.index') }}" class="brand">Админка моего блога</a></h2>
     </div>
     <div class="navbar-collapse">
       <ul class="nav nav-pills navbar-nav navbar-right ">
         <li>
           <a href="#" class="dropdown-toggle" data-toggle="dropdown">
             Добавить статью
             <span class="caret"></span>
           </a>
           <ul class="dropdown-menu">
             {% for type in ('post', 'video', 'image', 'quote') %}
             <li><a href="{{ url_for("admin.create", type=type) }}">{{ type|title }}</a></li>
             {% endfor %}
           </ul>
         </li>
       </ul>
     </div><!-- /.nav-collapse -->
   </div><!-- /.container -->
 </nav>
{%- endblock -%}

{% block js_footer %}
<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>
{% endblock %}

Теперь у вас есть блог, работающий на Fask и MongoEngine!

Дополнительные материалы

Исходный код доступен на Github: <https://github.com/ivn86/flask-tumblelog>.

Оригинал заметки находится по адресу: http://docs.mongodb.org/ecosystem/tutorial/write-a-tumblelog-application-with-flask-mongoengine/.

Добавлено: 2015-01-31, обновлено: 2015-04-15


Поделиться:

Один комментарий

2015-11-11 в 18:48 Александр написал:
Возможно ли в MongoEngine установить подключение через ssl?

Оставить комментарий

Комментарий появится после одобрения.

Поля со значком * обязательны для заполнения.