Browse Source

- Little cleanup in UserCP
- Profiles rely on API
- User Threads and Posts list

Ralfp 12 years ago
parent
commit
5643e3e5f3

+ 0 - 0
misago/profiles/content/__init__.py


+ 4 - 0
misago/profiles/content/profile.py

@@ -0,0 +1,4 @@
+from django.utils.translation import ugettext_lazy as _
+
+def register_profile_extension(request):
+    return (('user_posts', _('Posts and Threads')),)

+ 21 - 0
misago/profiles/content/urls.py

@@ -0,0 +1,21 @@
+from django.conf.urls import patterns, url
+
+def register_profile_urls(first=False):
+    urlpatterns = []
+    if first:
+        urlpatterns += patterns('misago.profiles.content.views',
+            url(r'^$', 'posts', name="user"),
+            url(r'^$', 'posts', name="user_posts"),
+            url(r'^(?P<page>\d+)/$', 'posts', name="user_posts"),
+            url(r'^threads/$', 'threads', name="user_threads"),
+        )
+    else:
+        urlpatterns += patterns('misago.profiles.content.views',
+            url(r'^posts/$', 'posts', name="user_posts"),
+            url(r'^posts/(?P<page>\d+)/$', 'posts', name="user_posts"),
+            url(r'^threads/$', 'threads', name="user_threads"),
+        )
+    urlpatterns += patterns('misago.profiles.content.views',
+        url(r'^threads/(?P<page>\d+)/$', 'threads', name="user_threads"),
+    )
+    return urlpatterns

+ 32 - 0
misago/profiles/content/views.py

@@ -0,0 +1,32 @@
+from misago.profiles.decorators import profile_view
+from misago.profiles.template import RequestContext
+from misago.utils import make_pagination
+
+@profile_view('user_posts')
+def posts(request, user, page=0):
+    queryset = user.post_set.filter(forum_id__in=request.acl.threads.get_readable_forums(request.acl)).filter(deleted=False).filter(moderated=False).select_related('thread', 'forum').order_by('-id')
+    count = queryset.count()
+    pagination = make_pagination(page, count, 40)
+    return request.theme.render_to_response('profiles/content_posts.html',
+                                            context_instance=RequestContext(request, {
+                                             'profile': user,
+                                             'tab': 'posts',
+                                             'items_total': count,
+                                             'items': queryset[pagination['start']:pagination['stop']],
+                                             'pagination': pagination,
+                                             }));
+
+
+@profile_view('user_threads')
+def threads(request, user, page=0):
+    queryset = user.thread_set.filter(forum_id__in=request.acl.threads.get_readable_forums(request.acl)).filter(deleted=False).filter(moderated=False).select_related('start_post', 'forum').order_by('-id')
+    count = queryset.count()
+    pagination = make_pagination(page, count, 4)
+    return request.theme.render_to_response('profiles/content_threads.html',
+                                            context_instance=RequestContext(request, {
+                                             'profile': user,
+                                             'tab': 'posts',
+                                             'items_total': count,
+                                             'items': queryset[pagination['start']:pagination['stop']],
+                                             'pagination': pagination,
+                                             }));

+ 22 - 0
misago/profiles/decorators.py

@@ -0,0 +1,22 @@
+from functools import wraps
+from misago.utils import slugify
+from misago.views import error404
+from misago.users.models import User
+
+def profile_view(fallback='user'):
+    def outer_decorator(f):
+        def inner_decorator(request, user, username, *args, **kwargs):
+            request = request
+            user_pk = int(user)
+            user_slug = username
+            try:
+                user = User.objects.get(pk=user_pk)
+                if user.username_slug != user_slug:
+                    # Force crawlers to take notice of updated username
+                    return redirect(reverse(fallback, args=(user.username_slug, user.pk)), permanent=True)
+                return f(request, user, *args, **kwargs)
+            except User.DoesNotExist:
+                return error404(request)
+    
+        return wraps(f)(inner_decorator)
+    return outer_decorator

+ 0 - 0
misago/profiles/details/__init__.py


+ 4 - 0
misago/profiles/details/profile.py

@@ -0,0 +1,4 @@
+from django.utils.translation import ugettext_lazy as _
+
+def register_profile_extension(request):
+    return (('user_details', _('Profile Details')),)

+ 14 - 0
misago/profiles/details/urls.py

@@ -0,0 +1,14 @@
+from django.conf.urls import patterns, url
+
+def register_profile_urls(first=False):
+    urlpatterns = []
+    if first:
+        urlpatterns += patterns('misago.profiles.details.views',
+            url(r'^$', 'details', name="user"),
+            url(r'^$', 'details', name="user_details"),
+        )
+    else:
+        urlpatterns += patterns('misago.profiles.details.views',
+            url(r'^details/$', 'details', name="user_details"),
+        )
+    return urlpatterns

+ 10 - 0
misago/profiles/details/views.py

@@ -0,0 +1,10 @@
+from misago.profiles.decorators import profile_view
+from misago.profiles.template import RequestContext
+
+@profile_view('user_details')
+def details(request, user):
+    return request.theme.render_to_response('profiles/details.html',
+                                            context_instance=RequestContext(request, {
+                                             'profile': user,
+                                             'tab': 'details',
+                                             }));

+ 0 - 0
misago/profiles/followers/__init__.py


+ 4 - 0
misago/profiles/followers/profile.py

@@ -0,0 +1,4 @@
+from django.utils.translation import ugettext_lazy as _
+
+def register_profile_extension(request):
+    return (('user_followers', _('Followers')),)

+ 14 - 0
misago/profiles/followers/urls.py

@@ -0,0 +1,14 @@
+from django.conf.urls import patterns, url
+
+def register_profile_urls(first=False):
+    urlpatterns = []
+    if first:
+        urlpatterns += patterns('misago.profiles.followers.views',
+            url(r'^$', 'followers', name="user"),
+            url(r'^$', 'followers', name="user_followers"),
+        )
+    else:
+        urlpatterns += patterns('misago.profiles.followers.views',
+            url(r'^followers/$', 'followers', name="user_followers"),
+        )
+    return urlpatterns

+ 10 - 0
misago/profiles/followers/views.py

@@ -0,0 +1,10 @@
+from misago.profiles.decorators import profile_view
+from misago.profiles.template import RequestContext
+
+@profile_view('user_followers')
+def followers(request, user):
+    return request.theme.render_to_response('profiles/profile.html',
+                                            context_instance=RequestContext(request, {
+                                             'profile': user,
+                                             'tab': 'followers',
+                                             }));

+ 0 - 0
misago/profiles/follows/__init__.py


+ 4 - 0
misago/profiles/follows/profile.py

@@ -0,0 +1,4 @@
+from django.utils.translation import ugettext_lazy as _
+
+def register_profile_extension(request):
+    return (('user_follows', _('Follows')),)

+ 14 - 0
misago/profiles/follows/urls.py

@@ -0,0 +1,14 @@
+from django.conf.urls import patterns, url
+
+def register_profile_urls(first=False):
+    urlpatterns = []
+    if first:
+        urlpatterns += patterns('misago.profiles.follows.views',
+            url(r'^$', 'follows', name="user"),
+            url(r'^$', 'follows', name="user_follows"),
+        )
+    else:
+        urlpatterns += patterns('misago.profiles.follows.views',
+            url(r'^follows/$', 'follows', name="user_follows"),
+        )
+    return urlpatterns

+ 10 - 0
misago/profiles/follows/views.py

@@ -0,0 +1,10 @@
+from misago.profiles.decorators import profile_view
+from misago.profiles.template import RequestContext
+
+@profile_view('user_follows')
+def follows(request, user):
+    return request.theme.render_to_response('profiles/profile.html',
+                                            context_instance=RequestContext(request, {
+                                             'profile': user,
+                                             'tab': 'follows',
+                                             }));

+ 24 - 0
misago/profiles/template.py

@@ -0,0 +1,24 @@
+from django.conf import settings
+from django.template import RequestContext as DjangoRequestContext
+from django.utils.importlib import import_module
+
+def RequestContext(request, context=None):
+    if not context:
+        context = {}
+    context['tabs'] = []
+    for extension in settings.PROFILE_EXTENSIONS:
+        profile_module = import_module(extension + '.profile')
+        try:
+            append_links = profile_module.register_profile_extension(request)
+            if append_links:
+                for link in append_links:
+                    link = list(link)
+                    token = link[0][link[0].find('_') + 1:]
+                    context['tabs'].append({
+                                            'route': link[0],
+                                            'active': context['tab'] == token,
+                                            'name': link[1],
+                                            })
+        except AttributeError:
+            pass
+    return DjangoRequestContext(request, context)

+ 19 - 7
misago/profiles/urls.py

@@ -1,11 +1,23 @@
-from django.conf.urls import patterns, url
+from django.conf import settings
+from django.conf.urls import patterns, include, url
+from django.utils.importlib import import_module
 
 
 urlpatterns = patterns('misago.profiles.views',
 urlpatterns = patterns('misago.profiles.views',
     url(r'^$', 'list', name="users"),
     url(r'^$', 'list', name="users"),
-    url(r'^(?P<username>\w+)-(?P<user>\d+)/$', 'profile', name="user"),
-    url(r'^(?P<username>\w+)-(?P<user>\d+)/threads/$', 'profile', name="user_threads", kwargs={'tab': 'threads'}),
-    url(r'^(?P<username>\w+)-(?P<user>\d+)/following/$', 'profile', name="user_following", kwargs={'tab': 'following'}),
-    url(r'^(?P<username>\w+)-(?P<user>\d+)/followiers/$', 'profile', name="user_followers", kwargs={'tab': 'followers'}),
-    url(r'^(?P<username>\w+)-(?P<user>\d+)/details/$', 'profile', name="user_details", kwargs={'tab': 'details'}),
-    url(r'^(?P<rank_slug>(\w|-)+)/$', 'list', name="users"),
 )
 )
+
+# Build extensions URLs
+iteration = 0
+for extension in settings.PROFILE_EXTENSIONS:
+    iteration += 1
+    profile_extension = import_module(extension + '.urls')
+    try:
+        urlpatterns += patterns('',
+            (r'^(?P<username>\w+)-(?P<user>\d+)/', include(profile_extension.register_profile_urls(iteration == 1))),
+        )
+    except AttributeError:
+        pass
+
+urlpatterns += patterns('misago.profiles.views',
+    url(r'^(?P<rank_slug>(\w|-)+)/$', 'list', name="users"),
+)

+ 0 - 58
misago/profiles/views.py

@@ -9,7 +9,6 @@ from misago.users.models import User
 from misago.utils import slugify
 from misago.utils import slugify
 from misago.views import error404
 from misago.views import error404
 
 
-
 def list(request, rank_slug=None):
 def list(request, rank_slug=None):
     ranks = Rank.objects.filter(as_tab=1).order_by('order')
     ranks = Rank.objects.filter(as_tab=1).order_by('order')
 
 
@@ -74,60 +73,3 @@ def list(request, rank_slug=None):
                                          'users': users,
                                          'users': users,
                                         },
                                         },
                                         context_instance=RequestContext(request));
                                         context_instance=RequestContext(request));
-
-
-def profile(request, user, username, tab='posts'):
-    user = int(user)
-    try:
-        user = User.objects.get(pk=user)
-        if user.username_slug != username:
-            # Force crawlers to take notice of updated username
-            return redirect(reverse('user', args=(user.username_slug, user.pk)), permanent=True)
-        return globals()['profile_%s' % tab](request, user)
-    except User.DoesNotExist:
-        return error404(request)
-
-
-def profile_posts(request, user):
-    return request.theme.render_to_response('profiles/profile.html',
-                                            {
-                                             'profile': user,
-                                             'tab': 'posts',
-                                            },
-                                            context_instance=RequestContext(request));
-
-
-def profile_threads(request, user):
-    return request.theme.render_to_response('profiles/profile.html',
-                                            {
-                                             'profile': user,
-                                             'tab': 'threads',
-                                            },
-                                            context_instance=RequestContext(request));
-
-
-def profile_following(request, user):
-    return request.theme.render_to_response('profiles/profile.html',
-                                            {
-                                             'profile': user,
-                                             'tab': 'following',
-                                            },
-                                            context_instance=RequestContext(request));
-
-
-def profile_followers(request, user):
-    return request.theme.render_to_response('profiles/profile.html',
-                                            {
-                                             'profile': user,
-                                             'tab': 'followers',
-                                            },
-                                            context_instance=RequestContext(request));
-
-
-def profile_details(request, user):
-    return request.theme.render_to_response('profiles/details.html',
-                                            {
-                                             'profile': user,
-                                             'tab': 'details',
-                                            },
-                                            context_instance=RequestContext(request));

+ 4 - 2
misago/settings_base.py

@@ -120,8 +120,10 @@ USERCP_EXTENSIONS = (
 
 
 # List of User Profile extensions
 # List of User Profile extensions
 PROFILE_EXTENSIONS = (
 PROFILE_EXTENSIONS = (
-    '',
-    '',
+    'misago.profiles.content',
+    'misago.profiles.follows',
+    'misago.profiles.followers',
+    'misago.profiles.details',
 )
 )
 
 
 # List of Markdown Extensions
 # List of Markdown Extensions

+ 25 - 9
misago/template/templatetags/django2jinja.py

@@ -16,15 +16,6 @@ def query_string(**kwargs):
     query = urllib.urlencode(kwargs)
     query = urllib.urlencode(kwargs)
     return '?%s' % (query if kwargs else '')
     return '?%s' % (query if kwargs else '')
 
 
-
-@register.filter(name='markdown')
-def parse_markdown(value, format=None):
-    import markdown
-    if not format:
-        format = settings.OUTPUT_FORMAT
-    return markdown.markdown(value, safe_mode='escape', output_format=format)
-
-
 @register.filter(name='low')
 @register.filter(name='low')
 def low(value):
 def low(value):
     if not value:
     if not value:
@@ -40,6 +31,31 @@ def low(value):
 def slugify_tag(format_string):
 def slugify_tag(format_string):
     return slugify(format_string)
     return slugify(format_string)
 
 
+
+"""
+Markdown filters
+"""
+@register.filter(name='markdown')
+def parse_markdown(value, format=None):
+    import markdown
+    if not format:
+        format = settings.OUTPUT_FORMAT
+    return markdown.markdown(value, safe_mode='escape', output_format=format)
+
+@register.filter(name='markdown_short')
+def short_markdown(value, length=300):
+    if len(value) <= length:
+        return ' '.join(value.splitlines())
+    value = ' '.join(value.splitlines())
+    value = value[0:length]
+    while value[-1] != ' ':
+        value = value[0:-1]
+    value = value.strip()
+    if value[-3:3] != '...':
+        value = '%s...' % value
+    return value
+
+
 """
 """
 Date and time filters
 Date and time filters
 """
 """

+ 17 - 17
misago/usercp/avatar/views.py

@@ -20,8 +20,8 @@ def avatar_view(f):
         if request.user.avatar_ban:
         if request.user.avatar_ban:
             return request.theme.render_to_response('usercp/avatar_banned.html',
             return request.theme.render_to_response('usercp/avatar_banned.html',
                                                     context_instance=RequestContext(request, {
                                                     context_instance=RequestContext(request, {
-                                                        'tab': 'avatar',
-                                                    }));
+                                                     'tab': 'avatar',
+                                                     }));
         return f(*args, **kwargs)
         return f(*args, **kwargs)
     return decorator
     return decorator
 
 
@@ -32,8 +32,8 @@ def avatar(request):
     message = request.messages.get_message('usercp_avatar')
     message = request.messages.get_message('usercp_avatar')
     return request.theme.render_to_response('usercp/avatar.html',
     return request.theme.render_to_response('usercp/avatar.html',
                                             context_instance=RequestContext(request, {
                                             context_instance=RequestContext(request, {
-                                              'message': message,
-                                              'tab': 'avatar',
+                                             'message': message,
+                                             'tab': 'avatar',
                                              }));
                                              }));
 
 
 
 
@@ -94,9 +94,9 @@ def gallery(request):
 
 
     return request.theme.render_to_response('usercp/avatar_gallery.html',
     return request.theme.render_to_response('usercp/avatar_gallery.html',
                                             context_instance=RequestContext(request, {
                                             context_instance=RequestContext(request, {
-                                              'message': message,
-                                              'galleries': galleries,
-                                              'tab': 'avatar',
+                                             'message': message,
+                                             'galleries': galleries,
+                                             'tab': 'avatar',
                                              }));
                                              }));
 
 
 
 
@@ -159,10 +159,10 @@ def upload(request):
 
 
     return request.theme.render_to_response('usercp/avatar_upload.html',
     return request.theme.render_to_response('usercp/avatar_upload.html',
                                             context_instance=RequestContext(request, {
                                             context_instance=RequestContext(request, {
-                                              'message': message,
-                                              'form': FormLayout(form),
-                                              'tab': 'avatar',
-                                            }));
+                                             'message': message,
+                                             'form': FormLayout(form),
+                                             'tab': 'avatar',
+                                             }));
 
 
 
 
 @block_guest
 @block_guest
@@ -220,9 +220,9 @@ def crop(request, upload=False):
 
 
     return request.theme.render_to_response('usercp/avatar_crop.html',
     return request.theme.render_to_response('usercp/avatar_crop.html',
                                             context_instance=RequestContext(request, {
                                             context_instance=RequestContext(request, {
-                                              'message': message,
-                                              'after_upload': upload,
-                                              'avatar_size': settings.AVATAR_SIZES[0],
-                                              'source': 'avatars/%s' % (request.user.avatar_temp if upload else request.user.avatar_original),
-                                              'tab': 'avatar',
-                                            }));
+                                             'message': message,
+                                             'after_upload': upload,
+                                             'avatar_size': settings.AVATAR_SIZES[0],
+                                             'source': 'avatars/%s' % (request.user.avatar_temp if upload else request.user.avatar_original),
+                                             'tab': 'avatar',
+                                             }));

+ 1 - 1
misago/usercp/blocked/views.py

@@ -5,5 +5,5 @@ from misago.usercp.template import RequestContext
 def blocked(request):
 def blocked(request):
     return request.theme.render_to_response('usercp/blocked.html',
     return request.theme.render_to_response('usercp/blocked.html',
                                             context_instance=RequestContext(request, {
                                             context_instance=RequestContext(request, {
-                                              'tab': 'blocked',
+                                             'tab': 'blocked',
                                              }));
                                              }));

+ 3 - 3
misago/usercp/credentials/views.py

@@ -41,9 +41,9 @@ def credentials(request):
 
 
     return request.theme.render_to_response('usercp/credentials.html',
     return request.theme.render_to_response('usercp/credentials.html',
                                             context_instance=RequestContext(request, {
                                             context_instance=RequestContext(request, {
-                                              'message': message,
-                                              'form': FormLayout(form),
-                                              'tab': 'credentials',
+                                             'message': message,
+                                             'form': FormLayout(form),
+                                             'tab': 'credentials',
                                              }));
                                              }));
 
 
 
 

+ 3 - 3
misago/usercp/options/views.py

@@ -30,7 +30,7 @@ def options(request):
 
 
     return request.theme.render_to_response('usercp/options.html',
     return request.theme.render_to_response('usercp/options.html',
                                             context_instance=RequestContext(request, {
                                             context_instance=RequestContext(request, {
-                                              'message': message,
-                                              'tab': 'options',
-                                              'form': FormLayout(form)
+                                             'message': message,
+                                             'tab': 'options',
+                                             'form': FormLayout(form)
                                              }));
                                              }));

+ 4 - 4
misago/usercp/signature/views.py

@@ -15,7 +15,7 @@ def signature(request):
     if request.user.signature_ban:
     if request.user.signature_ban:
         return request.theme.render_to_response('usercp/signature_banned.html',
         return request.theme.render_to_response('usercp/signature_banned.html',
                                                 context_instance=RequestContext(request, {
                                                 context_instance=RequestContext(request, {
-                                                  'tab': 'signature',
+                                                 'tab': 'signature',
                                                  }));
                                                  }));
 
 
     siggy_text = ''
     siggy_text = ''
@@ -39,7 +39,7 @@ def signature(request):
 
 
     return request.theme.render_to_response('usercp/signature.html',
     return request.theme.render_to_response('usercp/signature.html',
                                             context_instance=RequestContext(request, {
                                             context_instance=RequestContext(request, {
-                                              'message': message,
-                                              'tab': 'signature',
-                                              'form': FormLayout(form),
+                                             'message': message,
+                                             'tab': 'signature',
+                                             'form': FormLayout(form),
                                              }));
                                              }));

+ 6 - 6
misago/usercp/username/views.py

@@ -42,10 +42,10 @@ def username(request):
 
 
     return request.theme.render_to_response('usercp/username.html',
     return request.theme.render_to_response('usercp/username.html',
                                             context_instance=RequestContext(request, {
                                             context_instance=RequestContext(request, {
-                                              'message': message,
-                                              'changes_left': changes_left,
-                                              'form': FormLayout(form),
-                                              'next_change': next_change,
-                                              'changes_history': request.user.namechanges.order_by('-date')[:10],
-                                              'tab': 'username',
+                                             'message': message,
+                                             'changes_left': changes_left,
+                                             'form': FormLayout(form),
+                                             'next_change': next_change,
+                                             'changes_history': request.user.namechanges.order_by('-date')[:10],
+                                             'tab': 'username',
                                              }));
                                              }));

+ 3 - 0
static/sora/css/sora.css

@@ -1043,6 +1043,9 @@ td.lead-cell{color:#555555;font-weight:bold;}
 .diff table tr td.even{background-color:#ebebeb;}
 .diff table tr td.even{background-color:#ebebeb;}
 .diff table tr td.added{background-color:#cdeacd;}.diff table tr td.added.even{background-color:#b1deb1;}
 .diff table tr td.added{background-color:#cdeacd;}.diff table tr td.added.even{background-color:#b1deb1;}
 .diff table tr td.removed{background-color:#f4c8c5;}.diff table tr td.removed.even{background-color:#eea8a2;}
 .diff table tr td.removed{background-color:#f4c8c5;}.diff table tr td.removed.even{background-color:#eea8a2;}
+.shorts-list{border-top:1px solid #eeeeee;margin-top:12px;}.shorts-list li{border-bottom:1px solid #eeeeee;padding:0px;padding-bottom:12px;margin:0px;margin-top:12px;}.shorts-list li .avatar{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;float:left;width:40px;height:40px;}
+.shorts-list li p{margin:0px;margin-left:54px;}.shorts-list li p.message{color:#555555;}
+.shorts-list li p.location{color:#999999;font-size:80%;}
 .well-post.rank-team{border:1px solid #0099e6;-webkit-box-shadow:0px 0px 0px 3px #66ccff;-moz-box-shadow:0px 0px 0px 3px #66ccff;box-shadow:0px 0px 0px 3px #66ccff;}
 .well-post.rank-team{border:1px solid #0099e6;-webkit-box-shadow:0px 0px 0px 3px #66ccff;-moz-box-shadow:0px 0px 0px 3px #66ccff;box-shadow:0px 0px 0px 3px #66ccff;}
 .team-online.rank-team ul li{background-color:#0088cc;}.team-online.rank-team ul li div a{color:#ffffff;text-shadow:0px 1px 0px #000d13;}
 .team-online.rank-team ul li{background-color:#0088cc;}.team-online.rank-team ul li div a{color:#ffffff;text-shadow:0px 1px 0px #000d13;}
 .team-online.rank-team ul li div .muted{color:#02435e;}
 .team-online.rank-team ul li div .muted{color:#02435e;}

+ 36 - 0
static/sora/css/sora/utilities.less

@@ -102,3 +102,39 @@
   font-family: @monoFontFamily;
   font-family: @monoFontFamily;
   font-weight: bold;
   font-weight: bold;
 }
 }
+
+// Shorts-list
+// --------------------------------------------------
+.shorts-list {
+  border-top: 1px solid @grayLighter;
+  margin-top: 12px;
+  
+  li {
+    border-bottom: 1px solid @grayLighter;
+    padding: 0px;
+    padding-bottom: 12px;
+    margin: 0px;
+    margin-top: 12px;
+    
+    .avatar{
+      .border-radius(3px);
+      float: left;
+      width: 40px;
+      height: 40px;
+    }
+    
+    p {
+      margin: 0px;
+      margin-left: 54px;
+      
+      &.message {        
+        color: @gray;
+      }
+      
+      &.location {
+        color: @grayLight;
+        font-size: 80%;
+      }
+    }
+  }
+}

+ 73 - 0
templates/sora/profiles/content_posts.html

@@ -0,0 +1,73 @@
+{% extends "sora/profiles/profile.html" %}
+{% load i18n %}
+{% load humanize %}
+{% load url from future %}
+{% import "_forms.html" as form_theme with context %}
+{% import "sora/macros.html" as macros with context %}
+
+{% block title %}{{ macros.page_title(_('Posts'), profile.username) }}{% endblock %}
+
+{% block tab %}
+<h2>{% trans username=profile.username %}{{ username }}'s Posts{% endtrans %} <small>{% if items_total -%}
+    {% trans count=items_total, total=items_total|intcomma, username=profile.username -%}
+    {{ username }} has one post
+    {%- pluralize -%}
+    {{ username }} has {{ total }} posts
+    {%- endtrans %}
+    {%- else -%}
+    {% trans username=profile.username %}{{ username }} has no posts{% endtrans %}
+    {%- endif %}</small></h2>
+
+{% if items_total %}
+<div class="list-nav">
+  {{ pager() }}
+  <ul class="nav nav-pills pull-right">
+    <li class="primary"><a href="{% url 'user_posts' user=profile.id, username=profile.username_slug %}">{% trans %}Posts{% endtrans %}</a></li>
+    <li class="info"><a href="{% url 'user_threads' user=profile.id, username=profile.username_slug %}">{% trans %}Threads{% endtrans %}</a></li>
+  </ul>
+</div>
+<ul class="unstyled shorts-list">
+  {% for item in items %}
+  <li>
+    <img src="{{ profile.get_avatar(36) }}" class="avatar">
+    <p class="message">{{ item.post|markdown_short(300) }}</p>
+    <p class="location">{% if item.thread.start_post_id == item.pk -%}
+        {% trans thread=thread(item), forum=forum(item.forum), user=username(profile), date=item.date|reldate|low %}Thread {{ thread }} posted in {{ forum }} by {{ user }} {{ date }}{% endtrans %}
+        {%- else -%}
+        {% trans thread=thread(item), forum=forum(item.forum), user=username(profile), date=item.date|reldate|low %}Reply to {{ thread }} posted in {{ forum }} by {{ user }} {{ date }}{% endtrans %}
+        {%- endif %}</p>
+  </li>
+  {% endfor %}
+</ul>
+<div class="list-nav">
+  {{ pager() }}
+</div>
+{% else %}
+<p class="lead">{% trans username=profile.username %}{{ username }} has no posts{% endtrans %}</p>
+{% endif %}
+{% endblock %}
+
+{% macro pager() -%}
+  <ul class="pager pull-left">
+    {%- if pagination['prev'] > 1 %}<li><a href="{% url 'user_posts' user=profile.id, username=profile.username_slug %}" class="tooltip-top" title="{% trans %}Lastest Posts{% endtrans %}"><i class="icon-chevron-left"></i> {% trans %}Latest{% endtrans %}</a></li>{% endif -%}
+    {%- if pagination['prev'] > 0 %}<li><a href="{%- if pagination['prev'] > 1 %}{% url 'user_posts' user=profile.id, username=profile.username_slug, page=pagination['prev'] %}{% else %}{% url 'user_posts' user=profile.id, username=profile.username_slug %}{% endif %}" class="tooltip-top" title="{% trans %}Newer Posts{% endtrans %}"><i class="icon-chevron-left"></i></a></li>{% endif -%}
+    {%- if pagination['next'] > 0 %}<li><a href="{% url 'user_posts' user=profile.id, username=profile.username_slug, page=pagination['next'] %}" class="tooltip-top" title="{% trans %}Older Posts{% endtrans %}"><i class="icon-chevron-right"></i></a></li>{% endif -%}
+    <li class="count">
+    {%- trans current_page=pagination['page'], pages=pagination['total'] -%}
+    Page {{ current_page }} of {{ pages }}
+    {%- endtrans -%}
+    </li>
+  </ul>
+{%- endmacro %}
+
+{% macro thread(item) -%}
+<a href="{% url 'thread_find' thread=item.thread.pk, slug=item.thread.slug, post=item.pk %}">{{ item.thread.name }}</a>
+{%- endmacro %}
+
+{% macro forum(forum) -%}
+<a href="{% url 'forum' forum=forum.pk, slug=forum.slug %}">{{ forum.name }}</a>
+{%- endmacro %}
+
+{% macro username(user) -%}
+<a href="{% url 'user' user=user.pk, username=user.username_slug %}">{{ user.username }}</a>
+{%- endmacro %}

+ 69 - 0
templates/sora/profiles/content_threads.html

@@ -0,0 +1,69 @@
+{% extends "sora/profiles/profile.html" %}
+{% load i18n %}
+{% load humanize %}
+{% load url from future %}
+{% import "_forms.html" as form_theme with context %}
+{% import "sora/macros.html" as macros with context %}
+
+{% block title %}{{ macros.page_title(_('Threads'), profile.username) }}{% endblock %}
+
+{% block tab %}
+<h2>{% trans username=profile.username %}{{ username }}'s Threads{% endtrans %} <small>{% if items_total -%}
+    {% trans count=items_total, total=items_total|intcomma, username=profile.username -%}
+    {{ username }} started one thread
+    {%- pluralize -%}
+    {{ username }} started {{ total }} threads
+    {%- endtrans %}
+    {%- else -%}
+    {% trans username=profile.username %}{{ username }} has no threads{% endtrans %}
+    {%- endif %}</small></h2>
+
+{% if items_total %}
+<div class="list-nav">
+  {{ pager() }}
+  <ul class="nav nav-pills pull-right">
+    <li class="info"><a href="{% url 'user_posts' user=profile.id, username=profile.username_slug %}">{% trans %}Posts{% endtrans %}</a></li>
+    <li class="primary"><a href="{% url 'user_threads' user=profile.id, username=profile.username_slug %}">{% trans %}Threads{% endtrans %}</a></li>
+  </ul>
+</div>
+<ul class="unstyled shorts-list">
+  {% for item in items %}
+  <li>
+    <img src="{{ profile.get_avatar(36) }}" class="avatar">
+    <p class="message">{{ item.start_post.post|markdown_short(300) }}</p>
+    <p class="location">{% trans thread=thread(item), forum=forum(item.forum), user=username(profile), date=item.start|reldate|low %}Thread {{ thread }} posted in {{ forum }} by {{ user }} {{ date }}{% endtrans %}</p>
+  </li>
+  {% endfor %}
+</ul>
+<div class="list-nav">
+  {{ pager() }}
+</div>
+{% else %}
+<p class="lead">{% trans username=profile.username %}{{ username }} has no threads{% endtrans %}</p>
+{% endif %}
+{% endblock %}
+
+{% macro pager() -%}
+  <ul class="pager pull-left">
+    {%- if pagination['prev'] > 1 %}<li><a href="{% url 'user_threads' user=profile.id, username=profile.username_slug %}" class="tooltip-top" title="{% trans %}Lastest Posts{% endtrans %}"><i class="icon-chevron-left"></i> {% trans %}Latest{% endtrans %}</a></li>{% endif -%}
+    {%- if pagination['prev'] > 0 %}<li><a href="{%- if pagination['prev'] > 1 %}{% url 'user_threads' user=profile.id, username=profile.username_slug, page=pagination['prev'] %}{% else %}{% url 'user_threads' user=profile.id, username=profile.username_slug %}{% endif %}" class="tooltip-top" title="{% trans %}Newer Posts{% endtrans %}"><i class="icon-chevron-left"></i></a></li>{% endif -%}
+    {%- if pagination['next'] > 0 %}<li><a href="{% url 'user_threads' user=profile.id, username=profile.username_slug, page=pagination['next'] %}" class="tooltip-top" title="{% trans %}Older Posts{% endtrans %}"><i class="icon-chevron-right"></i></a></li>{% endif -%}
+    <li class="count">
+    {%- trans current_page=pagination['page'], pages=pagination['total'] -%}
+    Page {{ current_page }} of {{ pages }}
+    {%- endtrans -%}
+    </li>
+  </ul>
+{%- endmacro %}
+
+{% macro thread(item) -%}
+<a href="{% url 'thread' thread=item.pk, slug=item.slug %}">{{ item.name }}</a>
+{%- endmacro %}
+
+{% macro forum(forum) -%}
+<a href="{% url 'forum' forum=forum.pk, slug=forum.slug %}">{{ forum.name }}</a>
+{%- endmacro %}
+
+{% macro username(user) -%}
+<a href="{% url 'user' user=user.pk, username=user.username_slug %}">{{ user.username }}</a>
+{%- endmacro %}

+ 1 - 1
templates/sora/profiles/details.html

@@ -5,7 +5,7 @@
 {% import "_forms.html" as form_theme with context %}
 {% import "_forms.html" as form_theme with context %}
 {% import "sora/macros.html" as macros with context %}
 {% import "sora/macros.html" as macros with context %}
 
 
-{% block title %}{{ macros.page_title(profile.username, _('Member Details')) }}{% endblock %}
+{% block title %}{{ macros.page_title(_('Member Details'), profile.username) }}{% endblock %}
 
 
 {% block tab %}
 {% block tab %}
 <div class="row">
 <div class="row">

+ 3 - 5
templates/sora/profiles/profile.html

@@ -18,11 +18,9 @@
       </div>
       </div>
     </div>	
     </div>	
     <ul class="nav nav-tabs">
     <ul class="nav nav-tabs">
-      <li{% if tab == 'posts' %} class="active"{% endif %}><a href="{% url 'user' user=profile.pk, username=profile.username_slug %}">{% trans %}Last Posts{% endtrans %}</a></li>
-      <li{% if tab == 'threads' %} class="active"{% endif %}><a href="{% url 'user_threads' user=profile.pk, username=profile.username_slug %}">{% trans %}Last Threads{% endtrans %}</a></li>
-      <li{% if tab == 'following' %} class="active"{% endif %}><a href="{% url 'user_following' user=profile.pk, username=profile.username_slug %}">{% trans %}Following{% endtrans %}</a></li>
-      <li{% if tab == 'followers' %} class="active"{% endif %}><a href="{% url 'user_followers' user=profile.pk, username=profile.username_slug %}">{% trans %}Followers{% endtrans %}</a></li>
-      <li{% if tab == 'details' %} class="active"{% endif %}><a href="{% url 'user_details' user=profile.pk, username=profile.username_slug %}">{% trans %}Profile Details{% endtrans %}</a></li>
+      {% for link in tabs %}
+      <li{% if link.active %} class="active"{% endif %}><a href="{{ link.route|url(user=profile.pk, username=profile.username_slug) }}">{{ link.name }}</a></li>
+      {% endfor %}
     </ul>
     </ul>
   </div>
   </div>
   <div class="profile-tab{% if profile.get_style() %} {{ profile.get_style() }}{% endif %}">
   <div class="profile-tab{% if profile.get_style() %} {{ profile.get_style() }}{% endif %}">