Django: Древовидные комментарии.

Вступление: Противников велосипедостроения просьба сразу удалиться сюда: 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`а я расскажу в следующий раз (если кто-нибудь попросит).

Ссылки:



Комментарии

DPP 03.04.2008 03:44
namnem 24.07.2009 03:18

впамыпаиыпаиавпи

wer 17.12.2009 04:39
1 02.02.2011 09:29
1 02.02.2011 09:29
a 10.02.2011 01:10
аа 11.02.2011 05:43
аа 11.02.2011 05:43

rrrrrrrrrr

аа 11.02.2011 05:43

aaaaaaaaa

аа 11.02.2011 05:43

sssssssssssss

hh 03.03.2011 07:55
dfdsf 18.09.2009 03:41
sd 29.01.2010 04:02
укекуе 15.06.2010 03:26
куеп 05.01.2011 05:07

истмитмитмти

куеп 05.01.2011 05:07

ппвававпва

аа 11.02.2011 05:44
аа 11.02.2011 05:54

тупо что комент не уходит вконец

Boo 08.04.2008 03:55

http://dpp.su/blog/django-tree-comments/tcm.py - говорит 404

DPP 08.04.2008 02:18

Забыл прикрепить файлы и картинки. Поправил.

bloodtar 10.08.2009 08:03

Favicon сайта комментатора в качестве его аватарки. - отличная идея:)

root 22.11.2009 05:13
arc 07.09.2010 08:59

hfsh kshkf skfh skahf ksh fksjhdfkshadkf sakdfh

ыфв 26.10.2010 09:59
ывавыаыва 04.03.2011 12:15

выавыавыа

Boo 08.04.2008 04:06

> при валидации сервер запрашивает введенный урл и смотрит, что ответ не 404. Не хочу.
> Да и если у человека с сервером проблемы, что ему нельзя написать комментарий?

достаточно указать в verify_exists=False в URLField

Boo 16.05.2008 10:11

проверка

Boo 16.05.2008 10:12
Boo 11.12.2008 01:50
sssssssss 22.02.2010 03:18
Iv 05.03.2011 02:17
hello 04.05.2009 02:51
test 24.01.2010 07:26
test 13.05.2010 05:31
dpp 20.06.2008 02:26

спосибо, посмотрю

Boo 08.04.2008 04:08

комментарий еще стоит пропускать через темплейтный фильтр linebreaksbr, а то текст в одру строчку отображается

DPP 08.04.2008 02:09

Спасибо, не подумал об этом. Поправил.

Zada 08.04.2008 10:16

Ок. Жду продолджения про Ajax.

sopelkin 10.04.2008 01:38

У меня тоже недавно встала проблема сделать красивые древовидные комментарии, и тут мой взгляд упал на такую замечательную вешь как <a href="http://www.djangoproject.com/documentation/models/generic_relations/">Generic relations</a>.
С помощью этой штуки удалось сделать очень красивые комменты без привязки к конкретной модели...
А для дерева был выбран <a href="http://www.getinfo.ru/article610.html">Nested set</a>.
Вобщем получилось красиво! =) В ближайшее время постараюсь опубликовать это решение.

dpp 20.06.2008 02:27

спосибо, посмотрю. если опубликуешь - кидай ссылку.

mops 19.06.2008 04:00

Интересный примерчик. Вот только не пойму как ограничить дерево комментариев когда ответов получается большой глубины вложенности. Допустим больше десяти Т.к. я не хочу чтоб появлялся горизонтальный скролинг. А вообще примерчик уже попробывал все работает :)

DPP 20.06.2008 02:16

Вот ключевой момент:
<blockquote>Если использовать varchar(255) и описывать каждый узел дерева тремя цифрами, то мы получим вложенность 85 и тысячу комментариев на каждом уровне. Если четырьмя - 64 и 10000. Для не очень популярного ресурса вполне хватит.</blockquote>
Описывай коментарий тремя цифрами и смени varchar(255) на varchar(30) - получишь вложеность 10 и 1000 коментов на каждом уровне.

Как переписать TreeOrderField чтобы вместо 11го уровня получался очередной коммент на 10м думаю сообразишь.

dpp 20.06.2008 02:30

хмм. ответил на пару комментов, а уже стока багов логики в своем яваскрипте нашел... да и спам уже надоел. когда же у меня, наконец, будет время и желание дописать свой сайт...

bloodtar 11.08.2009 12:27
bloodtar 11.08.2009 12:28

бла лала

bloodtar 11.08.2009 12:28

бла ла ла

bloodtar 11.08.2009 12:28

бла ла ла

bloodtar 11.08.2009 12:28

бла ла ла

bloodtar 11.08.2009 12:28
dpp 15.08.2009 03:05
root 22.11.2009 05:13
dpp 11.02.2010 11:42
1 01.02.2011 08:20
somename 19.03.2011 05:02

выаываыа

ohu 12.07.2010 09:29
Вася 12.08.2008 03:28

ok okoj op

bloodtar 11.08.2009 12:29

бла ла ла

bloodtar 11.08.2009 12:29

блалала

laverov.com 03.11.2010 03:41
Петя 12.08.2008 03:33
zzxc 09.04.2009 10:38
bloodtar 11.08.2009 12:30
bloodtar 11.08.2009 12:30
dfdsf 17.09.2009 02:08
sdfsdf 11.08.2010 10:47
sdfsdf 11.08.2010 10:47

вложение 2

sdfsdf 11.08.2010 10:47

вложение 3

sdfsdf 11.08.2010 10:47

вложение 4

alex 27.09.2010 09:03
arman 15.10.2008 04:10

bNaLPP gks72nf95mdHfLav1Xpu

Alexander Artemenko 20.10.2008 09:38

Тест комментов.

И все-таки, чем оно лучше того же самого django-threadedcomments?

bers 04.12.2008 05:09

а чем лучше тот же django-threadedcomments? автор просто решил сделать свою реализацию

Alexander Artemenko 20.10.2008 09:59

Кстати, в стандартном URLField есть возможность отключения проверки доступности сайта по введенному урлу.

hell 02.10.2010 06:57
test 11.12.2008 03:18

mega-test

dfdsf 17.09.2009 02:09

mega-test!!!

tryam 12.10.2009 11:15

tryam tryam

Роман 24.02.2009 05:47

Можно увеличить глубину вложенности, или уменьшить размеры path если ложить туда seq в шестндацатиричном формате, типа так:
'%s%03X' % (getattr(parent, self.attname, ''), parent.seq)

Django 21.03.2009 01:00

Нада будет попробовать на своем сайтике, у меня как раз пока комментов нет.

Debianovec 02.04.2009 03:58

А поддержка продукта дальнейшая будет? Может вам на google code SNV сделать?

alekssad 14.07.2009 10:55

Интересно, а как еще можно изменить структуру комментариев? Что тут еще можно придумать нового? Google, Яндекс и Mail все уже давно придумали. Хотя и там не боги работают.

werweq rwe reqw 29.07.2009 06:11

qwewreqwr

bloodtar 10.08.2009 08:06

Автору респект, отлично постарался

dfdsf 17.09.2009 02:08
dfdsf 18.09.2009 03:44

ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ

dfdsf 18.09.2009 03:46
dfdsf 18.09.2009 03:46

XXXXXXXXXXXXX

dfdsf 18.09.2009 03:46
testtesttest 30.12.2009 06:50
Django 26.11.2009 04:47

Тоже ZZZZ

testtesttest 30.12.2009 01:57
ser 28.01.2011 04:59

проверим

testtesttest 30.12.2009 06:55

Не понял, как сортировать правильно. Если сортировать сначало по времени, а потом по path, то последний коммент уходит в самый конец, даже если он является ответом на первый комментарий.
Можно про контроллер поподробнее?

kahlishank 01.02.2010 01:09

anthropogenic time contends attributable start radiation

ivanff 11.02.2010 11:14

Вот я тоже такой проблемой занимался, комментариев древовидных, только почти полностью переделал джанговский + сделал умное кеширование блоков комментов, к любому объекту модели.
глянь """/comment/?page=2""" на сайте, который в поле URL
ВОТ :)
отпишись если станет интересно, оформляю как отдельное приложение пол работы уже сделано. могу дать доступ к репозиторию...

Teks 14.02.2010 02:47
Димон 05.03.2010 09:22

Круто!!!!!! Ачуменный пример!!!!! Хочу себе такой комент сделать!

ваыва 13.05.2010 07:55

авываыва

ваыва 13.05.2010 07:55

ваываыва

уку 12.06.2010 05:28

цукауцк

уку 12.06.2010 05:29
уку 12.06.2010 05:29
уку 12.06.2010 05:29
ywaoaieo 03.11.2010 05:51

WlWd8H Thanks for the post

kAIST 12.11.2010 08:04

just a test

serchedrochergo 06.01.2011 01:25

hYFkjo Hello, YA serche drocher!!!
p.s. serchedrocher!

ser 28.01.2011 05:01

Написать комментарий

Вы представились как:   e-mail: email (изменить)

Ссылки запрещены.


Copyright © DPP, 2008-2009