Django: GenericForeignKey — ограничить выбор типов

Django позволяет создавать очень гибкие связи между моделями посредством встроенного приложения contenttypes. Эта функциональность является надстройкой над иерархией приложение-модель и позволяет создавать связь модели с другими моделям разного типа.

Реализуется так:

class MyModel(models.Model):
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
    ...

По сути такая схема создает ключ из двух полей — тип модели и ID объекта этого типа.

А вот так можно ограничить возможность выбора типа модели:

class MyModel(models.Model):
    limit = models.Q(app_label='app1', model='model1') | models.Q(app_label='app2', model='model2')
    content_type = models.ForeignKey(ContentType, limit_choices_to=limit)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
    ...

Внимание! Аргументом model конструктора объекта Q должно быть имя модели, приведенное к нижнему регистру, т.е. ‘somemodel’ для модели SomeModel, соответственно.

Django: генерация SECRET_KEY

Частенько при настройке и запуске Django возникает подобная ошибка:

The SECRET_KEY setting must not be empty.

Это механизм проверки конфигурации Джанги ругается, что вы забыли указать SECRET_KEY в локальных настройках. А где взять это значение? Можно натарабанить по клаве самому, можно скопировать из базового модуля настроек, созданного в самом начале при startproject. Но правильно сгенерировать уникальное для данной инсталяции значение так, как это делается в startproject.

from django.utils.crypto import get_random_string

chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
get_random_string(50, chars)

И следите, чтобы SECRET_KEY ваших активных инсталяций не утекал в публичные репозитории или листинги.

Django: как получить значение по-умолчанию поля модели

Допустим, в Django приложение существует следующая модель:

class MyModel(models.Model):
    my_field = models.IntegerField(u"My field", default=0)

Тогда сослаться на значение по-умолчанию поля my_field можно так:

my_field_default_value = MyModel._meta.get_field('my_field').get_default()

UPD: Начиная с версии Django 1.8, устоявшаяся практика использования _meta стала абсолютно легальной. Теперь есть документированное API.

Django: автодополнение manage.py и django-admin.py

Случайно открыл, что репозиторий Django помимо самого фреймворка содержит директорию extra со вспомогательными скриптами. В последних стабильных релизах там находится только скрипт автодополнения команд manage.py и django-admin.py. Чтобы воспользоваться этим скриптом, нужно сохранить его куда-нибудь в укромное место в домашней директории и добавить в свой .bashrc строку:

. ~/укромное/место/django_bash_completion

И та-дам! Не нужно больше каждый раз вбивать в консоли ./manage.py makemigrations или что еще похлеще.

Django: загрузка FileField и ImageField из файловой системы

Не знаю как вы, а я чаще стал сталкиваться с ситуацией, когда мне нужно программно загрузить файл из локальной файловой системы или с помощью удаленного URL в модель Django. Это может понадобиться в контексте работы в оболочке Python, исполнения отдельного скрипта или команды управления Django.

В типичном веб-приложении такая задача встречается нечасто. Я заметил, что мы это делаем, когда переносим сайт с какой-то другой технологии на Django, и привязываем изображение к некой контентной сущности.

Если вы поищете ответ как сделать это в Google, то обнаружите документацию Django File Uploads, File objects и Managing Files, которые сами по себе отличные. Тем не менее они не содержат последовательного руководства для решения нашей задачи.

Первый порыв — присвоить полю модели путь к файлу, но это ведет к потере информации об относительном пути к файлу, в которой нуждается Django.

Я выделил время, чтобы написать этот пост в основном для собственного блага, чтобы не искать заново решение в будущем. Но, надеюсь, вы также получите некоторую пользу.

Этапы загрузки локального файла в Django ImageField

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

  1. Получить файл (если он удаленный) и сохранить его локально
  2. Открыть файл как обычный объект файла Python
  3. Конвертировать открытый файл в Django FIle
  4. Привязать его к полю вашей модели

Пример модели

Предположим, у нас есть модель вроде этой:

from django.db import models

class Company(models.Model):
    name = models.CharField(max_length=100) 
    logo = models.ImageField()

Давайте создадим запись для RevSys, скачав логотип с помощью библиотеки requests:

import requests
from django.core.files import File

from .models import Company

r = requests.get(“http://media.revsys.com/img/revsys-logo.png”)

with open(“/tmp/revsys-logo.png”, “wb”) as f:
    f.write(r.content)

reopen = open(“/tmp/revsys-logo.png”, “rb”)
django_file = File(reopen)

revsys = Company()
revsys.name = “Revolution Systems”
revsys.logo.save(“revsys-logo.png”, django_file, save=True)

Последняя строка здесь самая важная. Методу save передается три аргумента:

  1. Относительный путь и имя файла внутри MEDIA_ROOT.
  2. Файл, открытый с помощью File объекта Django.
  3. Логическое значение, показывающее хотим ли мы сохранить экземпляр revsys модели Company после того, как изображение сохранено.

Иногда, если вы работаете с набором изображений, вам требуется распарсить URL или пути к файлам для извлечения имен файлов. Нижеследующий код послужит отправной точкой. Обратите внимание, что тут используется Python 3:

import os 
from urllib.parse import urlparse

# from urlparse import urlparse for Python 2.7

url = “http://media.revsys.com/img/revsys-logo.png” 
filename = os.path.basename(urlparse(url).path)

# This returns ‘revsys-logo.png’ from the URL

…

revsys.logo.save(filename, django_file, save=True)`

Надеюсь, это вам поможет!

Эта статья перевод. Оригинал: Loading Django FileField and ImageFields from the file system

Django — ошибка во время миграции

Во время миграции схемы базы данных для модели, в которую добавлено ForeignKey поле, может возникнуть ошибка вида:

django.db.utils.OperationalError: (1005, "Can't create table '...' (errno: 150)")

Это происходит из-за разных типов хранилищ таблиц (MyISAM/InnoDB). В таком случае MySQL не может корректно создаться индекс по внешнему ключу.

Следует руками привести обе таблицы к типу хранилища InnoDB, сделав ALTER TABLE, и повторить миграцию.

Чеклисты

Я стартовал новый проект Чеклисты.

Чеклисты — незаменимый инструмент микропланирования и контроля, широко используемый как профессионалами в разнообразных областях, так и в быту.

Под капотом Django, обивка Bootsrap, рулевая jQuery.

Django под Windows — рецепт солянки

Настройка Linux среды Python/Django разработки под Windows с помощью VirtualBox и PyCharm на примере Debian

Ингридиенты

  1. Ноутбук с предустановленной Windows 8.1: 1,5 кг
  2. VirtualBox: 1 литр
  3. Debian: 0.5 тушки (можно заменить на целую тушку Ubuntu или другой Linux, который есть в вашем магазине, но с Debian наваристее)
  4. VirtualBox Guest Addition: 1 горсть
  5. PyCharm: 1 луковица
  6. deb-пакеты: 5-10 пакетов
  7. Python модули: все, что у вас есть в холодильнике
  8. Эстетическое неприятие дуалбут и необходимость работы с Photoshop: по вкусу

Время приготовления: 2 суток.

Установить VirtualBox

  1. Скачать и установить основной дистрибутив VirtualBox.
  2. Скачать и установить пакет дополнений гостевой ОС (см. вставку в соответствующем пункте).

Установить Debian 7

Проще всего использовать netinst образ соответствующий архитектуре вашей системы. Варить около получаса. Не пересаливать предустановленными пакетами.

Установить гостевые дополнения в VirtualBox

Внимание! При использовании версии дополнений 4.3.10 возникает ошибка во время монтирования общих папок! https://www.virtualbox.org/ticket/12879 Скачать образ дополнений 4.3.11 https://www.virtualbox.org/download/testcase/VBoxGuestAdditions_4.3.11-93070.iso

  1. Подключить скачанный образ. Устройства — Приводы оптических дисков — Выбрать образ оптического диска.
  2. Смонтировать cdrom

    # mount /dev/sr0 /media/cdrom
    
  3. Установить make, gcc, bzip2

    # apt-get install make gcc bzip2
    
  4. Запустить run скрипт с диска

    # cd /media/cdrom
    # ./VBoxLinuxAdditions.run
    

Настроить общие папки

Общие папки очень удобны для разработки, позволяя избежать загрузки файлов по FTP в гостевую ОС и исполнять код прямо из папки проекта хостящей ОС.

  1. Добавить новую общую папку. НастройкиОбщие папкиДобавить. Указать путь (например E:\Dev\django) к папке и ее имя (например, django). Поставить галку возле Создать постоянную папку.
  2. Примонтировать устройство в гостевой ОС

    # mount -t vboxsf django /home/myuser/django -o rw,dmode=775,uid=1000,gid=1000
    
  3. Если папка появилась в нужной точке в необходимом виде, то можно добавить правило в /etc/fstab для автоматического монтирования при старте системы.

    django /home/myuser/django vboxsf rw,dmode=775,uid=1000,gid=1000 0 0
    

    ВНИМАНИЕ! Не допускай опечатку в слове vboxsf!

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

  1. Python

    # apt-get install python-dev
    
  2. MySQL

    # apt-get install mysql-server mysql-client python-mysqldb libmysqlclient-dev
    
  3. PIL

    # apt-get install python-imaging
    
  4. Модули Python

    # apt-get install python-pip
    # pip install -U pip
    # pip install virtualenv
    

Настроить SSH доступ к гостевой ОС

  1. Установить OpenSSH сервер.

    # apt-get install openssh-server
    
  2. Пробросить произвольный порт хостящей ОС на 22 порт гостевой ОС. УстройстваСетьНастроить сеть. По умолчанию тут уже настроен тип подключения NAT. Зайти в раздел Проброс портов и настроить запись вида: имя — ssh, порт хоста — например, 3022, порт гостя — 22. Настроить подключение в Putty к 127.0.0.1:3022.

Установить и настроить MySQL Workbench

  1. Скачать
  2. Настроить соединение по SSH через проброшенный порт и гостевого пользователя
  3. Использовать по назначению

Настроить проект в PyCharm

  1. Настроить удаленный интерпретатор
  2. Установить python-зависимости
  3. Добавить необходимую запись в C:\Windows\System32\drivers\etc\hosts хоста
  4. Добавить необходимую запись в /etc/hosts гостевой ОС
  5. Пробросить порт с хоста (например, 8800) на 8000 порт гостевой ОС
  6. Настроить конфигурацию запуска dev-сервера в PyCharm. Задать отображение (меппинг) локальной рабочей директории на удаленную, чтобы удаленный интерпретатор обнаружил manage.py скрипт. В качестве хоста указать 0.0.0.0 для того, чтобы dev-сервер отвечал на любом сетевом интерфейсе. Порт оставить 8000.

Установить дополнительно

  1. 4t Tray Minimizer — позволяет сворачивать произвольную программу в системный трей, чтобы убрать раздражающее «залипание» на VirtualBox при Alt+Tab переключении.
  2. Установить PostgreSQL и инструменты для работы с ней аналогично MySQL.

Побочные эффекты

  1. В данный момент в PyCharm (2.7) недоступны пункты меню Run Django conaole и Run manage.py task при использовании удаленного интерпретатора — тикет в трекере.

Проблемы при деплое Django на виртуальный хостинг на примере sweb.ru

Продолжаю собирать шишки во время деплоя Django на виртуальный хостинг. Пока имею дело с хостерами рунета. Скучать не приходится. Каждый новый хостер — новая проблема.
Итак, Spaceweb открывает соединение с базой MySQL в национальной кодировке cp1251, а не толерантной utf-8. Это приводит к выбросу исключения вида «codec can’t decode byte…» где-то в недрах модуля MySQLdb.

Я расцениваю это исключительно как оголтелое проявление национализма и разжигание розни на национальной почве.

Лечится так


DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'xxx',
        'USER': 'xxx',
        'PASSWORD': 'xxx',
        'HOST': '',
        'PORT': '',
        'OPTIONS': {
            'init_command': 'set names utf8',
        },
    }
}

Проблемы при деплое Django на виртуальный хостинг на примере jino.ru

Деплой Django на виртуальный хостинг для меня частенько превращается в квест. Это происходит из-за большого количества зависимостей Django от программного системного окружения. Но беда даже не в их количестве, а в том, что существует несколько способов их разрешения. Это красота и боль UNIX-way, воплощенная в python.

Одним из инструментов, призванных облегчить работу в этом хаосе, является виртуальное Python-окружение virtualenv. Да, использование virtualenv безусловно полезная практика, но virtualenv не решает всех проблем. Некоторые python-модули написаны на C и требуют компиляции при установке, а также имеют зависимости от -dev версий системных библиотек. Такие зависимости невозможно разрешить самостоятельно, имея лишь права пользователя виртуального хостинга. Админы же хостинга не всегда считают нужным об этом позаботиться, а времени на переписку с саппортом частенько нет. Один из таких хостингов jino.ru.

Яркий пример таких «проблемных» пакетов жизненно необходимые в большинстве Django проектов mysql-python (MySQLdb) и PIL. Проблему можно попробовать решить, используя ключ —system-site-packages при создании виртуального окружения. И тогда, если тебе немного повезет, в системе уже будут установлены эти пакеты не очень древних версий. Обрати внимание, что поведение по умолчанию в отношении установки системных пакетов у virtualenv менялось с течением времени. Поэтому либо качай и используй свежую версию virtualenv, либо предварительно сверься с документацией к используемой версии.

В моем случае ситуацию дополнительно усложняло то, что я использую более свежий форк PIL-а Pillow, так как PIL уже много лет как заброшен своими мейнтейнерами и не умеет работать с новыми форматами изображений. А Pillow для обратной совместимости маскируется под PIL и импортируется в скрипты под его псевдонимом, что избавляет от правки исходников своих и сторонних пакетов. Таким образом, при наличии PIL в окружении (один из системных пакетов), становится невозможной работа с Pillow.

В случае с Джино, я попал в следующую ситуацию: с одной стороны мне нужны системные пакеты в virtualenv из-за MySQLdb, а с другой нет — из-за PIL. Возможности выборочной установки или деинсталяции системных пакетов в virtualenv на данный момент нет. Проблему решил хладнокровным удалением PIL из myenv/lib/pythonx.x/site-packages.