
Django — это прекрасно. В самом деле: если писать о том, как много хорошего появилось в мире с приходом и развитием Django, то можно писать очень долго, и всё равно не рассказать обо всём. Лично я на данный момент постоянно использую Django уже четыре с половиной года, и всё это время Django становилась всё лучше и лучше.
И всё же в какой-то момент понимаешь, что в документации есть не всё. И тут варианты решения задач появляются разные — можно открыть исходники Django, можно посмотреть, что пишут по этому поводу на Stackoverflow и в других местах (часто очень помогают списки рассылки, а также официальный Trac), но ещё лучше — всё это вместе.
Давайте рассмотрим всего несколько примеров того, что может понадобиться в повседневной работе, но чего (пока что) не прочтёшь в официальной документации. Впрочем, если вы считаете, что нужно добавить ещё что-то — пишите, может быть, это действительно так.
1. Выбор всех объектов модели (когда объектов больше, чем помещается на одной странице) и действия административного интерфейса (admin actions) с промежуточными страницами (intermediate pages).
У вас в административном интерфейсе есть собственные действия? А в них используются промежуточные страницы? Если да, то проверьте, откуда именно берётся queryset при отображении этой самой промежуточной страницы.
Потому что если это делается так (как описано в документации):
selected = request.POST.getlist(admin.ACTION_CHECKBOX_NAME)
То вы столкнётесь с тем, что действие будет срабатывать, но только, например, для первых 100 объектов (если на странице их выводится 100), а не для всех объектов модели, как было выбрано.
На самом деле обычно на промежуточной странице выводится форма, которая содержит скрытые поля («_selected_action») с номерами объектов.
То есть (в ModelAdmin):
class AssignBooksForm(forms.Form):
_selected_action = forms.CharField(widget=forms.MultipleHiddenInput)
user = forms.ModelChoiceField(queryset=User.objects.all(), label=u"Пользователь", empty_label=None)
def assign_books(self, request, queryset):
form = None
if 'cancel' in request.POST:
self.message_user(request, u'Назначение книг отменено.')
return
elif 'assign' in request.POST:
form = self.AssignBooksForm(request.POST)
if form.is_valid():
user = form.cleaned_data['user']
for book in queryset:
assign_book(book.pk, user.pk)
self.message_user(request, u'Выбранные книги (%s) назначены для %s.' % (queryset.count(), user.username))
return HttpResponseRedirect(request.get_full_path())
if not form:
form = self.AssignBooksForm(initial={'_selected_action': queryset.values_list('os_id', flat=True)})
return render_to_response('books/assign_books.html', {'books': queryset, 'form': form, 'path':request.get_full_path()}, context_instance=RequestContext(request))
assign_books.short_description = u'Назначить книги пользователю'
actions = ['assign_books']
Но реально велика вероятность, что вместо такой строчки:
form = self.AssignBooksForm(initial={'_selected_action': queryset.values_list('id', flat=True)})
Такая строчка (например, тут):
form = self.AssignBooksForm(initial={'_selected_action': request.POST.getlist(admin.ACTION_CHECKBOX_NAME)})
И вот в этом случае получается особенно интересно. Потому что когда пользователь выбрал все объекты (отметил галочку слева от названий столбцов, и затем нажал «Выбрать все … (…)» над таблицей со списком записей), JS указал для скрытого поля «select_across» значение 1. После этого ModelAdmin.response_action (contrib.admin.options), обнаружив, что в форме (contrib.admin.helpers.ActionForm) задан select_across (BooleanField), не ограничивает queryset до тех объектов, которые выбраны галочками, а вызывает функцию действия с полным queryset'ом (но содержимое объекта request метод response_action, разумеется, не меняет).
Причём если у вас в шаблоне выводится что-нибудь такое:
<p>Для выбранного пользователя будут назначены следующие книги:</p>
<ul>
{% for book in books %}
<li>{{ book }}</li>
{% endfor %}
</ul>
То там список будет правильный (он будет содержать все объекты, а не только, например, первые 100) — потому что в контексте в шаблон в переменной books именно queryset. А вот в форме как раз может быть не список идентификаторов на основе queryset'а, а список отмеченных галочек. В этом случае при отправке формы с промежуточной страницы в queryset'е будет содержаться уже только 100 записей, и, соответственно, действие будет выполнено только с ними.
Кстати, про это есть обсуждение в комментариях к этой записи, а также тикет в Trac'е.
2. Журналирование в текстовые файлы с указанием даты и времени.
В общем-то, очень простая задача, но как это сделать в документации не указано. Тем не менее, там вскользь упоминается возможность задавать словарь formatters и даже даётся небольшой пример (правда, без даты и времени).
На самом деле, всё действительно просто:
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '[%(levelname)s] %(asctime)s %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S',
},
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler'
},
'books_log_file':{
'level': 'DEBUG',
'class': 'logging.FileHandler',
'formatter': 'verbose',
'filename': os.path.join(PROJECT_ROOT, 'logs/books.log'),
},
},
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
'books': {
'handlers': ['books_log_file', 'mail_admins'],
'level': 'INFO',
'propagate': True,
},
}
}
Тут, конечно же, предполагается, что выше в настройках устанавливается PROJECT_ROOT. Например, так:
import os
PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
А если требуется сделать как-то иначе, то можно обратиться к документации по Python.
3. Локализация сайта в случае, если оригинальные текстовые строки написаны не на английском.
Встроенная в Django система локализации, в общем-то, работает хорошо, но есть одна особенность. Дело в том, что если вы переводите JS-строки, и эти строки написаны не на английском (а, например, на русском), то как только вы создадите английский перевод (django-admin.py makemessages -l en -d djangojs) и скомпилируете djangojs.po, этот перевод начнёт отображаться не только когда выбран английский язык, но и когда выбран русский.
То есть вы выбираете русский язык, а JS выводит строки на английском (потому что не может найти русский перевод, и вместо него берёт английский — хотя в данном случае перевод реально не нужен, так как оригиналы строк уже на русском).
Решение (если, конечно, вы сразу отказались от идеи создать русский пере��од русского же текста) тут вполне простое.
Нужно добавить в urls.py такой pattern:
url(r'^jsi18n/null/$', 'django.views.i18n.null_javascript_catalog'),И отредактировать шаблон (обычно base.html), добавив туда:
{% if request.LANGUAGE_CODE == 'ru' %}
<script type="text/javascript" src="{% url django.views.i18n.null_javascript_catalog %}"></script>
{% else %}
<script type="text/javascript" src="{% url django.views.i18n.javascript_catalog %}"></script>
{% endif %}
Предполагается, что urlpattern для django.views.i18n.javascript_catalog уже есть, конечно же.
4. Добавление простых страниц (flatpages) без указания сайтов.
В текущей стабильной версии Django есть один баг: если у вас подключено приложение flatpages, и вы, создавая такую страницу, случайно забудете выбрать сайты из списка, то вы получите ошибку 500, или, если включён DEBUG:
ValueError at /admin/flatpages/flatpage/add/
Cannot use None as a query value
Разумеется, об этой ошибке уже известно разработчикам — более того, она была исправлена три месяца назад. Собственно, в коммите, который это исправляет, внесены изменения всего в одну строчку (плюс тест), так что вы легко можете пропатчить Django у себя. В stable он пока не попал (в том числе в релиз Django 1.4.1, который был пару дней назад)
В общем, пользуйтесь поиском, проводите достаточно детальный сбор данных, тестируйте. Если даже какое-то решение на вид рабочее, то всё равно имейте в виду, что везде могут быть свои нюансы. Приятной разработки!