Наконец-то снова дошли руки до 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')
... и готов красивый и функциональный интерфейс:
Редактирование статей
Функциональность редактирования очень простая: статью может редактировать либо ее автор, либо модератор, никаких следов от редактирования не остается, статья не становится заново непрочтенной. Я решил, что этого вполне хватит.
Реализация состояла из трех мелких этапов:
- научиться выяснять, умеет ли конкретный юзер редактировать конкретную статью
- показывать в топиках ссылки на редактирование тем, кому это нужно
- описать форму, view'ху и шаблон
Несмотря на то, что логика "умеет ли юзер редактировать" прямо скажем элементарна, я решил завернуть ее в отдельный метод профиля пользователя:
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)
Выглядит это довольно лохмато, хотя смысл простой:
- принять из шаблона параметры и внутренности тега
- в "render" вызвать с параметрами готовую функцию
- в зависимости от результата либо рисовать, либо не рисовать содержимое
Самая запутанная и тупая часть — это функция тега "ifcanedit", которая вынимает из парсера шаблона параметры, ищет конец тега, а также запоминает внутреннее содержимое. Я ее никогда не пытаюсь воспроизвести на память, просто копирую из других проектов и адаптирую немножко :-).
Джеймс Беннетт (релиз-менеджер Джанго) даже как-то озвучивал идею, что неплохо бы завернуть все это в регистрирующий декоратор наподобие simple_tag и inclusion_tag, но пока, видимо, руки не дошли. Поэтому если кто возьмется, то у него есть неплохой шанс вписать себя в историю Джанго :-)
Ну вот. Осталось нарисовать форму редактирования из одного поля, сделать для нее view и шаблон. Это все крайне стандартно, поэтому повторяться не буду, а просто сошлюсь на статью, где уже описывал реализацию форм постинга.
Добавлю только, что во view, которая обрабатывает пост из формы, обязательно нужно еще раз сделать проверку на право редактирования, потому что одного скрытия ссылок на формы недостаточно: данные в приложение могут прийти откуда угодно и от кого угодно.
Комментарии: 29 (особо ценных: 1)
Вобщем-то, если модератор отредактировал статью, надо это как-то отображать все-таки.
Иван, спасибо за статью.
Планируется ли в cicero реализовать личные сообщения между пользователями?
Форкнул пару месяцев Ваш форум, Иван, надеюсь вы не против)) И сразу кое что для себя доделал, в том числе и редактирование статей. Все конечно сделал быстро и примитивно, но работает и очень рад, что благодаря Вам сэкономил массу времени - мне не пришлось писать все с нуля. Как это выглядит - на моем сайте(http://dogster.ru), он весь на джанге.
А есть ли планы и идея по реализации модераторства по каким-то участкам (по определенным тегам/категориям например)? На данный момент я тка понимаю получается глобальное модерирование...
Если к статье уже сть комментарии то ее
редактирование может привести к неприятным
последствиям. Я не заметил - а дата
редактирования сохраняется где-нить? Было-бы
полезно дату сохранять и показывать, это конечно
не убережет от последствий, подобных тем,
которые можно на лоре постоянно наблюдать, но
хоть что-то.
мечтательно так
А совсем круто(IMHO) было-бы хранить историю
(как в вики), т.е. в базе лежит последнее
состояние поста,комментариев и downgrade
diffs, с помощью которых можно посмотреть в что
было в статье(и комментах) раньше(а к каждому
посту добавить ссылку - состояние статьи и
дискусси на момент добавления поста).
(если редактирование комментариев не
предполагается то соотв. diffs ессно не нужны ).
Хм, а ведь интересная идея - я как-то не задумывался об идее применения этой штуки в форумах.
Правда, думается, должна быть галка "не сохранять изменения". Вдруг афтар погорячится и запостит не то, что запостил бы при трезвой голове? ;)
Дык это уже и будет вики, с её обсуждениями. Не надо делать из простого форума комбайн
От последствий должен грамотный модератор уберегать, чтоб контингент идиотов не собирался.
to Murkt
Я имел в виду не умственный уровень читателей/писателей а то что открываешь ветку,
начинаешь ее читать и не поймешь - что это за
бред тут написан. И только потом доходит что
автор подправил статью уже после
написания этого поста )).
to crash
Реализуется это на джанго на раз. До возможностей
полноценной вики этому очень далеко (сравнивать
с mediawiki например). К тому-же почему бы не
утянуть такую полезную фишку из соседствующего
приложения. И предложил я ее не просто так,
а с целью устранения(частичного) проблемы,
возникающей при правке статьи постфактум.
Почему? Пометка "отредактировано тогда-то" часто смысл спасает. Можно, например, добавить в модель поста два поля (для модератора и владельца) DateTime с null=True и выводить их в конце статьи в случае не None.
Присоединяюсь к вопросу. На форуме, чуть большим чем обычная гостевая книга, функциональность эта очень нужна.
"Не всё что можно сделать, следует делать на самом деле. Прыгать с крыши, например" (c)
На wiki возможность отката нужна прежде всего из-за того, что один объект содержимого могут редактировать несколько авторов. И время полезной жизни этого объекта (фактически — статической страницы сайта) значительно превышает оное у поста на форуме.
Я помоему весьма внятно написал зачем
была предложена история и слово откат в
объяснениях совсем не фигурирует.
Когда делал систему прав для http://hh.ru, пришёл к выводу, что role-based права - это слишком негибко. Как, например, с помощью ролей дать менеджеру компании право редактировать вакансии, принадлежащие всем менеджерам данной компании, но не других? А если принадлежащие только выбранным менеджерам?
В общем роли - это, конечно, хорошо, но очень ограниченно. Роли - всего лишь именованные права. А они могут быть много какие ещё помимо именованных.
По поводу ifcanedit или
А вот так получится:
Некий mikeivanov на http://www.djangosnippets.org/snippets/177/ предложил идею, по которой достаточно написать фильтр:
Особо ценный комментарий
Значит я выразил свою мысль недостаточно чётко. Исправляюсь.
Необходимость хранить историю изменений объекта тем выше, чем выше его значимость в отношении к общему количеству объектов в системе.
С добавлением исторического архива сложность системы однозначно возрастает. Может быть даже нелинейно.
Важность средней страницы wiki много больше важности среднего поста на форуме. Да и большинство пользователей форуме предпочитают думать перед нажатием кнопки отправки. Что так же снижает количество появления состояний "отредактировано".
Следовательно, ради добавления маловажной возможности, мы жертвуем простотой реализации. IMO, это очень плохо.
Мы значительно отошли от темы статьи, поэтому предлагаю перенести диалог, если хотите продолжить, в иное место.
Ок. Какие другие решения данной проблемы Вы можете
предложить? Или так и будем читать посты с давно
утраченным смыслом?
По поводу отображения редакций. Я пока не сделал ничего подобного по двум причинам. Первая — банальная лень, но вторая существенная. Мне кажется, что ситуаций, когда информация о редактировании реально нужна, в реальности достаточно мало. Обычно редактируются опечатки, добавляются какие-то забытые ссылки и т.д. И происходит это обычно в первые секунды/минуты после поста, когда человек его проглядывает. Отвечающему позже на этот пост знать об этом совершенно неинтересно.
Других случаев напридумывать можно много, но я захочу с ними что-то делать только когда они станут неединичными и понятными.
Мне это не нравится по сути. Синтаксис, безусловно, можно прогнуть многими различными способами, но использование фильтра в качестве мостика для вызова функций выглядит сильно неочевидным и опасно приближается к превращению шаблона в язык программирования.
Можно поставить условие на показывание состояния редактирования - к примеру, если с момента создания поста до его редактирования прошло больше пяти минут (десяти, получаса), то он считается отредактированным по сути; а не с исправленной орфографией.
В который раз повторяю что важны не комментарии
оставленные "позже"(а также откаты и проч.), а
комментарии иставленные раньше правки, т.к.
только они в и тоге могут потерять смысл.
В таком случае следующим логичным(IMO) шагом
будет запрет правки поста после внесения первого
комментария.
Вспомнил после поста Муркт.
Единственно более-менее приятной системой правок
обладает(IMHO) http://www.photosight.ru . Там править
пост можно только в течении определенного(то-ли 3
то-ли 5 минут) времени после его опубликолвания.
Где-то еще была интересная идея - править можно
только если нет ответов/цитирований поста, но это потребует боле тяжелой реализации.
Кодер, я не женского пола, и мой ник склоняется, раз уж он написан по-русски.
Извини, привык просто к никам, написанным по английски и, по привычке, просто скопипастил.
Да нет, я понял. Это как раз то, что представляется мне умозрительным, и с чем я ничего не хочу ничего делать до того, как это станет проблемой.
В ответ на теоретическую возможность, что редакция поста отменит смысл ответов, я могу придумать теоретическую возможность, что автор поста в состоянии это оценить, и не редактировать свой пост, а оставить новый ответ в духе "спасибо, уже разобрался" или что-нибудь похожее. Каких авторов будет больше — "глупых" или "умных" — понять заранее невозможно. Именно поэтому тратить время на такие проблемы имеет смысл только по мере их возникновения.
Спасибо за пример тега {% ifcanedit %}. Буквально толко что взял его за основу и сделал свой, очень полезный.
Вань, я не знаю, пользуешься ли ты форумами в действительности, но я пользуюсь.
И редактирование записей там - постоянная и необходимая практика.
Приведу конкретный пример.
На форуме macserialjunkie.com, где выкладываются хаки к мак-софту, каждый топик посвящен определенной программе. Чаще всего этот топик появляется с текстом REQ в теме, что означает, что там внутри человек просит кряк к программе.
Идет обсуждение, поиск, часто даже такой интерактивный взлом. В какой-то момент кряк находится.
Если он полноценный - REQ меняется на STW. Если не совсем полноценный, то на REQ/STW.
Также меняется и первое сообщение в топике. Туда потихоньку выползают зеркала на кряк, какие-то подробности, и тому подобное.
Главное о чем я хочу сказать - это абсолютно стандартная и популярная ситуация - редактирование первой статьи в топике. И оповещение о таком редактировании в том или ином виде - просто необходимо.
Впрочем, лучше подумать об исходном use case и решить необходимость выносить вручную ценные вещи в первую запись.
Например - позволить автору топика или модератору выделить текст в любой записи топика и нажать на кнопку "присоединить к топику" - которая либо прямо-таки вынесет выделенный текст на самый верх, или оставит наверху ссылку с кастомным текстом на текущую запись.
Серег, я не очень понял, против чего ты возражаешь. В Cicero будут редактироваться статьи, включая первую.
Но вот с необходимостью оповещения я не соглашусь. Ты описал один use case. Насколько он распространен вообще, я не знаю. Но поскольку я точно знаю, зачем я пишу этот форум — а именно, на замену своему текущему — я уверен, что там в оповещениях необходимости нет. Потому что каждый топик должен решать одну конкретную проблему, а не быть долгоживущим.
Нет, я все же напишу чуть более широко :-).
Коммуникативный софт — интересная вещь. В том смысле, что его успешность зависит от соответствия сложившимся привычкам пользователей гораздо меньше, чем у утилитарного софта. Потому что коммуникативный софт поддерживает некоторое конкретное коммьюнити, участникам которого интересно общаться друг с другом. Это самое главное, и если софт им слегка неудобен, они готовы немножко мириться. Именно поэтому в коммуникативном софте у дизайнера есть возможность забить на общую сложившуюся практику и сделать софт таким, чтобы он влиял на поведение коммьюнити. Потому что существует запас терпимости. Это конечно не означает, что над юзерами надо издеваться :-). Но направить — вполне можно.
Например, вот эти мелким решением не показывать ничего о редактировании статей я добиваюсь как раз своей цели: никто не будет продолжать один долгий топик, люди будут делать новый или писать в старый новый пост. И той же цели служит другая фича — возможность просто отцепить пост в новый топик.
Короче, такое поведение — by design.
практика использования форума не по назначению ^_^
для этого по хорошему нужно выделить отдельную сущность с флагами завершённости, с прилепленным к ней топиком и прочими присущими оной особенностями.