Статьи

Здание Риббит в Джанго

После реализации нашего Twitter-клона, Ribbit, на простом PHP и Rails , пришло время представить следующий обзор: Python! В этом уроке мы перестроим Ribbit, используя Django. Без дальнейших проволочек, начнем!

Home Page

На момент написания этой статьи Django 1.4 поддерживает Python 2.5 до 2.7.3. Прежде чем продолжить, убедитесь, что у вас есть версия apt, выполнив python -v в терминале. Обратите внимание, что Python 2.7.3 является предпочтительным. На протяжении всего этого руководства мы будем использовать pip в качестве менеджера пакетов и virtualenv для настройки виртуальных сред. Для их установки запустите терминал и выполните от имени пользователя root следующие команды

1
2
3
curl http://python-distribute.org/distribute_setup.py |
curl https://raw.github.com/pypa/pip/master/contrib/get-pip.py |
sudo pip install virtualenv

Чтобы настроить среду разработки Django, мы начнем с создания виртуальной среды. Выполните следующие команды в терминале (желательно внутри вашего каталога разработки)

1
2
virtualenv —no-site-packages ribbit_env
source ribbit_env/bin/activate

С нашей Виртуальной средой, настроенной и активированной (ваша командная строка должна быть изменена, чтобы отражать имя среды), давайте перейдем к установке зависимостей для проекта. Помимо Django, мы будем использовать South для обработки миграций базы данных. Мы будем использовать pip для их установки, выполнив. Обратите внимание, что с этого момента мы будем делать все внутри virtualenv. Поэтому убедитесь, что он активирован, прежде чем продолжить.

1
pip install Django South

После установки всех зависимостей мы готовы перейти к созданию нового проекта Django.


Мы начнем с создания нового проекта Django и нашего приложения. Перейдите в cd вами каталог и запустите:

1
2
3
django-admin.py startproject ribbit
cd ribbit
django-admin.py startapp ribbit_app

Далее мы инициализируем наш репозиторий git и создадим файл .gitignore внутри проекта Ribbit, который мы только что создали. Для этого запустите:

1
2
3
4
git init
echo «*.pyc» >> .gitignore
git add .
git commit -m ‘Initial Commit’

Давайте перейдем к редактированию ribbit/settings.py и настроим наш проект. Сначала мы определим некоторые константы. Добавьте следующее в начало файла:

1
2
3
4
import os
 
PROJECT_PATH = os.path.dirname(os.path.abspath(__file__))
LOGIN_URL = ‘/’

PROJECT_PATH будет хранить расположение каталога, в котором хранится PROJECT_PATH settings.py. Это позволит нам использовать относительные пути для будущих констант. LOGIN_URL , как следует из названия, обозначает, что корнем нашего сайта будет URL для входа в систему.

Двигаясь дальше, давайте настроим базу данных. Для среды разработки sqlite3 является идеальным выбором. Для этого отредактируйте константу DATABASES со следующими значениями:

01
02
03
04
05
06
07
08
09
10
DATABASES = {
    ‘default’: {
        ‘ENGINE’: ‘django.db.backends.sqlite3’, # Add ‘postgresql_psycopg2’, ‘mysql’, ‘sqlite3’ or ‘oracle’.
        ‘NAME’: os.path.join(PROJECT_PATH, ‘database.db’), # Or path to database file if using sqlite3.
        ‘USER’: », # Not used with sqlite3.
        ‘PASSWORD’: », # Not used with sqlite3.
        ‘HOST’: », # Set to empty string for localhost.
        ‘PORT’: », # Set to empty string for default.
    }
}

Django находит статические файлы из каталога, упомянутого в константе STATIC_ROOT и направляет запросы к файлам по пути, указанному в STATIC_URL . Настройте их так, чтобы они отражали следующее:

1
2
3
4
5
6
7
8
9
# Absolute path to the directory static files should be collected to.
# Don’t put anything in this directory yourself;
# in apps’ «static/» subdirectories and in STATICFILES_DIRS.
# Example: «/home/media/media.lawrence.com/static/»
STATIC_ROOT = os.path.join(PROJECT_PATH, ‘static’)
 
# URL prefix for static files.
# Example: «http://media.lawrence.com/static/»
STATIC_URL = ‘/static/’

Обратите внимание на использование os.path.join() . Функция позволяет нам относительно указать путь, используя константу PROJECT_PATH мы определили ранее.

Далее нам нужно указать местоположение, в котором Django должен искать файлы шаблонов. Мы отредактируем константу TEMPLATE_DIRS чтобы указать путь.

1
2
3
4
5
6
TEMPLATE_DIRS = (
    # Put strings here, like «/home/html/django_templates» or «C:/www/django/templates».
    # Always use forward slashes, even on Windows.
    # Don’t forget to use absolute paths, not relative paths.
    os.path.join(PROJECT_PATH, ‘templates’)
)

Наконец, давайте добавим South и ribbit_app в список INSTALLED_APPS .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
INSTALLED_APPS = (
    ‘django.contrib.auth’,
    ‘django.contrib.contenttypes’,
    ‘django.contrib.sessions’,
    ‘django.contrib.sites’,
    ‘django.contrib.messages’,
    ‘django.contrib.staticfiles’,
    ‘south’,
    ‘ribbit_app’,
    # Uncomment the next line to enable the admin:
    # ‘django.contrib.admin’,
    # Uncomment the next line to enable admin documentation:
    # ‘django.contrib.admindocs’,
)

Далее давайте создадим каталоги, которые мы определили в настройках, и создадим базу данных:

1
2
3
4
mkdir ribbit/static ribbit/templates ribbit_app/static
python manage.py syncdb
python manage.py schemamigration ribbit_app —initial
python manage.py migrate ribbit_app

Команда syncdb создаст необходимые таблицы и создаст учетную запись суперпользователя. Так как мы используем South для миграций, мы осуществляем начальную миграцию для приложения с использованием синтаксиса schemamigration <app_name> --initial и применяем его с помощью python manage.py migrate ribbit_app

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

1
python manage.py runserver

Если все действительно хорошо, вы должны увидеть следующую страницу при посещении http: // localhost: 8000

Landing Page

Далее дерево проекта должно выглядеть так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
ribbit
|— manage.py
|— ribbit
|
|
|
|
|
|
|
|
|
|
|
`— ribbit_app
    |— __init__.py
    |— __init__.pyc
    |— migrations
    |
    |
    |
    |
    |— models.py
    |— models.pyc
    |— static
    |— tests.py
    `— views.py

Прежде чем перейти к следующему шагу, давайте передадим наши изменения в репо.

1
2
git add .
git commit -m ‘Created app and configured settings’

Следуя ribbit_app/static по интерфейсу, загрузите ресурсы и поместите их в ribbit_app/static . Мы должны внести некоторые изменения в style.less для этого урока. Давайте начнем с добавления некоторых стилей для flash.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
.flash {
    padding: 10px;
    margin: 20px 0;
    &.error {
        background: #ffefef;
        color: #4c1717;
        border: 1px solid #4c1717;
    }
    &.warning {
        background: #ffe4c1;
        color: #79420d;
        border: 1px solid #79420d;
    }
    &.notice {
        background: #efffd7;
        color: #8ba015;
        border: 1px solid #8ba015;
    }
}

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

1
2
3
4
5
6
7
8
input {
    width: 179px;
    &.error {
        background: #ffefef;
        color: #4c1717;
        border: 1px solid #4c1717;
    }
}

Нам также нужно увеличить высоту #content.wrapper.panel.right .

1
height: 433px;

Наконец, давайте добавим правое поле к изображениям нижнего колонтитула.

1
2
3
4
5
6
7
footer {
    div.wrapper {
        img {
            margin-right: 5px;
        }
    }
}

Прежде чем двигаться дальше, давайте передадим изменения:

1
2
git add .
git commit -m ‘Added static files’

Далее, давайте создадим базовый шаблон, который будет наследоваться всеми другими шаблонами. Django использует собственный шаблонизатор (например, ERB или Jade). Определите шаблон в ribbit/templates/base.html со следующим содержанием:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<!DOCTYPE html>
<html>
<head>
    <link rel=»stylesheet/less» href=»{{ STATIC_URL }}style.less»>
    <script src=»{{ STATIC_URL }}less.js»></script>
</head>
<body>
    <header>
        <div class=»wrapper»>
            <img src=»{{ STATIC_URL }}gfx/logo.png»>
            <span>Twitter Clone
            {% block login %}
            <a href=»/»>Home</a>
            <a href=»/users/»>Public Profiles</a>
            <a href=»/users/{{ username }}»>My Profile</a>
            <a href=»/ribbits»>Public Ribbits</a>
            <form action=»/logout»>
                <input type=»submit» id=»btnLogOut» value=»Log Out»>
            </form>
            {% endblock %}
        </div>
    </header>
    <div id=»content»>
        <div class=»wrapper»>
            {% block flash %}
            {% if auth_form.non_field_errors or user_form.non_field_errors or ribbit_form.errors %}
            <div class=»flash error»>
                {{ auth_form.non_field_errors }}
                {{ user_form.non_field_errors }}
                {{ ribbit_form.content.errors }}
            </div>
            {% endif %}
            {% if notice %}
            <div class=»flash notice»>
                {{ notice }}
            </div>
            {% endif %}
            {% endblock %}
 
            {% block content %}
 
            {% endblock %}
        </div>
    </div>
    <footer>
        <div class=»wrapper»>
            Ribbit — A Twitter Clone Tutorial
            <a href=»http://net.tutsplus.com»>
                <img src=»{{ STATIC_URL }}gfx/logo-nettuts.png»>
            </a>
            <a href=»http://www.djangoproject.com/»>
                <img src=»https://www.djangoproject.com/m/img/badges/djangomade124x25.gif» border=»0″ alt=»Made with Django.»
            </a>
        </div>
    </footer>
</body>
</html>

В приведенной выше разметке {{ STATIC_URL }} печатает путь для статического URL, определенного в settings.py. Еще одной особенностью является использование блоков. Все содержимое блоков наследуется подклассам базового шаблона и не будет перезаписано, если блок явно не переопределен в них. Это дает нам некоторую гибкость для размещения навигационных ссылок в заголовке и замены их формой входа в систему, если пользователь не вошел в систему. Мы также используем блок с условием if чтобы проверить, являются ли какие-либо из переменных флэш-памяти не пустые и отображать сообщения соответствующим образом.

Хорошо! Время сделать еще один коммит:

1
2
git add .
git commit -m ‘Created base template’

Одна из лучших особенностей Django состоит в том, что он включает в себя довольно много моделей и форм, которые могут быть переопределены для удовлетворения многих целей. Для нашего приложения мы будем использовать модель User и добавим к ней несколько свойств, создав модель UserProfile . Далее, чтобы управлять ребрами, мы также создадим модель Ребра. Модель User предоставляемая Django, включает в себя поля для хранения имени пользователя, пароля, имени и фамилии, а также адреса электронной почты (с проверкой) и многих других. Я предлагаю вам взглянуть на API, чтобы узнать обо всех полях, поддерживаемых по умолчанию. Добавьте следующий код для моделей в ribbit_app/models.py .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
from django.db import models
from django.contrib.auth.models import User
import hashlib
 
 
class Ribbit(models.Model):
    content = models.CharField(max_length=140)
    user = models.ForeignKey(User)
    creation_date = models.DateTimeField(auto_now=True, blank=True)
 
 
class UserProfile(models.Model):
    user = models.OneToOneField(User)
    follows = models.ManyToManyField(‘self’, related_name=’followed_by’, symmetrical=False)
 
    def gravatar_url(self):
        return «http://www.gravatar.com/avatar/%s?s=50» % hashlib.md5(self.user.email).hexdigest()
 
 
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])

Давайте начнем с модели Ribbit . Атрибуты включают в себя CharField с максимальной длиной 140 символов для хранения содержимого, модель ForeignKey для пользователя (так что у нас есть отношение между двумя моделями) и DateTimeField который автоматически заполняется временем, когда экземпляр модели сохраняется

Переходя к модели UserProfile , у нас есть поле OneToOne которое определяет отношение «один к одному» с моделью User и поле ManyToMany для реализации отношения ManyToMany . Параметр related_name позволяет нам использовать отношение в обратном направлении, используя related_name нами имя. Мы также установили symmetrical значение False, чтобы гарантировать, что если пользователь A следует за B, то пользователь B не следует автоматически за A. Мы также определили функцию для получения ссылки на граватационное изображение на основе URL-адреса пользователя и свойства для get (если UserProfile существует для пользователя) или создайте его, когда мы используем синтаксис <user_object>.profile . Это позволяет нам легко UserProfile свойства UserProfile . Вот пример того, как вы можете использовать ORM для получения пользователей, за которыми следует данный пользователь, а затем:

1
2
3
superUser = User.object.get(id=1)
superUser.profile.follows.all() # Will return an iterator of UserProfile instances of all users that superUser follows
superUse.profile.followed_by.all() # Will return an iterator of UserProfile instances of all users that follow superUser

Теперь, когда наши модели определены, давайте сгенерируем миграции и применим их:

1
2
python manage.py schemamigration ribbit_app —auto
python manage.py migrate ribbit_app

Прежде чем двигаться дальше, давайте передадим изменения

1
2
git add .
git commit -m ‘Created Models’

Django позволяет нам создавать формы, чтобы мы могли легко проверять данные, принятые пользователем на предмет нарушений. Мы создадим пользовательскую форму для модели Ribbit создадим форму, которая наследует UserCreationForm предоставленную по умолчанию для управления регистрацией. Для управления аутентификацией мы расширим AuthenticationForm предоставляемый по умолчанию в Django. Давайте создадим новый файл ribbit_app/forms.py и добавим импорт.

1
2
3
4
5
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
from django.contrib.auth.models import User
from django import forms
from django.utils.html import strip_tags
from ribbit_app.models import Ribbit

Начнем с создания регистрационной формы. Мы UserCreateForm его UserCreateForm и его код приведен ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
class UserCreateForm(UserCreationForm):
   email = forms.EmailField(required=True, widget=forms.widgets.TextInput(attrs={‘placeholder’: ‘Email’}))
   first_name = forms.CharField(required=True, widget=forms.widgets.TextInput(attrs={‘placeholder’: ‘First Name’}))
   last_name = forms.CharField(required=True, widget=forms.widgets.TextInput(attrs={‘placeholder’: ‘Last Name’}))
   username = forms.CharField(widget=forms.widgets.TextInput(attrs={‘placeholder’: ‘Username’}))
   password1 = forms.CharField(widget=forms.widgets.PasswordInput(attrs={‘placeholder’: ‘Password’}))
   password2 = forms.CharField(widget=forms.widgets.PasswordInput(attrs={‘placeholder’: ‘Password Confirmation’}))
 
   def is_valid(self):
       form = super(UserCreateForm, self).is_valid()
       for f, error in self.errors.iteritems():
           if f != ‘__all_’:
               self.fields[f].widget.attrs.update({‘class’: ‘error’, ‘value’: strip_tags(error)})
       return form
 
   class Meta:
       fields = [’email’, ‘username’, ‘first_name’, ‘last_name’, ‘password1’,
                 ‘password2’]
       model = User

В приведенной выше форме мы явно установили некоторые поля как обязательные, передав значение required=True . Кроме того, я добавил атрибут placeholder к различным виджетам, используемым формами. Класс с именем error также добавляется в поля, содержащие ошибки. Это делается в функции is_valid() . Наконец, в классе Meta мы можем указать порядок, в котором мы хотим, чтобы наши поля формы отображались, и установить модель, по которой должна проверяться форма.

Далее напишем форму для аутентификации:

01
02
03
04
05
06
07
08
09
10
class AuthenticateForm(AuthenticationForm):
   username = forms.CharField(widget=forms.widgets.TextInput(attrs={‘placeholder’: ‘Username’}))
   password = forms.CharField(widget=forms.widgets.PasswordInput(attrs={‘placeholder’: ‘Password’}))
 
   def is_valid(self):
       form = super(AuthenticateForm, self).is_valid()
       for f, error in self.errors.iteritems():
           if f != ‘__all__’:
               self.fields[f].widget.attrs.update({‘class’: ‘error’, ‘value’: strip_tags(error)})
       return form

Как и в UserCreateForm , AuthenticateForm добавляет некоторые заполнители и классы ошибок.

Наконец, давайте закончим форму, чтобы принять новый Ribbit.

01
02
03
04
05
06
07
08
09
10
11
12
13
class RibbitForm(forms.ModelForm):
   content = forms.CharField(required=True, widget=forms.widgets.Textarea(attrs={‘class’: ‘ribbitText’}))
 
   def is_valid(self):
       form = super(RibbitForm, self).is_valid()
       for f in self.errors.iterkeys():
           if f != ‘__all__’:
               self.fields[f].widget.attrs.update({‘class’: ‘error ribbitText’})
       return form
 
   class Meta:
       model = Ribbit
       exclude = (‘user’,)

Параметр exclude в вышеприведенном Meta классе предотвращает отображение пользовательского поля. Нам это не нужно, поскольку пользователь Ribbit будет определяться с помощью сессий.

Давайте передадим изменения, которые мы сделали до сих пор

1
2
git add .
git commit -m ‘Created Forms’

Django предлагает большую гибкость, когда дело доходит до маршрутизации. Давайте начнем с определения некоторых маршрутов в ribbit/urls.py

1
2
3
4
5
6
7
urlpatterns = patterns(»,
    # Examples:
    url(r’^$’, ‘ribbit_app.views.index’), # root
    url(r’^login$’, ‘ribbit_app.views.login_view’), # login
    url(r’^logout$’, ‘ribbit_app.views.logout_view’), # logout
    url(r’^signup$’, ‘ribbit_app.views.signup’), # signup
)

Далее, давайте воспользуемся только что созданными моделями и формами и запишем соответствующие представления для каждого только что определенного маршрута. Начнем с добавления импорта в ribbit_app/views.py .

1
2
3
4
5
from django.shortcuts import render, redirect
from django.contrib.auth import login, authenticate, logout
from django.contrib.auth.models import User
from ribbit_app.forms import AuthenticateForm, UserCreateForm, RibbitForm
from ribbit_app.models import Ribbit

Затем следует индексное представление:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
def index(request, auth_form=None, user_form=None):
   # User is logged in
   if request.user.is_authenticated():
       ribbit_form = RibbitForm()
       user = request.user
       ribbits_self = Ribbit.objects.filter(user=user.id)
       ribbits_buddies = Ribbit.objects.filter(user__userprofile__in=user.profile.follows.all)
       ribbits = ribbits_self |
 
       return render(request,
                     ‘buddies.html’,
                     {‘ribbit_form’: ribbit_form, ‘user’: user,
                      ‘ribbits’: ribbits,
                      ‘next_url’: ‘/’, })
   else:
       # User is not logged in
       auth_form = auth_form or AuthenticateForm()
       user_form = user_form or UserCreateForm()
 
       return render(request,
                     ‘home.html’,
                     {‘auth_form’: auth_form, ‘user_form’: user_form, })

Для представления индекса мы сначала проверяем, вошел ли пользователь в систему или нет, и соответственно отображаем шаблоны. ribbits_self ribbits_buddies ribbits_self и ribbits_buddies объединяются с | оператор в приведенном выше коде. Также мы проверяем, был ли экземпляр формы передан методу (в определении функции), а если нет, то создаем новый. Это позволяет нам передавать экземпляры формы в соответствующие шаблоны и отображать ошибки.

Давайте приступим к редактированию шаблона «home.html», который будет использоваться для отображения страницы индекса для анонимных пользователей. В ribbit/templates/home.html добавьте следующий код.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{% extends «base.html» %}
 
{% block login %}
    <form action=»/login» method=»post»>{% csrf_token %}
        {% for field in auth_form %}
        {{ field }}
        {% endfor %}
        <input type=»submit» id=»btnLogIn» value=»Log In»>
    </form>
{% endblock %}
 
{% block content %}
{% if auth_form.non_field_errors or user_form.non_field_errors %}
<div class=»flash error»>
    {{ auth_form.non_field_errors }}
    {{ user_form.non_field_errors }}
</div>
{% endif %}
<img src=»{{ STATIC_URL}}gfx/frog.jpg»>
<div class=»panel right»>
    <h1>New to Ribbit?</h1>
    <p>
        <form action=»/signup» method=»post»>{% csrf_token %}
            {% for field in user_form %}
            {{ field }}
            {% endfor %}
            <input type=»submit» value=»Create Account»>
        </form>
    </p>
</div>
{% endblock %}

В этом шаблоне мы наследуем определенный ранее базовый шаблон, выполняем аутентификацию и подписываем формы, переопределяя блок входа. Отличительной особенностью Django является то, что он делает CSRF-защиту довольно простой! Все, что вам нужно сделать, это добавить csrf_token в каждую форму, которую вы используете в шаблоне.

Давайте перейдем к шаблону buddies.html , который покажет страницу Ribbit друзей. Отредактируйте ribbit/templates/buddies.html и добавьте следующий код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{% extends «base.html» %}
{% block login %}
    {% with user.username as username %}
        {{ block.super }}
    {% endwith %}
{% endblock %}
 
{% block content %}
    <div class=»panel right»>
        <h1>Create a Ribbit</h1>
        <p>
            <form action=»/submit» method=»post»>
            {% for field in ribbit_form %}{% csrf_token %}
            {{ field }}
            {% endfor %}
            <input type=»hidden» value=»{{ next_url }}» name=»next_url»>
            <input type=»submit» value=»Ribbit!»>
            </form>
        </p>
    </div>
    <div class=»panel left»>
        <h1>Buddies’ Ribbits</h1>
        {% for ribbit in ribbits %}
        <div class=»ribbitWrapper»>
            <a href=»/users/{{ ribbit.user.username }}»>
                <img class=»avatar» src=»{{ ribbit.user.profile.gravatar_url }}»>
                <span class=»name»>{{ ribbit.user.first_name }}
            </a>
            @{{ ribbit.user.username }}
            <p>
                {{ ribbit.content }}
            </p>
        </div>
        {% endfor %}
    </div>
{% endblock %}

В этом шаблоне мы предоставляем родительскому шаблону, т.е. base.html значение username, чтобы навигационная ссылка для входа в профиль пользователя отображалась правильно. Мы также используем экземпляр RibbitForm чтобы принимать новых Ribbits и перебирать и показывать текущие ribbits нашими друзьями.

Закончив шаблоны, давайте продолжим и напишем код для входа / выхода пользователя. Добавьте следующий код в ribbit_app/views.py

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
def login_view(request):
    if request.method == ‘POST’:
        form = AuthenticateForm(data=request.POST)
        if form.is_valid():
            login(request, form.get_user())
            # Success
            return redirect(‘/’)
        else:
            # Failure
            return index(request, auth_form=form)
    return redirect(‘/’)
 
 
def logout_view(request):
    logout(request)
    return redirect(‘/’)

Представления для входа в систему ожидают HTTP-запроса POST для входа в систему (так как метод формы — POST). Он проверяет форму и, в случае успеха, регистрирует пользователя с помощью метода login() который запускает сеанс, а затем перенаправляет на корневой URL-адрес. Если проверка не auth_form , мы передаем экземпляр auth_form полученный от пользователя, в функцию index и перечисляем ошибки, тогда как, если запрос не POST, то пользователь перенаправляется на корневой URL-адрес.

Вид выхода из системы относительно проще. Он использует функцию logout() в Django, которая удаляет сеанс и выводит пользователя из системы с последующим перенаправлением на корневой URL.

Теперь нам нужно написать представление, чтобы зарегистрироваться и зарегистрировать пользователя. Добавьте следующий код в ribbit_app / views.py.

01
02
03
04
05
06
07
08
09
10
11
12
13
def signup(request):
   user_form = UserCreateForm(data=request.POST)
   if request.method == ‘POST’:
       if user_form.is_valid():
           username = user_form.clean_username()
           password = user_form.clean_password2()
           user_form.save()
           user = authenticate(username=username, password=password)
           login(request, user)
           return redirect(‘/’)
       else:
           return index(request, user_form=user_form)
   return redirect(‘/’)

Подобно представлению входа в систему, представление регистрации также ожидает POST-запрос и перенаправляет на корневой URL-адрес в случае неудачной проверки. Если форма регистрации действительна, пользователь сохраняется в базе данных, аутентифицируется, входит в систему и затем перенаправляется на домашнюю страницу. В противном случае мы вызываем функцию index и передаем экземпляр user_form предоставленный пользователем, для вывода списка ошибок.

Давайте проверим наш прогресс, запустив сервер и протестировав просмотры, которые мы написали до сих пор, вручную.

1
python manage.py runserver

Давайте посетим наш сервер разработки и попробуем зарегистрировать нового пользователя. Если все идет хорошо, вы должны быть представлены на странице «Кролики друзей». Мы можем выйти из системы и повторно подтвердить подлинность вновь созданного пользователя, чтобы проверить, работают ли функции входа должным образом.

Buddies

Время совершать изменения!

1
2
git add .
git commit -m ‘Implemented User Login and Sign Up’

В шаблоне buddies.html который мы создали ранее, ribbit_form был отправлен в /submit . Давайте отредактируем ribbit/urls.py и добавим маршрут для формы и страницы, чтобы перечислить все публичные кролики.

1
2
3
4
5
6
7
8
urlpatterns = patterns(»,
    # Examples:
    url(r’^$’, ‘ribbit_app.views.index’), # root
    url(r’^login$’, ‘ribbit_app.views.login_view’), # login
    url(r’^logout$’, ‘ribbit_app.views.logout_view’), # logout
    url(r’^ribbits$’, ‘ribbit_app.views.public’), # public ribbits
    url(r’^submit$’, ‘ribbit_app.views.submit’), # submit new ribbit
)

Давайте напишем представление для проверки и хранения представленных ребрышек. Откройте ribbit_app/views.py и добавьте следующий код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
from django.contrib.auth.decorators import login_required
 
@login_required
def submit(request):
    if request.method == «POST»:
        ribbit_form = RibbitForm(data=request.POST)
        next_url = request.POST.get(«next_url», «/»)
        if ribbit_form.is_valid():
            ribbit = ribbit_form.save(commit=False)
            ribbit.user = request.user
            ribbit.save()
            return redirect(next_url)
        else:
            return public(request, ribbit_form)
    return redirect(‘/’)

Представление использует декоратор @login_required , который выполняет функцию, только если пользователь аутентифицирован; иначе пользователь перенаправляется на путь, указанный в константе LOGIN_URL в настройках. Если проверка формы прошла успешно, мы вручную устанавливаем пользователя на тот, который содержится в сеансе, а затем сохраняем записи. После фиксации базы данных пользователь перенаправляется на путь, указанный в поле next_url которое является скрытым полем формы, которое мы вручную для этого ввели в шаблон. Значение next_url передается в представлениях, которые отображают форму Ribbit.

Двигаясь дальше, давайте напишем представление, чтобы перечислить последние 10 публичных лент. Добавьте следующее в ribbit_app/views.py

1
2
3
4
5
6
7
8
@login_required
def public(request, ribbit_form=None):
    ribbit_form = ribbit_form or RibbitForm()
    ribbits = Ribbit.objects.reverse()[:10]
    return render(request,
                  ‘public.html’,
                  {‘ribbit_form’: ribbit_form, ‘next_url’: ‘/ribbits’,
                   ‘ribbits’: ribbits, ‘username’: request.user.username})

В публичном представлении мы запрашиваем базу данных о последних 10 ребрах, разрезая набор запросов по последним 10 элементам. Затем форма вместе с ребрами отображается в шаблоне. Давайте создадим ribbit/templates/public.html для этого представления

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{% extends «base.html» %}
 
{% block content %}
    <div class=»panel right»>
        <h1>Create a Ribbit</h1>
        <p>
            <form action=»/submit» method=»post»>
            {% for field in ribbit_form %}{% csrf_token %}
            {{ field }}
            {% endfor %}
            <input type=»hidden» value=»{{ next_url }}» name=»next_url»>
            <input type=»submit» value=»Ribbit!»>
            </form>
        </p>
    </div>
    <div class=»panel left»>
        <h1>Public Ribbits</h1>
        {% for ribbit in ribbits %}
        <div class=»ribbitWrapper»>
            <img class=»avatar» src=»{{ ribbit.user.profile.gravatar_url }}»>
            <span class=»name»>{{ ribbit.user.first_name }}
            <span class=»time»>{{ ribbit.creation_date|timesince }}
            <p>{{ ribbit.content }}</p>
        </div>
        {% endfor %}
    </div>
{% endblock %}

При циклическом перемещении по объектам ribbit шаблон использует timesince шаблона timesince для автоматического определения разницы во времени между датой создания ленты и текущим временем и печатает ее в стиле Twitter.

Убедитесь, что сервер разработки работает, создайте новый ribbit и загляните на страницу Public Ribbits, чтобы убедиться, что все в порядке.

First Ribbit

Давайте передадим изменения, прежде чем продолжить:

1
2
git add .
git commit -m ‘Implemented Ribbit Submission and Public Ribbits Views’

Твиттер-клон, профили пользователей и подписчики идут рука об руку. Давайте напишем маршруты для реализации этой функциональности. Обновите ribbit/urls.py следующим кодом:

01
02
03
04
05
06
07
08
09
10
11
urlpatterns = patterns(»,
    # Examples:
    url(r’^$’, ‘ribbit_app.views.index’), # root
    url(r’^login$’, ‘ribbit_app.views.login_view’), # login
    url(r’^logout$’, ‘ribbit_app.views.logout_view’), # logout
    url(r’^ribbits$’, ‘ribbit_app.views.public’), # public ribbits
    url(r’^submit$’, ‘ribbit_app.views.submit’), # submit new ribbit
    url(r’^users/$’, ‘ribbit_app.views.users’),
    url(r’^users/(?P<username>\w{0,30})/$’, ‘ribbit_app.views.users’),
    url(r’^follow$’, ‘ribbit_app.views.follow’),
)

Интересный маршрут выше — это путь к профилю конкретного пользователя. <?P<username> захватывает имя пользователя, запрошенное через GET, и \w{0,30} утверждает, что максимальная длина имени пользователя составляет 30 символов:

Далее мы продолжим написание представления для отображения профилей пользователей. Добавьте следующее в «ribbit_app / views.py».

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from django.db.models import Count
from django.http import Http404
 
 
def get_latest(user):
    try:
        return user.ribbit_set.order_by(‘-id’)[0]
    except IndexError:
        return «»
 
 
@login_required
def users(request, username=»», ribbit_form=None):
    if username:
        # Show a profile
        try:
            user = User.objects.get(username=username)
        except User.DoesNotExist:
            raise Http404
        ribbits = Ribbit.objects.filter(user=user.id)
        if username == request.user.username or request.user.profile.follows.filter(user__username=username):
            # Self Profile or buddies’ profile
            return render(request, ‘user.html’, {‘user’: user, ‘ribbits’: ribbits, })
        return render(request, ‘user.html’, {‘user’: user, ‘ribbits’: ribbits, ‘follow’: True, })
    users = User.objects.all().annotate(ribbit_count=Count(‘ribbit’))
    ribbits = map(get_latest, users)
    obj = zip(users, ribbits)
    ribbit_form = ribbit_form or RibbitForm()
    return render(request,
                  ‘profiles.html’,
                  {‘obj’: obj, ‘next_url’: ‘/users/’,
                   ‘ribbit_form’: ribbit_form,
                   ‘username’: request.user.username, })

Эта точка зрения, пожалуй, самая интересная из всех, что мы рассмотрели до сих пор. Мы начинаем с того, что только зарегистрированные пользователи могут просматривать профили. В маршрутах, которые мы определили в ribbit/urls.py , мы написали один для захвата имени пользователя. Этот захваченный параметр автоматически вызывается вместе с объектом запроса в представлении пользователей. Мы сталкиваемся с двумя вариантами для этого представления:

  • Имя пользователя передается в URL для отображения профиля конкретного пользователя.
  • Имя пользователя не передается, что подразумевает, что пользователь хочет просмотреть все профили

Мы начинаем с проверки, передано ли имя пользователя и не является ли оно пустым. Затем мы пытаемся получить объект User для соответствующего имени пользователя. Обратите внимание на использование блока try catch в приведенном выше коде. Мы просто вызываем исключение Http404, предоставленное Django, чтобы перенаправить на шаблон 404 по умолчанию. Затем нам нужно проверить, является ли запрошенный профиль зарегистрированным пользователем или одним из его друзей. Если это так, нам не нужно отображать следующую ссылку в профиле, поскольку связь уже установлена. В противном случае мы передаем параметр follow в представлении, чтобы распечатать ссылку Follow.

Переходя ко второму пункту, если в URL не указано имя пользователя, мы выбираем список всех пользователей и используем функцию annotate() чтобы добавить атрибут ribbit_count ко всем объектам, в котором хранится количество Ribbits, сделанных каждым пользователем. в наборе запросов. Это позволяет нам использовать что-то вроде <user_object>.ribbit_count чтобы получить <user_object>.ribbit_count Ribbit пользователя.

Получить последнюю версию Ribbit от каждого пользователя немного сложно. Для этого мы используем встроенную в Python функцию map() и вызываем get_latest() для всех элементов пользовательского get_latest() . Функция get_latest() определена в приведенном выше коде и использует обратное отношение в отношении User <-> Ribbit. Например, user.ribbit_set.all() вернул бы все ребра пользователем. Мы упорядочиваем ребра по id в порядке убывания и разрезаем первый элемент. Код заключен в блок try catch, чтобы перехватить исключение, если пользователь не создал ни одного кролика. Затем мы используем функцию Python zip() чтобы связать каждый элемент обоих итераторов (пользователей и ribbits), чтобы у нас был кортеж с парой User Object и Latest Ribbit. Затем мы передаем этот сжатый объект вместе с формами в шаблон. Давайте напишем наш последний взгляд, который примет запрос, чтобы следовать за пользователем

01
02
03
04
05
06
07
08
09
10
11
12
13
from django.core.exceptions import ObjectDoesNotExist
 
@login_required
def follow(request):
    if request.method == «POST»:
        follow_id = request.POST.get(‘follow’, False)
        if follow_id:
            try:
                user = User.objects.get(id=follow_id)
                request.user.profile.follows.add(user.profile)
            except ObjectDoesNotExist:
                return redirect(‘/users/’)
    return redirect(‘/users/’)

В представлении выше мы получаем значение параметра follow , передаваемого методом POST. Если идентификатор установлен, мы проверяем, существует ли пользователь, и добавляем отношение, в противном случае мы ловим исключение ObjectDoesNotExist и перенаправляем пользователя на страницу со списком всех профилей пользователей.

Мы закончили с представлениями здесь. Давайте напишем два оставшихся шаблона, необходимых для визуализации профилей пользователей. Откройте ваш текстовый редактор и отредактируйте ribbit/templates/profiles.html .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{% extends «base.html» %}
 
{% block content %}
    <div class=»panel right»>
        <h1>Create a Ribbit</h1>
        <p>
            <form action=»/submit» method=»post»>
            {% for field in ribbit_form %}{% csrf_token %}
            {{ field }}
            {% endfor %}
            <input type=»hidden» value=»{{ next_url }}» name=»next_url»>
            <input type=»submit» value=»Ribbit!»>
            </form>
        </p>
    </div>
    <div class=»panel left»>
        <h1>Public Profiles</h1>
        {% for user, ribbit in obj %}
        <div class=»ribbitWrapper»>
            <a href=»/users/{{ user.username }}»>
                <img class=»avatar» src=»{{ user.profile.gravatar_url }}»>
                <span class=»name»>{{ user.first_name }}
            </a>
            @{{ user.username }}
            <p>
                {{ user.ribbit_count}} Ribbits
                <span class=»spacing»>{{ user.profile.followed_by.count }} Followers
                <span class=»spacing»>{{ user.profile.follows.count }} Following
            </p>
            <p>{{ ribbit.content }}</p>
        </div>
        {% endfor %}
    </div>
{% endblock %}

Чтобы составить список пользователя вместе с его последним ribbit, мы используем общую конструкцию python для перебора списка кортежей: for user, ribbit in obj . Внутри цикла мы печатаем информацию о пользователе вместе с его статистикой ребра. Обратите внимание на использование обратной связи в {{ user.profile.followed_by.count }} . follow_by — это связанное имя, которое мы установили при определении атрибута ManyToManyField для модели UserProfile.

Наконец, давайте напишем шаблон для отображения профиля конкретного пользователя. Напишите следующий код в ribbit/templates/user.html .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
{% extends «base.html» %}
{% block login %}
    {% with user.username as username %}
        {{ block.super }}
    {% endwith %}
{% endblock %}
 
{% block content %}
    <div class=»panel left»>
        <h1>{{ user.first_name }}’s Profile</h1>
        <div class=»ribbitWrapper»>
            <a href=»/users/{{ user.username }}»>
                <img class=»avatar» src=»{{ user.profile.gravatar_url }}»>
                <span class=»name»>{{ user.first_name }}
            </a>
                @{{ user.username }}
            <p>
                {{ ribbits.count }} Ribbits
                <span class=»spacing»>{{ user.profile.follows.all.count }} Following
                <span class=»spacing»>{{ user.profile.followed_by.all.count }} Followers
            </p>
            {% if follow %}
            <form action=»/follow» method=»post»>
                {% csrf_token %}
                <input type=»hidden» name=»follow» value=»{{ user.id }}»>
                <input type=»submit» value=»Follow»>
            </form>
            {% endif %}
        </div>
    </div>
 
    <div class=»panel left»>
        <h1>{{ user.first_name }}’s Ribbits</h1>
        {% for ribbit in ribbits %}
        <div class=»ribbitWrapper»>
            <a href=»/users/{{ user.username }}»>
                <img class=»avatar» src=»{{ user.profile.gravatar_url }}»>
                <span class=»name»>{{ ribbit.user.first_name }}
            </a>
            @{{ ribbit.user.username }}
            <span class=»time»>{{ ribbit.creation_date|timesince }}
            <p>{{ ribbit.content }}</p>
        </div>
        {% endfor %}
    </div>
{% endblock %}

As in the buddies.html template, we pass the username of the logged in template to the parent template using the {% with %} construct. Next, we display the user’s profile and display the follow link only if the follow variable is set to True . After that, we list all the ribbits created by the user by iterating over the ribbits variable.

We’re finally done with all the views and templates. Browse around all the links and try following dummy users and explore the Twitter-clone you’ve just created.

Давайте передадим наши изменения:

1
2
git add .
git commit -m 'Implemented User Profiles and Following Relation on frontend'

I prefer to keep my development and production branches separate; git makes branching a breeze. Fire up the terminal and execute the following:

1
git branch -b production

This will create the production branch and switch to it immediately. Let’s update our .gitignore file and add the database to it. It’s contents should be

1
2
*.pyc
ribbit/database.db

Let’s install some packages to our virtualenv that are required for deployment.

1
pip install psycopg2 dj-database-url gunicorn

We’ll also create a file with all dependencies of the project so that they’re picked up by Heroku.

1
pip freeze > requirements.txt

Next, let’s edit ribbit/settings.py . The database settings should be changed like.

01
02
03
04
05
06
07
08
09
10
DATABASES = {
    ‘default’: {
        'ENGINE': 'django.db.backends.postgresql_psycopg2', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': 'ribbit', # Or path to database file if using sqlite3.
        'USER': 'username', # Not used with sqlite3.
        'PASSWORD': 'password', # Not used with sqlite3.
        'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '', # Set to empty string for default. Not used with sqlite3.
    }
}

At the end of the file, append the following so that Heroku can figure out the database settings.

1
2
3
import dj_database_url
 
DATABASES[‘default’] = dj_database_url.config()

Let’s create a Procfile to start the process on Heroku’s server. Place the following in a file, named Procfile .

1
web: gunicorn ribbit.wsgi

Also, if your application isn’t large scale, you can skip using storage services by appending the route for static files in ribbit/urls.py

1
2
3
urlpatterns += patterns('django.contrib.staticfiles.views',
       url(r'^static/(?P<path>.*)$', 'serve'),
   )

We’re all set! Давайте передадим изменения:

1
git commit -a -m 'Configured for Production'

Finally, we’ll create a Heroku app and push our repo to the remote repository:

1
2
heroku create
git push heroku production:master

Once the files are transferred, run syncdb and apply the migrations to the database.

1
2
heroku run python manage.py syncdb
heroku run python manage.py migrate ribbit_app

Finally, open the deployed app with:

1
heroku open

We’re finally done with Ribbit in Django! This tutorial was quite long, indeed, and the application still has much more potential that can be implemented. I hope that, if you are new to Django, this article has encouraged you to dig more deeply.

Feel free to play along with my deployment at Heroku . If you have any questions, I’d be glad to answer and all questions.