Наконец-то снова дошли руки до Cicero. Реализацию поиска я, правда, решил отложить, чтобы немножко еще в голове уложить, чего же я от него хочу. Вместо этого я занялся модераторами и редактированием статей.

Bzr-репозиторий http://softwaremaniacs.org/code/cicero/
Работающий форум http://softwaremaniacs.org/forum/

Модераторы

Казалось бы, самым очевидным способом раздать пользователям права является джанговская встроенная система прав. Однако я не стал ее использовать, потому что она представляет собой типичную ACL-систему, и предназначена в первую очередь для работы в связке с админкой. Как любая ACL она отличается универсальностью и простотой в управлении, но при этом некоторой негибкостью. Чтобы не вдаваться в теоретические размышления вот пример: как, имея в наличии модель статьи и стандартные операции add/change/delete, представить понятие "модератор может увеличивать рейтинг статьи на 10 единиц, в то время как обычный юзер — только на 1"?

Такие премудрости так или иначе требуют изменений кода, поэтому когда речь идет не об админке, а о собственном коде, я предпочитаю не выражать роли дроблением на искусственные атомарные операции, а просто вешать признак на пользователя, что он обладает определенной ролью. В коде это выглядит очень просто:

class Profile(models.Model):
  # ...
  moderator = models.BooleanField(default=False)

Заодно теперь в профилях пользователей показывается, какие у них есть специальные права. Хотя множественное число пока не актуально, потому что я там пока единственный модератор и админ :-).

Для управления же правами никакого отдельного интерфейса делать не понадобилось: с этим прекрасно справилась джанговская админка. Несколько опций:

class Profile(models.Model):
  # ...

  class Admin:
    list_display = ('user', 'openid', 'name', 'moderator')
    list_filter = ('moderator',)
    search_fields = ('openid', 'name', 'user__username')

... и готов красивый и функциональный интерфейс:

Редактирование статей

Функциональность редактирования очень простая: статью может редактировать либо ее автор, либо модератор, никаких следов от редактирования не остается, статья не становится заново непрочтенной. Я решил, что этого вполне хватит.

Реализация состояла из трех мелких этапов:

Несмотря на то, что логика "умеет ли юзер редактировать" прямо скажем элементарна, я решил завернуть ее в отдельный метод профиля пользователя:

class Profile(models.Model):
  # ...
  def can_edit(self, article):
    return self.moderator or article.author_id == self.user_id

Этот дополнительный уровень абстракции кажется мне полезным, потому что иначе детальную логику "если модератор или автор" нужно было бы дублировать минимум в двух разных местах — при показе ссылки в шаблоне и при проверке доступа во вьюхе. А потом еще и синхронно менять, если понятие "может редактировать" как-то изменится.

Следующий шаг — выставить эту функциональность в шаблон, чтобы рядом со статьями, которые пользователь может редактировать, рисовать соответствующую ссылку. В Джанго, как известно, нельзя в шаблонах свободно вызывать функции с аргументами, поэтому прямо вот так сделать не получится:

{% if profile.can_edit:"article" %}

Поэтому в таких случаях обычно делается свой if-подобный тег, который в шаблоне будет выглядеть так:

{% ifcanedit profile article %}
<a href="...">Редактировать</a>
{% endifcanedit %}

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

class IfCanEditNode(template.Node):
  def __init__(self, profile_expr, article_expr, node_list):
    self.profile_expr, self.article_expr, self.node_list = profile_expr, article_expr, node_list

  def render(self, context):
    profile = self.profile_expr.resolve(context)
    article = self.article_expr.resolve(context)
    if profile and profile.can_edit(article):
      return self.node_list.render(context)
    return ''

@register.tag
def ifcanedit(parser, token):
  '''
  Выводит содержимое блока только в том случае, если пользователь
  имеет право редактироват статью
  '''
  bits = token.contents.split()
  if len(bits) != 3:
    raise template.TemplateSyntaxError, '"%s" принимает 2 параметра: профиль и статью' % bits[0]
  node_list = parser.parse('end' + bits[0])
  parser.delete_first_token()
  return IfCanEditNode(parser.compile_filter(bits[1]), parser.compile_filter(bits[2]), node_list)

Выглядит это довольно лохмато, хотя смысл простой:

  1. принять из шаблона параметры и внутренности тега
  2. в "render" вызвать с параметрами готовую функцию
  3. в зависимости от результата либо рисовать, либо не рисовать содержимое

Самая запутанная и тупая часть — это функция тега "ifcanedit", которая вынимает из парсера шаблона параметры, ищет конец тега, а также запоминает внутреннее содержимое. Я ее никогда не пытаюсь воспроизвести на память, просто копирую из других проектов и адаптирую немножко :-).

Джеймс Беннетт (релиз-менеджер Джанго) даже как-то озвучивал идею, что неплохо бы завернуть все это в регистрирующий декоратор наподобие simple_tag и inclusion_tag, но пока, видимо, руки не дошли. Поэтому если кто возьмется, то у него есть неплохой шанс вписать себя в историю Джанго :-)

Ну вот. Осталось нарисовать форму редактирования из одного поля, сделать для нее view и шаблон. Это все крайне стандартно, поэтому повторяться не буду, а просто сошлюсь на статью, где уже описывал реализацию форм постинга.

Добавлю только, что во view, которая обрабатывает пост из формы, обязательно нужно еще раз сделать проверку на право редактирования, потому что одного скрытия ссылок на формы недостаточно: данные в приложение могут прийти откуда угодно и от кого угодно.

Комментарии: 29 (особо ценных: 1)

  1. 1smash1

    Вобщем-то, если модератор отредактировал статью, надо это как-то отображать все-таки.

  2. igorekk

    Иван, спасибо за статью.

    Планируется ли в cicero реализовать личные сообщения между пользователями?

  3. Sasha Bockin

    Форкнул пару месяцев Ваш форум, Иван, надеюсь вы не против)) И сразу кое что для себя доделал, в том числе и редактирование статей. Все конечно сделал быстро и примитивно, но работает и очень рад, что благодаря Вам сэкономил массу времени - мне не пришлось писать все с нуля. Как это выглядит - на моем сайте(http://dogster.ru), он весь на джанге.

  4. dobrych

    А есть ли планы и идея по реализации модераторства по каким-то участкам (по определенным тегам/категориям например)? На данный момент я тка понимаю получается глобальное модерирование...

  5. koder

    Если к статье уже сть комментарии то ее
    редактирование может привести к неприятным
    последствиям. Я не заметил - а дата
    редактирования сохраняется где-нить? Было-бы
    полезно дату сохранять и показывать, это конечно
    не убережет от последствий, подобных тем,
    которые можно на лоре постоянно наблюдать, но
    хоть что-то.

    мечтательно так

    А совсем круто(IMHO) было-бы хранить историю
    (как в вики), т.е. в базе лежит последнее
    состояние поста,комментариев и downgrade
    diffs, с помощью которых можно посмотреть в что
    было в статье(и комментах) раньше(а к каждому
    посту добавить ссылку - состояние статьи и
    дискусси на момент добавления поста).
    (если редактирование комментариев не
    предполагается то соотв. diffs ессно не нужны ).

  6. Alexander Solovyov

    А совсем круто(IMHO) было-бы хранить историю (как в вики)

    Хм, а ведь интересная идея - я как-то не задумывался об идее применения этой штуки в форумах.

    Правда, думается, должна быть галка "не сохранять изменения". Вдруг афтар погорячится и запостит не то, что запостил бы при трезвой голове? ;)

  7. crash

    Дык это уже и будет вики, с её обсуждениями. Не надо делать из простого форума комбайн

  8. Муркт

    это конечно не убережет от последствий, подобных тем, которые можно на лоре постоянно наблюдать, но
    хоть что-то.

    От последствий должен грамотный модератор уберегать, чтоб контингент идиотов не собирался.

  9. koder

    to Murkt

    От последствий должен грамотный модератор
    уберегать, чтоб контингент идиотов не
    собирался.

    Я имел в виду не умственный уровень читателей/писателей а то что открываешь ветку,
    начинаешь ее читать и не поймешь - что это за
    бред тут написан. И только потом доходит что
    автор подправил статью уже после
    написания этого поста )).

    to crash

    Дык это уже и будет вики, с её обсуждениями. Не
    надо делать из простого форума комбайн

    Реализуется это на джанго на раз. До возможностей
    полноценной вики этому очень далеко (сравнивать
    с mediawiki например). К тому-же почему бы не
    утянуть такую полезную фишку из соседствующего
    приложения. И предложил я ее не просто так,
    а с целью устранения(частичного) проблемы,
    возникающей при правке статьи постфактум.

  10. Maximbo

    никаких следов от редактирования не остается

    Почему? Пометка "отредактировано тогда-то" часто смысл спасает. Можно, например, добавить в модель поста два поля (для модератора и владельца) DateTime с null=True и выводить их в конце статьи в случае не None.

    А есть ли планы и идея по реализации модераторства по каким-то участкам (по определенным тегам/категориям например)?

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

    А совсем круто(IMHO) было-бы хранить историю (как в вики)
    Реализуется это на джанго на раз.

    "Не всё что можно сделать, следует делать на самом деле. Прыгать с крыши, например" (c)

    На wiki возможность отката нужна прежде всего из-за того, что один объект содержимого могут редактировать несколько авторов. И время полезной жизни этого объекта (фактически — статической страницы сайта) значительно превышает оное у поста на форуме.

  11. koder

    На wiki возможность отката нужна прежде всего
    из-за того, что один объект содержимого могут
    редактировать несколько авторов

    Я помоему весьма внятно написал зачем
    была предложена история и слово откат в
    объяснениях совсем не фигурирует.

  12. Slonopotamus

    Когда делал систему прав для http://hh.ru, пришёл к выводу, что role-based права - это слишком негибко. Как, например, с помощью ролей дать менеджеру компании право редактировать вакансии, принадлежащие всем менеджерам данной компании, но не других? А если принадлежащие только выбранным менеджерам?

    В общем роли - это, конечно, хорошо, но очень ограниченно. Роли - всего лишь именованные права. А они могут быть много какие ещё помимо именованных.

  13. Alexander

    По поводу ifcanedit или

    В Джанго, как известно, нельзя в шаблонах свободно вызывать функции с аргументами, поэтому прямо вот так сделать не получится:
    {% if profile.can_edit:"article" %}

    А вот так получится:

    {% if profile|can_edit:article %}
    

    Некий mikeivanov на http://www.djangosnippets.org/snippets/177/ предложил идею, по которой достаточно написать фильтр:

    @register.filter
    def can_edit(value,arg):
        return arg.can_edit(value)
    
  14. Maximbo

    Особо ценный комментарий

    Я помоему весьма внятно написал зачем
    была предложена история и слово откат в
    объяснениях совсем не фигурирует.

    Значит я выразил свою мысль недостаточно чётко. Исправляюсь.

    Необходимость хранить историю изменений объекта тем выше, чем выше его значимость в отношении к общему количеству объектов в системе.

    С добавлением исторического архива сложность системы однозначно возрастает. Может быть даже нелинейно.

    Важность средней страницы wiki много больше важности среднего поста на форуме. Да и большинство пользователей форуме предпочитают думать перед нажатием кнопки отправки. Что так же снижает количество появления состояний "отредактировано".

    Следовательно, ради добавления маловажной возможности, мы жертвуем простотой реализации. IMO, это очень плохо.

    Мы значительно отошли от темы статьи, поэтому предлагаю перенести диалог, если хотите продолжить, в иное место.

  15. koder

    Ок. Какие другие решения данной проблемы Вы можете
    предложить? Или так и будем читать посты с давно
    утраченным смыслом?

  16. Иван Сагалаев

    По поводу отображения редакций. Я пока не сделал ничего подобного по двум причинам. Первая — банальная лень, но вторая существенная. Мне кажется, что ситуаций, когда информация о редактировании реально нужна, в реальности достаточно мало. Обычно редактируются опечатки, добавляются какие-то забытые ссылки и т.д. И происходит это обычно в первые секунды/минуты после поста, когда человек его проглядывает. Отвечающему позже на этот пост знать об этом совершенно неинтересно.

    Других случаев напридумывать можно много, но я захочу с ними что-то делать только когда они станут неединичными и понятными.

  17. Иван Сагалаев

    А вот так получится:

    {% if profile|can_edit:article %}
    

    Мне это не нравится по сути. Синтаксис, безусловно, можно прогнуть многими различными способами, но использование фильтра в качестве мостика для вызова функций выглядит сильно неочевидным и опасно приближается к превращению шаблона в язык программирования.

  18. Муркт

    Можно поставить условие на показывание состояния редактирования - к примеру, если с момента создания поста до его редактирования прошло больше пяти минут (десяти, получаса), то он считается отредактированным по сути; а не с исправленной орфографией.

  19. koder

    Отвечающему позже на этот пост знать об этом
    совершенно неинтересно.

    В который раз повторяю что важны не комментарии
    оставленные "позже"(а также откаты и проч.), а
    комментарии иставленные раньше правки, т.к.
    только они в и тоге могут потерять смысл.

    И происходит это обычно в первые секунды/минуты
    после поста, когда человек его проглядывает.

    В таком случае следующим логичным(IMO) шагом
    будет запрет правки поста после внесения первого
    комментария.

  20. koder

    Вспомнил после поста Муркт.

    Единственно более-менее приятной системой правок
    обладает(IMHO) http://www.photosight.ru . Там править
    пост можно только в течении определенного(то-ли 3
    то-ли 5 минут) времени после его опубликолвания.
    Где-то еще была интересная идея - править можно
    только если нет ответов/цитирований поста, но это потребует боле тяжелой реализации.

  21. Муркт

    Кодер, я не женского пола, и мой ник склоняется, раз уж он написан по-русски.

  22. koder

    Извини, привык просто к никам, написанным по английски и, по привычке, просто скопипастил.

  23. Иван Сагалаев

    В который раз повторяю что важны не комментарии
    оставленные “позже”(а также откаты и проч.), а
    комментарии иставленные раньше правки, т.к.
    только они в и тоге могут потерять смысл.

    Да нет, я понял. Это как раз то, что представляется мне умозрительным, и с чем я ничего не хочу ничего делать до того, как это станет проблемой.

    В ответ на теоретическую возможность, что редакция поста отменит смысл ответов, я могу придумать теоретическую возможность, что автор поста в состоянии это оценить, и не редактировать свой пост, а оставить новый ответ в духе "спасибо, уже разобрался" или что-нибудь похожее. Каких авторов будет больше — "глупых" или "умных" — понять заранее невозможно. Именно поэтому тратить время на такие проблемы имеет смысл только по мере их возникновения.

  24. Murkt

    Спасибо за пример тега {% ifcanedit %}. Буквально толко что взял его за основу и сделал свой, очень полезный.

  25. Сергей Петров

    Вань, я не знаю, пользуешься ли ты форумами в действительности, но я пользуюсь.

    И редактирование записей там - постоянная и необходимая практика.

    Приведу конкретный пример.

    На форуме macserialjunkie.com, где выкладываются хаки к мак-софту, каждый топик посвящен определенной программе. Чаще всего этот топик появляется с текстом REQ в теме, что означает, что там внутри человек просит кряк к программе.

    Идет обсуждение, поиск, часто даже такой интерактивный взлом. В какой-то момент кряк находится.

    Если он полноценный - REQ меняется на STW. Если не совсем полноценный, то на REQ/STW.

    Также меняется и первое сообщение в топике. Туда потихоньку выползают зеркала на кряк, какие-то подробности, и тому подобное.

    Главное о чем я хочу сказать - это абсолютно стандартная и популярная ситуация - редактирование первой статьи в топике. И оповещение о таком редактировании в том или ином виде - просто необходимо.

    Впрочем, лучше подумать об исходном use case и решить необходимость выносить вручную ценные вещи в первую запись.

    Например - позволить автору топика или модератору выделить текст в любой записи топика и нажать на кнопку "присоединить к топику" - которая либо прямо-таки вынесет выделенный текст на самый верх, или оставит наверху ссылку с кастомным текстом на текущую запись.

  26. Иван Сагалаев

    Серег, я не очень понял, против чего ты возражаешь. В Cicero будут редактироваться статьи, включая первую.

    Но вот с необходимостью оповещения я не соглашусь. Ты описал один use case. Насколько он распространен вообще, я не знаю. Но поскольку я точно знаю, зачем я пишу этот форум — а именно, на замену своему текущему — я уверен, что там в оповещениях необходимости нет. Потому что каждый топик должен решать одну конкретную проблему, а не быть долгоживущим.

    Нет, я все же напишу чуть более широко :-).

    Коммуникативный софт — интересная вещь. В том смысле, что его успешность зависит от соответствия сложившимся привычкам пользователей гораздо меньше, чем у утилитарного софта. Потому что коммуникативный софт поддерживает некоторое конкретное коммьюнити, участникам которого интересно общаться друг с другом. Это самое главное, и если софт им слегка неудобен, они готовы немножко мириться. Именно поэтому в коммуникативном софте у дизайнера есть возможность забить на общую сложившуюся практику и сделать софт таким, чтобы он влиял на поведение коммьюнити. Потому что существует запас терпимости. Это конечно не означает, что над юзерами надо издеваться :-). Но направить — вполне можно.

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

    Короче, такое поведение — by design.

  27. dark-demon

    И редактирование записей там - постоянная и необходимая практика.

    практика использования форума не по назначению ^_^

    для этого по хорошему нужно выделить отдельную сущность с флагами завершённости, с прилепленным к ней топиком и прочими присущими оной особенностями.

  28. [...] примера реализации if-подобного тега могу предложить свой пост. Там как раз ifcanedit [...]

  29. [...] 16.02.2009 14:46 Во view права проверять все равно надо обязательно - данные могут прийти откуда угодно, злой хакер не будет посылать их через вашу форму.В шаблоне можно делать примерно так:{% if can_edit %} {{ form }}{% else %} Вы не можете редактировать запись.{% endif %}Еще почитайте: http://softwaremaniacs.org/blog/2007/06/10/moderators-and-article-editing/ [...]

Добавить комментарий