воскресенье, 27 ноября 2011 г.

Django: пишем свой менеджер модели


Недавно натолкнулся на часто востребованную операцию "получить следующее по дате" в моих Django-моделях. Встроенный менеджер objects имеет прекрасный метод latest. Симметричного ему , что-то типа next, я не нашел (может плохо искал).
И так как сейчас я как раз в самом разгаре прокачки django-скиллов (подобно новому персонажу в diablo , когда ну очень много нового и очень быстро происходят level-апы :), то я с радостью покопал тему расширения функционала менеджера и сделал нужный мне метод.



#models.py

#Менеджер (обратите внимание на базовый класс)
class EventManager(models.Manager):
    def next(self):
        import datetime
        try:
            return Event.objects.order_by('date').filter(date__gte=datetime.datetime.now())[0]
        except:
            return None
#Модель, для которой нужен метод next
class Event(models.Model):
    title = models.CharField()
    date  = models.DateTimeField()
    objects = MatchManager() #Явно указываем, кто нами рулит


После этого в соответствующем view я спокойно могу использовать:

Event.objects.next(), что гораздо удобнее и менее хрупко , чем конструирование фильтра.

Пара пояснений, любая модель, пронаследованная явно или опосредованно от models.Model имеет поле objects, значением которого является встроенный менеджер модели (объект класса models.Manager), который имеет кучу полезных методов. Мы создали класс-потомок и чуток его дополнили.
При желании можно параметризовать поле, по которому будет искать объекты метод next - подобно тому, как это делает latest. Но пока этого не требовалось, буду тратить время на что-то другое.
Что такое менеджер модели, во всяком случае, мне стало чуток понятнее =)

Django: лимитированные запросы - не забываем про исключения


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


event = Event.objects.order_by('date').filter(date__gte=datetime.datetime.now())[0]



[0] - как раз и отвечает за лимитирование.

Но здесь есть небольшой подводный камушек. Результат этого вызова нельзя сразу передавать в шаблон. Потому что , если событий , соответствующих такому критерию не будет найдено , возникнет исключение IndexOutOfRange.
Избежать 500-ток или дебаг-страниц можно просто:


try:
  event = Event.objects.order_by('date').filter(date__gte=datetime.datetime.now())[0]
except:
  event = None


понедельник, 21 ноября 2011 г.

Django: кастомизируем форму комментариев от django-comments


DJANGO-фреймворк включает в себя встроенный механизм комментирования контента произвольного типа. Довольно удобная штука. Имеет даже встроенные механизмы борьбы со спамом, модерацией, удобный интерфейс администрирования... Но, как и любое коробочное решение , рано или поздно требует доводки.
В моем случае не потребовалось писать кастомное приложение с наследованием классов встроенных комментариев - надо было лишь изменить интерфейс формы комментария.
Вот, что надо сделать, чтобы отрисовать свою форму:

1. включить шаблонный тег: {%load comments%} в самую "верхушку" шаблона страницы, откуда будет приниматься комментарий пользователя
2. в том месте, где будет показываться форма , поместить шаблонный тег: {% render_comment_form for entry%}
3. в основной директории для хранения шаблонов создать подкаталог 'comments' и поместить в него файл form.html , который будет, например с таким содержанием:

{% if user.is_authenticated %}
<form action="/comments/post/" method="post">
{%csrf_token%}
<textarea name="comment" id="id_comment" rows="2" style="width: 90%;"></textarea>
<input type="submit" name="post" value="Add">
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
</form>
{% else %}
<p class="red big">авторизуйтесь!<br>только зарегистрированные пользователи могут оставлять комментарии.</p>
{% endif %}

Инструкция 'if user.is_authenticated' необязательна - Вы можете и разрешить комментарии для неавторизованных пользователей (вы можете даже вообще не использовать встроенный механизм авторизации =)

Тег {%csrf_token%} также не имеет никакого отношения к топику, но для полноты картины оставил и его.

Далее идет html-код моей формы, в которой от кучи "коробочных" полей , я оставил только текст комментария. Остальное мне не нужно было. Автор комментария (а это будет id - авторизованного пользователя) проставится в соответствующее поле самим приложением comments.

Шаблонный цикл, вставляющий некие hidden-поля ОБЯЗАТЕЛЕН - без этих самых полей засабмитить форму не получится. Для понимания: в скрытых полях , которые я добавил таким образом, передаются тип контента (ContentType), идентификатор объекта (ForeignKey), которому относится комментарий и еще поле для защиты от спама, которое не-бот никогда не увидит и не заполнит, а вот тупой бот может и перемудрить, отправив заполненным и его =)

С удалением полей просто. Но следует понимать, что ДОБАВЛЯЯ свои собственные поля, надо не забыить НАУЧИТЬ понимать и принимать эти поля само приложение. Правильнее всего в этом случае написать свое кастомное приложение для комментирования. Об этом неплохо написано на djangoproject.com - копипастить уже написанное смысла не вижу :)

пятница, 18 ноября 2011 г.

Django: превращаем RawQuerySet в JSON-ответ


Всем хорош Djanfo-фреймворк. Да только вот ORM далеко не самый удобный. Хорошо еще что появилась возможность выполнять Raw-запросы. Например, у менеджера objects() есть метод raw(), с помощью которого можно сконструировать произвольный select.
Но радость от такой возможности омрачается тем, что у RawQuerySet, который получаем на выходе из raw() , нет привычных методов count(), values(), values_list() и тому подобных, так любезно предоставляемых нам классом QuerySet.
Я натолкнулся на это, когда мне потребовалось сформировать HTTP-ответ в формате JSON, содержащий результат довольно хитрой выборки из БД...В итоге список словарей пришлось конструировать вручную.

Примерно так это выглядит:

from django.core.serializers.json import DjangoJSONEncoder
from django.utils import simplejson

def sample_view(request):
    mymodels = MyModels.objects.raw("select * from myapp_mymodel")
    mlist = list()
    for m in mymodels:
        mlist.append(
         {'id': m.id,'title': m.title,'start': m.date,'end':m.date,}
    )
    data = simplejson.dumps(mlist, cls=DjangoJSONEncoder)
    return HttpResponse(data)

среда, 16 ноября 2011 г.

django: контроль операций INSERT, UPDATE, DELETE

Механизм моделей Django позволяет контролировать операции вставки , изменения и удаления объектов из БД двумя способами (не считая триггеров непосредственно в БД):

Способ первый: Cигналы

Модуль django.dj.models содержит основной набор сигналов, связанных с операциями с БД: pre_save, ost_save, pre_delete, post_delete.
Довольно подробно работа с сигналами описана здесь
Мне, как еще не до конца искушенному django_разработчику для решения реальных задач сигналы пока не приходилось использовать. Поэтому какими-либо тонкостями работы с ними делиться было бы глупо))
А вот второй способ использую довольно активно.

Способ второй: переопределение методов Save, Delete класса Models в классах-наследниках.

А именно такими классами и описываются модели Django-приложений.
- Почему нет отдельного метода для update?
- Потому что для этого используется save.
Сигнатура метода save содержит 1 параметр, которым должен быть self. То есть ссылка на сам объект. У объекта, который еще ни разу не сохранялся в БД поле id пустое, а у того, который был считан из БД и предназначен для обновления (update) идентификатор уже инициализирован. Соответственно, простейшая проверка внутри обработчика сигнала позволяет разделить код обработки save от кода обработки update.