Django: приколы нашего…
Получив базовые понятия о языке программирования Python, после чего у меня хоть что-то сдвинулось в мозгу на ниве объектно-ориентированного программирования (ООП), я плавно подошёл к django. В принципе, инструментов для связки Python — web много. Но сейчас я остановился на этом фреймворке. Подумал, что если осилю этого монстра, то с остальными будет попроще.
Документации о django и всяких сообществ в Интернете море. И найти ответ на возникающие по ходу изучения вопросы можно особо не напрягаясь. Но в процессе написания своей любимой тестовой программки я столкнулся с несколькими граблями, описание которых я не нашёл. Возможно, что мой опыт django-неофита, в вопросах решения этих проблем, кому-то тоже пригодится.
Пояснение
Всё нижесказанное относится ко 2-ой версии django.
Пояснение
Я рассчитываю, что у вас есть базовые знания в программировании, СУБД и терминологии django.
Проблемка с составными ключами в БД.
Об этом известно, но повторить надо. Django не поддерживает составные ключи в базе. Хотя правильней, наверное будет сказать, что встроенный ORM django их не поддерживает.
Пояснение
ORM избавляет нас от необходимости использовать SQL при обращении с базой данных. Ну, пытается избавить. Например, на момент написания этой заметки, в своей тестовой программке я в коде пока не написал ни одного SQL запроса. Хотя база у меня не простая по структуре.
Пояснение
Я бы хотел уточнить терминологию по БД. Есть та что используется django. В ней хранятся её служебный таблицы и таблицы созданные в вашем проекте с вашими данными. Условно я называю эту базу внутренней.
Соответственно, есть внешняя БД, это база которая досталась вам в наследство, передана сторонними партнёрами, сделана для других проектов и т.п. Но с ней так же надо работать и обмениваться с ней данными.
Надо понимать, что тут я говорю именно о базах данных, а не о системах управления ими. Django поддерживает работу с различными СУБД и вы внутреннюю базу можете держать, например, на MySQL или PostgreSQL. Соответственно, и внешняя база может находится под управлением любой поддерживаемой СУБД.
Так вот, если у вас в базе используются составные ключи, то встроенный джанговский ORM не для вас. В моём случае, так исторически сложилось, что использовались составные ключи. Пришлось добавить поле и свести в него эти ключики, благо тип char() (в терминах MySQL) обоих полей это позволял сделать легко простой конкатенацией. Как вам придётся с этим бороться, это уже вам решать. Естественно, ни кто не запрещает вам оставить существующие ключи и продолжать их использовать другими приложениями.
Вообще, изначально считается, что вы всё будете делать средствами django во внутренней базе. Т.е. использование внешних баз, это уже некая отдельная тема. Поэтому, составные ключи там и не рассматриваются. Но, так как у меня база уже была, да мы и не ищем лёгких путей, то всё началось с «продвинутого» варианта: прикручивание внешней базы и её использование в проекте. Это не так уж сложно и информации о таком прикручивании и использовании много. Я же сейчас не делаю очередное описание «как настроить», я пишу о граблях в процессе настроек.
Подключения внешних баз.
В целом, всё очень хорошо написано в документации.
Единственно, очень надо не забыть добавить в settings.py секцию DATABASE_ROUTERS = [‘path.to.router’, ‘path.to.anotherrouter’,]. Учитывайте, что тут под path подразумевается имя файла и имя класса в котором описан роутинг.
Например, пусть файлик мы назвали db_router.py, положили его рядом с файлом manage.py и добавили в него следующее
apps = ['imgbase', 'show',]
db_name = 'imgshow'
"""
A router to control all database operations on models from applications in apps
"""
class DB_Router:
def db_for_read(self, model, **hints):
"""Point all operations on apps models to db_name"""
if model._meta.app_label in apps :
return db_name
return None
def db_for_write(self, model, **hints):
"""Point all operations on apps models to db_name"""
if model._meta.app_label in apps:
return db_name
return None
def allow_relation(self, obj1, obj2, **hints):
"""Allow any relation if a model in apps is involved"""
if obj1._meta.app_label in apps or obj2._meta.app_label in apps:
return True
return None
def allow_syncdb(self, db, model):
"""Make sure the apps only appears on the db_name db"""
if db == db_name:
return model._meta.app_label in apps
elif model._meta.app_label in apps:
return False
return None
Т.е. наш класс-обработчик — DB_router. Тогда в settings.py вставляем
DATABASE_ROUTERS = [
"db_router.DB_Router",
]
Ну, и для полноты мой вариант объявления DATABASES в settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': os.getenv('MYSQL_DATABASE'),
'USER': os.getenv('MYSQL_USER'),
'PASSWORD': os.getenv('MYSQL_PASSWORD'),
'HOST': os.getenv('MYSQL_HOST'),
'PORT': int(os.getenv('MYSQL_PORT'))
},
'imgshow': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'imgshow',
'USER': os.getenv('MYSQL_USER'),
'PASSWORD': os.getenv('MYSQL_PASSWORD'),
'HOST': os.getenv('MYSQL_HOST'),
'PORT': int(os.getenv('MYSQL_PORT'))
}
}
Лично у меня изначально вызвало небольшое затруднение заполнение DATABASE_ROUTERS. Сперва я за этот параметр вообще забыл (его нет в базовом settings.py), а потом разбирался что в нём должно быть прописано.
Нюансы описания моделей для работы с внешними базами.
Модель, это отображение таблиц БД в классы ООП. То есть, когда вы в файле models.py пишите, что-то типа
class Place_of_publication(models.Model):
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=40, verbose_name=u'Наименование', help_text=u'Название места публикации')
url = models.URLField(max_length=300, verbose_name=u'URL', help_text=u'URL-адрес')
description = models.CharField(max_length=1000, verbose_name=u'Описание', help_text=u'Описание')
то этим вы определяете а) название таблицы в базе (Place_of_publication) и б) поля в этой таблице (id, name, url, description). И если это внутренняя база, то django сам создаст таблицу с этим именем и этими полями.
Так вот, если у вас внешняя база и в ней есть ключевое поле с именем, например, composite_key, то в случае, если это поле используется для связи с другой таблицей и, соответственно, описывается через models.ForeignKey, то django будет настоятельно добавлять к названию этого поля ‘_id’!

Рисунок 1. Структура тестовой базы. Обратите внимание, что в таблицах Photos, Exif, Categories и Publication ранее использовались два ключевых поля (id и ID_dirname) из которых и делался составной ключ, а для проекта на django они были сведены в composite_key.
Например, в моей базе (см.рисунок 1) все поля, которые были сделаны через ADD CONSTRAINT `fk_name_1` FOREIGN KEY изначально носили имя composite_key. И при описании модели я указывал
composite_key = models.ForeignKey(Photo, on_delete=models.CASCADE)
Так вот, при использовании такой записи в таблице ссылающейся на ключевую (многие к одному), django искал в ней поле composite_key_id. Пришлось переименовывать это поле в таблицах Exif, Categories и Publication, чтобы django вздохнул спокойно.
При этом, если такое поле изначально называлось вроде Place_id, то при описании модели достаточно было написать
place = models.ForeignKey(Place_of_publication, on_delete=models.SET(None))
что бы всё работало.
А ещё я возможно напишу про использование шаблонизатора ninja с django.