Russian (Pусский) translation by Alex Grigorovich (you can also view the original English article)
После реализации нашего Twitter-клона, Ribbit, на чистом PHP и Rails, настало время сделать следующий шаг: Python! В этой статье, мы пересоздадим наш Ribbit используя Django. Итак, без лишних слов, начинаем!

Step 0 - Bootstrapping
На время написания этой статьи, Django 1.4 поддерживал Python 2.5 по 2.7.3. Прежде всего, убедитесь что ваша версия Python соответствует требованиям, набрав python -v
в терминале. Обратите внимание что предпочтительно использовать версию Python 2.7.3. На протяжении всей статьи, мы будем использовать pip как пакетный менеджер и virtualenv в качестве виртуального окружения. Что бы установить их, запустите терминал и выполните следующие команды от имени root
curl https://python-distribute.org/distribute_setup.py | sudo python curl https://raw.github.com/pypa/pip/master/contrib/get-pip.py | sudo python sudo pip install virtualenv
Устанавливать среду разработки Django, мы начнем с создания виртуального окружения (Virtual Environment). Выполните нижеследующие команды в вашем терминале (желательно внутри папки предназначенной для поекта)
virtualenv --no-site-packages ribbit_env source ribbit_env/bin/activate
Теперь наше виртуальное окружение установлено и активировано (ваша командная строка должна изменить вид, приглашение к вводу должно отображать имя вашего окружения), настало время установить все зависимости проекта. Что бы управлять миграцией нашей базы данных мы будем использовать, независимо от Django, утилиту South. Для их установки мы будем использовать pip
. Стоить заметить что начиная с текущего момента все, что мы будем делать, будет делаться внутри виртуального окружения (virtualenv). Как и раньше, перед работой, убедитесь что вы внутри него.
pip install Django South
Теперь, когда все зависимости установлены, мы готовы перейти к созданию нового Django проекта.
Шаг 1 - Создание проекта и Ribbit App
Начнем же создание нового Django проекта и нашего приложения. cd
в выбранную вами директорию и выполните:
django-admin.py startproject ribbit cd ribbit django-admin.py startapp ribbit_app
Далее, инициализируем наш git репозиторий и создадим файл .gitignore
внутри Ribbit проекта, которое мы только что создали. Сделаем это, выполнив:
git init echo "*.pyc" >> .gitignore git add . git commit -m 'Initial Commit'
Теперь приступим к редактированию ribbit/settings.py
и конфигурированию нашего проекта. Для начала, мы определим некоторые константы. Добавим следующие строчки в начало файла:
import os PROJECT_PATH = os.path.dirname(os.path.abspath(__file__)) LOGIN_URL = '/'
Константа PROJECT_PATH
будет хранить в себе путь к директории в которой лежит settings.py. Это позволит нам использовать относительные пути для определения будущих констант. LOGIN_URL
, как и предполагает название константы, обозначает, что корень нашего сайта будет URL-адресом для входа (обычный Login).
Двигаемся дальше! Давайте теперь сконфигурируем базу данных. Для первоначальной разработки, sqlite3 это просто идеальный выбор. Для этого, отредактируем константу DATABASES
как показано ниже:
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. Not used with sqlite3. 'PORT': '', # Set to empty string for default. Not used with sqlite3. } }
Django ищет статические файлы в директории указанной в константе STATIC_ROOT
и направляет все запросы к файлам используя путь написанный в STATIC_URL
. Сконфигурируем их, как показано ниже:
# Absolute path to the directory static files should be collected to. # Don't put anything in this directory yourself; store your static files # 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
что бы указать путь.
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
.
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', )
Далее, давайте создадим директории, которые мы определили в настройках, и базу данных:
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 <имя приложения> --initial
и применим ее, командой python manage.py migrate ribbit_app
Давайте запустим наш тестовый сервер, что бы убедиться что все работает правильно.
python manage.py runserver
И если все прошло гладко, то вас должна приветствовать нижеследующая страница, как только вы войдете по адресу http://localhost:8000

В итоге дерево проекта должно выглядеть следующим образом:
ribbit |-- manage.py |-- ribbit | |-- database.db | |-- __init__.py | |-- __init__.pyc | |-- settings.py | |-- settings.pyc | |-- static | |-- templates | |-- urls.py | |-- urls.pyc | |-- wsgi.py | `-- wsgi.pyc `-- ribbit_app |-- __init__.py |-- __init__.pyc |-- migrations | |-- 0001_initial.py | |-- 0001_initial.pyc | |-- __init__.py | `-- __init__.pyc |-- models.py |-- models.pyc |-- static |-- tests.py `-- views.py
Но перед тем как двигаться дальше, давайте закоммитим все изменения в наш репозиторий.
git add . git commit -m 'Created app and configured settings'
Шаг 2 - Базовый шаблон и статичные файлы
Перейдем на страничку уроков по интерфейсу, скачаем все файлы и положим их в папку ribbit_app/static
. Нам надо только сделать некоторые изменения в файле style.less
для этого урока. Давайте начнем с добавления стиля для вспомогательных сообщений.
.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; } }
Далее, обновим ширину элемента ввода и добавим новый класс для ошибок. Заметьте, что код, приведенный ниже, содержит только те строчки кода, что надо добавить или обновить. Остальной код останется без изменений.
input { width: 179px; &.error { background: #ffefef; color: #4c1717; border: 1px solid #4c1717; } }
Нам так же надо увеличить высоту #content.wrapper.panel.right
.
height: 433px;
Наконец, давайте добавим для картинок футера правый отступ.
footer { div.wrapper { img { margin-right: 5px; } } }
Но перед тем как двигаться дальше, закоммитим наши изменения:
git add . git commit -m 'Added static files'
Далее, давайте создадим базовый шаблон, который будет унаследован всеми остальными нашими шаблонами. Django использует свой собственный шаблонный движок (по типу ERB или Jade). Определим шаблон в ribbit/templates/base.html
со следующим содержимым:
<!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</span> {% 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." title="Made with Django." /> </a> </div> </footer> </body> </html>
В разметке, что наверху, {{ STATIC_URL }}
выводит путь для статичных url, определенных в settings.py. Другая интересная возможность, это использование блоков. Все содержимое блоков наследуется потомками базового шаблона и не будет перезаписано, покуда блоки явно не будут переопределены в них. Это предоставляет нам некоторую гибкость при размещении навигации в шапке сайта и замене его на форму входа, если пользователь не авторизован. Мы так же используем блок с if
условием, что бы проверить, есть ли любые переменные вспомогательных сообщений и есть ли что отображать.
Отлично! Время сделать еще один коммит:
git add . git commit -m 'Created base template'
Шаг 3 - Создание моделей
Одна из самых лучших черт в Django то, что он включает в себя множество уже готовых моделей и форм, которые могут быть переопределены для множества целей. Для нашего приложения мы будем использовать модель User
и добавим несколько свойств к ней, при создании UserProfile
. Кроме того, для того чтобы управлять пользователями Ribbit
, мы создадим модель Ribbit. В Django есть поля для User
имени пользователя, пароля, имени, фамилии и адреса электронной почты (с проверкой), а также другие поля. Я предлагаю вам взглянуть на API, чтобы изучить все доступные поля профайла. Теперь, добавьте следующий код для моделей в ribbit_app / models.py
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
для реализации отношения next / follow_by. Параметр related_name
позволяет нам использовать отношение в обратном направлении, используя имя по нашему выбору. Мы также устанавливаем значение symmetrical
на False, чтобы гарантировать, что если пользователь A следует B, то пользователь B автоматически не следует A.Мы также определили функцию для получения ссылки на изображение gravatar на основе URL-адреса пользователя и свойства (если пользовательский файл существует для пользователя) или создать его, когда мы используем синтаксис <user_object>.profile
. Это
позволяет легко получить свойства объекта UserProfile
. Вот
пример того, как вы можете использовать ORM, чтобы получить данные пользователей,
на кого они подписаны и кто подписан на них.
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
Теперь, когда наши модели определены, давайте создадим файл миграции и применим их:
python manage.py schemamigration ribbit_app --auto python manage.py migrate ribbit_app
Прежде чем двигаться дальше, давайте применим изменения
git add . git commit -m 'Created Models'
Шаг 4 - Создание форм
Django
позволяет нам создавать формы, чтобы мы могли легко проверять данные, принятые
пользователем Мы создадим пользовательскую форму для модели Ribbit
и создадим форму, которая наследует UserCreationForm
, представленную по умолчанию для управления регистрацией. Для управления аутентификацией мы расширим параметр AuthenticationForm
, предоставленный по умолчанию в Django. Давайте создадим новый файл ribbit_app/forms.py
и добавим импорт необходимых модулей.
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
, пример кода приведен ниже:
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
мы можем указать порядок, в котором мы хотим, чтобы формы поля отображались, и задали модель, с которой должна быть проверена форма.
Затем давайте напишем форму для аутентификации:
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.
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 будет
обработа с помощью сеансов в Ribbit
Теперь применим изменения
git add . git commit -m 'Created Forms'
Шаг 5 – Регистрация и вход в систему
Django
очень гибкая система, когда дело доходит до маршрутизации. Начнем с определения некоторых маршрутов в ribbit / urls.py
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 )
Далее, давайте использовать модели и формы, которые мы уже создали, и напишем соответствующие представления для каждого только что определенного маршрута.
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
Начнем с добавления импорта в ribbit_app / views.py.
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 | ribbits_buddies 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, })
Для
представления index мы сначала проверяем, зарегистрирован ли пользователь или
нет, и соответственно визуализируем шаблон. Запросы ribributes_bibdies
и ribbits_buddies
объединены с символом оператора |
в приведенном выше коде. Кроме
того, мы проверяем, был ли экземпляр формы передан методу (в определении
функции), и если мы не создаем новый. Это позволяет нам передавать экземпляры форм
соответствующим шаблонам и отображать ошибки.
Давайте
продолжим редактирование шаблона 'home.html', который будет использоваться для отображения индексной страницы для анонимных пользователей. В файле theribbit / templates / home.html
добавьте следующий код.
{% 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 Protection! Все, что вам нужно сделать, это добавитьcsrf_token
в каждую форму, которую вы используете в шаблоне.
Перейдем к шаблону buddies.html
, в котором будет отображаться страница друзей Ribbit. Отредактируйте ribbit / templates / buddies.html
и добавьте следующий код:
{% 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 }}</span> </a> @{{ ribbit.user.username }} <p> {{ ribbit.content }} </p> </div> {% endfor %} </div> {% endblock %}
В этом шаблоне мы предоставляем родительский шаблон i.e. base.html
со значением имени пользователя, чтобы ссылка на навигацию для зарегистрированного пользователя отображалась правильно. Мы также используем экземпляр RibbitForm
, чтобы добавлять новых пользователей и показывать новых друзей наших пользователей
После
того, как только шаблоны доработаны, давайте допишем код для входа / выхода
пользователя. Добавьте следующий код ribbit_app / views.py
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
от пользователя к функции индекса и видим ошибки, тогда как, если запрос не использует метод POST, пользователь перенаправляется на корневой URL-адрес.
Вид
выхода довольно прост. Он использует функцию logout()
в Django, которая удаляет сеанс и регистрирует пользователя, а затем перенаправляется на корневой URL.
Теперь нам нужно написать представление, чтобы зарегистрировать пользователя и чтобы он смог войти. Подключите следующий код к ribbit_app/views.py.
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-адрес, если проверка завершилась неудачей. Если форма регистрации действительна, пользователь сохраняется в базе данных, аутентифицируется, регистрируется и затем перенаправляется на домашнюю страницу. В противном случае мы вызываем функцию индекса и передаем в экземпляр user_form
, представленный пользователем, чтобы перечислять ошибки.
Какой же у нас прогресс? Запустив сервер и протестировав просмотры, которые мы написали до сих.
python manage.py runserver
Перейдем к серверу разработки и попробуем зарегистрировать нового пользователя. Если все идет хорошо, то вы увидите страницу друзей Ribbits. Мы можем выйти из системы и повторно проверить страницу созданного пользователя, для проверки всех функций в режиме ожидании.

Время применять изменения!
git add . git commit -m 'Implemented User Login and Sign Up'
Шаг 6 –регистрация новых пользователей Ribbits и список публичных аккаунтов Ribbits
В шаблоне buddies.html
, который мы создали ранее, ribbit_form отправлялась через/submit
. Давайте отредактируем файлы ribbitbit / urls.py
и добавим маршрут для формы и страницы,чтобы отобразить список аккаунтов.
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
и добавьте следующий код:
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 пользователей Ribbits. Добавьте следующий код в файл ribbit_app/views.py
.
@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
для этого представления
{% 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>@{{ ribbit.user.username }} <span class="time">{{ ribbit.creation_date|timesince }}</span> <p>{{ ribbit.content }}</p> </div> {% endfor %} </div> {% endblock %}
В то время, пока обрабатывается цикл с объектами пользователей, шаблон использует образец тега timesince
, чтобы автоматически определять разницу между временем создания аккаунта и текущим временем и отображает это в стиле Twitter.
Убедитесь, что сервер разработки запущен, создайте нового пользователя и посмотрите на страницу Public Ribbits, чтобы все работало верно.

Давайте сохраним изменения и продолжим:
git add . git commit -m 'Implemented Ribbit Submission and Public Ribbits Views'
Шаг 7-аккаунты пользователя и подписчиков
Наш
Твиттер-клон, пользовательские профили и подписчики идут рука об руку. Давайте
напишем маршруты для реализации этого функционала. Обновите
файл ribbit/urls.py
следующим образом:
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 '.
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 для соответствующего имени пользователя. Обратите
внимание на использование блока catch try в коде выше. Мы
обрабатываем исключение Http404, используя возможности Django, чтобы перенаправить на страницу
ошибки 404. Затем
нам нужно проверить, имеет ли запрашиваемый профиль друзей среди зарегистрированных
пользователей. Если
это так, нам не нужно отображать следующую ссылку в профиле, поскольку
отношения между пользователями уже установлены. В противном случае мы передаем параметр follow
в представлении для отображения ссылки «Подписаться»
Переходя ко второму этапу, в случае, если в URL-адресе не указано имя пользователя, мы получаем список всех доступных пользователей и выполняем функцию annotate()
чтобы добавить свойства ribbit_count
ко всем объектам, которые содержат количество запросов на подписку каждого пользователя Ribbits. Это
позволяет нам использовать следующее значение в строке <user_name>.ribbit_count Ribbit Count пользователя,чтобы получит
количество подписчиков.
Получение
данных о последних зарегистрированных пользователях немного сложно. Мы используем для этого встроенную функцию map()
в Python и применяем функцию get_latest()
длявсех элементов в запросе. Функция get_latest()
определена в коде выше и создает двустороннее отношение дляпользователей Ribbit. Например, реализация функции user.ribbit_set.all()
вернет значение всех пользователей для конкретного пользователя. Мы
упорядочиваем пользователей по их id в порядке убывания и получаем первый
элемент. Код заключен в блок catch try, чтобы получить данные, в
случае, если у пользователя нет подписчиков. Затем мы используем функцию zip()
в Python для соединения каждого элемента обоих итераторов (пользователей и подписчиков), чтобы у нас была связь с пользовательским объектом и последней парой пользователей Затем
мы передаем этот связанный объект вместе с формами в шаблон. Давайте
напишем наше последнее представление, которое будет принимать запрос от
пользователя по подписку:
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. Если идентификатор установлен, мы проверяем, существует ли пользователь и добавляем отношение, в противном случае мы получаем ошибку bjectDoesNotExist
и перенаправляем пользователя на страницу со списком всех доступных пользователей.
Мы
завершили работу с представлениями на
данном этапе. Давайте
напишем оставшиеся два шаблона, необходимые для отображения профилей
пользователей. Откройте
текстовый редактор и отредактируйте файл ribbit/templates/profiles.html
{% 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 }}</span> </a> @{{ user.username }} <p> {{ user.ribbit_count}} Ribbits <span class="spacing">{{ user.profile.followed_by.count }} Followers</span> <span class="spacing">{{ user.profile.follows.count }} Following</span> </p> <p>{{ ribbit.content }}</p> </div> {% endfor %} </div> {% endblock %}
Чтобы отобразить пользователя вместе со своим последним подписчиком, мы используем общую конструкцию в Python для перебора списка взаимосвязей for user, ribbit in obj j.
Внутри
цикла мы отображаем информацию о
пользователе вместе с подписчиками. Обратите
внимание на использование обратной связи в {{user.profile.followed_by.count}}
. follow_by
- это имя пользователя, которое мы установили при определении свойства ManyToMany длямодели UserProfile.
Наконец, давайте напишем шаблон для отображения профиля конкретного пользователя. Запишите следующий код в файл ribbit / templates / user.html.
{% 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 }}</span> </a> @{{ user.username }} <p> {{ ribbits.count }} Ribbits <span class="spacing">{{ user.profile.follows.all.count }} Following</span> <span class="spacing">{{ user.profile.followed_by.all.count }} Followers</span> </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 }}</span> </a> @{{ ribbit.user.username }} <span class="time">{{ ribbit.creation_date|timesince }}</span> <p>{{ ribbit.content }}</p> </div> {% endfor %} </div> {% endblock %}
Как и в шаблоне buddies.html
, мы передаем имя пользователя зарегистрированного шаблона в родительский , используя конструкцию {% with%}
. Затем мы отображаем профиль пользователя и показываем следующую ссылку только в том случае, если для переменной follow
установлено значение True
. После
этого мы перечислим всех подписчиков для пользователя, прибавляя значения к
переменной ribbits.
Мы, наконец, сделали все представления и шаблоны. Проверьте все ссылки и попробуйте подписаться на пользователей и наслаждайтесь только что созданным вами клоном Twitter.
Давайте сохраним наши изменения:
git add . git commit -m 'Implemented User Profiles and Following Relation on frontend'
Шаг 8 – Размещение приложения на Heroku
Я предпочитаю разделять разработку на две ветви: разработка и публикация. С помощью git это можно сделать легко.
git branch -b production
Запустите терминал и выполните следующие команды: Давайте обновим наш файл .gitignore
и добавим к нему базу данных. Этот
файл должен содержать следующее:
*.pyc ribbit/database.db
Давайте установим некоторые расширения в наш virtualenv, которые необходимы для публикации на сервере.
pip install psycopg2 dj-database-url gunicorn
Мы также создадим файл со всеми зависимостями проекта, чтобы они были применены Heroku.
pip freeze > requirements.txt
Затем отредактируем файл ribbit / settings.py.
Настройки базы данных должны быть изменены соответственно:
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. } }
В конце файла добавьте следующее, чтобы Heroku мог определить настройки базы данных.
import dj_database_url DATABASES['default'] = dj_database_url.config()
Давайте создадим Procfile для запуска процесса на сервере Heroku. Добавьте следующий код в файл Procfile
.
web: gunicorn ribbit.wsgi
Кроме того, если ваше приложение не большое , вы можете
пропустить настройки служб хранения, добавив маршрут для статических файлов
в ribbit/urls.py
urlpatterns += patterns('django.contrib.staticfiles.views', url(r'^static/(?P<path>.*)$', 'serve'), )
Мы всё настроили! Давайте закоммитим изменения:
git commit -a -m 'Configured for Production'
Наконец, мы создадим приложение Heroku и переместим наши файлы на удаленный сервер:
heroku create git push heroku production:master
После передачи файлов запустите syncdb
и примените настройки
миграции к базе данных.
heroku run python manage.py syncdb heroku run python manage.py migrate ribbit_app
Наконец, откройте приложение с помощью:
heroku open
Заключение
Наконец, мы закончили создание приложения Ribbit с помощью Django! Этот урок был довольно долгим и, на самом деле, приложение обладает гораздо большим потенциалом, чем тот. который мы создали. Надеюсь, что если вы новичок в Django, эта статья вдохновит вас на более детальное изучение Django.
Экспериментируйте вместе с моим приложением в Heroku. Если у вас есть какие-либо вопросы, я был бы рад ответить на них.
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post