Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
505 changes: 505 additions & 0 deletions pootle/apps/pootle_log/formatters.py

Large diffs are not rendered by default.

Empty file.
17 changes: 17 additions & 0 deletions pootle/apps/pootle_log/templatetags/log_events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) Pootle contributors.
#
# This file is a part of the Pootle project. It is distributed under the GPL3
# or later license. See the LICENSE file for a copy of the license and the
# AUTHORS file for copyright and authorship information.

from django import template


register = template.Library()


@register.inclusion_tag("includes/log_event.html")
def log_event(event):
return dict(event=event)
30 changes: 21 additions & 9 deletions pootle/apps/pootle_log/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,6 @@ class ComparableLogEvent(BaseProxy):
if x not in ["__lt__", "__gt__", "__call__"])

def __cmp__(self, other):
# valuable revisions are authoritative
if self.revision is not None and other.revision is not None:
if self.revision > other.revision:
return 1
elif self.revision < other.revision:
return -1

# timestamps have the next priority
if self.timestamp and other.timestamp:
if self.timestamp > other.timestamp:
Expand Down Expand Up @@ -86,7 +79,7 @@ def submission_qs(self):

@property
def created_units(self):
return self.source_qs.select_related("unit", "created_by")
return self.source_qs.select_related("unit", "created_by", "unit__store")

@property
def suggestions(self):
Expand All @@ -96,7 +89,7 @@ def suggestions(self):
@property
def submissions(self):
return self.submission_qs.select_related(
"unit", "submitter", "unit__unit_source")
"unit", "submitter", "unit__unit_source", "quality_check")

@cached_property
def event(self):
Expand Down Expand Up @@ -302,6 +295,25 @@ def get_events(self, **kwargs):
for event in self.get_submission_events(**kwargs):
yield event

def get_contributors(self, **kwargs):
event_sources = kwargs.pop(
"event_sources",
("submission", "suggestion", "unit_source"))
users = set()
if "unit_source" in event_sources:
users |= set(
self.filtered_created_units().order_by().values_list(
"created_by", flat=True).distinct())
if "suggestion" in event_sources:
users |= set(
self.filtered_suggestions().order_by().values_list(
"reviewer", flat=True).distinct())
if "submission" in event_sources:
users |= set(
self.filtered_submissions().order_by().values_list(
"submitter", flat=True).distinct())
return get_user_model().objects.filter(id__in=users)


class StoreLog(Log):
include_meta = True
Expand Down
5 changes: 3 additions & 2 deletions pootle/apps/pootle_misc/templatetags/common_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ def time_since(timestamp):


@register.inclusion_tag('includes/avatar.html')
def avatar(username, email_hash, size):
def avatar(username, email_hash, size, css_class=""):
# TODO: return sprite if its a system user
if username == "system":
return dict(icon="icon-pootle")
return dict(icon="icon-pootle", css_class=css_class)
return dict(
css_class=css_class,
avatar_url=(
'https://secure.gravatar.com/avatar/%s?s=%d&d=mm'
% (email_hash, size)))
Expand Down
1 change: 1 addition & 0 deletions pootle/apps/pootle_profile/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ class PootleProfileConfig(AppConfig):

def ready(self):
importlib.import_module("pootle_profile.getters")
importlib.import_module("pootle_profile.providers")
18 changes: 18 additions & 0 deletions pootle/apps/pootle_profile/providers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) Pootle contributors.
#
# This file is a part of the Pootle project. It is distributed under the GPL3
# or later license. See the LICENSE file for a copy of the license and the
# AUTHORS file for copyright and authorship information.

from django.contrib.auth import get_user_model

from pootle.core.delegate import event_formatters
from pootle.core.plugin import provider
from pootle_log.formatters import base_formatters


@provider(event_formatters, sender=get_user_model())
def gather_user_event_formatters(**kwargs_):
return base_formatters
6 changes: 4 additions & 2 deletions pootle/apps/pootle_profile/templatetags/profile_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ def profile_activity(profile, request_lang=None):
context = dict(profile=profile)
if profile.user.is_meta:
return context
context["user_last_event"] = (
context["profile"].user.last_event(locale=request_lang))
context["user_events"] = context["profile"].get_events(n=5)
if not context["user_events"]:
context["user_last_event"] = (
context["profile"].user.last_event(locale=request_lang))
return context
33 changes: 26 additions & 7 deletions pootle/apps/pootle_profile/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
# AUTHORS file for copyright and authorship information.

from datetime import timedelta
from itertools import groupby

from django.utils import timezone
from django.utils.functional import cached_property

from pootle.core.delegate import (
comparable_event, log, membership, scores, site_languages)
comparable_event, event_formatters, log, membership, scores, site_languages)
from pootle.core.utils.templates import render_as_template
from pootle.i18n.gettext import ugettext_lazy as _

Expand All @@ -30,6 +31,14 @@ def avatar(self):
username=self.user.username,
email_hash=self.user.email_hash))

@cached_property
def tiny_avatar(self):
return render_as_template(
"{% load common_tags %}{% avatar username email_hash 13 %}",
context=dict(
username=self.user.username,
email_hash=self.user.email_hash))

@cached_property
def log(self):
return log.get(self.user.__class__)(self.user)
Expand All @@ -52,13 +61,23 @@ def display_name(self):
def get_events(self, start=None, n=None):
sortable = comparable_event.get(self.log.__class__)
start = start or (timezone.now() - timedelta(days=30))
events = sorted(
sortable(ev)
for ev
in self.log.get_events(start=start))
events = groupby(
sorted(
sortable(ev)
for ev
in self.log.get_events(start=start)),
lambda event: event.timestamp.replace(second=0))
_events = []
formatters = event_formatters.gather(self.user.__class__)
for timestamp, evts in events:
evs = list(evts)
if len(evs) == 1:
_events.append(formatters.get(evs[0].action)(evs[0]))
else:
_events.append(formatters.get("group")(self.user, evs))
if n is not None:
events = events[-n:]
return reversed(events)
_events = _events[-n:]
return reversed(_events)


class UserMembership(object):
Expand Down
62 changes: 62 additions & 0 deletions pootle/apps/pootle_store/formatters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) Pootle contributors.
#
# This file is a part of the Pootle project. It is distributed under the GPL3
# or later license. See the LICENSE file for a copy of the license and the
# AUTHORS file for copyright and authorship information.

from pootle.i18n.gettext import ugettext as _
from pootle_log.formatters import (
FormattedEvent, FormattedSubmissionEvent, UnitCreatedEvent)


class StoreUnitCreatedEvent(UnitCreatedEvent):
pass


class StoreUnitStateChangedEvent(FormattedSubmissionEvent):

@property
def message(self):
return _("State changed")


class StoreUnitTargetUpdatedEvent(FormattedSubmissionEvent):

@property
def message(self):
return _("Translation updated")


class StoreSuggestionAddedEvent(FormattedEvent):

@property
def message(self):
return _("Suggestion added")

@property
def method(self):
return ""


class StoreSuggestionAcceptedEvent(FormattedEvent):

@property
def message(self):
return _("Suggestion accepted")

@property
def method(self):
return ""


class StoreSuggestionRejectedEvent(FormattedEvent):

@property
def message(self):
return _("Suggestion rejected")

@property
def method(self):
return ""
56 changes: 56 additions & 0 deletions pootle/apps/pootle_store/panels.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) Pootle contributors.
#
# This file is a part of the Pootle project. It is distributed under the GPL3
# or later license. See the LICENSE file for a copy of the license and the
# AUTHORS file for copyright and authorship information.

from datetime import timedelta
from itertools import groupby

from django.utils import timezone

from pootle.core.delegate import event_formatters, log, profile
from pootle.core.views.panels import Panel
from pootle_log.utils import ComparableLogEvent


class StoreActivityPanel(Panel):
template_name = "browser/includes/store_activity.html"
panel_name = "store-activity"

def get_context_data(self):
ctx = {}
formatters = event_formatters.gather(self.view.object.__class__)
ctx["contributors"] = set()
start = timezone.now() - timedelta(days=30)
# start = None
event_log = log.get(self.view.object.__class__)(self.view.object)
events = groupby(
sorted(
ComparableLogEvent(ev)
for ev
in event_log.get_events(start=start)),
lambda event: (
event.timestamp and event.timestamp.replace(second=0),
(event.value.user
if event.action == "suggestion_accepted"
else event.user)))
_events = []
for timestamp, evts in events:
evs = list(evts)
if len(evs) == 1:
evt = formatters.get(evs[0].action)(evs[0])
else:
evt = formatters.get("group")(self.view.object, evs)
_events.append(evt)
ctx["contributors"] = [
profile.get(contrib.__class__)(contrib)
for contrib
in event_log.get_contributors()]
n = 10
if n is not None:
_events = _events[-n:]
ctx["events"] = reversed(_events)
return ctx
18 changes: 16 additions & 2 deletions pootle/apps/pootle_store/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@
# AUTHORS file for copyright and authorship information.

from pootle.core.delegate import (
event_formatters, format_diffs, format_syncers, format_updaters, unitid)
event_formatters, format_diffs, format_syncers, format_updaters,
panels, unitid)
from pootle.core.plugin import provider
from pootle_log.formatters import base_formatters
from pootle_log.utils import LogEvent
from pootle_store.unit import timeline
from pootle_translationproject.views import TPBrowseStoreView

from .diff import DiffableStore
from .models import Unit
from .models import Store, Unit
from .panels import StoreActivityPanel
from .syncer import StoreSyncer
from .updater import StoreUpdater
from .utils import DefaultUnitid
Expand Down Expand Up @@ -52,3 +56,13 @@ def gather_event_formatters(**kwargs_):
comment_updated=timeline.CommentUpdatedEvent,
check_muted=timeline.CheckMutedEvent,
check_unmuted=timeline.CheckUnmutedEvent)


@provider(panels, sender=TPBrowseStoreView)
def activity_panel_provider(**kwargs_):
return dict(activity=StoreActivityPanel)


@provider(event_formatters, sender=Store)
def gather_stores_event_formatters(**kwargs_):
return base_formatters
4 changes: 2 additions & 2 deletions pootle/apps/pootle_translationproject/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,6 @@ class TPStoreMixin(TPMixin):
browse_url_path = "pootle-tp-store-browse"
translate_url_path = "pootle-tp-store-translate"
is_store = True
panels = ()

@property
def permission_context(self):
Expand Down Expand Up @@ -292,10 +291,11 @@ def score_context(self):
class TPBrowseStoreView(TPStoreMixin, TPBrowseBaseView):

disabled_items = False
panel_names = ("activity", )

@property
def cache_key(self):
return ""
return self.object.pootle_path


class TPBrowseView(TPDirectoryMixin, TPBrowseBaseView):
Expand Down
9 changes: 8 additions & 1 deletion pootle/static/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -2061,7 +2061,7 @@ html[dir="rtl"] #tabs li
{
display: inline-block;
overflow: hidden;
border-radius: 50%;
border-radius: 10%;
}

.avatar-system
Expand Down Expand Up @@ -2619,3 +2619,10 @@ form.formtable tfoot .formtable-actions td
float: right;
color: #c30;
}

.evt-state-changed
{
font-style: italic;
color: #777;
font-size: 0.9em;
}
Loading