Наша совместная команда Banwar.org

Связаться с нами

  • (097) ?601-88-87
    (067) ?493-44-27
    (096) ?830-00-01

Статьи

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

  1. Постановка задачі
  2. Модель

Django

Вступ: Противників велосипедостроєнія прохання відразу піти сюди: django-threadedcomments або прокрутити до списку посилань на алгоритми в кінці статті. Досвідчені джанговоди також не знайдуть тут нічого цікавого.

Однією з найбільш важливих для мене фішок є деревовидні коментарі. Вони дозволяють легше стежити за ходом обговорення, що по-моєму дуже важливо. Їх відсутність або кривизна реалізації в open-source блог-двигунах стало однією з причин написати свій.

Постановка задачі

  • Відсутність реєстрації (навіщо мучити користувача? Будуть проблеми зі спамом - будемо думати).
  • Запам'ятовування даних коментатора в cookie.
  • Маркер авторитетного коментаря і коментаря автора. (При великій кількості коментарів особисто я завжди вишукую коментарі автора і інших авторитетних людей.)
  • Favicon сайту коментатора як його аватарки.
  • Вибірка всіх коментарів до статті одним запитом. Додавання не повинно перелопачувати всі коментарі.
  • Валідація, перегляд і додавання без перезавантаження сторінки. Коректна робота без JavaScript
  • PS. Не забути де-небудь використовувати Django :)

Для початку, реалізуємо всі на 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`а я розповім наступного разу (якщо хто-небудь попросить).

посилання:

  • django-threadedcomments - реалізація коментарів, що використовує рекурсію при виведенні.
  • django-mptt реалізація множинної моделі зберігання дерев в БД.

Навіщо мучити користувача?
Та й якщо у людини з сервером проблеми, що йому не можна написати коментар?

Новости

Banwar.org
Наша совместная команда Banwar.org. Сайт казино "Пари Матч" теперь доступен для всех желающих, жаждущих волнения и азартных приключений.