diff options
author | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2011-05-24 02:19:28 -0400 |
---|---|---|
committer | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2011-05-24 02:19:28 -0400 |
commit | 1243d42844613a5e04d8f40b7d3cc525f9ff8979 (patch) | |
tree | 01fa326b3edfc2386f98e8dc83a1a4cb6339792b | |
parent | 67ed525e48eb7d917a2fd5f8a9155ccb287c353f (diff) | |
download | askbot-1243d42844613a5e04d8f40b7d3cc525f9ff8979.tar.gz askbot-1243d42844613a5e04d8f40b7d3cc525f9ff8979.tar.bz2 askbot-1243d42844613a5e04d8f40b7d3cc525f9ff8979.zip |
follow user feature works, still need to modify the subscription model
-rw-r--r-- | askbot/doc/source/optional-modules.rst | 13 | ||||
-rw-r--r-- | askbot/models/__init__.py | 18 | ||||
-rw-r--r-- | askbot/models/question.py | 7 | ||||
-rwxr-xr-x | askbot/skins/default/media/style/style.css | 21 | ||||
-rw-r--r-- | askbot/skins/default/templates/macros.html | 28 | ||||
-rw-r--r-- | askbot/skins/default/templates/main_page/nothing_found.html | 4 | ||||
-rw-r--r-- | askbot/skins/default/templates/main_page/tab_bar.html | 4 | ||||
-rw-r--r-- | askbot/skins/default/templates/user_profile/user_info.html | 4 | ||||
-rw-r--r-- | askbot/skins/default/templates/user_profile/user_network.html | 25 | ||||
-rw-r--r-- | askbot/skins/default/templates/user_profile/user_tabs.html | 6 | ||||
-rw-r--r-- | askbot/skins/default/templates/users.html | 23 | ||||
-rw-r--r-- | askbot/tests/email_alert_tests.py | 2 | ||||
-rw-r--r-- | askbot/tests/follow_tests.py | 8 | ||||
-rw-r--r-- | askbot/views/users.py | 70 |
14 files changed, 167 insertions, 66 deletions
diff --git a/askbot/doc/source/optional-modules.rst b/askbot/doc/source/optional-modules.rst index d7ca2f24..3689db19 100644 --- a/askbot/doc/source/optional-modules.rst +++ b/askbot/doc/source/optional-modules.rst @@ -5,12 +5,21 @@ Optional modules Askbot supports a number of optional modules, enabling certain features, not available in askbot by default. +Follow users +============ + +Install ``django-followit`` app:: + + pip install -e git+git://github.com/ASKBOT/django-followit.git#egg=followit + +Then add ``followit`` to the ``INSTALLED_APPS`` and run ``syncdb`` management command. + Uploaded avatars ================ To enable uploadable avatars (in addition to :ref:`gravatars <gravatar>`), please install development version of -application ``django-avatar``, with the following command: +application ``django-avatar``, with the following command:: pip install -e git+git://github.com/ericflo/django-avatar.git#egg=django-avatar @@ -19,6 +28,8 @@ and run (to install database table used by the avatar app): python manage.py syncdb +Also, settings ``MEDIA_ROOT`` and ``MEDIA_URL`` will need to be added to your ``settings.py`` file. + .. note:: Version of the ``avatar`` application available at pypi may not diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index 87e5fe0f..7c5f4ea7 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -1706,6 +1706,16 @@ def user_follow_question(self, question = None): if self not in question.followed_by.all(): question.followed_by.add(self) +def user_is_following_question(user, question): + """True if user is following a question""" + followers = question.followed_by.all() + try: + followers.get(id = user.id) + return True + except User.DoesNotExist: + return False + + def upvote(self, post, timestamp=None, cancel=False): return _process_vote( self,post, @@ -1870,6 +1880,7 @@ User.add_to_class('delete_messages', delete_messages) User.add_to_class('toggle_favorite_question', toggle_favorite_question) User.add_to_class('follow_question', user_follow_question) User.add_to_class('unfollow_question', user_unfollow_question) +User.add_to_class('is_following_question', user_is_following_question) User.add_to_class('mark_tags', user_mark_tags) User.add_to_class('decrement_response_count', user_decrement_response_count) User.add_to_class('increment_response_count', user_increment_response_count) @@ -2434,8 +2445,11 @@ signals.post_updated.connect( signals.site_visited.connect(record_user_visit) #set up a possibility for the users to follow others -import followit -followit.register(User) +try: + import followit + followit.register(User) +except ImportError: + pass __all__ = [ 'signals', diff --git a/askbot/models/question.py b/askbot/models/question.py index ccb587e7..5ad64a32 100644 --- a/askbot/models/question.py +++ b/askbot/models/question.py @@ -209,7 +209,12 @@ class QuestionQuerySet(models.query.QuerySet): else: raise Exception('UNANSWERED_QUESTION_MEANING setting is wrong') elif scope_selector == 'favorite': - qs = qs.filter(favorited_by = request_user) + favorite_filter = models.Q(favorited_by = request_user) + if 'followit' in settings.INSTALLED_APPS: + followed_users = request_user.get_followed_users() + favorite_filter |= models.Q(author__in = followed_users) + favorite_filter |= models.Q(answers__author__in = followed_users) + qs = qs.filter(favorite_filter) #user contributed questions & answers if author_selector: diff --git a/askbot/skins/default/media/style/style.css b/askbot/skins/default/media/style/style.css index 3d04ec7d..a4e7d581 100755 --- a/askbot/skins/default/media/style/style.css +++ b/askbot/skins/default/media/style/style.css @@ -771,11 +771,10 @@ a:hover.medal { height: 24px; line-height: 26px; margin-top: 3px; - padding: 0px 11px 0px 11px; } -.tabsA a.on, tabsA a.on:hover { - padding: 0px 6px 0px 11px; +.tabsA a.rev.on, tabsA a.rev.on:hover { + padding: 0px 2px 0px 7px; } .tabsA a, .tabsC a{ @@ -790,7 +789,7 @@ a:hover.medal { height: 20px; line-height: 22px; margin: 5px 0 0 4px; - padding: 0 11px 0 11px; + padding: 0 7px; text-decoration: none; } @@ -2162,5 +2161,17 @@ pre.prettyprint { padding: 3px; border: 0px solid #888; } } .follow-toggle { - color: #406A24; + border-radius: 3px; + -moz-border-radius: 3px; + background: #fff0e0; + color: #777; + font-weight: bolder; + border: 1px solid #aaa; + cursor: pointer; +} + +.follow-toggle.unfollow:hover { + background: #a40000; + color: #fff; + border: 1px solid #d40000; } diff --git a/askbot/skins/default/templates/macros.html b/askbot/skins/default/templates/macros.html index d1b2bc40..fb3e3898 100644 --- a/askbot/skins/default/templates/macros.html +++ b/askbot/skins/default/templates/macros.html @@ -102,6 +102,30 @@ {{ user_country_name_and_flag(user) }} {%- endmacro -%} +{%- macro user_list(users, profile_section = None) -%} +<div class="userList"> + <table class="list-table"> + <tr> + <td class="list-td"> + {% for user in users %} + <div class="user"> + <ul> + <li class="thumb">{{ gravatar(user, 32) }}</li> + <li><a href="{% url user_profile user.id, user.username|slugify %}{% if profile_section %}?sort={{profile_section}}{% endif %}">{{user.username}}</a>{{ user_country_flag(user) }}</li> + <li>{{ user_score_and_badge_summary(user) }}</li> + </ul> + </div> + {% if loop.index is divisibleby 7 %} + </td> + <td> + {% endif %} + {% endfor %} + </td> + </tr> + </table> +</div> +{%- endmacro -%} + {%- macro paginator(p, position='left') -%}{# p is paginator context dictionary #} {% spaceless %} {% if p.is_paginated %} @@ -538,12 +562,12 @@ poor design of the data or methods on data objects #} {% if sort == key_name + "-asc" %}{# "worst" first #} <a id="by_{{key_name}}" href="?sort={{key_name}}-desc" - class="on" + class="rev on" title="{{desc_tooltip}}"><span>{{label}} ▲</span></a> {% elif sort == key_name + "-desc" %}{# "best first" #} <a id="by_{{key_name}}" href="?sort={{key_name}}-asc" - class="on" + class="rev on" title="{{asc_tooltip}}"><span>{{label}} ▼</span></a> {% else %}{# default, when other button is active #} <a id="by_{{key_name}}" diff --git a/askbot/skins/default/templates/main_page/nothing_found.html b/askbot/skins/default/templates/main_page/nothing_found.html index bc58fc27..50f2f340 100644 --- a/askbot/skins/default/templates/main_page/nothing_found.html +++ b/askbot/skins/default/templates/main_page/nothing_found.html @@ -4,8 +4,8 @@ {% trans %}There are no unanswered questions here{% endtrans %} {% endif %} {% if scope == "favorite" %} - {% trans %}No favorite questions here. {% endtrans %} - {% trans %}Please start (bookmark) some questions when you visit them{% endtrans %} + {% trans %}No questions here. {% endtrans %} + {% trans %}Please star (bookmark) some questions or follow some users.{% endtrans %} {% endif %} </p> {% if query or search_tags or author_name %} diff --git a/askbot/skins/default/templates/main_page/tab_bar.html b/askbot/skins/default/templates/main_page/tab_bar.html index 12096a3b..e398be87 100644 --- a/askbot/skins/default/templates/main_page/tab_bar.html +++ b/askbot/skins/default/templates/main_page/tab_bar.html @@ -17,8 +17,8 @@ <a id="favorite" class="{% if scope == 'favorite' %}on{% else %}off{% endif %}" href="?scope=favorite" - title="{% trans %}see your favorite questions{% endtrans %}" - ><span>{% trans %}favorite{% endtrans %}</span></a> + title="{% trans %}see your followed questions{% endtrans %}" + ><span>{% trans %}followed{% endtrans %}</span></a> {% endif %} </div> <div id="sort_tabs" class="tabsA"> diff --git a/askbot/skins/default/templates/user_profile/user_info.html b/askbot/skins/default/templates/user_profile/user_info.html index 63c1b163..5aa5c094 100644 --- a/askbot/skins/default/templates/user_profile/user_info.html +++ b/askbot/skins/default/templates/user_profile/user_info.html @@ -23,7 +23,9 @@ </div> <div class="scoreNumber">{{view_user.reputation|intcomma}}</div> <p><b style="color:#777;">{% trans %}reputation{% endtrans %}</b></p> - {{ macros.follow_user_toggle(visitor = request.user, subject = view_user) }} + {% if user_follow_feature_on %} + {{ macros.follow_user_toggle(visitor = request.user, subject = view_user) }} + {% endif %} </td> <td width="360" style="padding-left:5px;vertical-align: top;"> <table class="user-details"> diff --git a/askbot/skins/default/templates/user_profile/user_network.html b/askbot/skins/default/templates/user_profile/user_network.html new file mode 100644 index 00000000..cc741bb1 --- /dev/null +++ b/askbot/skins/default/templates/user_profile/user_network.html @@ -0,0 +1,25 @@ +{% extends "user_profile/user.html" %} +{% import "macros.html" as macros %} +<!-- user_network.html --> +{% block profileseciton %} + {% trans %}network{% endtrans %} +{% endblock %} +{% block usercontent %} + {% if followed_users or followers %} + {% if followers %} + <h2>{% trans count=followers|length %}Followed by {{count}} person{% pluralize count %}Followed by {{count}} people{% endtrans %}</h2> + {{ macros.user_list(followers, profile_section = 'network') }} + {% endif %} + {% if followed_users %} + <h2>{% trans count=followed_users|length %}Following {{count}} person{% pluralize count %}Followed by {{count}} people{% endtrans %}</h2> + {{ macros.user_list(followed_users, profile_section = 'network') }} + {% endif %} + {% else %} + {% if request.user == view_user %} + <p>{% trans %}Your network is empty. Would you like to follow someone? - Just visit their profiles and click "follow"{% endtrans %}</p> + {% else %} + <p>{% trans username = view_user.username %}{{user}}'s network is empty{% endtrans %}</p> + {% endif %} + {% endif %} +{% endblock %} +<!-- end user_network.html --> diff --git a/askbot/skins/default/templates/user_profile/user_tabs.html b/askbot/skins/default/templates/user_profile/user_tabs.html index 276590c9..92c42ea8 100644 --- a/askbot/skins/default/templates/user_profile/user_tabs.html +++ b/askbot/skins/default/templates/user_profile/user_tabs.html @@ -11,6 +11,12 @@ href="{% url user_profile view_user.id, view_user.username|slugify %}?sort=inbox" ><span>{% trans %}inbox{% endtrans %}</span></a> {% endif %} + {% if user_follow_feature_on %} + <a id="network" {% if tab_name=="network" %}class="on"{% endif %} + title="{% trans %}followers and followed users{% endtrans %}" + href="{% url user_profile view_user.id, view_user.username|slugify %}?sort=network" + ><span>{% trans %}network{% endtrans %}</span></a> + {% endif %} <a id="reputation" {% if tab_name=="reputation" %}class="on"{% endif %} title="{% trans %}graph of user reputation{% endtrans %}" href="{% url user_profile view_user.id, view_user.username|slugify %}?sort=reputation" diff --git a/askbot/skins/default/templates/users.html b/askbot/skins/default/templates/users.html index 74c171c8..750b3abb 100644 --- a/askbot/skins/default/templates/users.html +++ b/askbot/skins/default/templates/users.html @@ -40,28 +40,7 @@ <span>{% trans %}Nothing found.{% endtrans %}</span> {% endif %} </p> -<div class="userList"> - <table class="list-table"> - <tr> - <td class="list-td"> - {% for user in users.object_list %} - <div class="user"> - <ul> - <li class="thumb">{{ macros.gravatar(user, 32) }}</li> - <li><a href="{% url user_profile user.id, user.username|slugify %}">{{user.username}}</a>{{ macros.user_country_flag(user) }}</li> - <li>{{ macros.user_score_and_badge_summary(user) }}</li> - </ul> - </div> - - {% if loop.index is divisibleby 7 %} - </td> - <td> - {% endif %} - {% endfor %} - </td> - </tr> - </table> -</div> +{{ macros.user_list(users.object_list) }} <div class="pager"> {{ macros.paginator(paginator_context) }} </div> diff --git a/askbot/tests/email_alert_tests.py b/askbot/tests/email_alert_tests.py index 0c239be1..7f086152 100644 --- a/askbot/tests/email_alert_tests.py +++ b/askbot/tests/email_alert_tests.py @@ -258,7 +258,7 @@ class EmailAlertTests(TestCase): timestamp = self.setup_timestamp if follow is None: - if author.is_following(question): + if author.is_following_question(question): follow = True else: follow = False diff --git a/askbot/tests/follow_tests.py b/askbot/tests/follow_tests.py index 86df684b..f583c836 100644 --- a/askbot/tests/follow_tests.py +++ b/askbot/tests/follow_tests.py @@ -1,6 +1,12 @@ +from django.conf import settings as django_settings from askbot.tests.utils import AskbotTestCase -class FollowUserTests(AskbotTestCase): +if 'followit' in django_settings.INSTALLED_APPS: + TEST_PROTOTYPE = AskbotTestCase +else: + TEST_PROTOTYPE = object + +class FollowUserTests(TEST_PROTOTYPE): def setUp(self): self.u1 = self.create_user('user1') diff --git a/askbot/views/users.py b/askbot/views/users.py index d96ceece..cd3aa421 100644 --- a/askbot/views/users.py +++ b/askbot/views/users.py @@ -54,14 +54,14 @@ repute_type_id = repute_type.id def owner_or_moderator_required(f): @functools.wraps(f) - def wrapped_func(request, profile_owner): + def wrapped_func(request, profile_owner, context): if profile_owner == request.user: pass elif request.user.is_authenticated() and request.user.can_moderate_user(profile_owner): pass else: raise Http404 #todo: change to access forbidden? - return f(request, profile_owner) + return f(request, profile_owner, context) return wrapped_func def users(request): @@ -131,7 +131,7 @@ def users(request): return render_into_skin('users.html', data, request) @csrf.csrf_protect -def user_moderate(request, subject): +def user_moderate(request, subject, context): """user subview for moderation """ moderator = request.user @@ -213,7 +213,6 @@ def user_moderate(request, subject): 'tab_name': 'moderation', 'tab_description': _('moderate this user'), 'page_title': _('moderate user'), - 'view_user': subject, 'change_user_status_form': user_status_form, 'change_user_reputation_form': user_rep_form, 'send_message_form': send_message_form, @@ -222,7 +221,8 @@ def user_moderate(request, subject): 'user_rep_changed': user_rep_changed, 'user_status_changed': user_status_changed } - return render_into_skin('user_profile/user_moderate.html', data, request) + context.update(data) + return render_into_skin('user_profile/user_moderate.html', context, request) #non-view function def set_new_email(user, new_email, nomessage=False): @@ -278,7 +278,7 @@ def edit_user(request, id): } return render_into_skin('user_profile/user_edit.html', data, request) -def user_stats(request, user): +def user_stats(request, user, context): question_filter = {'author': user} if request.user != user: @@ -379,7 +379,6 @@ def user_stats(request, user): 'tab_name' : 'stats', 'tab_description' : _('user profile'), 'page_title' : _('user profile overview'), - 'view_user' : user, 'user_status_for_display': user.get_status_display(soft = True), 'questions' : questions, 'favorited_myself': favorited_myself, @@ -394,9 +393,10 @@ def user_stats(request, user): 'awarded_badge_counts': dict(awarded_badge_counts), 'total_awards' : total_awards, } - return render_into_skin('user_profile/user_stats.html', data, request) + context.update(data) + return render_into_skin('user_profile/user_stats.html', context, request) -def user_recent(request, user): +def user_recent(request, user, context): def get_type_name(type_id): for item in const.TYPE_ACTIVITY: @@ -663,13 +663,13 @@ def user_recent(request, user): 'tab_name' : 'recent', 'tab_description' : _('recent user activity'), 'page_title' : _('profile - recent activity'), - 'view_user' : user, 'activities' : activities[:const.USER_VIEW_DATA_SIZE] } - return render_into_skin('user_profile/user_recent.html', data, request) + context.update(data) + return render_into_skin('user_profile/user_recent.html', context, request) @owner_or_moderator_required -def user_responses(request, user): +def user_responses(request, user, context): """ We list answers for question, comments, and answer accepted by others for this user. @@ -730,13 +730,24 @@ def user_responses(request, user): 'inbox_section':section, 'tab_description' : _('comments and answers to others questions'), 'page_title' : _('profile - responses'), - 'view_user' : user, 'responses' : response_list, } - return render_into_skin('user_profile/user_inbox.html', data, request) + context.update(data) + return render_into_skin('user_profile/user_inbox.html', context, request) + +def user_network(request, user, context): + if 'followit' not in django_settings.INSTALLED_APPS: + raise Http404 + data = { + 'tab_name': 'network', + 'followed_users': user.get_followed_users(), + 'followers': user.get_followers(), + } + context.update(data) + return render_into_skin('user_profile/user_network.html', context, request) @owner_or_moderator_required -def user_votes(request, user): +def user_votes(request, user, context): votes = [] question_votes = models.Vote.objects.extra( @@ -794,12 +805,12 @@ def user_votes(request, user): 'tab_name' : 'votes', 'tab_description' : _('user vote record'), 'page_title' : _('profile - votes'), - 'view_user' : user, 'votes' : votes[:const.USER_VIEW_DATA_SIZE] } - return render_into_skin('user_profile/user_votes.html', data, request) + context.update(data) + return render_into_skin('user_profile/user_votes.html', context, request) -def user_reputation(request, user): +def user_reputation(request, user, context): reputes = models.Repute.objects.filter(user=user).order_by('-reputed_at') #select_related() adds stuff needed for the query reputes = reputes.select_related( @@ -834,9 +845,10 @@ def user_reputation(request, user): 'reputation': reputes, 'reps': reps } - return render_into_skin('user_profile/user_reputation.html', data, request) + context.update(data) + return render_into_skin('user_profile/user_reputation.html', context, request) -def user_favorites(request, user): +def user_favorites(request, user, context): favorited_q_id_list= models.FavoriteQuestion.objects.filter( user = user ).values_list('question__id', flat=True) @@ -860,13 +872,13 @@ def user_favorites(request, user): 'page_title' : _('profile - favorite questions'), 'questions' : questions, 'favorited_myself': favorited_q_id_list, - 'view_user' : user } - return render_into_skin('user_profile/user_favorites.html', data, request) + context.update(data) + return render_into_skin('user_profile/user_favorites.html', context, request) @owner_or_moderator_required @csrf.csrf_protect -def user_email_subscriptions(request, user): +def user_email_subscriptions(request, user, context): logging.debug(get_request_info(request)) if request.method == 'POST': @@ -900,14 +912,14 @@ def user_email_subscriptions(request, user): 'tab_name': 'email_subscriptions', 'tab_description': _('email subscription settings'), 'page_title': _('profile - email subscriptions'), - 'view_user': user, 'email_feeds_form': email_feeds_form, 'tag_filter_selection_form': tag_filter_form, 'action_status': action_status, } + context.update(data) return render_into_skin( 'user_profile/user_email_subscriptions.html', - data, + context, request ) @@ -915,6 +927,7 @@ user_view_call_table = { 'stats': user_stats, 'recent': user_recent, 'inbox': user_responses, + 'network': user_network, 'reputation': user_reputation, 'favorites': user_favorites, 'votes': user_votes, @@ -943,4 +956,9 @@ def user(request, id, slug=None): else: user_view_func = user_stats - return user_view_func(request, profile_owner) + context = { + 'view_user': profile_owner, + 'user_follow_feature_on': ('followit' in django_settings.INSTALLED_APPS), + } + + return user_view_func(request, profile_owner, context) |