slav0nic's blog

Заметки о python, linux и других занимательных вещах

Первый "блин" на Django

Давно ничего не писал в блог, т.к. сдавал диплом и писал футбольный портал (пока не презентабельно) Вот захотелось поделиться впечатлениями и имхами по ряду вещей и django в целом. Впечатления остались ... хорошими %) Последний раз смотрел на джангу года 3 назад, разница конечно большая.

Итак пару слов о проекте. Портал представляет из себя систему с блоками новостей и разделами, тегами, простыми блогами, форумом, регистрацией юзеров, турнирными таблицами, баннерной системой. Писать такое на web.py ммм, конечно можно, но пришлось бы изобретать ряд велосипедов.

Не думал что всё вышеперечисленное будет так просто реализовать%) Из основных модулей использовал:

  • django-registration
  • django-tagging
  • django-robots
  • django-forum
  • UrlMiddleware + ThreadLocals

Вкратце о задачах и возникших сложностях.

Как оказалось, APPEND_SLASH не рaботает c FlatPages, то есть /about/ и /about - разные страницы. Решил сию проблему путём подключения UrlMiddleware и задания APPEND_SLASH = False.

При добавлении новостей было необходимо определять автора, давать пользователю задавать поле "автор" показалось не сильно корректным, но в джанге удобных средств для решения задачи не оказалось. Пришлось воспользоваться мидльварей ThreadLocals, которая при каждом запросе выдирает значение user из запроса и присваивает его глобальной переменной (на самом деле локальному треду, но это не столь важно =) ), который можно получить через ф-цию get_current_user(). Выглядит это примерно так:

 def save(self):
    if not self.id:
        self.author = threadlocals.get_current_user()
    super(News, self).save()

В блогах потребовалось разграничить доступ для пользователей, то есть после авторизации пользователь должен видить только свои объекты (записи). Это легко можно сделать при помощи NFA, путём переопределения queryset() метода:

class EntryAdmin(admin.ModelAdmin):
    ...
def queryset(self, request):
    qs = super(EntryAdmin, self).queryset(request)
    if not request.user.is_superuser:
        qs = qs.filter(blog__author=request.user)
    return qs

Таким образом мы получим только записи из блога залогиненого пользователя, довольно удобная штука (спасибо Кошелеву) =)

Когда я только начал писать проект, оказалось что ф-ции агрегации в ORM отсутвуют, но после рефакторинга-qs, кое-что появилось, о чём мало кто знает. В одной из задач возникла необходимость в группировки объектов, теперь через ORM это делается так:

   qs = Schedule.objects.select_related().filter(pub_date__gte=datetime.now())
   qs.query.group_by = ['country_id']
   return {'schedule': qs}

Но вот Inline редактирование после NFA не понравилось, в старой админке это делалось 1й строкой, теперь же надо напистаь около 10 строк%). Пример прикручивания профиля к стандартной модели User:

from django.contrib import admin
from profile.models import UserProfile
from django.contrib.auth.models import User

class ProfileInline(admin.StackedInline):
        model = UserProfile
        extra = 1
        max_num = 1

class ProfileAdmin(admin.ModelAdmin):
        inlines = (ProfileInline,)

admin.site.unregister(User)  #!!!
admin.site.register(User, ProfileAdmin)

По началу кажется избыточно, но потом привыкаешь (а ещё надо въехать где какую модель подставлять %)) Вместе с радостями жизни, узнал о get_profile()... это ужасная штука, которую я больше никогда не буду юзать, хотя снизить число запросов на 1 странице удалось при помощи тега cache в шаблонах Кстати в примере из документации, по подключению админки, пропущен вызов admin.autodiscover() в urls.py, так как без него мы получим пустую админку, если её описание задано в admin.py (данная ф-ция пробегает по всем apps и пытается импортировать admin.py) Кстати не советую объявлять прямо в модели, ибо вылазят ошибки, говорящие о том, что админка для модели уже задана. А вот FormSet'ы не осилил, долго маялся как убрать чекбокс delete над inline объектом (в старой админке это делалось через указание одной переменной). Хотя понял что надо задать can_delete = False, но где и как пока не ведомо) ибо по формсетам вменяемых примеров маловато, особенно по BaseInlineFormSet.

Комментарии решил хранить в форуме на базе django-forum. Кстати хочется оторвать яйца тому, кто когда-то в своём блоге хвалил сею поделку%) отсутсвие пагинации и по 30 запросов на 1 страницу это уж слишком, но при помощи ловкости рук и select_related() удалось довести до ума:

{% cache 600 profile comment.author %}
{% if comment.author.get_profile.city %}
        ({{comment.author.get_profile.city}})
{% endif %}
{% endcache %}

comment.author необходим для уникальности кеша

И последний момент - newforms. Было необходимо построить форму для модели (по началу думал руками нарисовать и не париться%), было бы быстрее...), но возникла необходимость заменить стандартный "class" на указанный в css:

from django.newforms.models import ModelForm
from django.contrib.auth.models import User
from profile.models import UserProfile
from django import forms

class ProfileForm(ModelForm):
        class Meta:
            model = UserProfile
            fields=('city',)

class UserForm(ModelForm):
        class Meta:
            model = User
            fields=('first_name', 'last_name')

#add class=reg4 to input field
for form in (ProfileForm, UserForm):
    for  f in form.base_fields:
        form.base_fields[f].widget.attrs.update({'class': 'rega4'})

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

 def create_profile_for_user(sender, instance, signal, created, *args, **kwargs):
        if created:
                try:
                        UserProfile.objects.get(user = instance)
                except (UserProfile.DoesNotExist, AssertionError):
                        p = UserProfile( user = instance )
                        p.save()
dispatcher.connect(create_profile_for_user, signal=signals.post_save, sender=User)

Метод выдрал из какой-то рассылки.

А вот пагинацию я вам не покажу)) ибо уж сильно она убога, а django-pagination поздно заметил.

Держит сие чудо по 25 запросов/сек (без кеша), а местами за 70 %) на простом VPS'e, работает под SCGI на lighttpd в 1 процесс/threaded режим, поедает 20Мб. Имхо не дурно =)

Чёт смотрю на подзаголовок "Вкратце..." ... но переименовывать уже лень=)

Спасибо Кошелеву, Соловьёву и lorien'y за подсказки на старте :] Пожалуй всё. Надеюсь я не принёс в сей мир очередную порцию "гавнакода", а внёс гармонию и красоту :)

redbaron post on 2008-07-21 14:28:45
А джангу можно с уже существующими базами дружить?
slav0nic post on 2008-07-21 14:42:17
да можно, http://www.djangoproject.com/documentation/django-admin/#inspectdb да и была где-то презентация по теме
crash post on 2008-07-21 17:05:03
А профиль можно через наследование сделать.
Yury Yurevich post on 2008-07-22 09:17:21
С почином. Одним джангонавтом больше ;-)
Dmitriy post on 2008-07-26 12:21:05
Только начал изучать этот язык! Тоже первый блин на подобии делал! Респект! Ошибочки подправил...
Григорий Петухов post on 2008-09-23 08:41:37
Давай ещё блог на django переводи :o)) Задавать пользователя новости можно, если во время конструирования формы передать в __init__ пользователя и сохранить его в self.user, а затем в save формы заюзать.
Олег post on 2008-12-02 23:11:02
def create_profile_for_user(sender, instance, signal, created, *args, **kwargs): if created: try: UserProfile.objects.get(user = instance) except (UserProfile.DoesNotExist, AssertionError): p = UserProfile( user = instance ) p.save() dispatcher.connect(create_profile_for_user, signal=signals.post_save, sender=User) Где то видел более оптимизированный метод, сейчас поищу, скину
Makki post on 2009-02-24 12:42:05
Спасибо большое, очень полезная статья. Сейчас пинаю всех чтобы писали на джанго но документации не по всему хватает, как вы уже заметили. Еще раз спасибо за материал!
Makki post on 2009-02-24 12:42:40
да и еще переезжайте на http://www.webfaction.com :)
Ярик post on 2009-08-27 12:42:20
Пост отличный. Побольше б таких! ;)

web.py