Простой блог с комментариями на Django: разработка и развертывание для самых маленьких

Данная статья предназначена для новичков в web-программировании и освещает вопросы разработки блога на Django с использованием Twitter Bootstrap и его развертывания на бесплатном хостинге PythonAnywhere. Я старался написать как можно более проще и понятнее. Более опытным пользователям данное чтиво не расскажет ничего нового, да и некоторые приемы возможно покажутся неэффективными.

dce775787d6c04aac8f8b66e8620235a1

Предполагаю, что читатель уже ознакомлен с синтаксисом Python, имеет минимальное представление о Django (для начала неплохо пройти обучение на http://codeacademy.com по соответствующей теме и прочитать туториал по Django), а также владеет приемами работы в командной строке.
Итак, начинаем с организации рабочего окружения на локальном компьютере. В принципе, для наших запросов подойдет любая операционная система в которой вы чувствуете себя уверенно, здесь я описываю процесс для GNU/Linux, а для других систем шаги могут незначительно отличаться. В системе должна быть установлена virtualenv — утилита для создания изолированного рабочего окружения (чтобы библиотеки, которые мы используем не мешались другим программам и проектам).

Cоздаем и активируем окружение:

mkdir ~/projects
cd ~/projects
virtualenv env
source env/bin/activate 

В ОС Windows последняя команда должна быть такой:

env/Scripts/activate 

Устанавливаем Django с помощью pip — менеджера пакетов Python.

pip install django

Создаем новый проект. Назовем его как-нибудь оригинально — например mysite.

django-admin.py startproject mysite && cd mysite

Скрипт отработает и создаст каталог mysite с еще одним каталогом mysite и несколькими *.py файлами внутри.

Используем скрипт manage.py для создания приложения django с именем blog.

python manage.py startapp blog

Отредактируем настройки в файле mysite/settings.py (обратите внимание: я имею ввиду ~/projects/mysite/mysite/settings.py) добавив туда следующее:

# coding: utf-8
import os

BASE_DIR = os.path.dirname(os.path.dirname(__file__))

В первой строке укажем кодировку в которой работаем, во избежание путаницы и глюков предлагаю указывать ее во всех изменяемых файлах *.py, перекодируя их соответственно в UTF-8.
В BASE_DIR будет храниться полный путь к нашему проекту, что бы использовать относительные пути при дальнейшем конфигурировании

Настроим базу данных, в нашем проекте вполне можно использовать SQLite

DATABASES = { 'default':
    {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

Настроим часовой пояс и язык:

TIME_ZONE = 'Europe/Moscow'
LANGUAGE_CODE = 'ru-ru'

Для того что бы Django узнало о созданном приложении, добавим ‘blog’ в кортеж INSTALLED_APPS, а также раскомментируем строку ‘django.contrib.admin’ для включения встроенной админки:

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.admin',
    'blog',
)

Чтобы админка заработала, редактируем mysite/urls.py

from django.conf.urls import patterns, include, url

from django.contrib import admin
admin.autodiscover()  #функция автоматического обнаружения файлов admin.py в наших приложениях

urlpatterns = patterns('',
    url(r'^admin/', include(admin.site.urls)), #URL админки http://имя_сайта/admin/
)

Cоздаем модель в blog/models.py

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=255) # заголовок поста
    datetime = models.DateTimeField(u'Дата публикации') # дата публикации
    content = models.TextField(max_length=10000) # текст поста

    def __unicode__(self):
        return self.title

    def get_absolute_url(self):
        return "/blog/%i/" % self.id

На основании этой модели Django автоматически создаст таблицы в базе данных.

Регистрируем ее в админке blog\admin.py

from django.contrib import admin
from blog.models import Post # наша модель из blog/models.py

admin.site.register(Post)

Создадим таблицы командой:

python manage.py syncdb

При первом вызове этой команды Django предложит создать суперпользователя, воспользуемся данным предложением.

Запускаем отладочный сервер который предоставляет Django:

python manage.py runserver

Теперь вводим url в браузере

http://localhost:8000/admin/

Если все прошло хорошо, мы должны видеть это:
996d5f156f5d1efd6cde95cb1fdec8e51

Заходим с ранее созданным логином\паролем в админку — теперь мы имеем возможность добавлять и удалять посты (кнопки справа от Posts)

Создадим несколько постов для отладки.

Теперь займемся созданием фронтенда, нам требуется всего две шаблонных страницы — одна с перечислением всех постов, вторая — содержание поста.

Редактируем blog/views.py

from blog.models import Post 
from django.views.generic import ListView, DetailView

class PostsListView(ListView): # представление в виде списка
    model = Post                   # модель для представления 

class PostDetailView(DetailView): # детализированное представление модели
    model = Post

Добавим в urlpatterns mysite\urls.py строку

url(r'^blog/', include('blog.urls')),

Для того что бы все URL-ы начинающиеся с /blog/ будут обрабатываться с помощью urls.py из модуля blog, и создаем сам файл urls.py в каталоге blog со следующим содержанием:

#coding: utf-8
from django.conf.urls import patterns, url

from blog.views import PostsListView, PostDetailView 

urlpatterns = patterns('',
url(r'^$', PostsListView.as_view(), name='list'), # то есть по URL http://имя_сайта/blog/ 
                                               # будет выводиться список постов
url(r'^(?P<pk>\d+)/$', PostDetailView.as_view()), # а по URL http://имя_сайта/blog/число/ 
                                              # будет выводиться пост с определенным номером

)

Теперь нужно создать шаблоны страниц. По умолчанию для класса PostListView Django будет искать шаблон в blog/templates/blog/post_list.html (такой длинный и странный путь связан с логикой работы фреймворка, в силах разработчика поменять это поведение, но в данной статье я этого не касаюсь)

создадим этот файл:

{% block content %}
    {% for post in object_list %}
        <p>{{ post.datetime }}</p>
        <h2><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h2>
        <p>{{ post.content }}</p>
    {% empty %}
    <p>Нет постов</p>
    {% endfor %}

{% endblock %}

Что же, давайте попробуем как все работает, зайдя по URL на http://localhost:8000/blog/. Если ошибок нет, то мы увидим список постов, где заголовок каждого поста является ссылкой.
Пока эти ссылки ведут в никуда, надо это исправить. По умолчанию для класса PostDetailView шаблон находится в blog\templates\blog\post_detail.html.

Создаем его:

{% block content %}
    <p>{{ post.datetime }}</p>
    <h2>{{ post.title }}</h2>
    <p>{{ post.content }}</p>
{% endblock %}

И снова проверяем: http://localhost:8000/blog/1/

Добавим возможность комментирования наших записей, в этих целях мы воспользуемcя услугами DISQUS, который установим с помощью pip

pip install django-disqus 

Этот модуль предоставляет функционал комментариев, с защитой от спама, аватарками и прочим, а также берет на себя заботу по их хранению:

Добавляем в post_detail.html перед {% endblock %}

<p>
    {% load disqus_tags %}
    {% disqus_dev %}
    {% disqus_show_comments %}
</p>

В INSTALLED_APPS файла settings.py добавляем ‘disqus’

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.admin',
    'blog',
    'disqus',
)

А также добавляем в settings.py

DISQUS_API_KEY = '***'
DISQUS_WEBSITE_SHORTNAME = '***'

Два последних значения получаем, зарегистрировавшись на http://disqus.com.

Тестируем проект в браузере. Отлично, функционал нашего приложения внушает, но надо что-то делать с оформлением. Самый простой, и в тоже время современный вариант, использовать Twitter Bootstrap.

Качаем архив http://twitter.github.io/bootstrap/assets/bootstrap.zip и разархивируем его в каталог static нашего проекта (я имею в виду ~/projects/mysite/static — создаем его)

Редактируем settings.py, что бы Django знало где искать статические страницы.

STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),
)

Создаем файл blog/templates/blog/base.html следующего содержания

<!DOCTYPE html>
<html lang="ru">
    <head>
        <meta charset="utf-8" />
        <title>MyBlog</title>
        <link href="{{STATIC_URL}}bootstrap/css/bootstrap.css" rel="stylesheet">
        <style>
            body {
                padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
            }
        </style>
        <link href="{{STATIC_URL}}bootstrap/css/bootstrap-responsive.css" rel="stylesheet">
        <!--[if lt IE 9]>
        <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
        <![endif]-->
        <script src="{{STATIC_URL}}bootstrap/js/bootstrap.js" type="text/javascript"></script>
        {% block extrahead %}
        {% endblock %}
        <script type="text/javascript">
        $(function(){
        {% block jquery %}
        {% endblock %}
        });
        </script>
    </head>
<body>

<div class="navbar navbar-inverse navbar-fixed-top">
    <div class="navbar-inner">
        <div class="container">
            <div class="brand">My Blog</div>
            <ul class="nav">
                <li><a href="{% url 'list' %}" class="">Список постов</a></li>
            </ul>
        </div>
    </div>

</div>

<div class="container">
     {% block content %}Empty page{% endblock %}
</div> <!-- container -->

</body>
</html>

Это основной шаблон для наших страниц, включаем его в post_list.html и post_detail.html дописав первой строкой в них

{% extends 'blog/base.html' %}

Проверяем работоспособность. Вот теперь красота наведена, можно приступить развертыванию приложения на бесплатном облачном хостинге.

Регистрируем бесплатный N00b аккаунт на PythonAnywhere. Мне понравился этот сервис легкостью в установке Django, все происходит практически так же как и на локальном компьютере.

Допустим мы создали пользователя на PythonAnywhere с именем djangotest, тогда наше приложение будет располагаться по адресу djangotest.pythonanywhere.com. Внимание: заменяйте далее по текстк ‘djangotest’ на свой логин в PythonAnywhere.

Меняем в settings.py

DEBUG = False

и дописываем

ALLOWED_HOSTS = ['djangotest.pythonanywhere.com']

Закачиваем файлы на хостинг любым из доступных способов.

На мой взгляд, для неискушенного пользователя, проще всего заархивировать папку проекта, залить архив на сервер (в разделе Files->Upload a file) и разархивировать его на сервере командой в bash — шелле (Раздел Consoles -> bash):

например если мы залили файл mysite.tar.gz то выполняем в консоли PythonAnywhere


tar -zxvf mysite.tar.gz

Теперь настраиваем рабочее окружение на сервере, вводим в консоли PythonAnywhere:

virtualenv env

source env/bin/activate

pip remove django
pip install django django-disqus

Настраиваем статические страницы в разделе Web -> Static files:
36f21dc3890d207dc7b32bf2f5ad61ac1
В первой строке — место где лежит bootstrap, во второй статические файлы встроенной админки Django.

Настраиваем WSGI (Web -> It is configured via a WSGI file stored at: …):

activate_this = '/home/djangotest/env/bin/activate_this.py'
execfile(activate_this, dict(__file__=activate_this))

import os
import sys

path = '/home/djangotest/mysite'
if path not in sys.path:
    sys.path.append(path)
os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'

import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()

Нажимаем кнопку Web -> Reload djangotest.pythonanywhere.com

Заходим на djangotest.pythonanywere.com/blog/ — поздравляю, это было нелегко, но Вы справились. Теперь у Вас есть собственный уютненький блог, разработанный своими руками на самых современных веб-технологиях!