понедельник, 8 марта 2010 г.

Знакомство с django, Работа с БД.

Сразу скажу спасибо www.djbook.ru, русский перевод онлайн книги http://djangobook.com/en/2.0/, именно отсюда я черпал данные для написания поста. Здесь я попытаюсь кратко описать общие сведения.
Современные приложения не мыслимы без использования баз данных, в связи с этим требуется уделять особое внимание взаимодействию приложений с базой данных. Django обладает мощным api для работы с БД.
Здесь я создам проект, сформирую модель БД, построю таблицы и выполню простейшие 3 действия вставка (insert), обновление (update), удаление (delete).

Начну с перечисления приложений которые будут использоваться:

  1. python 2.6
  2. django 1.1.1
  3. postgreSQL 8.4.2
  4. python-psycopg2 2.0.8-0

Вот вроде и всё, приступим.

Первым делом, что необходимо сделать, это создать новый проект. Для этого необходимо определиться с именем каталога проекта и местом его расположения, всё остальное Django сделает сам.

django-admin.py startproject hellowDjango

Команда приведёт к созданию шаблона нового проекта в текущем каталоге с названием hellowDjango. Подробнее о том, что происходит при выполнении команды можно прочитать здесь.

Следующим шагом, будет настройка проекта на взаимодействие с БД. Я предполагаю, что БД установлена и настроена. Если нет, то вот ссылка, которая помогла мне. Итак БД и её пользователь созданы. Открываем файл настроек, settings.py находящийся в каталоге проекта hellowDjango, находим там такие строчки:
DATABASE_ENGINE = #'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
DATABASE_NAME = # Or path to database file if using sqlite3.
DATABASE_USER = # Not used with sqlite3.
DATABASE_PASSWORD = # Not used with sqlite3.
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
Я сделал следующее:
DATABASE_ENGINE = 'postgresql_psycopg2' #'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
DATABASE_NAME =     myDBName    # Or path to database file if using sqlite3.
DATABASE_USER =      myUserDB    # Not used with sqlite3.
DATABASE_PASSWORD = myUserDBPasswor # Not used with sqlite3.
Остальные строчки оставил без изменений.

  • 'postgresql_psycopg2' - драйвер через который приложение будет выполнять запросы к БД и получать данные;
  • myDBName - название БД;
  • myUserDB - основной пользователь БД;
  • myUserDBPasswor - пароль основного пользователя.

Теперь необходимо проверить, что всё настроено правильно, для этого нужно выполнить команду в каталоге проекта:

python manage.py shell

Данная команда запустит python с настройками проекта. Далее выполним несколько команд для проверки корректного подключения к БД

>>> from django.db import connection
>>> cursor = connection.cursor()

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

После успешной настройки БД в django необходимо создать приложение.
Приложение - набор модели БД и представления хранящиеся в одном пакете python. Для того, чтобы создать приложение, необходимо находясь в папке проекта выполнить команду:

python manage.py startapp MyName

Команда создаст каталог MyName, в каталоге проекта hellowDjango, а так же создаст файлы приложения в каталоге MyName.

Следующий шаг - это описание модели БД в приложении. Модель БД - описание хранимых данных в БД на языке python. Для создания модели приложения необходимо отредактировать файл MyName/models.py. В модели будет описано много классов, каждый из которых будет описывать одну таблицу БД. Одно свойство класса - это один столбец таблицы. Класс должен быть унаследован от models.Model описанного в пакете django.db.models. Свойства класса должны иметь типы описанные там же. Все типы данных и их использование можно посмотреть здесь. Я в своих целях использовал следующие типы данных:

  1. models.DateTimeField() - поле содержащее в себе Дату и Время
  2. models.CharField(max_length= xx) - Текстовое поле ограниченной длины. Длина строки = xx символов
  3. models.BooleanField() - Поле содержащее в себе булево значение
  4. models.ForeignKey() - ссылка на внешнюю таблицу
  5. models.PositiveIntegerField() - положительное целое значение
  6. models.URLField(max_length=хх) - ссылка на web страницу, длина ссылки ограничена xx символами
  7. models.DateField() - поле содержащее Дату

Вот пример моей таблицы.
#! -*- coding: cp1251 -*-
class Product(models.Model):
  """
    таблица продуктов, которые предоставляет поставщик и которые могут заказать покупатели
    name - название продукта
    price - цена продукта
    product_class_id - ссылка на тип продукта по которой будет группироваться предложение
    diler_id - ссылка на поставщика
    picture_link - ссылка на url, где будет храниться картинка на продукты
    registration_date - дата регистрации
  """
  name = models.CharField(max_length=255)
  price = models.PositiveIntegerField()
  product_class = models.ForeignKey(ProductClass)
  diler = models.ForeignKey(Dealer)
  picture_link = models.URLField(max_length=500)
  registration_date = models.DateField()

  def __unicode__(self):
    return self.name

  class Meta:
    ordering = ["diler"]
Можно заметить подкласс Meta и функцию __unicode__(). Функция __unicode__() позволит получить юникодное описание элементов таблицы по выбранным полям, единственное ограничение, поля должны быть текстовыми. Класс Meta со свойством ordering позволяет получить выборку данных упорядоченную по указанным полям. В моём примере сортировка будет проходить по полю diler, но полей может быть множество. Так же для сортировки в обратном направлении перед именем поля ставится знак "-" (для моего примера это выглядело бы так ordering = ["-diler"]). Выборка будет сортироваться по данному полю до тех пор пока в коде явно не будет указано поле для сортировки, например так:
>>> item = Product.objects.order_by("name") # item - колекция записей из таблице product упорядоченных по полю "name", а не по "diler" указанному в модели
При создании модели данных у меня возникла проблема с внешними ключами. При ссылке на внешную таблицу которая описывалась ниже происходила ошибка, простая перестановка таблиц местами решила данную проблему. Так же нужно отметить, что при создании внешних ключей, django добавляет к имени поля с внешним ключем постфикс _id.

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

python manage.py validate

После того, как модель прошла проверку, можно посмотреть как django предложит сгенерировать таблицы. Для этого выполним ещё одну команду:

python manage.py sqlall MyName

Изменений в БД не произайдет. На экране просто отобразится сгенерированный скрипт создания таблиц.
для того, чтобы создать данную модель в БД выполним следующую команду:

python manage.py syncdb

Вы увидете протокол создания таблиц в БД.

Осталось пройтись по основным командам работы с БД в коде приложения.
Ниже представлены варианты вставки данных в БД

python manage.py shell # запускаем интерпретатор
>>> import MyName.models as mo # импортируем все модели проекта

>>> type = mo.ProductClass() # создаём экземпляр класса Типы Продуктов
>>> type.class_name = 'сырьё' # название типа
>>> type.save() # записываем экземпляр в БД
# метод save() вызывает выполнение sql оператора insert в таком контексте
# INSERT INTO MyName_ProductClass
# (class_name)
# VALUES
# ('сырьё');
>>> type # вернулось не понятное описание, потому что в модели не определён метод __unicode__(self)
< ProductClass: ProductClass object >

>>> type.id # смотрим сгенериванное id для данной записи
1L # результат выполнения команды
# так же можно воспользоваться вставкой данных другого вида
>>> mo.Dealer(organization_name = "ООО Рога И Копыта").save() # создаст запись в таблице Dealer
>>> mo.Dealer.objects.all() # выполняем выборку данных из таблици Dealer
[< Dealer: ООО Рога И Копыта>] # поскольку запись в таблице только 1 то и колекция содержит всего 1 элемент, обратиться к нему можно через индексы.
>>> col = cm.Dealer.objects.all() # сохранение выборки в колекцию
>>> col[0] # обращение к 1ому элементу колекции
< Dealer: ООО Рога И Копыта > # возвращается понятное описание вида " ООО Рога И Копыта" балгодоря методу __unicode__(self).
>>> col[0].id # получение id данной записи
1

>>> item = mo.Product() # создаём экземпляр класса Продукт
>>> item.name = 'мука' # передаём название продукта
>>> item.price = 100 # цена продукта
>>> item.diler = col[0]
>>> item.product_class = type
>>> item.save()

# если для создания записи будет не хватать каких либо данных вы увидете протокол ошибок. Например такой:
>>> mo.Product(name = 'овёс', price = 50, product_class = type).save()
Traceback (most recent call last):
  ...
IntegrityError: null value in column "diler_id" violates not-null constraint

>>> mo.Product(name = 'овёс', price = 50, product_class = type, diler = col[0]).save() # вторая запись в таблице Product

Пора перейти к вариантам обновления записей.
# для того, что-бы провести обновление записи необходимо выполнить следующие действия
>>> item2 = mo.Product.objects.get(name = 'овёс')
>>> item2.name = "овёс золотистый"
>>> item2.save()
Данный пример прост, но иимеет свой недостаток, он выполняет обновление всех полей, что может привести к "гонке", когда происходит массовое изменение данных в Таблице многими пользователями. Для решения такой проблемы правильно будет использовать метод update. Данный метод изменяет только указанные поля.
>>> mo.Product.objects.filter(id=3).update(name='oves')
1
>>> cole[2]
< Product: oves >
Выше уже встречалась полная выборка данных из таблицы, рассмотрим дополнительные возможности выборок.
# полная выборка из таблицы
>>> mo.Product.objects.all()
[< Product: мука>, < Product: овёс>, < Product: рожь>]

# выборка по полю name
>>> mo.Product.objects.filter(name = 'овёс')
[< Product: овёс>]
# в sql это будет выглядеть так
# SELECT id, name, price, diler, product_class, picture_link, registration_date
# FROM MyName_Product
# WHERE name = 'овёс';

# выборка по частичному совпадению
>>> mo.Product.objects.filter(name__contains = 'о')
[< Product: овёс>, < Product: рожь>]
# в sql это будет выглядеть так
# SELECT id, name, price, diler, product_class, picture_link, registration_date
# FROM MyName_Product
# WHERE name LIKE '%o%';
Последнее, что хочется описать, это удаление записей из БД. Существует два способа удаления записей:
#первый удаление всех данных из таблицы
>>> cm.Dealer.objects.all()
[< Dealer: ООО Рога И Копыта>]
>>> cm.Dealer.objects.all().delete()
>>> cm.Dealer.objects.all()
[]
Интересно, что удаление данных не предупреждает о нарушении целостности, а просто удаляет все зависимые записи :(. Весьма не ожиданное поведение.
#второй удаление отобранных записей
>>> cm.ProductClass.objects.all()[0].id
1
>>> cm.ProductClass.objects.filter(id=1).delete()
>>> cm.ProductClass.objects.all()
[]
Более подробно о всех командах api работы с БД и постфиксов можно почитать здесь.

Вот краткое описание возможностей работы с БД в django, надеюсь данная информация будет полезна. Вскоре продолжу цикл статей о django

5 комментариев:

  1. >> Интересно, что удаление данных не предупреждает о нарушении целостности, а просто удаляет все зависимые записи :(. Весьма не ожиданное поведение.

    А как оно(Джанго?) должно предупреждать?

    ОтветитьУдалить
  2. >> Интересно, что удаление данных не предупреждает о нарушении целостности, а просто удаляет все зависимые записи :(. Весьма не ожиданное поведение.

    А как оно должно об этом предупреждать?

    ОтветитьУдалить
  3. Ну было бы не плохо предупредить, что будут удалены зависимые записи. Или сообщить, что не возможно удалить запись из за внешних ключей.

    ОтветитьУдалить
  4. этого можно добиться констрейтами в базе и получением исключения в случае такой ситуации. иначе джанго станет настоящим тормозом, ибо проверка его средствами будет очень долгой.

    ОтветитьУдалить
  5. :) Спасибо. Я не знаток БД. Думал, что это обычное поведение.

    ОтветитьУдалить