Вступление: Противников велосипедостроения просьба сразу удалиться сюда: django-threadedcomments или прокрутить до списка ссылок на алгоритмы в конце статьи. Опытные джанговоды также не найдут здесь ничего интересного.
Одной из наиболее важных для меня фишек являются древовидные комментарии. Они позволяют легче следить за ходом обсуждения, что по-моему очень важно. Их отсутствие или кривость реализации в open-source блог-движках стало одной из причин написать свой.
Для начала, реализуем все на Django, потом прикрутим JavaScript.
Модель комментария:
class Comment(models.Model):
article=models.ForeignKey(Article)
parent=models.ForeignKey('self', blank=True, null=True, related_name='child_set')
author_name=models.CharField(max_length=32)
author_email=models.EmailField()
author_url=models.URLField(blank=True)
text=models.TextField()
pub_date=models.DateTimeField('date published', default=datetime.now)
admin_comment=models.BooleanField(default=False)
Модель использует стандартные поля Django. Древовидность реализуется ссылкой на родителя.
Может быть пользователя стоило вынести в отдельную модель, но для простоты пока оставлю так. Если нужна регистрация - можно использовать стандартного юзера Django.
Присмотревшись чуть внимательнее к реализации стандартного поля URLField, я понимаю, что оно мне не подходит: при валидации сервер запрашивает введенный урл и смотрит, что ответ не 404. Не хочу. Да и если у человека с сервером проблемы, что ему нельзя написать комментарий?
Однако валидация нужна, чтобы не получались ссылки вида http://dpp.su/blog/ivan.ivanovich.googlepages.com. По этому пишем свой URLField:
class SimpleURLField(CharField):
def pre_save(self, model_instance, add):
url=getattr(model_instance, self.attname, '')
if add and url and not url.startswith('http://'):
url='http://%s'%url
setattr(model_instance, self.attname, url)
return url
return CharField.pre_save(self, model_instance, add)
Теперь пришло время задуматься выборкой комментариев из базы. Рекурсивные запросы делать бы очень не хотелось. Хотя, как говорят тут, если убрать рекурсию в хранимую процедуру, то скорость MySQL будет приемлемой. Может быть и так, но мне такой подход не нравится.
Перед нами классическая задача: хранение древовидных структур в БД. Но, все-таки, вооружившись здравым (или не очень) смыслом, начинаем изобретать велосипед.
На баше как раз сегодня цитата очень в тему: гениальную фразу сейчас услышал на семинаре по работе с zend framework - "Можно не изобретать велосипед, а ПОГНУТЬ уже существующий"
Первая мысль: если хранить полный путь до корня, то правильная очередность комментариев достигается простой сортировкой по этому полю. Однако это накладывает ограничение на количество комментариев и глубину дерева. Но давайте посмотрим на это ограничение внимательней.
Если использовать varchar(255) и описывать каждый узел дерева тремя цифрами, то мы получим вложенность 85 и тысячу комментариев на каждом уровне. Если четырьмя - 64 и 10000. Для не очень популярного ресурса вполне хватит.
Если поискать информацию в инете можно заметить, что этот способ почти всегда предлагают первым. Просто и работает быстрее всего. Но только в том случае, если нам не важно ограничение этого метода. Сначала я думал написать сравнение наиболее популярных методов, но начав писать передумал: уж слишком много подобных сравнений. Да и Django оказывается не при чем.
Вернемся к предложенному решению. Если идея вдруг не понятна, приведу пример хранения коментариев в табице. Коментарии:

А вот как они будут храниться в базе:

Отсортировав таблицу по полю path мы получаем список комментариев в нужном нам порядке. Теперь нужно написать функцию генерирующую значение поля path. Значение генерируется очень просто - берется значение родителя и дописывается id добавляемого объекта.
path=('%s-%03d'%(parent.path, self.id, ))[:255]
Однако, при добавлении объекта мы не можем узнать его id, что приведет к дополнительному UPDATE добавленного объекта после его сохранения. Также необходимо задуматься о следующем: если использовать id для генерации path, то ограниечение на количество кометариев при этом методе получается общим для всего сайта. 10000 коментариев к статье - это много, а ко всему сайту - не очень. Поэтому использовать id при генерации пути плохо. Придется создать дополнительную колонку в таблице для хранения относительного id.
Используя подход, предлагаемый Django мы должны создать свой тип поля, наследованный от базового поля модели Django, и перекрить у него функцию сохранения. Получаем следущее:
class TreeOrderField(models.CharField): def pre_save(self, model_instance, add): if add: parent=(model_instance.parent or model_instance.article); if parent.seq<1000: parent.seq+=1; parent.save(); else: # update all comments to use 4digit masks pass value='%s%03d'%(getattr(parent, self.attname, ''), parent.seq, )[:255] setattr(model_instance, self.attname, value) return value return models.CharField.pre_save(self, model_instance, add)
Тут можно реализовать по-разному, но избавиться от дополнительного UPDATE не получается ни в одном из случаев. В частном случае PostgreSQL наверно можно использовать sequence и тогда лищних udate`ов не будет.
Для определения отступа комментария в дереве можно использовать принцип построения path:
@property def level(self): return max(0,len(self.path)/3-1)
Таким образом мы получили следующее решение: пример.
А опробовать работоспособность продложенного метода можно: тут.
Про отображение и контроллер комментариев на Django, а также про использование Ajax`а я расскажу в следующий раз (если кто-нибудь попросит).
Ссылки:
test
впамыпаиыпаиавпи
wer
111
1
u
аав
rrrrrrrrrr
aaaaaaaaa
sssssssssssss
j
fff
sdsd
укеу
истмитмитмти
ппвававпва
new
тупо что комент не уходит вконец
http://dpp.su/blog/django-tree-comments/tcm.py - говорит 404
Забыл прикрепить файлы и картинки. Поправил.
Favicon сайта комментатора в качестве его аватарки. - отличная идея:)
some
hfsh kshkf skfh skahf ksh fksjhdfkshadkf sakdfh
фыв
выавыавыа
> при валидации сервер запрашивает введенный урл и смотрит, что ответ не 404. Не хочу.
> Да и если у человека с сервером проблемы, что ему нельзя написать комментарий?
достаточно указать в verify_exists=False в URLField
проверка
тест
test
yo
test
235235
test
test
спосибо, посмотрю
комментарий еще стоит пропускать через темплейтный фильтр linebreaksbr, а то текст в одру строчку отображается
Спасибо, не подумал об этом. Поправил.
Ок. Жду продолджения про Ajax.
У меня тоже недавно встала проблема сделать красивые древовидные комментарии, и тут мой взгляд упал на такую замечательную вешь как <a href="http://www.djangoproject.com/documentation/models/generic_relations/">Generic relations</a>.
С помощью этой штуки удалось сделать очень красивые комменты без привязки к конкретной модели...
А для дерева был выбран <a href="http://www.getinfo.ru/article610.html">Nested set</a>.
Вобщем получилось красиво! =) В ближайшее время постараюсь опубликовать это решение.
спосибо, посмотрю. если опубликуешь - кидай ссылку.
Интересный примерчик. Вот только не пойму как ограничить дерево комментариев когда ответов получается большой глубины вложенности. Допустим больше десяти Т.к. я не хочу чтоб появлялся горизонтальный скролинг. А вообще примерчик уже попробывал все работает :)
Вот ключевой момент:
<blockquote>Если использовать varchar(255) и описывать каждый узел дерева тремя цифрами, то мы получим вложенность 85 и тысячу комментариев на каждом уровне. Если четырьмя - 64 и 10000. Для не очень популярного ресурса вполне хватит.</blockquote>
Описывай коментарий тремя цифрами и смени varchar(255) на varchar(30) - получишь вложеность 10 и 1000 коментов на каждом уровне.
Как переписать TreeOrderField чтобы вместо 11го уровня получался очередной коммент на 10м думаю сообразишь.
хмм. ответил на пару комментов, а уже стока багов логики в своем яваскрипте нашел... да и спам уже надоел. когда же у меня, наконец, будет время и желание дописать свой сайт...
,kf kfkf
бла лала
бла ла ла
бла ла ла
бла ла ла
бла
test
bla
test
bla
выаываыа
=)
ok okoj op
бла ла ла
блалала
test
hjg jhg
xzczxc
1
111
df
dfsdfsdf
вложение 2
вложение 3
вложение 4
test
bNaLPP gks72nf95mdHfLav1Xpu
Тест комментов.
И все-таки, чем оно лучше того же самого django-threadedcomments?
а чем лучше тот же django-threadedcomments? автор просто решил сделать свою реализацию
Кстати, в стандартном URLField есть возможность отключения проверки доступности сайта по введенному урлу.
hell
mega-test
mega-test!!!
tryam tryam
Можно увеличить глубину вложенности, или уменьшить размеры path если ложить туда seq в шестндацатиричном формате, типа так:
'%s%03X' % (getattr(parent, self.attname, ''), parent.seq)
Нада будет попробовать на своем сайтике, у меня как раз пока комментов нет.
А поддержка продукта дальнейшая будет? Может вам на google code SNV сделать?
Интересно, а как еще можно изменить структуру комментариев? Что тут еще можно придумать нового? Google, Яндекс и Mail все уже давно придумали. Хотя и там не боги работают.
qwewreqwr
Автору респект, отлично постарался
sdff
ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
fff
XXXXXXXXXXXXX
ZZZZ
test 2
Тоже ZZZZ
22222
проверим
Не понял, как сортировать правильно. Если сортировать сначало по времени, а потом по path, то последний коммент уходит в самый конец, даже если он является ответом на первый комментарий.
Можно про контроллер поподробнее?
anthropogenic time contends attributable start radiation
Вот я тоже такой проблемой занимался, комментариев древовидных, только почти полностью переделал джанговский + сделал умное кеширование блоков комментов, к любому объекту модели.
глянь """/comment/?page=2""" на сайте, который в поле URL
ВОТ :)
отпишись если станет интересно, оформляю как отдельное приложение пол работы уже сделано. могу дать доступ к репозиторию...
test
Круто!!!!!! Ачуменный пример!!!!! Хочу себе такой комент сделать!
авываыва
ваываыва
цукауцк
werwerf
werfer
1
WlWd8H Thanks for the post
just a test
hYFkjo Hello, YA serche drocher!!!
p.s. serchedrocher!
тест