Первый "блин" на 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 за подсказки на старте :] Пожалуй всё. Надеюсь я не принёс в сей мир очередную порцию "гавнакода", а внёс гармонию и красоту :)