Не так давно нашёл файл с записями, которые я делал в 2012 году, когда осваивал веб-фреймворк Django. Я подумал, что не стоит зря пропадать этим записям и решил выложить их здесь. Хотя в этом файле есть что дополнить, в процессе подготовки публикации радикальных правок в этот файл я вносить не стал, т.к. фреймворк очень большой и начав раскрывать частности, легко раздуть и без того большой файл до невероятных размеров.
Программы, написанные с использованием Django, являются совокупностью отдельных приложений, объединённых в один проект. В отличие от многих других веб-фреймворков, в Django не используется архитектура MVC - Модель-Представление-Контроллер, вместо неё используется собственная архитектура MVT - Модель-Представление-Шаблон. Шаблон Django по функциям ближе всего к представлению в MVC, а представление Django по функциям ближе всего к контроллеру в MVC. Представления Django привязываются к определённому URL через маршруты.
В Debian и производных от него дистрибутивах установить Python и Django можно из репозиториев:
# apt-get install python python-django
Создаём проект dj:
$ django-admin startproject dj
Переходим в каталог проекта:
$ cd dj
Смотрим содержимое каталога проекта:
$ find . . ./manage.py ./dj ./dj/wsgi.py ./dj/settings.py ./dj/__init__.py ./dj/urls.py
Создаём в проекте новое приложение app:
$ ./manage.py startapp app
В каталоге проекта появится новый каталог с именем app, созданный специально для размещения приложения:
$ find . . ./manage.py ./dj ./dj/wsgi.py ./dj/settings.py ./dj/__init__.py ./dj/urls.py ./dj/settings.pyc ./dj/__init__.pyc ./app ./app/tests.py ./app/views.py ./app/models.py ./app/__init__.py
Создадим новое представление hello в приложении app. Представление принимает в качестве аргумента объект HttpRequest и возвращает объект HttpResponse.
Откроем для редактирования файл app/views.py и придадим ему следующий вид:
# -*- coding: UTF-8 -*- from django.http import HttpResponse def hello(request): return HttpResponse(u'Здравствуй, мир!')
Теперь настроим маршрут, вызывающий это представление для url. Для этого откроем файл dj/urls.py и добавим в него пару строчек.
Первую строчку добавим в начало файла, после других строчек импорта. Строчка импортирует представление hello из приложения app:
from app.views import hello
Теперь найдём функцию patterns, возвращаемое значение которой присваивается переменной urlpatterns, и впишем в аргументы функции следующую строчку:
('^hello/$', hello),
В итоге у меня содержимое файла dj/urls.py приняло следующий вид:
from django.conf.urls import patterns, include, url from app.views import hello # Uncomment the next two lines to enable the admin: # from django.contrib import admin # admin.autodiscover() urlpatterns = patterns('', # Examples: # url(r'^$', 'dj.views.home', name='home'), # url(r'^dj/', include('dj.foo.urls')), # Uncomment the admin/doc line below to enable admin documentation: # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), # Uncomment the next line to enable the admin: # url(r'^admin/', include(admin.site.urls)), ('^hello/$', hello), )
В шаблоне url в конце обязательно должна быть косая черта, т.к. если клиент запросит страницу без косой черты в конце, Django автоматически добавит её и попытается найти представление, соответствующее этому URL. Нужно ли добавлять косую черту, регулируется настройкой APPEND_SLASH. Если установить её в False, то косая черта добавляться не будет. Шаблоном для корня сайта является '^$', то есть соответствие пустой строке.
Чтобы в проекте использовалось наше приложение, его нужно подключить к проекту. Для этого закомментируем на время список стандартных приложений в файле dj/settings.py и пропишем наше приложение:
INSTALLED_APPS = ( #'django.contrib.auth', #'django.contrib.contenttypes', #'django.contrib.sessions', #'django.contrib.sites', #'django.contrib.messages', #'django.contrib.staticfiles', # Uncomment the next line to enable the admin: # 'django.contrib.admin', # Uncomment the next line to enable admin documentation: # 'django.contrib.admindocs', 'app', )
Также закомментируем в настройках подключение приложений-прослоек:
MIDDLEWARE_CLASSES = ( #'django.middleware.common.CommonMiddleware', #'django.contrib.sessions.middleware.SessionMiddleware', #'django.middleware.csrf.CsrfViewMiddleware', #'django.contrib.auth.middleware.AuthenticationMiddleware', #'django.contrib.messages.middleware.MessageMiddleware', # Uncomment the next line for simple clickjacking protection: # 'django.middleware.clickjacking.XFrameOptionsMiddleware', )
Теперь настало время запустить сервер разработчика с только что созданным простейшим приложением:
$ ./manage.py runserver 0.0.0.0:8000
Можно попытаться зайти в браузере на страницу /hello/. У меня ссылка для открытия страницы выглядела так:
Итак, мы создали наш первый проект на Django, который при обращении к странице /hello/ выводит надпись "Здравствуй, мир!" Возможности Django в этом проекте практически не используются - мы не использовали ни моделей, ни шаблонов, но этот проект даёт общее представление о структуре программ, написанных с использованием Django.
Для начала настроим каталог, в котором будут находиться шаблоны. Для этого отредактируем файл dj/settings.py и впишем в кортеж TEMPLATES_DIRS полный путь к каталогу с шаблонами.
После редактирования файла эта настройка в файле settings.py приняла следующий вид:
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. "/home/stupin/dj/templates", )
Не забывайте в конце одноэлементного кортежа поставить запятую, чтобы Python мог отличить кортеж от простого выражения в скобках.
Теперь создадим каталог для шаблонов:
$ mkdir templates
И создадим в нём новый шаблон с именем time.tmpl и со следующим содержимым:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Текущее время</title> </head> <body> <div align="center"> Сейчас {{ time }} </div> </body> </html>
Теперь добавим в файл app/views.py импорт функции для загрузки и отрисовки шаблона:
from django.shortcuts import render_to_response
И создадим представление current_datetime следующим образом:
def current_datetime(request): return render_to_response('time.tmpl', {'time' : datetime.now()})
Осталось настроить в файле urls.py маршрут к этому представлению:
('^time/$', current_datetime),
Если сейчас попробовать открыть страницу http://localhost:800/time/, то можно увидеть текущее время на английском языке в часовом поясе Гринвичской обсерватории.
Чтобы время отображалось правильно - в часовом поясе сервера, пропишем в файл настроек местный часовой пояс. Для этого откроем файл dj/settings.py и пропишем в переменную TIME_ZONE значение 'Asia/Yekaterinburg':
TIME_ZONE = 'Asia/Yekaterinburg'
Чтобы время на странице отображалось в соответствии с правилами, принятыми в России, пропишем в файл настроек язык проекта. Откроем файл dj/settings.py и пропишем в переменную LANGUAGE_CODE значение 'ru-RU':
LANGUAGE_CODE = 'ru-RU'
Теперь текущее время должно отображаться на русском языке в часовом поясе, по которому живёт Уфа :)
Попробуем добавить страницы, которые будут вычитать из текущего времени часы, фигурирующие в URL запрошенной страницы. Для этого в файл dj/urls.py добавим маршруты:
(r'^time/plus/(\d{1,2})$', hours_plus), (r'^time/minus/(\d{1,2})$', hours_minus),
В каталоге с шаблонами templates разместим два новых шаблона.
Файл templates/time_minus.tmpl со следующим содержимым:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Прошлое время</title> </head> <body> <div align="center"> {{ delta }} часов назад было {{ time }} </div> </body> </html>
Файл templates/time_plus.tmpl со следующим содержимым:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Будущее время</title> </head> <body> <div align="center"> Через {{ delta }} часов будет {{ time }} </div> </body> </html>
В файл app/views.py пропишем два представления, которые будут использовать два новых шаблона:
def hours_plus(request, delta): try: delta = int(delta) except ValueError: raise Http404() time = datetime.now() + timedelta(hours=delta) return render_to_response('time_plus.tmpl', {'delta' : delta, 'time' : time}) def hours_minus(request, delta): try: delta = int(delta) except ValueError: raise Http404() time = datetime.now() - timedelta(hours=delta) return render_to_response('time_minus.tmpl', {'delta' : delta, 'time' : time})
Файл app/views.py целиком примет следующий вид:
# -*- coding: UTF-8 -*- from django.http import HttpResponse, Http404 from django.shortcuts import render_to_response from datetime import datetime, timedelta def hello(request): return HttpResponse(u'Здравствуй, мир!') def current_datetime(request): return render_to_response('time.tmpl', {'time' : datetime.now()}) def hours_plus(request, delta): try: delta = int(delta) except ValueError: raise Http404() time = datetime.now() + timedelta(hours=delta) return render_to_response('time_plus.tmpl', {'delta' : delta, 'time' : time}) def hours_minus(request, delta): try: delta = int(delta) except ValueError: raise Http404() time = datetime.now() - timedelta(hours=delta) return render_to_response('time_minus.tmpl', {'delta' : delta, 'time' : time})
Теперь можно перейти по ссылкам http://localhost:8000/time/plus/1 или http://localhost:8000/time/plus/2 и увидеть получающиеся страницы.
Условие:
{% if today_is_weekend %} <p>Сегодня выходной!</p> {% endif %}
Условие с двумя вариантами:
{% if today_is_weekend %} <p>Сегодня выходной!</p> {% else %} <p>Пора работать.</p> {% endif %}
Ложным значениями являются: пустой список [], пустой кортеж (), пустой словарь {}, ноль - 0, объект None и объект False.
Можно использовать сочетания условий при помощи and и or, причём and имеет более высокий приоритет. Скобки в условиях не поддерживаются, без них можно обойтись с помощью вложенных условий. Также возможно использовать операторы ==, !=, <, >, >=, <= и in для вычисления условий, по смыслу совпадающих с условными операторами в самом Python.
Циклы для перебора значений из списка:
{% for athlete in athlete_list %} <li>{{ athlete.name }}</li> {% endfor %}
Можно перебирать значения из списка в обратном порядке:
{% for athlete in athlete_list reversed %} ... {% endfor %}
Внутри циклов существуют следующие переменные:
Комментарии в шаблоне:
{# Это комментарий #}
или
{% comment %} Многострочный комментарий. {% endcomment %}
В шаблон можно включать другой шаблон, в качестве фрагмента. Таким образом можно повторно использовать одни и те же фрагменты в разных шаблонах, а также для удобства разбивать большие сложные шаблоны на небольшие фрагменты.
Включение подшаблона из файла:
{% include "includes/nav.html" %}
Включение подшаблона из переменной:
{% include template_name %}
Наследование шаблонов - очень удобная концепция, которая встречается не во всех шаблонизаторах. В шаблонизаторе Django такая поддержка наследования шаблонов имеется. Наследование позволяет определить определить в базовом шаблоне общий дизайн для множества страниц. При этом в страницах-наследницах переопределяются отдельные блоки базовой страницы. В этих блоках будет выводиться содержимое, специфичное конкретно для этой страницы.
Продолжим эксперименты с нашим тестовым проектом, добавив в него базовый шаблон.
Базовый шаблон base.tmpl:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>{% block title %}{% endblock %}</title> </head> <body> <h1>Бесполезный сайт с часами</h1> {% block content %}{% endblock %} {% block footer %} <hr> <p>Благодарим за посещение нашего сайта.</p> {% endblock %} </body> </html>
В базовом шаблоне помечаются блоки, в которых прописывается часть шаблона, специфичная для этой страницы.
Производный шаблон. Для примера приведу только содержимое шаблона time_plus.tmpl:
{% extends "base.tmpl" %} {% block title %}Будущее время{% endblock %} {% block content %} <p>Через {{ delta }} часов будет {{ time }}</p> {% endblock %}
В производном шаблоне указывается базовый шаблон и переопределяется содержимое блоков базового шаблона. Шаблоны других страниц можно отредактировать сходным образом, чтобы и они использовали дизайн, общий для всех страниц.
После того, как мы рассмотрели шаблоны Django, пришло время заняться моделями Django. Но прежде чем приступить непосредственно к изучению моделей, нужно установить необходимые модули, выставить настройки как в самой СУБД, так и в проекте Django.
Установим модуль для доступа к базе данных MySQL из Python (разработчики фреймворка Django рекомендуют использовать PostgreSQL, но мы воспользуемся MySQL, поддержка которого тоже имеется):
# apt-get install python-mysqldb
Настроим кодировку сервера MySQL и порядок сортировки. Для этого в файле /etc/mysql/my.cnf в секцию [mysqld] впишем следующие настройки:
character_set_server=utf8 collation_server=utf8_unicode_ci
Перезапустим сервер базы данных:
# /etc/init.d/mysql restart
В результате вышеописанных действий должен получиться такой результат:
mysql> show variables like 'coll%'; +----------------------+-----------------+ | Variable_name | Value | +----------------------+-----------------+ | collation_connection | utf8_general_ci | | collation_database | utf8_unicode_ci | | collation_server | utf8_unicode_ci | +----------------------+-----------------+ 3 rows in set (0.00 sec) mysql> show variables like 'char%'; +--------------------------+----------------------------+ | Variable_name | Value | +--------------------------+----------------------------+ | character_set_client | utf8 | | character_set_connection | utf8 | | character_set_database | utf8 | | character_set_filesystem | binary | | character_set_results | utf8 | | character_set_server | utf8 | | character_set_system | utf8 | | character_sets_dir | /usr/share/mysql/charsets/ | +--------------------------+----------------------------+ 8 rows in set (0.00 sec)
Создадим базу данных для проекта:
CREATE USER app@localhost IDENTIFIED BY "app_password"; CREATE DATABASE app; GRANT ALL ON app.* TO app@localhost;
Пропишем в файл настроек проекта dj/settings.py настройки подключения к базе данных:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. 'NAME': 'app', # Or path to database file if using sqlite3. 'USER': 'app', # Not used with sqlite3. 'PASSWORD': 'app_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. } }
Проверяем правильность настройки:
$ ./manage.py shell
Если всё правильно, то откроется отладочная оболочка Python. Попробуем ввести следующие команды:
from django.db import connection cursor = connection.cursor()
Если сообщений об ошибках не было, значит всё настроено правильно.
Каждая модель Django описывают одну таблицу в базе данных. В Django имеется система отображения таблиц в объекты - ORM - Object-Relational Mapping - объектно-реляционное отображение. ORM позволяет манипулировать единичными строками таблиц как объектами Python, а также осуществлять операции массовой выборки, обновления и удаления строк.
В качестве примера, рассмотрим адресный справочник домов в городах. Откроем файл app/models.py и приведём его к следующему виду:
# -*- coding: UTF-8 -*- from django.db import models class City(models.Model): country = models.ForeignKey(Country) title = models.CharField(max_length=150) class Street(models.Model): city = models.ForeignKey(City) title = models.CharField(max_length=150) class Area(models.Model): city = models.ForeignKey(City) title = models.CharField(max_length=150) class House(models.Model): area = models.ForeignKey(Area) street = models.ForeignKey(Street) house = models.IntegerField() frac = models.CharField(max_length=30) comment = models.CharField(max_length=100)
Мы описали объекты, составляющие адресный справочник, и описали взаимоотношения между ними через внешние ключи - ForeignKey. В городе имеются улицы и районы, а каждый дом находится на одной улице и принадлежит одному из районов города.
В Django есть не только текстовые поля и внешние ключи. Имеются числовые поля, списковые поля, логические поля, поля для связей один-к-одному и для связей многие-ко-многим. У полей можно прописать значения по умолчанию, разрешить или запретить использовать значение NULL и запретить или разрешить вводить в строковые поля пустые строки. У поля можно прописать его имя в базе данных, выставить признак - нужно ли создавать индекс по этому полю, можно прописать текстовое описание поля, которое будет использоваться в веб-интерфейсе администратора и в объектах форм Django. У классов моделей, в свою очередь, можно тоже указывать их текстовые описания, прописывать составные индексы, ограничения уникальности записей и т.п. Создание моделей - это обширная тема, рассмотреть которую сколь-нибудь подробно в рамках этого небольшого учебника вряд ли получится.
Теперь мы можем проверить правильность синтаксиса и логики моделей:
$ ./manage.py validate
Чтобы увидеть команды SQL для создания структуры базы данных, требуемой для моделей из приложения app, введём следующую команду:
$ ./manage.py sqlall app
Чтобы выполнить эти операторы SQL и создать в базе данных таблицы, соответствующие моделям, нужно выполнить следующую команду:
$ ./manage.py syncdb
Можно войти в базу данных клиентом и увидеть созданную структуру таблиц и их взаимосвязи.
Для входа в базу данных с настройками проекта, можно воспользоваться следующей командой:
$ ./manage.py dbshell
Откроем оболочку Python:
$ ./manage.py shell
Импортируем описание моделей:
from address.models import *
Создадим объект "город":
c = City(title=u'Уфа')
И сохраним его в базу данных:
c.save()
Теперь создадим объект "улица" в этом городе:
s = Street(title=u'ул. Карла Маркса', city=c)
И сохраним объект с улицей:
s.save()
Если нужно отредактировать объект, то можно прописать в него новое свойство и сохранить:
c.title = u'г. Уфа' c.save()
Недостаток такого рода редактирования объектов заключается в том, что в базе данных обновляются все поля в отредактированной строке таблицы, а не только то поле, которое действительно было изменено. Для раздельного редактирования полей строк можно воспользоваться массовым редактированием, о котором будет рассказано далее.
Откроем оболочку Python:
$ ./manage.py shell
Импортируем описание модели:
from address.models import *
Загрузим все объекты типа "город":
c = City.objects.all()
Загрузим объект "город", имеющий имя "г. Уфа":
c = City.objects.filter(title=u'г. Уфа')
И убедимся, что загрузился именно он:
print c
Выбор списка объектов:
Условия в фильтре можно комбинировать между собой не только указывая их через запятую, но создавая каскады фильтров:
Obj.objects.filter(field1__iendswith='a').filter(field2='b')
Чтобы отобрать объекты, не подходящие под указанное условие, можно воспользоваться методом фильтрации exclude. Например, следующее выражение отберёт те записи, у которых начало первого поля без учёта регистра совпадает с a, а второе поле не равно b:
Obj.objects.filter(field1__iendswith='a').exclude(field2='')
Выбор одного объекта осуществляется точно таким же способом, как выбор списка, за исключением того, что вместо метода filter используется метод get:
Obj.objects.get(field='')
Если ни один объект не был найден, будет сгенерировано исключение Obj.DoesNotExist. Если указанным критериям соответствует несколько записей, то будет сгенерировано исключение Obj.MultipleObjectsReturned.
Как и в случае выборки списка объектов, можно комбинировать фильтры друг с другом. Но для выборки одного объекта последним методом в цепочке должен быть get. Например, вот так:
Obj.objects.filter(field1__iendswith='a').exclude(field2='').get()
Или, что то же самое, вот так:
Obj.objects.exclude(field2='').get(field1__iendswith='a')
В Django версий 1.6 и более поздних имеется метод first(), не принимающий аргументов, который возвращает первую запись из списка, если она есть. В противном случае возвращается None. Стоит учитывать, что этот метод никак не обрабатывает случаи, когда условию соответствует несколько записей сразу.
Сортировка данных:
Сортировку по умолчанию можно настроить в свойствах объекта модели, добавив вложенный класс со свойством ordering, которому присвоен список полей для сортировки:
class Meta: ordering = ["field"]
Можно комбинировать методы:
Obj.objects.filter(field="").order_by("-field")
Можно выбирать необходимый фрагмент списка объектов. Например, вот этот запрос вернёт два первых объекта:
Obj.objects.filter(field="").order_by("-field")[0:2]
Массовое обновление объектов осуществляется следующим образом:
Obj.objects.filter(id=52).update(field='')
Запрос возвращает количество обновлённых строк таблицы.
Удалить один объект можно следующим образом:
o = Obj.objects.get(field='') o.delete()
Удалить объекты массово можно так:
Obj.objects.filter(field='').delete()
Пожалуй самая приятная особенность фреймворка Django - это встроенный веб-интерфейс администратора, который позволяет манипулировать записями на уровне отдельных таблиц. В большинстве случаев веб-интерфейс администратора позволяет сэкономить время на реализации большого количество весьма однообразных функций. Даже если понадобится сделать нечто необычное, веб-интерфейс поддаётся очень глубокой и тонкой настройке через объекты административного интерфейса, объекты форм, дополнительные действия и т.д. И лишь в совсем редких случаях может понадобиться реализовывать собственные страницы для манипуляции объектами приложения.
Для включения интерфейса администрирования нужно внести изменения в файл настроек dj/settings.py:
Вносим изменения в базу данных, чтобы в ней создались таблицы, необходимые для работы интерфейса администрирования:
$ ./manage.py syncdb
Если при этом отказаться от создания суперпользователя, то потом его можно создать с помощью команды:
$ ./manage.py createsuperuser
Теперь в начало файла dj/urls.py добавим использование модуля и его инициализацию:
from django.contrib import admin admin.autodiscover()
И пропишем маршрут к интерфейсу администрирования:
urlpatterns = patterns('', # ... (r'^admin', include(admin.site.urls)), # ... )
Для того, чтобы объекты можно было редактировать прямо из интерфейса администратора, нужно создать в каталоге приложения app файл admin.py со следующим содержимым:
from app.models import * from django.contrib import admin admin.site.register(City) admin.site.register(Area) admin.site.register(Street) admin.site.register(House)
После этого можно перейти по ссылке http://localhost:8000/admin/, войти под учётными данными, указанными команде createsuperuser, и пользоваться интерфейсом администрирования для добавления, редактирования и удаления записей в таблицах.
Как уже было сказано, интерфейс администрирования поддаётся глубокой и тонкой настройке, но его настройка выходит за рамки этого учебника.
За рамками этого учебника также остались подробности описания моделей, выражения Q и F для конструирования более сложных запросов, не рассмотрены формы и модельные формы, не рассмотрена миграция структуры базы данных при изменении моделей и многое другое.