зеркало из https://github.com/mozilla/batucada.git
Merge branch 'master' of git://github.com/paulosman/batucada
This commit is contained in:
Коммит
c971afdc09
|
@ -0,0 +1,5 @@
|
|||
from activity.models import Activity, RemoteObject
|
||||
|
||||
|
||||
def send(actor, verb, object, target=None):
|
||||
pass
|
|
@ -0,0 +1,42 @@
|
|||
from django.contrib.syndication.views import Feed
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.feedgenerator import Atom1Feed
|
||||
|
||||
from activity.models import Activity
|
||||
from users.models import UserProfile
|
||||
|
||||
|
||||
class UserActivityFeed(Feed):
|
||||
"""Atom feed of user activities."""
|
||||
|
||||
feed_type = Atom1Feed
|
||||
|
||||
def author_name(self, user):
|
||||
return user.name
|
||||
|
||||
def title(self, user):
|
||||
return user.name
|
||||
|
||||
def subtitle(self, user):
|
||||
return _('Activity feed for %s' % (user.name,))
|
||||
|
||||
def link(self, user):
|
||||
return reverse('users_profile_view',
|
||||
kwargs={'username': user.username})
|
||||
|
||||
def get_object(self, request, username):
|
||||
return get_object_or_404(UserProfile, username=username)
|
||||
|
||||
def items(self, user):
|
||||
return Activity.objects.filter(actor=user)[:25]
|
||||
|
||||
def item_title(self, item):
|
||||
return item.verb
|
||||
|
||||
def item_description(self, item):
|
||||
return u"%s activity performed by %s" % (item.verb, item.actor.name)
|
||||
|
||||
def item_link(self, item):
|
||||
return u'http://blah.com'
|
|
@ -0,0 +1,24 @@
|
|||
from django.db import models
|
||||
from drumbeat.models import ModelBase
|
||||
|
||||
|
||||
class RemoteObject(models.Model):
|
||||
"""Represents an object originating from another system."""
|
||||
object_type = models.URLField(verify_exists=False)
|
||||
link = models.ForeignKey('links.Link')
|
||||
title = models.CharField(max_length=255)
|
||||
uri = models.URLField(null=True)
|
||||
created_on = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
|
||||
class Activity(ModelBase):
|
||||
"""Represents a single activity entry."""
|
||||
actor = models.ForeignKey('users.UserProfile')
|
||||
verb = models.URLField(verify_exists=False)
|
||||
status = models.ForeignKey('statuses.Status', null=True)
|
||||
project = models.ForeignKey('projects.Project', null=True)
|
||||
target_user = models.ForeignKey('users.UserProfile', null=True,
|
||||
related_name='target_user')
|
||||
remote_object = models.ForeignKey(RemoteObject, null=True)
|
||||
parent = models.ForeignKey('self', null=True)
|
||||
created_on = models.DateTimeField(auto_now_add=True)
|
|
@ -0,0 +1,63 @@
|
|||
from django.utils.translation import ugettext_lazy as _lazy
|
||||
|
||||
# a list of verbs defined in the activity schema
|
||||
verbs = {
|
||||
'favorite': 'http://activitystrea.ms/schema/1.0/favorite',
|
||||
'follow': 'http://activitystrea.ms/schema/1.0/follow',
|
||||
'like': 'http://activitystrea.ms/schema/1.0/like',
|
||||
'make-friend': 'http://activitystrea.ms/schema/1.0/make-friend',
|
||||
'join': 'http://activitystrea.ms/schema/1.0/join',
|
||||
'play': 'http://activitystrea.ms/schema/1.0/play',
|
||||
'post': 'http://activitystrea.ms/schema/1.0/post',
|
||||
'save': 'http://activitystrea.ms/schema/1.0/save',
|
||||
'share': 'http://activitystrea.ms/schema/1.0/share',
|
||||
'tag': 'http://activitystrea.ms/schema/1.0/tag',
|
||||
'update': 'http://activitystrea.ms/schema/1.0/update',
|
||||
'rsvp-yes': 'http://activitystrea.ms/schema/1.0/rsvp-yes',
|
||||
'rsvp-no': 'http://activitystrea.ms/schema/1.0/rsvp-no',
|
||||
'rsvp-maybe': 'http://activitystrea.ms/schema/1.0/rsvp-maybe',
|
||||
}
|
||||
|
||||
verbs_by_uri = {}
|
||||
for key, value in verbs.iteritems():
|
||||
verbs_by_uri[value] = key
|
||||
|
||||
past_tense = {
|
||||
'favorite': _lazy('favorited'),
|
||||
'follow': _lazy('started following'),
|
||||
'like': _lazy('liked'),
|
||||
'make-friend': _lazy('is now friends with'),
|
||||
'join': _lazy('joined'),
|
||||
'play': _lazy('played'),
|
||||
'post': _lazy('posted'),
|
||||
'save': _lazy('saved'),
|
||||
'share': _lazy('shared'),
|
||||
'tag': _lazy('tagged'),
|
||||
'update': _lazy('updated'),
|
||||
'rsvp-yes': _lazy('is attending'),
|
||||
'rsvp-no': _lazy('is not attending'),
|
||||
'rsvp-maybe': _lazy('might be attending'),
|
||||
}
|
||||
|
||||
# a list of base object types defined in the activity schema
|
||||
object_types = {
|
||||
'article': 'http://activitystrea.ms/schema/1.0/article',
|
||||
'audio': 'http://activitystrea.ms/schema/1.0/audio',
|
||||
'bookmark': 'http://activitystrea.ms/schema/1.0/bookmark',
|
||||
'comment': 'http://activitystrea.ms/schema/1.0/comment',
|
||||
'file': 'http://activitystrea.ms/schema/1.0/file',
|
||||
'folder': 'http://activitystrea.ms/schema/1.0/folder',
|
||||
'group': 'http://activitystrea.ms/schema/1.0/group',
|
||||
'note': 'http://activitystrea.ms/schema/1.0/note',
|
||||
'person': 'http://activitystrea.ms/schema/1.0/person',
|
||||
'photo': 'http://activitystrea.ms/schema/1.0/photo',
|
||||
'photo-album': 'http://activitystrea.ms/schema/1.0/photo-album',
|
||||
'place': 'http://activitystrea.ms/schema/1.0/place',
|
||||
'playlist': 'http://activitystrea.ms/schema/1.0/playlist',
|
||||
'product': 'http://activitystrea.ms/schema/1.0/product',
|
||||
'review': 'http://activitystrea.ms/schema/1.0/review',
|
||||
'service': 'http://activitystrea.ms/schema/1.0/service',
|
||||
'status': 'http://activitystrea.ms/schema/1.0/status',
|
||||
'video': 'http://activitystrea.ms/schema/1.0/video',
|
||||
'event': 'http://activitystrea.ms/schema/1.0/event',
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
from django import template
|
||||
|
||||
from activity import schema
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
def truncate(value, arg):
|
||||
"""
|
||||
Truncates a string after a given number of chars
|
||||
Argument: Number of chars to truncate after
|
||||
"""
|
||||
try:
|
||||
length = int(arg)
|
||||
except ValueError: # invalid literal for int()
|
||||
return value # Fail silently.
|
||||
if not isinstance(value, basestring):
|
||||
value = str(value)
|
||||
if (len(value) > length):
|
||||
return value[:length] + "..."
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
@register.filter
|
||||
def should_hyperlink(activity):
|
||||
if activity.verb == schema.verbs['follow'] and activity.target_user:
|
||||
return True
|
||||
if not activity.remote_object:
|
||||
return False
|
||||
if not activity.remote_object.uri:
|
||||
return False
|
||||
if activity.remote_object.object_type != schema.object_types['article']:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@register.filter
|
||||
def get_link(activity):
|
||||
if activity.remote_object and activity.remote_object.uri:
|
||||
return activity.remote_object.uri
|
||||
if activity.target_user:
|
||||
return activity.target_user.get_absolute_url()
|
||||
|
||||
|
||||
@register.filter
|
||||
def get_link_name(activity):
|
||||
if activity.remote_object:
|
||||
return activity.remote_object.title
|
||||
if activity.target_user:
|
||||
return activity.target_user.name
|
||||
|
||||
|
||||
@register.filter
|
||||
def activity_representation(activity):
|
||||
if activity.status:
|
||||
return activity.status
|
||||
if activity.remote_object:
|
||||
if activity.remote_object.title:
|
||||
return activity.remote_object.title
|
||||
return None
|
||||
|
||||
|
||||
@register.filter
|
||||
def should_show_verb(activity):
|
||||
if activity.status:
|
||||
return False
|
||||
if activity.remote_object:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@register.filter
|
||||
def friendly_verb(activity):
|
||||
try:
|
||||
verb = schema.verbs_by_uri[activity.verb]
|
||||
return schema.past_tense[verb].capitalize()
|
||||
except KeyError:
|
||||
return activity.verb
|
|
@ -0,0 +1,13 @@
|
|||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
from activity.feeds import UserActivityFeed
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^activity/(?P<activity_id>[\d]+)/', 'activity.views.index',
|
||||
name='activity_index'),
|
||||
url(r'^activity/delete/$', 'activity.views.delete',
|
||||
name='activity_delete'),
|
||||
url(r'^(?P<username>[\w-]+)/feed', UserActivityFeed(),
|
||||
name='activity_user_feed'),
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
from django.http import HttpResponse
|
||||
|
||||
|
||||
def index(request, activity_id):
|
||||
return HttpResponse('activity_index')
|
||||
|
||||
|
||||
def delete(request):
|
||||
return HttpResponse('activity_delete')
|
|
@ -27,10 +27,10 @@ def dashboard(request):
|
|||
project_ids = [p.pk for p in projects_following]
|
||||
user_ids = [u.pk for u in users_following]
|
||||
activities = Activity.objects.select_related(
|
||||
'actor', 'target', 'actor__user').filter(
|
||||
Q(actor__user__exact=request.user) |
|
||||
Q(actor__user__in=user_ids) | Q(target_id__in=project_ids) |
|
||||
Q(object_id__in=project_ids),
|
||||
'actor', 'status', 'project', 'remote_object',
|
||||
'remote_object__link').filter(
|
||||
Q(actor__exact=profile) |
|
||||
Q(actor__in=user_ids) | Q(project__in=project_ids),
|
||||
).order_by('-created_on')[0:25]
|
||||
user_projects = Project.objects.filter(created_by=profile)
|
||||
return render_to_response('dashboard/dashboard.html', {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import sys
|
||||
import urllib2
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -7,8 +6,7 @@ from celery.task import Task
|
|||
from django_push.subscriber.models import Subscription
|
||||
|
||||
from links import utils
|
||||
|
||||
import activity
|
||||
from activity.models import RemoteObject, Activity
|
||||
|
||||
|
||||
class SubscribeToFeed(Task):
|
||||
|
@ -77,22 +75,49 @@ class HandleNotification(Task):
|
|||
When a notification of a new or updated entry is received, parse
|
||||
the entry and create an activity representation of it.
|
||||
"""
|
||||
def get_activity_namespace_prefix(self, feed):
|
||||
"""Discover the prefix used for the activity namespace."""
|
||||
namespaces = feed.namespaces
|
||||
activity_prefix = [prefix for prefix, ns in namespaces.iteritems()
|
||||
if ns == 'http://activitystrea.ms/spec/1.0/']
|
||||
if activity_prefix:
|
||||
return activity_prefix[0]
|
||||
return None
|
||||
|
||||
def get_namespaced_attr(self, entry, prefix, attr):
|
||||
"""Feedparser prepends namespace prefixes to attribute names."""
|
||||
qname = '_'.join((prefix, attr))
|
||||
return getattr(entry, qname, None)
|
||||
|
||||
def create_activity_entry(self, entry, sender, activity_prefix=None):
|
||||
"""Create activity feed entries for the provided feed entry."""
|
||||
verb, object_type = None, None
|
||||
if activity_prefix:
|
||||
verb = self.get_namespaced_attr(
|
||||
entry, activity_prefix, 'verb')
|
||||
object_type = self.get_namespaced_attr(
|
||||
entry, activity_prefix, 'object-type')
|
||||
if not verb:
|
||||
verb = 'http://activitystrea.ms/schema/1.0/post'
|
||||
if not object_type:
|
||||
object_type = 'http://activitystrea.ms/schema/1.0/article'
|
||||
title = getattr(entry, 'title', None)
|
||||
uri = getattr(entry, 'link', None)
|
||||
if not (title and uri):
|
||||
return
|
||||
for link in sender.link_set.all():
|
||||
remote_obj = RemoteObject(
|
||||
link=link, title=title, uri=uri, object_type=object_type)
|
||||
remote_obj.save()
|
||||
activity = Activity(
|
||||
actor=link.user, verb=verb, remote_object=remote_obj)
|
||||
activity.save()
|
||||
|
||||
def run(self, notification, sender, **kwargs):
|
||||
"""Parse feed and create activity entries."""
|
||||
log = self.get_logger(**kwargs)
|
||||
|
||||
prefix = self.get_activity_namespace_prefix(notification)
|
||||
for entry in notification.entries:
|
||||
log.debug("Received notification of entry: %s, %s" % (
|
||||
entry.title, entry.link))
|
||||
if isinstance(entry.content, list):
|
||||
content = entry.content[0]
|
||||
if 'value' in content:
|
||||
content = content['value']
|
||||
else:
|
||||
content = entry.content
|
||||
for link in sender.link_set.all():
|
||||
activity.send(link.user.user, 'post', {
|
||||
'type': 'note',
|
||||
'title': entry.title,
|
||||
'content': content,
|
||||
})
|
||||
self.create_activity_entry(entry, sender, activity_prefix=prefix)
|
||||
|
|
|
@ -88,3 +88,31 @@ class TestLinkParsing(TestCase):
|
|||
self.assertEqual(
|
||||
'http://pubsubhubbub.appspot.com/',
|
||||
hub_url)
|
||||
|
||||
def test_normalize_url(self):
|
||||
url = '/feed.rss'
|
||||
base_url = 'http://example.com'
|
||||
self.assertEqual('http://example.com/feed.rss',
|
||||
utils.normalize_url(url, base_url))
|
||||
|
||||
def test_normalize_url_two_slashes(self):
|
||||
url = '/feed.rss'
|
||||
base_url = 'http://example.com/'
|
||||
self.assertEqual('http://example.com/feed.rss',
|
||||
utils.normalize_url(url, base_url))
|
||||
|
||||
def test_normalize_url_trailing_slash_base(self):
|
||||
url = 'feed.rss'
|
||||
base_url = 'http://example.com/'
|
||||
self.assertEqual('http://example.com/feed.rss',
|
||||
utils.normalize_url(url, base_url))
|
||||
|
||||
def test_normalize_url_no_slashes(self):
|
||||
url = 'feed.rss'
|
||||
base_url = 'http://example.com'
|
||||
self.assertEqual('http://example.com/feed.rss',
|
||||
utils.normalize_url(url, base_url))
|
||||
|
||||
def test_normalize_url_good_url(self):
|
||||
url = 'http://example.com/atom'
|
||||
self.assertEqual(url, utils.normalize_url(url, 'http://example.com'))
|
||||
|
|
|
@ -17,8 +17,15 @@ def normalize_url(url, base_url):
|
|||
parts = urlparse.urlparse(url)
|
||||
if parts.scheme and parts.netloc:
|
||||
return url # looks fine
|
||||
if not base_url:
|
||||
return url
|
||||
base_parts = urlparse.urlparse(base_url)
|
||||
return base_parts.scheme + '://' + base_parts.netloc + '/' + url
|
||||
server = '://'.join((base_parts.scheme, base_parts.netloc))
|
||||
if server[-1] != '/' and url[0] != '/':
|
||||
server = server + '/'
|
||||
if server[-1] == '/' and url[0] == '/':
|
||||
server = server[:-1]
|
||||
return server + url
|
||||
|
||||
|
||||
class FeedHandler(sax.ContentHandler):
|
||||
|
@ -85,7 +92,7 @@ def parse_feed_url(content, url=None):
|
|||
return None
|
||||
|
||||
|
||||
def parse_hub_url(content, base_url):
|
||||
def parse_hub_url(content, base_url=None):
|
||||
"""Parse the provided xml and find a hub link."""
|
||||
handler = FeedHandler()
|
||||
parser = sax.make_parser()
|
||||
|
|
|
@ -87,8 +87,11 @@ def project_creation_handler(sender, **kwargs):
|
|||
target_project=project).save()
|
||||
|
||||
try:
|
||||
import activity
|
||||
activity.send(project.created_by.user, 'post', project)
|
||||
from activity.models import Activity
|
||||
act = Activity(actor=project.created_by,
|
||||
verb='http://activitystrea.ms/schema/1.0/post',
|
||||
project=project)
|
||||
act.save()
|
||||
except ImportError:
|
||||
return
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ import logging
|
|||
|
||||
from django import http
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.shortcuts import render_to_response, get_object_or_404
|
||||
from django.template import RequestContext
|
||||
|
@ -24,10 +23,8 @@ def show(request, slug):
|
|||
if request.user.is_authenticated():
|
||||
profile = request.user.get_profile()
|
||||
is_following = profile.is_following(project)
|
||||
project_type = ContentType.objects.get_for_model(project)
|
||||
activities = Activity.objects.filter(
|
||||
target_id=project.id,
|
||||
target_content_type=project_type).order_by('-created_on')[0:10]
|
||||
project=project).order_by('-created_on')[0:10]
|
||||
nstatuses = Status.objects.filter(project=project).count()
|
||||
context = {
|
||||
'project': project,
|
||||
|
|
|
@ -8,6 +8,7 @@ from django.db.models.signals import post_save
|
|||
from django.utils.translation import ugettext as _
|
||||
|
||||
from drumbeat.models import ModelBase
|
||||
from activity.models import Activity
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -43,7 +44,6 @@ class Relationship(ModelBase):
|
|||
'to': repr(self.target_user or self.target_project),
|
||||
}
|
||||
|
||||
|
||||
admin.site.register(Relationship)
|
||||
|
||||
###########
|
||||
|
@ -55,14 +55,11 @@ def follow_handler(sender, **kwargs):
|
|||
rel = kwargs.get('instance', None)
|
||||
if not isinstance(rel, Relationship):
|
||||
return
|
||||
try:
|
||||
import activity
|
||||
if rel.target_user:
|
||||
activity.send(rel.source.user, 'follow', rel.target_user.user)
|
||||
else:
|
||||
activity.send(rel.source.user, 'follow', rel.target_project)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
activity = Activity(actor=rel.source,
|
||||
verb='http://activitystrea.ms/schema/1.0/follow')
|
||||
if rel.target_user:
|
||||
activity.target_user = rel.target_user
|
||||
else:
|
||||
activity.project = rel.target_project
|
||||
activity.save()
|
||||
post_save.connect(follow_handler, sender=Relationship)
|
||||
|
|
|
@ -5,6 +5,7 @@ from django.db import models
|
|||
from django.db.models.signals import post_save
|
||||
from django.utils.timesince import timesince
|
||||
|
||||
from activity.models import Activity
|
||||
from drumbeat.models import ModelBase
|
||||
|
||||
|
||||
|
@ -36,11 +37,12 @@ def status_creation_handler(sender, **kwargs):
|
|||
status = kwargs.get('instance', None)
|
||||
if not isinstance(status, Status):
|
||||
return
|
||||
try:
|
||||
import activity
|
||||
activity.send(
|
||||
status.author.user, 'post', status, target=status.project)
|
||||
except ImportError:
|
||||
return
|
||||
|
||||
activity = Activity(
|
||||
actor=status.author,
|
||||
verb='http://activitystrea.ms/schema/1.0/post',
|
||||
status=status,
|
||||
)
|
||||
if status.project:
|
||||
activity.project = status.project
|
||||
activity.save()
|
||||
post_save.connect(status_creation_handler, sender=Status)
|
||||
|
|
|
@ -17,6 +17,7 @@ from users.decorators import anonymous_only
|
|||
from links.models import Link
|
||||
from projects.models import Project
|
||||
from drumbeat import messages
|
||||
from activity.models import Activity
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -173,6 +174,9 @@ def profile_view(request, username):
|
|||
projects = profile.following(model=Project)
|
||||
followers = profile.followers()
|
||||
links = Link.objects.select_related('subscription').filter(user=profile)
|
||||
activities = Activity.objects.select_related(
|
||||
'actor', 'status', 'project').filter(
|
||||
actor=profile).order_by('-created_on')[0:25]
|
||||
return render_to_response('users/profile.html', {
|
||||
'profile': profile,
|
||||
'following': following,
|
||||
|
@ -181,6 +185,7 @@ def profile_view(request, username):
|
|||
'skills': profile.tags.filter(category='skill'),
|
||||
'interests': profile.tags.filter(category='interest'),
|
||||
'links': links,
|
||||
'activities': activities,
|
||||
}, context_instance=RequestContext(request))
|
||||
|
||||
|
||||
|
|
|
@ -1,21 +1,29 @@
|
|||
Django==1.2.3
|
||||
pil
|
||||
django-messages
|
||||
south
|
||||
jogging
|
||||
feedparser
|
||||
BeautifulSoup
|
||||
IPython
|
||||
django-cache-machine
|
||||
django-taggit
|
||||
djcelery
|
||||
# Development
|
||||
ipython==0.10.1
|
||||
nose==0.11.1
|
||||
|
||||
-e git://github.com/jbalogh/django-nose#egg=django-nose
|
||||
-e git://github.com/clouserw/tower.git#egg=tower
|
||||
-e git://github.com/robhudson/django-debug-toolbar.git#egg=django-debug-toolbar
|
||||
|
||||
# Production
|
||||
django==1.2.3
|
||||
python-memcached==1.45
|
||||
celery==2.0.3
|
||||
django-celery==2.0.2
|
||||
django-taggit==0.9.1
|
||||
beautifulsoup==3.2.0
|
||||
feedparser==4.1
|
||||
jogging==0.2.2
|
||||
south==0.7.3
|
||||
django-messages==0.4.4
|
||||
|
||||
-e git://github.com/jbalogh/django-cache-machine.git#egg=django-cache-machine
|
||||
-e git://github.com/jsocol/commonware.git#egg=commonware
|
||||
-e git://github.com/paulosman/python-xrd#egg=xrd
|
||||
-e git://github.com/paulosman/django-wellknown.git#egg=wellknown
|
||||
-e git://github.com/paulosman/django-activity.git#egg=activity
|
||||
-e git://github.com/jsocol/commonware.git#egg=commonware
|
||||
-e git://github.com/robhudson/django-debug-toolbar.git#egg=django-debug-toolbar
|
||||
-e git://github.com/mozilla/django-recaptcha.git#egg=django-recaptcha
|
||||
-e git://github.com/brutasse/django-push.git#egg=django_push
|
||||
|
||||
# Compiled
|
||||
MySQL-python==1.2.3c1
|
||||
PIL==1.1.7
|
||||
|
|
|
@ -4,48 +4,39 @@
|
|||
<li class="post-container">
|
||||
|
||||
<a href="#" class="reply-to">Reply</a>
|
||||
<form method="post" action="{% locale_url activity_delete %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="id" value="{{ activity.id }}">
|
||||
<a href="#" class="delete activity-delete">Delete</a>
|
||||
</form>
|
||||
|
||||
{% if activity.actor.is_remote %}
|
||||
|
||||
<img class="member-picture" width="54" height="54" src="{{ MEDIA_URL }}images/member-missing.png">
|
||||
|
||||
<div class="post-contents">
|
||||
<div class="post-details">
|
||||
{{ activity.actor.get_full_name }}
|
||||
<a href="{{ activity.get_absolute_url }}">{{ activity.created_on|timesince }} {{ _('ago') }}</a>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
|
||||
<a href="{{ activity.actor.user.get_profile.get_absolute_url }}">
|
||||
<img class="member-picture" width="54" height="54" src="{{ MEDIA_URL }}{{ activity.actor.user.get_profile.image_or_default }}">
|
||||
<a href="{{ activity.actor.get_absolute_url }}">
|
||||
<img class="member-picture" width="54" height="54" src="{{ MEDIA_URL }}{{ activity.actor.image_or_default }}">
|
||||
</a>
|
||||
|
||||
<div class="post-contents">
|
||||
|
||||
<div class="post-details">
|
||||
<a class="member-name" href="{{ activity.actor.user.get_profile.get_absolute_url }}">{{ activity.actor.user.get_profile.name }}</a>
|
||||
<a href="{{ activity.get_absolute_url }}">{{ activity.created_on|timesince }} {{ _(' ago') }}</a>
|
||||
<a class="member-name" href="{{ activity.actor.get_absolute_url }}">{{ activity.actor.name }}</a>
|
||||
{{ activity.created_on|timesince }} {{ _(' ago') }}
|
||||
{% if activity.remote_object %}
|
||||
{{ _('via ') }}<a href="{{ activity.remote_object.link.url }}">{{ activity.remote_object.link.name }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
<div class="post-body">
|
||||
{% if activity.verb == 'http://activitystrea.ms/schema/1.0/post' %}
|
||||
{{ activity.object|urlize }}
|
||||
{% if activity|should_show_verb %}
|
||||
{{ activity|friendly_verb }}
|
||||
{% endif %}
|
||||
{% if activity|should_hyperlink %}
|
||||
{% if activity.target_user %}
|
||||
<a href="{{ activity|get_link }}">{{ activity|get_link_name }}</a>
|
||||
{% else %}
|
||||
{{ activity|get_link_name }} <a href="{{ activity|get_link }}">{{ activity|get_link|truncate:50 }}</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{{ activity.verb|friendly }} <a href="{{ activity.obj.get_absolute_url }}">{{ activity.object }}</a>
|
||||
{{ activity|activity_representation }}
|
||||
{% endif %}
|
||||
</div> <!-- /.post-body -->
|
||||
|
||||
{% if activity.target %}
|
||||
{% if activity.project %}
|
||||
<ul class="post-tags">
|
||||
<li><a href="{{ activity.target.get_absolute_url }}">{{ activity.target }}</a></li>
|
||||
<li><a href="{{ activity.project.get_absolute_url }}">{{ activity.project }}</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
|
|
2
urls.py
2
urls.py
|
@ -8,7 +8,7 @@ urlpatterns = patterns('',
|
|||
(r'^admin/', include(admin.site.urls)),
|
||||
(r'', include('dashboard.urls')),
|
||||
(r'', include('wellknown.urls')),
|
||||
(r'^activity/', include('activity.urls')),
|
||||
(r'', include('activity.urls')),
|
||||
(r'^statuses/', include('statuses.urls')),
|
||||
(r'^projects/', include('projects.urls')),
|
||||
(r'^events/', include('events.urls')),
|
||||
|
|
Загрузка…
Ссылка в новой задаче