Skip to content
Browse files

Store recent logs for resources in redis and expose them through a te…

…mplatetag.
  • Loading branch information...
1 parent 1801c09 commit 293cc1789c13ffafc84468a7f55d1a8bd511ed91 Apostolos Bessas committed
View
0 transifex/actionlog/management/__init__.py
No changes.
View
0 transifex/actionlog/management/commands/__init__.py
No changes.
View
48 transifex/actionlog/management/commands/tx_resource_history_init.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+from django.core.management.base import BaseCommand, CommandError
+from django.db.models import get_model
+from django.contrib.contenttypes.models import ContentType
+from transifex.actionlog.models import LogEntry
+from transifex.actionlog.queues import redis_key_for_resource
+from datastores.txredis import TxRedisMapper
+
+
+class Command(BaseCommand):
+
+ help = (
+ "Populate the latest history of a resource.\n"
+ "You can specify the slug(s) of a project to initialize "
+ "only the resources of those projects."
+ )
+
+ def handle(self, *args, **options):
+ Resource = get_model('resources', 'Resource')
+ if not args:
+ resources = Resource.objects.all().iterator()
+ else:
+ resources = Resource.objects.filter(
+ project__slug__in=args
+ ).iterator()
+ for idx, r in enumerate(resources):
+ if int(options.get('verbosity')) > 1:
+ self.stdout.write("Resource %d: %s\n" % (idx, r.slug))
+ self._populate_history(r)
+
+ def _populate_history(self, resource):
+ """Store the latest action log items for the specified resources."""
+ Resource = get_model('resources', 'Resource')
+ entries = LogEntry.objects.filter(
+ content_type=ContentType.objects.get_for_model(Resource),
+ object_id=resource.id
+ )[:5]
+ r = TxRedisMapper()
+ key = redis_key_for_resource(resource)
+ for entry in entries:
+ data = {
+ 'action_time': entry.action_time,
+ 'message': entry.message,
+ }
+ r.lpush(key, data=data)
+ r.ltrim(key, 0, 4)
+
View
74 transifex/actionlog/models.py
@@ -1,3 +1,4 @@
+from __future__ import absolute_import
import datetime
from django.db import models
from django.db.models import get_model
@@ -11,14 +12,7 @@
from django.utils.translation import get_language, activate
from notification.models import NoticeType
from transifex.txcommon.log import logger
-try:
- from datastores import TxRedisMapper, ConnectionError, \
- redis_exception_handler
- USE_REDIS = True
-except ImportError, e:
- logger.debug("Redis backend not imported.")
- USE_REDIS = False
-
+from .queues import log_to_queues
def _get_formatted_message(label, context):
"""
@@ -265,67 +259,7 @@ def action_logging(user, object_list, action_type, message=None, context=None):
action_time = time,
message = message)
l.save()
- if USE_REDIS:
- _log_to_queues(object, user.pk, time, message)
+ if settings.USE_REDIS:
+ log_to_queues(object, user.pk, time, message)
except TypeError:
raise TypeError("The 'object_list' parameter must be iterable")
-
-
-def _log_to_queues(o, user_id, action_time, message):
- """Log actions to redis' queues."""
- from transifex.projects.models import Project
-
- try:
- if isinstance(o, Project):
- _log_to_recent_project_actions(o, user_id, action_time, message)
- _log_to_project_history(o, action_time, message)
- except ConnectionError, e:
- logger.critical("Cannot connect to redis: %s" % e, exc_info=True)
- return
-
-
-def _log_to_recent_project_actions(p, user_id, action_time, message):
- """Log actions that refer to projects to a queue of most recent actions.
-
- We use redis' list for that. We skip actions that refer to private projects.
- """
- from transifex.projects.models import Project
- if p.private:
- return
- private_slugs = Project.objects.filter(
- private=True
- ).values_list('slug', flat=True)
- for slug in private_slugs:
- if ('/projects/p/%s/' % slug) in message:
- return
-
- key = 'event_feed'
- data = {
- 'name': force_unicode(p)[:200],
- 'user_id': user_id,
- 'action_time': action_time,
- 'message': message
- }
- try:
- r = TxRedisMapper()
- r.lpush(key, data=data)
- r.ltrim(key, 0, 11)
- except ConnectionError, e:
- logger.critical("Cannot connect to redis: %s" % e, exc_info=True)
- raise
- except Exception, e:
- msg = "Error saving latest event to redis: %s"
- logger.error(msg % e, exc_info=True)
-
-
-@redis_exception_handler
-def _log_to_project_history(project, action_time, message):
- """Log a message to a project's history queue."""
- Project = get_model('projects', 'Project')
- data = {
- 'action_time': action_time,
- 'message': message,
- }
- r = TxRedisMapper()
- r.lpush(project.recent_history_key, data=data)
- r.ltrim(key, 0, 4)
View
85 transifex/actionlog/queues.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+
+"""
+Redis related stuff for action logs.
+"""
+
+from django.db.models import get_model
+from django.utils.encoding import force_unicode
+from transifex.txcommon.log import logger
+from datastores.txredis import TxRedisMapper, redis_exception_handler
+
+
+def redis_key_for_resource(resource):
+ return 'resource:history:%s:%s' % (resource.project_id, resource.slug)
+
+
+def redis_key_for_project(project):
+ return 'project:history:%s' % project.slug
+
+
+@redis_exception_handler
+def log_to_queues(o, user_id, action_time, message):
+ """Log actions to redis' queues."""
+ Project = get_model('projects', 'Project')
+ Resource = get_model('resources', 'Resource')
+ if isinstance(o, Project):
+ _log_to_recent_project_actions(o, user_id, action_time, message)
+ _log_to_project_history(o, action_time, message)
+ elif isinstance(o, Resource):
+ _log_to_resource_history(o, action_time, message)
+
+
+def _log_to_recent_project_actions(p, user_id, action_time, message):
+ """Log actions that refer to projects to a queue of most recent actions.
+
+ We use redis' list for that. We skip actions that refer to private projects.
+ """
+ Project = get_model('projects', 'Project')
+ if p.private:
+ return
+ private_slugs = Project.objects.filter(
+ private=True
+ ).values_list('slug', flat=True)
+ for slug in private_slugs:
+ if ('/projects/p/%s/' % slug) in message:
+ return
+
+ key = 'event_feed'
+ data = {
+ 'name': force_unicode(p)[:200],
+ 'user_id': user_id,
+ 'action_time': action_time,
+ 'message': message
+ }
+ r = TxRedisMapper()
+ r.lpush(key, data=data)
+ r.ltrim(key, 0, 11)
+
+
+@redis_exception_handler
+def _log_to_project_history(project, action_time, message):
+ """Log a message to a project's history queue."""
+ Project = get_model('projects', 'Project')
+ key = project.recent_history_key
+ data = {
+ 'action_time': action_time,
+ 'message': message,
+ }
+ r = TxRedisMapper()
+ r.lpush(key, data=data)
+ r.ltrim(key, 0, 4)
+
+
+@redis_exception_handler
+def _log_to_resource_history(resource, action_time, message):
+ """Log a message to a respurce's history queue."""
+ Resource = get_model('resource', 'Resource')
+ key = redis_key_for_resource(resource)
+ data = {
+ 'action_time': action_time,
+ 'message': message,
+ }
+ r = TxRedisMapper()
+ r.lpush(key, data=data)
+ r.ltrim(key, 0, 4)
View
49 transifex/actionlog/templatetags/tx_action_log.py
@@ -1,5 +1,9 @@
from django import template
+from django.conf import settings
+from django.db.models import get_model
from actionlog.models import LogEntry
+from actionlog.queues import redis_key_for_resource
+from datastores.txredis import TxRedisMapper, redis_exception_handler
register = template.Library()
@@ -68,5 +72,50 @@ def __call__(self, parser, token):
"Fourth argument in '%s' must be either 'user' or "
"'object'" % self.tag_name)
+
+class RecentLogNode(template.Node):
+ """Node to get the most recent action logs for an item."""
+
+ def __init__(self, model, obj, key_func, context_var):
+ self.model = model
+ self.obj = obj
+ self.key_func = key_func
+ self.context_var = context_var
+
+ def render(self, context):
+ self.obj = template.Variable(self.obj).resolve(context)
+ redis_key = self.key_func(self.obj)
+ events = self._action_logs_from_redis(redis_key)
+ if events is None:
+ events = LogEntry.objects.filter(
+ content_type=ContentType.objects.get_for_model(self.model),
+ object_id=self.obj.id
+ )
+ context[self.context_var] = events
+
+ @redis_exception_handler
+ def _action_logs_from_redis(self, key):
+ """Get the action logs for the key from redis."""
+ if not settings.USE_REDIS:
+ return None
+ r = TxRedisMapper()
+ return r.lrange(key, 0, -1)
+
+
+def recent_resource_log(parser, token):
+ """Return the most recent logs of the specified resource."""
+ tokens = token.split_contents()
+ if len(tokens) != 4:
+ msg = "Wrong number of arguments for %s."
+ raise template.TemplateSyntaxError(msg % tokens[0])
+ elif tokens[2] != 'as':
+ msg = "Wrong syntax for %s: third argument must be the keyword 'as'"
+ raise template.TemplateSyntaxError(msg % tokens[0])
+ resource = tokens[1]
+ context_var = tokens[3]
+ Resource = get_model('resources', 'Resource')
+ return RecentLogNode(Resource, resource, redis_key_for_resource, context_var)
+
register.tag('get_log', DoGetLog('get_log'))
register.tag('get_public_log', DoGetLog('get_public_log'))
+register.tag('recent_resource_log', recent_resource_log)
View
1 transifex/settings/21-datastores.conf
@@ -1,2 +1,3 @@
REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379
+USE_REDIS = True
View
12 transifex/templates/resources/resource_detail.html
@@ -98,7 +98,7 @@
<h3 class="sh-label">{% blocktrans %}Details{% endblocktrans %}</h3>
{% if perms.projects.edit_resource or is_maintainer or request.user.is_superuser %}
<div class="separate-buttons"><a class="i16 edit nude-button" href="{% url resource_edit project_slug=resource.project.slug resource_slug=resource.slug %}">{% trans "Edit resource" %}</a></div>
- {% endif %}
+ {% endif %}
</div>
@@ -118,7 +118,7 @@ <h3 class="sh-label">{% blocktrans %}Details{% endblocktrans %}</h3>
{% trans "No translations accepted" %}
{% endif %}
</dd>
-
+
{% if resource.priority %}
{% with resource.priority.level as priority_level %}
{% with resource.priority.display_level as display_level %}
@@ -156,7 +156,7 @@ <h3 class="sh-label">{% blocktrans %}Details{% endblocktrans %}</h3>
{% get_permission "project_perm.maintain" for request.user and resource.project as "is_maintainer" %}
<div class="separate-header clearfix">
<h3 class="sh-label">{% blocktrans with resource.available_languages|length as num_of_langs %}Available languages ({{ num_of_langs }}){% endblocktrans %}</h3>
- {% if can_submit_translation and resource.accept_translations or is_maintainer %}
+ {% if can_submit_translation and resource.accept_translations or is_maintainer %}
<div class="separate-buttons"><a id="new_translation1" class="i16 nude-button add tipsy_enable linkstyle" title="{% blocktrans %}If you cannot see your language, click the button below to translate( You must belong to a language team of this project, to be able to translate! ){% endblocktrans %}">{% trans "Add new translation" %}</a></div>
{% endif %}
</div>
@@ -192,7 +192,7 @@ <h3 class="sh-label">{% blocktrans with resource.available_languages|length as
<img class="tipsy_enable bullet" src="{{ STATIC_URL }}images/icons/bullet_lock.png" title="{% trans "Locked" %}"/>
{% endif %}
</td>
- <td class="tablecompletion">
+ <td class="tablecompletion">
{% with 200 as barwidth %}
{% stats_bar_simple stat barwidth %}
{% endwith %}
@@ -230,14 +230,14 @@ <h3 class="sh-label">{% trans 'History' %}</h3>
</div>
{% load tx_action_log %}
-{% get_log 5 as action_log for_object resource %}
+{% recent_resource_log resource as action_log %}
{% if not action_log %}
<p class="i16 infomsg">{% trans 'No actions recorded yet :)' %}</p>
{% else %}
<ul class="actionlist nomargin">
{% for entry in action_log %}
<li class="i16 {{entry.action_type}}">
- <p>{{ entry.message|safe }}</p>
+ <p>{{ entry.message|safe }}</p>
<span class="timestamp">{{ entry.action_time|timesince }} ago</span>
</li>
{% endfor %}

0 comments on commit 293cc17

Please sign in to comment.
Something went wrong with that request. Please try again.