В прошлой части я рассказал, как использую Redis в Django для показа онлайн пользователей. Во второй же части я расскажу, как сделать показ новых комментариев для постов (количество у каждого поста и подсветка новых в посте).
Разберемся сначала, из каких частей будет состоять вся “индикация”:
1. Тэг, показывающий сколько новых комментариев в топике у данного пользователя
2. Функция, возвращающая номера новых комментариев в посте и удаляющая их из базы
3. Функция добавляющая новый комментарий в список ‘новых’ для всех пользователей
Для понимания работы этих функций, рассмотрим ключи в базе:
‘users:%username%:posts’ - содержит номера новых постов
‘users:%username%:%post.id%’ - содержит id комментариев для поста
‘users:%username%’ - общее количество новых комментариев
Теперь, рассмотрим функции по работе с базой и их применение для каждого случая.
Все начинается с того, что при сохранении комментария его id записывается в базу для каждого юзера:
def add_comment(sender, **kwargs):
comment = kwargs['instance']
if redis_db.exists('users'):
users = redis_db.smembers('users')
if comment.author.username not in users:
redis_db.sadd('users', comment.author.username)
users.add(comment.author.username)
else:
redis_db.sadd('users', comment.author.username)
users = redis_db.smembers('users')
for user in users:
if user == comment.author.username:
continue
if redis_db.exists('users:%s' % user):
if int(redis_db.get('users:%s' % user)) < getattr(settings, 'UNREAD_LIMIT', 100):
redis_db.incr('users:%s' % user)
redis_db.sadd('users:%s:%s' % (user, comment.post.id), comment.id)
redis_db.sadd('users:%s:posts' % user, comment.post.id)
else:
continue
else:
redis_db.set('users:%s' % user, 0)
Работает код просто, сначала достаем список со всеми пользователями (ключ ‘users’), а потом проходимся по каждому и добавляем номер комментария в ключ с номером поста (‘users:%username%:%post.id%’) и увеличиваем количество новых комментариев.
Данная функция сделана как сигнал и используется при сохранении комментария:
post_save.connect(add_comment, sender=Comment)
Для отображения количества новых комментариев у каждого поста, используется такой тэг:
register = template.Library()
@register.tag(name='newcom_count')
def get_count_newcom(parser, token):
bits = token.split_contents()
if len(bits) == 3:
return NewcomCountNode(bits[1], bits[2])
elif len(bits) == 5 and bits[3] == 'as':
return NewcomCountNode(bits[1], bits[2], bits[4])
else:
raise template.TemplateSyntaxError, "%r tag requires a two argument, username and post_id" % token.contents.split()[0]
class NewcomCountNode(template.Node):
def __init__(self, username, count, varname=None):
self.username = template.Variable(username)
self.count = template.Variable(count)
self.varname = varname
def render(self, context):
try:
username = self.username.resolve(context)
except template.VariableDoesNotExist:
username = ''
try:
count = self.count.resolve(context)
except template.VariableDoesNotExist:
count = 1
if self.varname:
context[self.varname] = get_post_newcom(username, count)
return ''
return get_post_newcom(username, count)
Он используется в темплейтах, как
{% newcom_count user.username post.id as newcom %}
Функция для работы с базой:
def get_post_newcom(username, post_id):
try:
count = redis_db.scard('users:%s:%s' % (username, post_id))
except:
count = 0
return int(count)
Она всего-лишь достает количество новых комментариев, если таких нет, то вернет 0.
В самом посте, чтобы узнать какой комментарий является новым, используем такую функцию:
def del_comment(post, username):
if redis_db.srem('users:%s:posts' % username, post.id):
comments = redis_db.smembers('users:%s:%s' % (username, post.id))
redis_db.delete('users:%s:%s' % (username, post.id))
count = int(redis_db.get('users:%s' % username))
redis_db.set('users:%s' % username, count - len(comments))
else:
comments = []
return [int(x) for x in comments]
На вход она принимает instance post и имя пользователя, а на выход отдает список с id комментариев. В своей работе, она также удаляет из ключа новые посты. Передача его в шаблон, позволяет простой проверкой узнавать, новый или нет комментарий.
Я использую во вьюхе вывода поста:
newcomments = del_comment(post, request.user.username)
А в шаблоне делаю проверку:
{% if comment.id in newcom %}
На этом все и заканчивается. Как видно из кода, все достаточно просто, по сравнению с использованием реляционных баз.