Данная статья рассчитывает на то, что вы уже имеете некоторую базу и знания языка Python. Цель статьи — научиться использовать Django на примере создания клона Twitter.
На момент написания данной статьи 1.6 поддерживает Python от версии 2.6 и до 3.2 включительно. Перед тем, как начать, убедитесь, что у Вас установлен Python нужной версии. Для этого выполните
Для настройки окружения Djanjo создадим виртуальное окружение. Выполните следующие команды:
Теперь, когда у нас есть виртуальное окружение, установим все необходимые зависимости. Кроме Django мы будем также использовать South для выполнения миграций базы данных. Обратите внимание, что все команды далее стоит выполнять внутри нашего виртуального окружения.
Когда все зависимости удовлетворены, мы можем перейти непосредственно к созданию самого проекта.
Мы начнем с создания нового Django проекта. Зайдите в нужную Вам директорию и запустите:
Продолжим. Откройте файл
Следующий наш шаг — настройка базы данных. Для разработки sqlite3 — идеальный выбор. Чтобы использовать именно ее измените константу
Django ищет все статические файлы в директории, которая указана в
Стоит учесть, что использование
Теперь, нам нужно определить директорию, в которой Django следует искать шаблоны:
Наконец, добавим South и twitter_app в список установленных приложений
Далее, создадим директории, на который мы ссылаемся в настройках, а также саму базу данных:
Шаг предварительной настройки завершен. Запустим сервер, чтобы удостовериться, что все работает:
Я считаю, что в этой статье основной упор нужно делать на функционал приложения, который описывается на Django, а не на дизайн, поэтому описание CSS стилей будут минимальны.
В папке
Также нам нужно изменить ширину элементов ввода:
Теперь, создадим базовый шаблон, которым будут «оборачиваться» все остальные наши представления. Стоит учесть, что у Django существует свой собственный шаблонизатор. Первым нашим шаблоном будет
В той разметке, которая приведена выше
Одна из лучшей вещей, которую имеет Django — встроенный набор моделей и форм, который могут быть расширены в случае необходимости. В нашем приложением нам понадобится модель
Начнем с модели Tweet. У этой модели существует три поля: текстовое, с максимальной длинной в 140 символов, для хранения самого сообщения, внешний ключ, который указывает на автора сообщения, а также дата время создания сообщения, которое будет выставляться автоматически.
Перейдем к модели UserProfile. У нас есть поле, которое определяет связь один к одному с моделью User и поле много-много, чтобы определить связь между читаемыми/читателями. Параметр
Django позволяет нам создавать формы, который могут быть подвергнуты проверке. Если данные не соответствуют нужным нам критериям, то пользователь будет уведомлен об этом. Мы создадим форму для модели Tweet и форму, которая расширяет UserCreationForm, для обеспечения возможности регистрации. Для управления аутентификацией нам нужно будет расширить форму AuthenticationForm. Создадим файл twitter_app/forms.py
Первым шагом будет форма регистрации. Мы назовем ее UserCreateForm и она будет иметь следующий код
В форме выше мы указываем, что некоторые из полей являются обязательными, указывая
А так будет выглядеть форма, которая отвечает за аутентификацию. Как и в предыдущем классе, мы указываем класс, который должен добавляться в случае ошибки, а также placeholder
Последней формой, которую мы создадим будет форма создания нового твита
Опция
Когда дело доходит до управления маршрутами (роутинга), Django предоставляет широчайшие возможности. Нам нужно определить несколько маршрутов в twitter/urls.py
Теперь, нам нужно использовать модели и формы, которые мы написали и создать для них представления. Начнем с добавления подключения зависимостей в файле twitter_app/views.py
Основное представление
В нашем основном представлении, мы отображаем контент в зависимости от того аутентифицирован ли пользовать или нет. Результаты запросов, которые хранятся в
Продолжением нашей деятельность будет создание отображения, которое будет показано в случае, если пользователь анонимен. Файл
Данный шаблон расширяет базовое представление, которое мы определили ранее, отображает формы входа и регистрации.
Перейдем к файлу
В данном шаблоне мы передаем родительскому шаблону имя пользователя, чтобы он мог отобразить его на панели навигации. Также, экземпляр формы TweetForm готов принимать у пользователя новые сообщения. Теперь наше приложение умеет отображать все сообщения, которые относятся к нашему пользователю.
Когда шаблон завершен, перейдем к коду, который будет отвечать за вход/выход пользователя. Отредактируем twitter_app/views.py
Мы ожидаем POST запрос с данными от формы, затем проверяем ее, если все хорошо, то авторизуем пользователя, вызвав метод
Обработка запроса уничтожения сессии относительно проще. Она просто вызывает функцию
Теперь, нам осталось научится регистрировать пользователя в системе. Добавим еще один метод в файл twitter_app/views.py
Подобно методу для входа, метод регистрации ожидает POST запрос. Если форма заполнена правильно, то пользователь будет сохранен в базу данных и перенаправлен на главную страницу, при этом он уже будет автоматически считаться вошедшим в систему. Если же у нас есть ошибки, то мы вернем их список и так же отправим на главную.
Проверим все ли у нас работает, запустив сервер
Зайдем на наш сайт и попробуем зарегистрироваться. Если все прошло хорошо, то мы увидим список твитов пользователя. Попробуйте выйти и заново войти, чтобы проверить весь наш функционал. В случае, если что-то не работает, вернитесь назад и проверьте, все ли сделано правильно.
В шаблоне
Опишем валидацию и сохранение присланных нам твитов. Откройте twitter_app/views.py
Мы используем модификатор
Теперь, перейдем к виду, который будет отображать последние 10 твитов. Еще раз изменим
Теперь создадим отображение для данного действия. Перейдем к файлу twitter/templates/public.html
Когда мы перебираем все твиты, шаблон использует
Убедитесь, что сервер запущен. Создайте новый твит и перейдите на страницу со всеми твитами, чтобы убедиться, что все работает
Какой же клон твитера может быть без пользовательских профилей и возможности читать друг друга? Для начала, добавим пару новый роутов
Самый интересный маршрут — обрабатывающий пользовательский профиль.
Подготовка
На момент написания данной статьи 1.6 поддерживает Python от версии 2.6 и до 3.2 включительно. Перед тем, как начать, убедитесь, что у Вас установлен Python нужной версии. Для этого выполните
python -v
в терминале. Давайте сначала настроим нужное нам окружение. Откройте командную строку от имени администратора и выполните следующее:curl http://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
Для настройки окружения Djanjo создадим виртуальное окружение. Выполните следующие команды:
virtualenv --no-site-packages twitter_env
source twitter_env/bin/activate
Теперь, когда у нас есть виртуальное окружение, установим все необходимые зависимости. Кроме Django мы будем также использовать South для выполнения миграций базы данных. Обратите внимание, что все команды далее стоит выполнять внутри нашего виртуального окружения.
pip install Django South
Когда все зависимости удовлетворены, мы можем перейти непосредственно к созданию самого проекта.
Создание проекта
Мы начнем с создания нового Django проекта. Зайдите в нужную Вам директорию и запустите:
django-admin.py startproject twitter
cd twitter
django-admin.py startapp twitter_app
Продолжим. Откройте файл
twitter/setting.py
, чтобы изменить настройки нашего проекта. Первое, мы определим несколько констант. В самом начале файла добавьте следующее:import os
PROJECT_PATH = os.path.dirname(os.path.abspath(__file__))
LOGIN_URL = '/'
PROJECT_PATH
будет хранить в себе расположение директории, где находится сам файл settings.py
. Это позволит нам использовать относительные пути в будущем. LOGIN_URL
, как можно угадать по названию, будет говорить проекту, что корень нашего сайта и есть URL для входа.Следующий наш шаг — настройка базы данных. Для разработки 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
. Их также нужно отредактировать:STATIC_ROOT = os.path.join(PROJECT_PATH, 'static')
STATIC_URL = '/static/'
Стоит учесть, что использование
os.path.join()
позволяет использовать относительные пути, через константу PROJECT_PATH
.Теперь, нам нужно определить директорию, в которой Django следует искать шаблоны:
TEMPLATE_DIRS = (
os.path.join(PROJECT_PATH, 'templates')
)
Наконец, добавим South и twitter_app в список установленных приложений
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'south',
'twitter_app',
)
Далее, создадим директории, на который мы ссылаемся в настройках, а также саму базу данных:
mkdir twitter/static twitter/templates twitter_app/static
python manage.py syncdb
python manage.py schemamigration twitter_app --initial
python manage.py migrate twitter_app
Шаг предварительной настройки завершен. Запустим сервер, чтобы удостовериться, что все работает:
python manage.py runserver
Шаблоны и статические файлы
Я считаю, что в этой статье основной упор нужно делать на функционал приложения, который описывается на Django, а не на дизайн, поэтому описание CSS стилей будут минимальны.
В папке
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;
}
}
Теперь, создадим базовый шаблон, которым будут «оборачиваться» все остальные наши представления. Стоит учесть, что у Django существует свой собственный шаблонизатор. Первым нашим шаблоном будет
twitter/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="/tweets">Public Tweets</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 tweet_form.errors %}
<div class="flash error">
{{ auth_form.non_field_errors }}
{{ user_form.non_field_errors }}
{{ tweet_form.content.errors }}
</div>
{% endif %}
{% if notice %}
<div class="flash notice">
{{ notice }}
</div>
{% endif %}
{% endblock %}
{% block content %}
{% endblock %}
</div>
</div>
</body>
</html>
В той разметке, которая приведена выше
{{ STATIC_URL }}
будет равносилен тому URL, который мы определили в setting.py. Создание моделей
Одна из лучшей вещей, которую имеет Django — встроенный набор моделей и форм, который могут быть расширены в случае необходимости. В нашем приложением нам понадобится модель
User
и модель UserProfile
, которая добавит нашему пользователю несколько дополнительных полей. Позже нам также понадобится модель Tweet для управления нашими твитами. Модель User
, которую предоставляет Django содержит поля, позволяющие хранить имя пользователя, пароль, имя и фамилию, email, а так же еще пару полей. Чтобы узнать подробнее обо всех полях, Вам стоит взглянуть в документацию. Для наших моделей добавим следующий код в twitter_app/model.pyfrom django.db import models
from django.contrib.auth.models import User
import hashlib
class Tweet(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])
Начнем с модели Tweet. У этой модели существует три поля: текстовое, с максимальной длинной в 140 символов, для хранения самого сообщения, внешний ключ, который указывает на автора сообщения, а также дата время создания сообщения, которое будет выставляться автоматически.
Перейдем к модели UserProfile. У нас есть поле, которое определяет связь один к одному с моделью User и поле много-много, чтобы определить связь между читаемыми/читателями. Параметр
related_name
позволяет использовать любое имя пользователя, которое захочет человек. У нас есть функция, которая возвращает URL к изображению gravatar.Создание форм
Django позволяет нам создавать формы, который могут быть подвергнуты проверке. Если данные не соответствуют нужным нам критериям, то пользователь будет уведомлен об этом. Мы создадим форму для модели Tweet и форму, которая расширяет UserCreationForm, для обеспечения возможности регистрации. Для управления аутентификацией нам нужно будет расширить форму AuthenticationForm. Создадим файл twitter_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 twitter_app.models import Tweet
Первым шагом будет форма регистрации. Мы назовем ее 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
мы определяем в каком порядке будут отображены поля формы и какая модель отвечает за валидацию формы.А так будет выглядеть форма, которая отвечает за аутентификацию. Как и в предыдущем классе, мы указываем класс, который должен добавляться в случае ошибки, а также placeholder
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
Последней формой, которую мы создадим будет форма создания нового твита
class TweetForm(forms.ModelForm):
content = forms.CharField(required=True, widget=forms.widgets.Textarea(attrs={'class': 'tweetText'}))
def is_valid(self):
form = super(TweetForm, self).is_valid()
for f in self.errors.iterkeys():
if f != '__all__':
self.fields[f].widget.attrs.update({'class': 'error tweetText'})
return form
class Meta:
model = Tweet
exclude = ('user',)
Опция
exclude
в классе Meta
предотвращает отображение поля для указания автора. Поле будет автоматически заполняться приложением на основе сессииРегистрация и вход
Когда дело доходит до управления маршрутами (роутинга), Django предоставляет широчайшие возможности. Нам нужно определить несколько маршрутов в twitter/urls.py
urlpatterns = patterns('',
url(r'^$', 'twitter_app.views.index'), # root
url(r'^login$', 'twitter_app.views.login_view'), # login
url(r'^logout$', 'twitter_app.views.logout_view'), # logout
url(r'^signup$', 'twitter_app.views.signup'), # signup
)
Теперь, нам нужно использовать модели и формы, которые мы написали и создать для них представления. Начнем с добавления подключения зависимостей в файле twitter_app/views.py
from django.shortcuts import render, redirect
from django.contrib.auth import login, authenticate, logout
from django.contrib.auth.models import User
from twitter_app.forms import AuthenticateForm, UserCreateForm, TweetForm
from twitter_app.models import Tweet
Основное представление
def index(request, auth_form=None, user_form=None):
# User is logged in
if request.user.is_authenticated():
tweet_form = TweetForm()
user = request.user
tweets_self = Twitter.objects.filter(user=user.id)
tweets_buddies = Twitter.objects.filter(user__userprofile__in=user.profile.follows.all)
tweets = tweets_self | tweets_buddies
return render(request,
'buddies.html',
{'tweet_form': tweet_form, 'user': user,
'tweets': tweets,
'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, })
В нашем основном представлении, мы отображаем контент в зависимости от того аутентифицирован ли пользовать или нет. Результаты запросов, которые хранятся в
tweets_self
и tweets_buddies
объединяются в одну сущность при помощи оператора |
. Также, мы проверяем, если у нас есть данные от формы, то мы должны их обработать и вернуть экземпляр этой формы представлению, иначе просто создадим новый экземпляр. Это позволит нам сообщить об ошибках, полученных в ходе проверки.Продолжением нашей деятельность будет создание отображения, которое будет показано в случае, если пользователь анонимен. Файл
twitter/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 Twitter?</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 %}
Данный шаблон расширяет базовое представление, которое мы определили ранее, отображает формы входа и регистрации.
Перейдем к файлу
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 Tweet</h1>
<p>
<form action="/submit" method="post">
{% for field in tweet_form %}{% csrf_token %}
{{ field }}
{% endfor %}
<input type="hidden" value="{{ next_url }}" name="next_url">
<input type="submit" value="Tweet!">
</form>
</p>
</div>
<div class="panel left">
<h1>Buddies' Tweets</h1>
{% for tweet in tweets %}
<div class="tweetWrapper">
<a href="/users/{{ tweet.user.username }}">
<img class="avatar" src="{{ tweet.user.profile.gravatar_url }}">
<span class="name">{{ tweet.user.first_name }}</span>
</a>
@{{ tweet.user.username }}
<p>
{{ tweet.content }}
</p>
</div>
{% endfor %}
</div>
{% endblock %}
В данном шаблоне мы передаем родительскому шаблону имя пользователя, чтобы он мог отобразить его на панели навигации. Также, экземпляр формы TweetForm готов принимать у пользователя новые сообщения. Теперь наше приложение умеет отображать все сообщения, которые относятся к нашему пользователю.
Когда шаблон завершен, перейдем к коду, который будет отвечать за вход/выход пользователя. Отредактируем twitter_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('/')
Мы ожидаем POST запрос с данными от формы, затем проверяем ее, если все хорошо, то авторизуем пользователя, вызвав метод
login()
, который начнет сессию. Если где-то произошла ошибка, то мы вернем форму обратно, добавив к ней список ошибок. Если запрос был сделан не через POST, то пользователь будет перенаправлен на главную страницу.Обработка запроса уничтожения сессии относительно проще. Она просто вызывает функцию
logout()
, которую предоставляет Django для уничтожения сессии, а затем перенаправляет нас на главную.Теперь, нам осталось научится регистрировать пользователя в системе. Добавим еще один метод в файл twitter_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 запрос. Если форма заполнена правильно, то пользователь будет сохранен в базу данных и перенаправлен на главную страницу, при этом он уже будет автоматически считаться вошедшим в систему. Если же у нас есть ошибки, то мы вернем их список и так же отправим на главную.
Проверим все ли у нас работает, запустив сервер
python manage.py runserver
Зайдем на наш сайт и попробуем зарегистрироваться. Если все прошло хорошо, то мы увидим список твитов пользователя. Попробуйте выйти и заново войти, чтобы проверить весь наш функционал. В случае, если что-то не работает, вернитесь назад и проверьте, все ли сделано правильно.
Создание новых твитов и их вывод
В шаблоне
buddies.html
, который мы создали до этого, форма tweet_form отправляется по адресу /submit. Давайте добавим этот маршрут, а так же маршрут для вывода всех твитовurlpatterns = patterns('',
# Examples:
url(r'^$', 'twitter_app.views.index'), # root
url(r'^login$', 'twitter_app.views.login_view'), # login
url(r'^logout$', 'twitter_app.views.logout_view'), # logout
url(r'^tweets$', 'twitter_app.views.public'), # public tweets
url(r'^submit$', 'twitter_app.views.submit'), # submit new tweet
)
Опишем валидацию и сохранение присланных нам твитов. Откройте twitter_app/views.py
from django.contrib.auth.decorators import login_required
@login_required
def submit(request):
if request.method == "POST":
tweet_form = TweetForm(data=request.POST)
next_url = request.POST.get("next_url", "/")
if tweet_form.is_valid():
tweet = tweet_form.save(commit=False)
tweet.user = request.user
tweet.save()
return redirect(next_url)
else:
return public(request, tweet_form)
return redirect('/')
Мы используем модификатор
@login_required
для того, чтобы метод выполнялся только тогда, когда пользователь представился, в противном случае он будет перенаправлен на страницу входа. Если проверка формы пройдет успешно, то мы записываем к твиту id пользователя и сохраняем запись. После изменения базы данных, пользователь будет перенаправлен по адресу next_url
.Теперь, перейдем к виду, который будет отображать последние 10 твитов. Еще раз изменим
twitter_app/views.py
@login_required
def public(request, tweet_form=None):
tweet_form = tweet_form or TweetForm()
tweets = Twitter.objects.reverse()[:10]
return render(request,
'public.html',
{'tweet_form': tweet_form, 'next_url': '/tweets',
'tweets': tweets, 'username': request.user.username})
Теперь создадим отображение для данного действия. Перейдем к файлу twitter/templates/public.html
{% extends "base.html" %}
{% block content %}
<div class="panel right">
<h1>Create a Tweet</h1>
<p>
<form action="/submit" method="post">
{% for field in tweet_form %}{% csrf_token %}
{{ field }}
{% endfor %}
<input type="hidden" value="{{ next_url }}" name="next_url">
<input type="submit" value="Tweet!">
</form>
</p>
</div>
<div class="panel left">
<h1>Public Tweets</h1>
{% for tweet in tweets %}
<div class="tweetWrapper">
<img class="avatar" src="{{ tweet.user.profile.gravatar_url }}">
<span class="name">{{ tweet.user.first_name }}</span>@{{ tweet.user.username }}
<span class="time">{{ tweet.creation_date|timesince }}</span>
<p>{{ tweet.content }}</p>
</div>
{% endfor %}
</div>
{% endblock %}
Когда мы перебираем все твиты, шаблон использует
timesince
, чтобы вычислить разницу между двумя временами и отобразить время публикации в том виде, в котором его показывает твитерУбедитесь, что сервер запущен. Создайте новый твит и перейдите на страницу со всеми твитами, чтобы убедиться, что все работает
Профили пользователей и подписки
Какой же клон твитера может быть без пользовательских профилей и возможности читать друг друга? Для начала, добавим пару новый роутов
urlpatterns = patterns('',
# Examples:
url(r'^$', 'twitter_app.views.index'), # root
url(r'^login$', 'twitter_app.views.login_view'), # login
url(r'^logout$', 'twitter_app.views.logout_view'), # logout
url(r'^tweets$', 'twitter_app.views.public'), # public tweets
url(r'^submit$', 'twitter_app.views.submit'), # submit new tweet
url(r'^users/$', 'twitter_app.views.users'),
url(r'^users/(?P<username>\w{0,30})/$', 'twitter_app.views.users'),
url(r'^follow$', 'twitter_app.views.follow'),
)
Самый интересный маршрут — обрабатывающий пользовательский профиль.
<?P отлавливает имя пользователя, которое запрошено через GET, а \w{0,30}
говорит о том, что максимальная длина не больше 30 символов
Далее, напишем вид, который отображает профиль. Для этого нам понадобится еще раз отредактировать файл twitter_app/views.py
from django.db.models import Count
from django.http import Http404
def get_latest(user):
try:
return user.tweet.order_by('-id')[0]
except IndexError:
return ""
@login_required
def users(request, username="", tweet_form=None):
if username:
# Show a profile
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
raise Http404
tweets = Twitter.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, 'tweets': tweets, })
return render(request, 'user.html', {'user': user, 'tweets': tweets, 'follow': True, })
users = User.objects.all().annotate(tweets_count=Count('tweet'))
tweets = map(get_latest, users)
obj = zip(users, tweets)
tweet_form = tweet_form or TweetForm()
return render(request,
'profiles.html',
{'obj': obj, 'next_url': '/users/',
'tweet_form': tweet_form,
'username': request.user.username, })
Эта часть - самое интересное из того, что мы сейчас проделали. Мы начинаем с того, что разрешаем выполнять данное действие только вошедшим пользователям. Маршрут, который мы описали ранее передает в данную функцию имя пользователя. И в зависимоти от того, что нам было передано мы выполняем нужное нам действие. Если передано имя пользователя, то нам нужно отобразить страницу конкртеного пользователя, но если оно пустое, тогда мы показываем пользователю страницу со всеми профилями.
Первый наш шаг - проверка содержимого переменной username
. Затем, мы пытаемся найти соответствующую информацию для пользователя с конкретным именем. Это мы делаем в блоке try
. Если пользователя не существует, то рано или поздно возникнет ошибка. Наш сервер в данном случае должен отдавать ошибку 404. Если запрашиваемый профиль принадлежит самому пользователю, тогда вместо ссылки "Читать" мы должны отобразить форму для отправки твита.
Вторая часть - если имя пользователя пустое. Мы ищем всех пользователей, добавляем к каждому из них количество написанных сообщений. Чуть сложнее будет найти последний твит каждого пользователя. Мы используем встроенную в Python функцию map()
. В коде выше мы так же объявляем функцию get_latest()
.
Опишем функцию, которая будет обрабатывать запрос на чтение пользователя
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/')
В функции выше мы получаем значение параметра, который обязательно должен быть передан через POST. Если id задан, тогда мы проверяем, что пользователь существует и создаем связь. Иначе мы получаем ошибку и перенаправляем пользователя на страницу со всеми профилями.
Нам осталось написать два отображения, которые показывают профиль пользователя. Создайте файл twitter/templates/profiles.html
{% extends "base.html" %}
{% block content %}
<div class="panel right">
<h1>Create a Tweet</h1>
<p>
<form action="/submit" method="post">
{% for field in tweet_form %}{% csrf_token %}
{{ field }}
{% endfor %}
<input type="hidden" value="{{ next_url }}" name="next_url">
<input type="submit" value="Tweet!">
</form>
</p>
</div>
<div class="panel left">
<h1>Public Profiles</h1>
{% for user, tweet in obj %}
<div class="tweetWrapper">
<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.tweet_count}} Tweets
<span class="spacing">{{ user.profile.followed_by.count }} Followers</span>
<span class="spacing">{{ user.profile.follows.count }} Following</span>
</p>
<p>{{ tweet.content }}</p>
</div>
{% endfor %}
</div>
{% endblock %}
Чтобы вывести список пользователей с их последними сообщениями, мы используем встроенные в python конструкцию. Внутри цикла мы выводим информацию о каждом пользователе, вклчая их сообщение.
Наконец, давайте напишем шаблон для вывода профиля какого-то отдельного пользователя. Создайте файл twitter/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="tweetWrapper">
<a href="/users/{{ user.username }}">
<img class="avatar" src="{{ user.profile.gravatar_url }}">
<span class="name">{{ user.first_name }}</span>
</a>
@{{ user.username }}
<p>
{{ tweets.count }} Tweets
<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 Tweets</h1>
{% for tweet in tweets %}
<div class="tweetWrapper">
<a href="/users/{{ user.username }}">
<img class="avatar" src="{{ user.profile.gravatar_url }}">
<span class="name">{{ tweet.user.first_name }}</span>
</a>
@{{ tweet.user.username }}
<span class="time">{{ tweet.creation_date|timesince }}</span>
<p>{{ tweet.content }}</p>
</div>
{% endfor %}
</div>
{% endblock %}
Так же, как и в шаблоне buddies.html, мы передаем имя пользователя родительскому шаблону, используя конструкцию {% %}
. Затем, мы отображаем профиль пользователя и ссылку на чтение, если это возможно. После всего, мы выводим список твитов
Теперь, мы закончили со всеми представлениями и шаблонами. Теперь Вы можете оттестировать приложение.
Заключение
Мы закончили написание нашего твитера на Django. Статья получилась довольно-таки объемной, но есть еще много вещей, которые можно реализовать. Надеюсь, что эта статья научила Вас некоторым приемам, которые можно использовать в Ваших настоящих проектах.