summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--MANIFEST.in1
-rw-r--r--askbot/doc/source/optional-modules.rst15
-rw-r--r--askbot/models/__init__.py53
-rw-r--r--askbot/models/question.py7
-rw-r--r--askbot/setup_templates/settings.py1
-rw-r--r--askbot/skins/default/media/js/user.js78
-rwxr-xr-xaskbot/skins/default/media/style/style.css23
-rw-r--r--askbot/skins/default/templates/blocks/bottom_scripts.html3
-rw-r--r--askbot/skins/default/templates/macros.html56
-rw-r--r--askbot/skins/default/templates/main_page/nothing_found.html4
-rw-r--r--askbot/skins/default/templates/main_page/tab_bar.html4
-rw-r--r--askbot/skins/default/templates/user_profile/user.html8
-rw-r--r--askbot/skins/default/templates/user_profile/user_info.html3
-rw-r--r--askbot/skins/default/templates/user_profile/user_network.html25
-rw-r--r--askbot/skins/default/templates/user_profile/user_tabs.html6
-rw-r--r--askbot/skins/default/templates/users.html23
-rw-r--r--askbot/tests/__init__.py1
-rw-r--r--askbot/tests/email_alert_tests.py2
-rw-r--r--askbot/tests/follow_tests.py40
-rw-r--r--askbot/views/users.py70
-rw-r--r--requirements.txt16
21 files changed, 340 insertions, 99 deletions
diff --git a/MANIFEST.in b/MANIFEST.in
index 556620c4..1512a8ac 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -13,6 +13,7 @@ exclude urls.py
exclude askbot/upfiles/*.*
recursive-exclude avatar *
recursive-exclude adzone *
+recursive-exclude follow *
recursive-exclude categories *
recursive-exclude follow *
recursive-exclude env
diff --git a/askbot/doc/source/optional-modules.rst b/askbot/doc/source/optional-modules.rst
index 8a96acbd..15db2e94 100644
--- a/askbot/doc/source/optional-modules.rst
+++ b/askbot/doc/source/optional-modules.rst
@@ -5,6 +5,17 @@ Optional modules
Askbot supports a number of optional modules, enabling certain features, not available
in askbot by default.
+.. _follow-users:
+
+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.
+
.. _embedding-video:
Embedding video
@@ -59,7 +70,7 @@ 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
@@ -68,6 +79,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 24ec6418..42690512 100644
--- a/askbot/models/__init__.py
+++ b/askbot/models/__init__.py
@@ -1208,19 +1208,6 @@ def user_edit_answer(
timestamp = timestamp
)
-def user_is_following(self, followed_item):
- if isinstance(followed_item, Question):
- followers = User.objects.filter(
- id = self.id,
- followed_questions = followed_item,
- )
- if self in followers:
- return True
- else:
- return False
- else:
- raise NotImplementedError('function only works for questions so far')
-
def user_post_answer(
self,
question = None,
@@ -1719,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,
@@ -1872,8 +1869,8 @@ 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('is_following', user_is_following)
User.add_to_class('update_response_counts', user_update_response_counts)
User.add_to_class('can_have_strong_url', user_can_have_strong_url)
User.add_to_class('is_administrator', user_is_administrator)
@@ -2434,27 +2431,12 @@ signals.post_updated.connect(
)
signals.site_visited.connect(record_user_visit)
-#todo: wtf??? what is x=x about?
-
-Question = Question
-QuestionRevision = QuestionRevision
-QuestionView = QuestionView
-FavoriteQuestion = FavoriteQuestion
-AnonymousQuestion = AnonymousQuestion
-
-Answer = Answer
-AnswerRevision = AnswerRevision
-AnonymousAnswer = AnonymousAnswer
-
-
-BadgeData = BadgeData
-Award = Award
-Repute = Repute
-
-Activity = Activity
-ActivityAuditStatus = ActivityAuditStatus
-EmailFeedSetting = EmailFeedSetting
-#AuthKeyUserAssociation = AuthKeyUserAssociation
+#set up a possibility for the users to follow others
+try:
+ import followit
+ followit.register(User)
+except ImportError:
+ pass
__all__ = [
'signals',
@@ -2487,3 +2469,4 @@ __all__ = [
'get_model'
]
+
diff --git a/askbot/models/question.py b/askbot/models/question.py
index 797d1c84..41579c11 100644
--- a/askbot/models/question.py
+++ b/askbot/models/question.py
@@ -211,7 +211,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/setup_templates/settings.py b/askbot/setup_templates/settings.py
index 4e22346a..65007676 100644
--- a/askbot/setup_templates/settings.py
+++ b/askbot/setup_templates/settings.py
@@ -164,6 +164,7 @@ INSTALLED_APPS = (
'django_countries',
'djcelery',
'djkombu',
+ 'follow',
#'avatar',#experimental use git clone git://github.com/ericflo/django-avatar.git$
#requires setting of MEDIA_ROOT and MEDIA_URL
)
diff --git a/askbot/skins/default/media/js/user.js b/askbot/skins/default/media/js/user.js
index 7877a828..ec169f9d 100644
--- a/askbot/skins/default/media/js/user.js
+++ b/askbot/skins/default/media/js/user.js
@@ -97,3 +97,81 @@ $(document).ready(function(){
}
);
});
+
+/**
+ * @constructor
+ * allows to follow/unfollow users
+ */
+var FollowUser = function(){
+ WrappedElement.call(this);
+ this._user_id = null;
+ this._user_name = null;
+};
+inherits(FollowUser, WrappedElement);
+
+/**
+ * @param {string} user_name
+ */
+FollowUser.prototype.setUserName = function(user_name){
+ this._user_name = user_name;
+};
+
+FollowUser.prototype.decorate = function(element){
+ this._element = element;
+ this._user_id = parseInt(element.attr('id').split('-').pop());
+ this._available_action = element.hasClass('follow') ? 'follow':'unfollow';
+ var me = this;
+ setupButtonEventHandlers(this._element, function(){ me.go() });
+};
+
+FollowUser.prototype.go = function(){
+ if (askbot['data']['userIsAuthenticated'] === false){
+ var message = gettext('Please <a href="%(signin_url)s">signin</a> to follow %(username)s');
+ var message_data = {
+ signin_url: askbot['urls']['user_signin'] + '?next=' + window.location.href,
+ username: this._user_name
+ }
+ message = interpolate(message, message_data, true);
+ showMessage(this._element, message);
+ return;
+ }
+ var user_id = this._user_id;
+ if (this._available_action === 'follow'){
+ var url = askbot['urls']['follow_user'];
+ } else {
+ var url = askbot['urls']['unfollow_user'];
+ }
+ var me = this;
+ $.ajax({
+ type: 'POST',
+ cache: false,
+ dataType: 'json',
+ url: url.replace('{{userId}}', user_id),
+ success: function(){ me.toggleState() }
+ });
+};
+
+FollowUser.prototype.toggleState = function(){
+ if (this._available_action === 'follow'){
+ this._available_action = 'unfollow';
+ this._element.removeClass('follow');
+ this._element.addClass('unfollow');
+ var fmts = gettext('unfollow %s');
+ } else {
+ this._available_action = 'follow';
+ this._element.removeClass('unfollow');
+ this._element.addClass('follow');
+ var fmts = gettext('follow %s');
+ }
+ this._element.html(interpolate(fmts, [this._user_name]));
+};
+
+(function(){
+ var fbtn = $('.follow-user');
+ if (fbtn.length === 1){
+ var follow_user = new FollowUser();
+ follow_user.decorate(fbtn);
+ follow_user.setUserName(askbot['data']['viewUserName']);
+ }
+})();
+
diff --git a/askbot/skins/default/media/style/style.css b/askbot/skins/default/media/style/style.css
index ff8b997d..24b01672 100755
--- a/askbot/skins/default/media/style/style.css
+++ b/askbot/skins/default/media/style/style.css
@@ -778,11 +778,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{
@@ -797,7 +796,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;
}
@@ -2172,3 +2171,19 @@ pre.prettyprint { padding: 3px; border: 0px solid #888; }
.atn { color: #404; }
.atv { color: #060; }
}
+
+.follow-toggle {
+ 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/blocks/bottom_scripts.html b/askbot/skins/default/templates/blocks/bottom_scripts.html
index 3ba2d959..b5b88bce 100644
--- a/askbot/skins/default/templates/blocks/bottom_scripts.html
+++ b/askbot/skins/default/templates/blocks/bottom_scripts.html
@@ -20,6 +20,9 @@
askbot['urls']['mark_read_message'] = '{% url "read_message" %}';
askbot['urls']['get_tags_by_wildcard'] = '{% url "get_tags_by_wildcard" %}';
askbot['urls']['get_tag_list'] = '{% url "get_tag_list" %}';
+ askbot['urls']['follow_user'] = scriptUrl + 'followit/follow/user/{{'{{'}}userId{{'}}'}}/';
+ askbot['urls']['unfollow_user'] = scriptUrl + 'followit/unfollow/user/{{'{{'}}userId{{'}}'}}/';
+ askbot['urls']['user_signin'] = '{% url "user_signin" %}';
</script>
<script
type="text/javascript"
diff --git a/askbot/skins/default/templates/macros.html b/askbot/skins/default/templates/macros.html
index 0eb0b303..fb3e3898 100644
--- a/askbot/skins/default/templates/macros.html
+++ b/askbot/skins/default/templates/macros.html
@@ -20,6 +20,34 @@
{% endif %}
{%- endmacro -%}
+{%- macro follow_toggle(follow, name, alias, id) -%}
+ {# follow - boolean; name - object type name; alias - e.g. users name; id - object id #}
+ <div
+ class="follow-toggle follow-{{name}} {% if follow %}follow{% else %}unfollow{% endif %}"
+ id="follow-{{ name }}-{{ id }}"
+ >
+ {% if follow %}
+ {% trans %}follow {{alias}}{% endtrans %}
+ {% else %}
+ {% trans %}unfollow {{alias}}{% endtrans %}
+ {% endif %}
+ </div>
+{%- endmacro -%}
+
+{%- macro follow_user_toggle(visitor = None, subject = None) -%}
+ {% if visitor.is_anonymous() %}
+ {{ follow_toggle(True, 'user', subject.username, subject.id) }}
+ {% else %}
+ {% if visitor != subject %}
+ {% if visitor.is_following(subject) %}
+ {{ follow_toggle(False, 'user', subject.username, subject.id) }}
+ {% else %}
+ {{ follow_toggle(True, 'user', subject.username, subject.id) }}
+ {% endif %}
+ {% endif %}
+ {% endif %}
+{%- endmacro -%}
+
{%- macro user_long_score_and_badge_summary(user) -%}
<a class="user-micro-info"
href="{{user.get_absolute_url()}}?sort=reputation"
@@ -74,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 %}
@@ -510,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}} &#9650;</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}} &#9660;</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.html b/askbot/skins/default/templates/user_profile/user.html
index e8abdf4b..cc1895fb 100644
--- a/askbot/skins/default/templates/user_profile/user.html
+++ b/askbot/skins/default/templates/user_profile/user.html
@@ -21,12 +21,14 @@
</div>
{% endblock %}<!-- end user.html -->
{% block endjs %}
- {% if request.user|can_moderate_user(view_user) %}
- <script type='text/javascript' src='{{"/js/jquery.form.js"|media}}'></script>
- {% endif %}
<script type="text/javascript">
var viewUserID = {{view_user.id}};
+ askbot['data']['viewUserName'] = '{{ view_user.username }}';
</script>
+ {% if request.user|can_moderate_user(view_user) %}
+ <script type='text/javascript' src='{{"/js/jquery.form.js"|media}}'></script>
+ {% endif %}
+ <script type="text/javascript" src='{{"/js/user.js"|media}}'></script>
{% block userjs %}
{% endblock %}
{% endblock %}
diff --git a/askbot/skins/default/templates/user_profile/user_info.html b/askbot/skins/default/templates/user_profile/user_info.html
index 6c6868c7..5aa5c094 100644
--- a/askbot/skins/default/templates/user_profile/user_info.html
+++ b/askbot/skins/default/templates/user_profile/user_info.html
@@ -23,6 +23,9 @@
</div>
<div class="scoreNumber">{{view_user.reputation|intcomma}}</div>
<p><b style="color:#777;">{% trans %}reputation{% endtrans %}</b></p>
+ {% 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/__init__.py b/askbot/tests/__init__.py
index 1cb8d37b..a2d0bdc0 100644
--- a/askbot/tests/__init__.py
+++ b/askbot/tests/__init__.py
@@ -8,3 +8,4 @@ from askbot.tests.badge_tests import *
from askbot.tests.management_command_tests import *
from askbot.tests.search_state_tests import *
from askbot.tests.form_tests import *
+from askbot.tests.follow_tests import *
diff --git a/askbot/tests/email_alert_tests.py b/askbot/tests/email_alert_tests.py
index 069ad264..071aa5ad 100644
--- a/askbot/tests/email_alert_tests.py
+++ b/askbot/tests/email_alert_tests.py
@@ -261,7 +261,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
new file mode 100644
index 00000000..f583c836
--- /dev/null
+++ b/askbot/tests/follow_tests.py
@@ -0,0 +1,40 @@
+from django.conf import settings as django_settings
+from askbot.tests.utils import 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')
+ self.u2 = self.create_user('user2')
+ self.u3 = self.create_user('user3')
+
+ def test_multiple_follow(self):
+
+ self.u1.follow_user(self.u2)
+ self.u1.follow_user(self.u3)
+ self.u2.follow_user(self.u1)
+
+ self.assertEquals(
+ set(self.u1.get_followers()),
+ set([self.u2])
+ )
+
+ self.assertEquals(
+ set(self.u2.get_followers()),
+ set([self.u1])
+ )
+
+ self.assertEquals(
+ set(self.u1.get_followed_users()),
+ set([self.u2, self.u3])
+ )
+
+ def test_unfollow(self):
+ self.u1.follow_user(self.u2)
+ self.u1.unfollow_user(self.u2)
+ self.assertEquals(self.u1.get_followed_users().count(), 0)
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)
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 00000000..ba72edb8
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,16 @@
+django>=1.1.2
+Jinja2
+Coffin>=0.3
+South>=0.7.1
+oauth2
+recaptcha-client
+markdown2
+html5lib
+django-keyedcache
+django-threaded-multihost
+django-robots
+unidecode
+django-countries==1.0.5
+django-celery==2.2.3
+django-kombu==0.9.2
+-e git+git://github.com/ASKBOT/django-followit.git#egg=followit