Перенос ваших приложений с Django 0.96 на 1.0

Django 1.0 нарушает совместимость с 0.96 в некоторых областях.

Это руководство поможет вам перенести проекты и приложения версии 0.96 на версию 1.0. Первая часть этого документа включает общие изменения, необходимые для работы с 1.0. Если после изучения первой части ваш код все еще ломается, обратитесь к разделу Less-common Changes, где приведен список менее распространенных проблем совместимости.

См.также

1.0 release notes. Этот документ более глубоко объясняет новые возможности версии 1.0; руководство по переносу больше направлено на то, чтобы помочь вам быстро обновить ваш код.

Общие изменения

В этом разделе описаны изменения между 0.96 и 1.0, которые потребуется внести большинству пользователей.

Использовать Юникод

Измените строковые литералы ('foo') на литералы Unicode (u'foo'). Django теперь использует строки Unicode повсеместно. В большинстве мест необработанные строки будут продолжать работать, но обновление на использование литералов Unicode предотвратит некоторые неясные проблемы.

Подробную информацию см. в разделе Данные Юникода.

Модели

Общие изменения в файле моделей:

Переименуйте maxlength в max_length

Переименуйте аргумент maxlength в max_length (он был изменен для соответствия полям формы):

Замените __str__ на __unicode__

Замените функцию __str__ вашей модели на метод __unicode__, и убедитесь, что вы use Unicode (u'foo') в этом методе.

Удалить prepopulated_from

Удалите аргумент prepopulated_from для полей модели. Он больше не действителен и был перемещен в класс ModelAdmin в admin.py. Более подробно об изменениях в админке смотрите the admin, ниже.

Удалить core

Удалите аргумент core из полей вашей модели. В нем больше нет необходимости, поскольку эквивалентная функциональность (часть inline editing) теперь по-другому обрабатывается интерфейсом администратора. Вам не нужно беспокоиться о встроенном редактировании, пока вы не перейдете к разделу the admin, ниже. Пока же удалите все ссылки на core.

Замените class Admin: на admin.py

Удалите все внутренние объявления class Admin из ваших моделей. Они ничего не сломают, если вы их оставите, но они также ничего не сделают. Чтобы зарегистрировать приложения в администраторе, вы перенесете эти объявления в файл admin.py; подробнее см. ниже the admin.

См.также

Автор djangosnippets написал сценарий, который scan your models.py and generate a corresponding admin.py.

Пример

Ниже приведен пример файла models.py со всеми изменениями, которые вам нужно будет внести:

Старый (0.96) models.py:

class Author(models.Model):
    first_name = models.CharField(maxlength=30)
    last_name = models.CharField(maxlength=30)
    slug = models.CharField(maxlength=60, prepopulate_from=('first_name', 'last_name'))

    class Admin:
        list_display = ['first_name', 'last_name']

    def __str__(self):
        return '%s %s' % (self.first_name, self.last_name)

Новый (1.0) models.py:

class Author(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    slug = models.CharField(max_length=60)

    def __unicode__(self):
        return u'%s %s' % (self.first_name, self.last_name)

Новый (1.0) admin.py:

from django.contrib import admin
from models import Author

class AuthorAdmin(admin.ModelAdmin):
    list_display = ['first_name', 'last_name']
    prepopulated_fields = {
        'slug': ('first_name', 'last_name')
    }

admin.site.register(Author, AuthorAdmin)

Администратор

Одним из самых больших изменений в версии 1.0 является новый администратор. Административный интерфейс Django (django.contrib.admin) был полностью переработан; определения администратора теперь полностью отделены от определений модели, фреймворк был переписан для использования новой библиотеки обработки форм Django и переработан с учетом расширяемости и настройки.

Практически это означает, что вам придется переписать все ваши объявления class Admin. Вы уже видели в models выше, как заменить class Admin на admin.site.register() вызов в admin.py файле. Ниже приведены некоторые подробности о том, как переписать это объявление Admin в новом синтаксисе.

Используйте новый встроенный синтаксис

Все новые опции edit_inline были перенесены в admin.py. Вот пример:

Старый (0,96):

class Parent(models.Model):
    ...

class Child(models.Model):
    parent = models.ForeignKey(Parent, edit_inline=models.STACKED, num_in_admin=3)

Новый (1.0):

class ChildInline(admin.StackedInline):
    model = Child
    extra = 3

class ParentAdmin(admin.ModelAdmin):
    model = Parent
    inlines = [ChildInline]

admin.site.register(Parent, ParentAdmin)

Более подробную информацию см. в разделе InlineModelAdmin объекты.

Упростите fields, или используйте fieldsets

Старый синтаксис fields был довольно запутанным, и теперь он упрощен. Старый синтаксис по-прежнему работает, но вместо него нужно использовать fieldsets.

Старый (0,96):

class ModelOne(models.Model):
    ...

    class Admin:
        fields = (
            (None, {'fields': ('foo','bar')}),
        )

class ModelTwo(models.Model):
    ...

    class Admin:
        fields = (
            ('group1', {'fields': ('foo','bar'),   'classes': 'collapse'}),
            ('group2', {'fields': ('spam','eggs'), 'classes': 'collapse wide'}),
        )

Новый (1.0):

class ModelOneAdmin(admin.ModelAdmin):
    fields = ('foo', 'bar')

class ModelTwoAdmin(admin.ModelAdmin):
    fieldsets = (
        ('group1', {'fields': ('foo','bar'),   'classes': 'collapse'}),
        ('group2', {'fields': ('spam','eggs'), 'classes': 'collapse wide'}),
    )

См.также

  • Более подробную информацию об изменениях и их причинах можно найти на сайте NewformsAdminBranch wiki page
  • Новый администратор поставляется с тонной новых возможностей; вы можете прочитать о них в admin documentation.

URLs

Обновите свой корень urls.py

Если вы используете админку сайта, вам нужно обновить корень urls.py.

Старый (0.96) urls.py:

from django.conf.urls.defaults import *

urlpatterns = patterns('',
    (r'^admin/', include('django.contrib.admin.urls')),

    # ... the rest of your URLs here ...
)

Новый (1.0) urls.py:

from django.conf.urls.defaults import *

# The next two lines enable the admin and load each admin.py file:
from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    (r'^admin/(.*)', admin.site.root),

    # ... the rest of your URLs here ...
)

Представления

Используйте django.forms вместо newforms

Замените django.newforms на django.forms – Django 1.0 переименовал модуль newforms (представленный в 0.96) в обычный старый forms. Модуль oldforms также был удален.

Если вы уже используете библиотеку newforms, и вы использовали рекомендуемый нами синтаксис оператора import, все, что вам нужно сделать, это изменить свои операторы импорта.

Старый:

from django import newforms as forms

Новое:

from django import forms

Если вы используете старую систему форм (ранее известную как django.forms и django.oldforms), вам придется переписать свои формы. Хорошее место для начала - forms documentation

Работа с загруженными файлами с помощью нового API

Замените использование загруженных файлов - то есть записей в request.FILES - в качестве простых словарей новым UploadedFile. Старый синтаксис словарей больше не работает.

Таким образом, в таком представлении, как:

def my_view(request):
    f = request.FILES['file_field_name']
    ...

…вам нужно внести следующие изменения:

Старый (0,96) Новый (1.0)
f['content'] f.read()
f['filename'] f.name
f['content-type'] f.content_type

Работа с полями файлов с помощью нового API

Внутренняя реализация django.db.models.FileField изменилась. Видимым результатом этого является изменение способа доступа к специальным атрибутам (URL, имя файла, размер изображения и т.д.) этих полей модели. Вам нужно будет внести следующие изменения, если предположить, что FileField вашей модели называется myfile:

Старый (0,96) Новый (1.0)
myfile.get_content_filename() myfile.content.path
myfile.get_content_url() myfile.content.url
myfile.get_content_size() myfile.content.size
myfile.save_content_file() myfile.content.save()
myfile.get_content_width() myfile.content.width
myfile.get_content_height() myfile.content.height

Обратите внимание, что атрибуты width и height имеют смысл только для полей ImageField. Более подробную информацию можно найти в документации по model API.

Используйте Paginator вместо ObjectPaginator

ObjectPaginator в версии 0.96 был удален и заменен улучшенной версией django.core.paginator.Paginator.

Шаблоны

Научитесь любить автоэскейп

По умолчанию система шаблонов теперь автоматически выводит HTML-эскейп каждой переменной. Чтобы узнать больше, смотрите Автоматическое экранирование HTML.

Чтобы отключить автозавершение для отдельной переменной, используйте фильтр safe:

This will be escaped: {{ data }}
This will not be escaped: {{ data|safe }}

Чтобы отключить автозавершение для всего шаблона, оберните шаблон (или только определенный раздел шаблона) в тег autoescape:

{% autoescape off %}
   ... unescaped template content here ...
{% endautoescape %}

Менее распространенные изменения

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

Сигналы

  • Добавьте **kwargs ко всем зарегистрированным обработчикам сигналов.
  • Подключение, отключение и отправка сигналов через методы на объекте Signal вместо методов модуля в django.dispatch.dispatcher.
  • Удалите любое использование опций отправителя Anonymous и Any; они больше не существуют. Вы все еще можете получать сигналы, посылаемые любым отправителем, используя sender=None
  • Сделайте все объявленные вами пользовательские сигналы экземплярами django.dispatch.Signal вместо анонимных объектов.

Вот краткое описание изменений в коде, которые вам нужно будет сделать:

Старый (0,96) Новый (1.0)
def callback(sender) def callback(sender, **kwargs)
sig = object() sig = django.dispatch.Signal()
dispatcher.connect(callback, sig) sig.connect(callback)
dispatcher.send(sig, sender) sig.send(sender)
dispatcher.connect(callback, sig, sender=Any) sig.connect(callback, sender=None)

Коментарі

Если вы использовали приложение django.contrib.comments в Django 0.96, вам необходимо перейти на новое приложение комментариев, представленное в 1.0. Подробности смотрите в руководстве по обновлению.

Теги шаблона

spaceless тег

Тег шаблона spaceless теперь удаляет все пробелы между HTML-тегами, вместо того, чтобы сохранять один пробел.

Местные ароматы

Местный колорит США

django.contrib.localflavor.usa был переименован в django.contrib.localflavor.us. Это изменение было сделано для того, чтобы соответствовать схеме именования других локальных вкусов. Чтобы перенести ваш код, достаточно изменить импорты.

Сессии

Получение нового ключа сеанса

SessionBase.get_new_session_key() был переименован в _get_new_session_key(). get_new_session_object() больше не существует.

Приспособления

Загрузка строки больше не вызывает save()

Ранее загрузка строки автоматически запускала метод save() модели. Теперь это не так, поэтому любые поля (например, метки времени), которые автоматически заполнялись методом save(), теперь нуждаются в явных значениях в любом приспособлении.

Настройки

Улучшенные исключения

Старый EnvironmentError разделился на ImportError, когда Django не может найти модуль настроек, и RuntimeError, когда вы пытаетесь перенастроить настройки после того, как они уже были использованы.

LOGIN_URL переехал

Константа LOGIN_URL переместилась из django.contrib.auth в модуль settings. Вместо использования from django.contrib.auth import LOGIN_URL обратитесь к settings.LOGIN_URL.

APPEND_SLASH поведение было обновлено

В версии 0.96, если URL не заканчивался косой чертой или точкой в последнем компоненте пути, а APPEND_SLASH был равен True, Django перенаправлял на тот же URL, но с косой чертой в конце. Теперь Django проверяет, соответствует ли шаблон без косой черты чему-то из ваших шаблонов URL. Если да, то перенаправления не происходит, так как предполагается, что вы намеренно хотели поймать этот шаблон.

Для большинства людей это не потребует никаких изменений. Однако у некоторых людей шаблоны URL выглядят следующим образом:

r'/some_prefix/(.*)$'

Ранее эти шаблоны перенаправлялись с использованием косой черты. Если вы хотите, чтобы в таких URL всегда присутствовала косая черта, перепишите шаблон как:

r'/some_prefix/(.*/)$'

Небольшие изменения в моделях

Исключение, отличное от get()

Менеджеры теперь возвращают исключение MultipleObjectsReturned вместо AssertionError:

Старый (0,96):

try:
    Model.objects.get(...)
except AssertionError:
    handle_the_error()

Новый (1.0):

try:
    Model.objects.get(...)
except Model.MultipleObjectsReturned:
    handle_the_error()

LazyDate был уволен

Класс-помощник LazyDate больше не существует.

Значения полей по умолчанию и аргументы запроса могут быть вызываемыми объектами, поэтому экземпляры LazyDate могут быть заменены ссылкой на datetime.datetime.now:

Старый (0,96):

class Article(models.Model):
    title = models.CharField(maxlength=100)
    published = models.DateField(default=LazyDate())

Новый (1.0):

import datetime

class Article(models.Model):
    title = models.CharField(max_length=100)
    published = models.DateField(default=datetime.datetime.now)

DecimalField является новым, а FloatField теперь является правильным float

Старый (0,96):

class MyModel(models.Model):
    field_name = models.FloatField(max_digits=10, decimal_places=3)
    ...

Новый (1.0):

class MyModel(models.Model):
    field_name = models.DecimalField(max_digits=10, decimal_places=3)
    ...

Если вы забудете сделать это изменение, вы увидите ошибки о том, что FloatField не принимает атрибут max_digits в __init__, потому что новый FloatField не принимает аргументы, связанные с точностью.

Если вы используете MySQL или PostgreSQL, никаких дополнительных изменений не требуется. Типы столбцов базы данных для DecimalField такие же, как и для старого FloatField.

Если вы используете SQLite, вам необходимо заставить базу данных отображать соответствующие столбцы как десятичные типы, а не как плавающие. Для этого необходимо перезагрузить данные. Сделайте это после того, как вы перейдете на использование DecimalField в своем коде и обновите код Django.

Предупреждение

Сначала создайте резервную копию базы данных!

Для SQLite это означает создание копии единственного файла, в котором хранится база данных (имя этого файла - DATABASE_NAME в вашем файле settings.py).

Чтобы перевести каждое приложение на использование DecimalField, вы можете сделать следующее, заменив <app> в приведенном ниже коде на имя каждого приложения:

$ ./manage.py dumpdata --format=xml <app> > data-dump.xml
$ ./manage.py reset <app>
$ ./manage.py loaddata data-dump.xml

Примечания:

  1. Важно, чтобы вы не забыли использовать формат XML на первом этапе этого процесса. Мы используем особенность дампов данных XML, которая делает возможным перенос плавающих чисел в десятичные с помощью SQLite.
  2. На втором этапе вам будет предложено подтвердить, что вы готовы потерять данные для данного приложения (приложений). Скажите «да»; мы восстановим эти данные на третьем шаге.
  3. DecimalField не используется ни в одном из приложений, поставляемых с Django до внесения этого изменения, поэтому вам не нужно беспокоиться о выполнении этой процедуры для любой из стандартных моделей Django.

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

Интернационализация

django.views.i18n.set_language() теперь требует POST-запрос

Ранее использовался запрос GET. Старое поведение означало, что state (локаль, используемая для отображения сайта) может быть изменена запросом GET, что противоречит рекомендациям спецификации HTTP. Код, вызывающий это представление, должен убедиться, что теперь выполняется POST-запрос, а не GET. Это означает, что вы больше не можете использовать ссылку для доступа к представлению, а должны использовать форму отправки какого-либо запроса (например, кнопку).

_() больше нет во встроенных модулях

_() (вызываемый объект, имя которого состоит из одного символа подчеркивания) больше не встраивается во встроенные модули - то есть, он больше не доступен волшебным образом в каждом модуле.

Если вы ранее полагались на то, что _() всегда присутствует, то теперь вам следует явно импортировать ugettext или ugettext_lazy, если это уместно, и псевдоним _ самостоятельно:

from django.utils.translation import ugettext as _

Объекты запросов/ответов HTTP

Доступ к словарю HttpRequest

Объекты HttpRequest больше не поддерживают прямой доступ в стиле словаря; ранее данные GET и POST были доступны непосредственно на объекте HttpRequest (например, вы могли проверить наличие части данных формы с помощью if 'some_form_key' in request или путем чтения request['some_form_key']. Это больше не поддерживается; если вам нужен доступ к комбинированным данным GET и POST, используйте вместо этого request.REQUEST.

Однако настоятельно рекомендуется всегда явно искать в соответствующем словаре тип запроса, который вы ожидаете получить (request.GET или request.POST); полагаясь на комбинированный словарь request.REQUEST, можно скрыть происхождение входящих данных.

Доступ к заголовкам HTTPResponse

django.http.HttpResponse.headers был переименован в _headers, а HttpResponse теперь поддерживает проверку содержимого напрямую. Поэтому используйте if header in response: вместо if header in response.headers:.

Общие отношения

Общие отношения были перенесены из ядра

Классы общих отношений – GenericForeignKey и GenericRelation – переместились в модуль django.contrib.contenttypes.

Тестирование

django.test.Client.login() изменилось

Старый (0,96):

from django.test import Client
c = Client()
c.login('/path/to/login','myuser','mypassword')

Новый (1.0):

# ... same as above, but then:
c.login(username='myuser', password='mypassword')

Команды управления

Выполнение команд управления из вашего кода

django.core.management был значительно переработан.

Вызовы служб управления в вашем коде теперь должны использовать call_command. Например, если у вас есть тестовый код, который вызывает flush и load_data:

from django.core import management
management.flush(verbosity=0, interactive=False)
management.load_data(['test_data'], verbosity=0)

…вам нужно изменить этот код следующим образом:

from django.core import management
management.call_command('flush', verbosity=0, interactive=False)
management.call_command('loaddata', 'test_data', verbosity=0)

Теперь подкоманды должны предшествовать опциям

django-admin.py и manage.py теперь требуют, чтобы подкоманды предшествовали опциям. Таким образом:

$ django-admin.py --settings=foo.bar runserver

…больше не работает и должен быть заменен на:

$ django-admin.py runserver --settings=foo.bar

Синдикация

Feed.__init__ изменилось

Метод __init__() класса Feed фреймворка синдикации теперь принимает объект HttpRequest в качестве второго параметра, вместо URL фида. Это позволяет фреймворку синдикации работать без использования фреймворка сайтов. Это влияет только на код, который является подклассом Feed и переопределяет метод __init__(), а также на код, который вызывает Feed.__init__() напрямую.

Структуры данных

SortedDictFromList исчезла

django.newforms.forms.SortedDictFromList был удален. django.utils.datastructures.SortedDict теперь может быть инстанцирован последовательностью кортежей.

Чтобы обновить свой код:

  1. Используйте django.utils.datastructures.SortedDict везде, где вы использовали django.newforms.forms.SortedDictFromList.
  2. Поскольку django.utils.datastructures.SortedDict.copy не возвращает глубокое копирование, как это делает SortedDictFromList.copy(), вам нужно будет обновить свой код, если вы полагались на глубокое копирование. Сделайте это, используя copy.deepcopy напрямую.

Функции бэкенда базы данных

Функции бэкенда базы данных были переименованы

Почти все функции уровня бэкенда базы данных были переименованы и/или перемещены. Ни одна из них не была документирована, но вам придется изменить свой код, если вы используете какую-либо из этих функций, все они находятся в django.db:

Старый (0,96) Новый (1.0)
backend.get_autoinc_sql connection.ops.autoinc_sql
backend.get_date_extract_sql connection.ops.date_extract_sql
backend.get_date_trunc_sql connection.ops.date_trunc_sql
backend.get_datetime_cast_sql connection.ops.datetime_cast_sql
backend.get_deferrable_sql connection.ops.deferrable_sql
backend.get_drop_foreignkey_sql connection.ops.drop_foreignkey_sql
backend.get_fulltext_search_sql connection.ops.fulltext_search_sql
backend.get_last_insert_id connection.ops.last_insert_id
backend.get_limit_offset_sql connection.ops.limit_offset_sql
backend.get_max_name_length connection.ops.max_name_length
backend.get_pk_default_value connection.ops.pk_default_value
backend.get_random_function_sql connection.ops.random_function_sql
backend.get_sql_flush connection.ops.sql_flush
backend.get_sql_sequence_reset connection.ops.sequence_reset_sql
backend.get_start_transaction_sql connection.ops.start_transaction_sql
backend.get_tablespace_sql connection.ops.tablespace_sql
backend.quote_name connection.ops.quote_name
backend.get_query_set_class connection.ops.query_set_class
backend.get_field_cast_sql connection.ops.field_cast_sql
backend.get_drop_sequence connection.ops.drop_sequence_sql
backend.OPERATOR_MAPPING connection.operators
backend.allows_group_by_ordinal connection.features.allows_group_by_ordinal
backend.allows_unique_and_pk connection.features.allows_unique_and_pk
backend.autoindexes_primary_keys connection.features.autoindexes_primary_keys
backend.needs_datetime_string_cast connection.features.needs_datetime_string_cast
backend.needs_upper_for_iops connection.features.needs_upper_for_iops
backend.supports_constraints connection.features.supports_constraints
backend.supports_tablespaces connection.features.supports_tablespaces
backend.uses_case_insensitive_names connection.features.uses_case_insensitive_names
backend.uses_custom_queryset connection.features.uses_custom_queryset
Вернуться на верх