summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--askbot/models/__init__.py19
-rw-r--r--askbot/tests/__init__.py1
-rw-r--r--askbot/tests/follow_tests.py23
-rw-r--r--follower/TODO.rst3
-rw-r--r--follower/__init__.py147
-rw-r--r--follower/models.py3
-rw-r--r--follower/tests.py39
-rw-r--r--follower/views.py1
8 files changed, 227 insertions, 9 deletions
diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py
index 59967a88..a260cd11 100644
--- a/askbot/models/__init__.py
+++ b/askbot/models/__init__.py
@@ -15,6 +15,7 @@ from django.conf import settings as django_settings
from django.contrib.contenttypes.models import ContentType
from django.core import exceptions as django_exceptions
from django_countries.fields import CountryField
+import follow
import askbot
from askbot import exceptions as askbot_exceptions
from askbot import const
@@ -1719,6 +1720,16 @@ def user_follow_question(self, question = None):
if self not in question.followed_by.all():
question.followed_by.add(self)
+def user_follow_user(self, other_user):
+ """call when ``self`` user wants to follow ``other_user``
+ """
+ follow.follow(self, other_user)
+
+def user_unfollow_user(self, other_user):
+ """call when ``self`` user wants to unfollow ``other_user``
+ """
+ follow.unfollow(self, other_user)
+
def upvote(self, post, timestamp=None, cancel=False):
return _process_vote(
self,post,
@@ -1885,6 +1896,8 @@ User.add_to_class('follow_question', user_follow_question)
User.add_to_class('unfollow_question', user_unfollow_question)
User.add_to_class('mark_tags', user_mark_tags)
User.add_to_class('is_following', user_is_following)
+User.add_to_class('follow_user', user_follow_user)
+User.add_to_class('unfollow_user', user_unfollow_user)
User.add_to_class('decrement_response_count', user_decrement_response_count)
User.add_to_class('increment_response_count', user_increment_response_count)
User.add_to_class('clean_response_counts', user_clean_response_counts)
@@ -2448,9 +2461,9 @@ signals.post_updated.connect(
signals.site_visited.connect(record_user_visit)
#set up a possibility for the users to follow others
-from follow import util as follow_util
-follow_util.register(User, m2m = True)
-follow_util.register(Question, m2m = True)
+import follower
+follower.register(User)
+follower.register(Question)
__all__ = [
'signals',
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/follow_tests.py b/askbot/tests/follow_tests.py
index b6eb555a..460d7a01 100644
--- a/askbot/tests/follow_tests.py
+++ b/askbot/tests/follow_tests.py
@@ -7,17 +7,28 @@ class UserFollowTests(AskbotTestCase):
self.u2 = self.create_user('user2')
self.u3 = self.create_user('user3')
- def test_user_follow(self):
+ def test_multiple_follow(self):
- self.u1.follow(self.u2)
- self.u1.follow(self.u3)
- self.u2.follow(self.u1)
+ self.u1.follow_user(self.u2)
+ self.u1.follow_user(self.u3)
+ self.u2.follow_user(self.u1)
self.assertEquals(
- set(self.u1.followers()),
+ set(self.u1.get_followers()),
set([self.u2])
)
self.assertEquals(
- set(self.u1.
+ 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/follower/TODO.rst b/follower/TODO.rst
new file mode 100644
index 00000000..b71b92ae
--- /dev/null
+++ b/follower/TODO.rst
@@ -0,0 +1,3 @@
+* create tests & test runner script
+* create doc pages
+* publish on pypi
diff --git a/follower/__init__.py b/follower/__init__.py
new file mode 100644
index 00000000..20d8fee9
--- /dev/null
+++ b/follower/__init__.py
@@ -0,0 +1,147 @@
+"""A Django App allowing :class:`~django.contrib.auth.models.User` to
+follow instances of any other django models, including other users
+
+To use this module:
+* add "follower" to the ``INSTALLED_APPS`` in your ``settings.py``
+* in your app's ``models.py`` add:
+
+ import follower
+ follower.register(Thing)
+
+* run ``python manage.py syncdb``
+* then anywhere in your code you can do the following:
+
+ user.follow(some_thing_instance)
+ user.unfollow(some_thing_instance)
+
+ user.get_followed_things() #note that "things" is from the name of class Thing
+ some_thing.get_followers()
+
+Copyright 2011 Evgeny Fadeev evgeny.fadeev@gmail.com
+"""
+from django.contrib.auth.models import User
+from django.db.models.fields.related import ManyToManyField, ForeignKey
+
+REGISTRY = []
+
+def get_model_name(model):
+ return model._meta.module_name
+
+
+def get_bridge_class_name(model):
+ return 'Follow' + get_model_name(model)
+
+
+def get_bridge_model_for_object(obj):
+ """returns bridge model used to follow items
+ like the ``obj``
+ """
+ bridge_model_name = get_bridge_class_name(obj.__class__)
+ from django.db import models as django_models
+ return django_models.get_model('follower', bridge_model_name)
+
+
+def get_object_followers(obj):
+ """returns query set of users following the object"""
+ bridge_lookup_field = get_bridge_class_name(obj.__class__).lower()
+ obj_model_name = get_model_name(obj.__class__)
+ filter_criterion = 'followed_' + obj_model_name + '_records__followed'
+ filter = {filter_criterion: obj}
+ return User.objects.filter(**filter)
+
+
+def make_followed_objects_getter(model):
+ """returns query set of objects of a class ``model``
+ that are followed by a user"""
+
+ #something like followX_set__user
+ def followed_objects_getter(user):
+ filter = {'follower_records__follower': user}
+ return model.objects.filter(**filter)
+
+ return followed_objects_getter
+
+
+def make_follow_method(model):
+ """returns a method that adds a FollowX record
+ for an object
+ """
+ def follow_method(user, obj):
+ """returns ``True`` if follow operation created a new record"""
+ bridge_model = get_bridge_model_for_object(obj)
+ bridge, created = bridge_model.objects.get_or_create(follower = user, followed = obj)
+ return created
+ return follow_method
+
+
+def make_unfollow_method(model):
+ """returns a method that allows to unfollow an item
+ """
+ def unfollow_method(user, obj):
+ """attempts to find an item and delete it, no
+ exstence checking
+ """
+ bridge_model = get_bridge_model_for_object(obj)
+ objects = bridge_model.objects.get(follower = user, followed = obj)
+ objects.delete()
+ return unfollow_method
+
+
+def register(model):
+ """returns model class that connects
+ User with the followed object
+
+ ``model`` - is the model class to follow
+
+ The ``model`` class gets new method - ``get_followers``
+ and the User class - a method - ``get_followed_Xs``, where
+ the ``X`` is the name of the model
+
+ Note, that proper pluralization of the model name is not supported,
+ just "s" is added
+ """
+ from follower import models as follower_models
+ from django.db import models as django_models
+
+ model_name = get_model_name(model)
+ if model in REGISTRY:
+ return
+
+ #1) - create a new class FollowX
+ class Meta(object):
+ app_label = 'follower'
+
+ fields = {
+ 'follower': ForeignKey(
+ User,
+ related_name = 'followed_' + model_name + '_records'
+ ),
+ 'followed': ForeignKey(
+ model,
+ related_name = 'follower_records'
+ ),
+ '__module__': follower_models.__name__,
+ 'Meta': Meta
+ }
+
+
+ #create the bridge model class
+ bridge_class_name = get_bridge_class_name(model)
+ bridge_model = type(bridge_class_name, (django_models.Model,), fields)
+ setattr(follower_models, bridge_class_name, bridge_model)
+
+ #2) patch ``model`` with method ``get_followers()``
+ model.add_to_class('get_followers', get_object_followers)
+
+ #3) patch ``User`` with method ``get_followed_Xs``
+ method_name = 'get_followed_' + model_name + 's'
+ getter_method = make_followed_objects_getter(model)
+ User.add_to_class(method_name, getter_method)
+
+ #4) patch ``User`` with method ``follow_X``
+ follow_method = make_follow_method(model)
+ User.add_to_class('follow_' + model_name, follow_method)
+
+ #5) patch ``User`` with method ``unfollow_X``
+ unfollow_method = make_unfollow_method(model)
+ User.add_to_class('unfollow_' + model_name, unfollow_method)
diff --git a/follower/models.py b/follower/models.py
new file mode 100644
index 00000000..71a83623
--- /dev/null
+++ b/follower/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/follower/tests.py b/follower/tests.py
new file mode 100644
index 00000000..806b2f2b
--- /dev/null
+++ b/follower/tests.py
@@ -0,0 +1,39 @@
+"""
+Test cases for the follower module
+"""
+from django.db import models, transaction
+from django.contrib.contenttypes.management import update_contenttypes
+from django.core.management.commands import syncdb
+from django.test import TestCase
+
+import follower
+
+class SomeJunk(models.Model):
+ yeah = models.BooleanField(default = True)
+
+class SomeTrash(models.Model):
+ yeah = models.BooleanField(default = True)
+
+class FollowerTests(TestCase):
+ """idea taken from Dan Rosemans blog
+ http://blog.roseman.org.uk/2010/04/13/temporary-models-django/
+ create a temp model in setUp(), run tests on it, then destroy it in dearDown()
+ """
+
+ def setUp(self):
+ models.register_models('followertests', SomeJunk)
+ models.signals.post_syncdb.disconnect(update_contenttypes)
+
+ def test_register_fk_follow(self):
+ follower.register(SomeJunk)
+ #call_command('syncdb')
+ cmd = syncdb.Command()#south messes up here - call django's command directly
+ cmd.execute()
+ transaction.commit()
+ #test that table `follower_followsomejunk` exists
+ model = models.get_model('follower', 'FollowSomeJunk')
+ self.assertEquals(model.objects.count(), 0)
+
+ #def tearDown(self):
+ # cursor = connection.cursor()
+ # cursor.execute('DROP TABLE `followertests_somejunk`')
diff --git a/follower/views.py b/follower/views.py
new file mode 100644
index 00000000..60f00ef0
--- /dev/null
+++ b/follower/views.py
@@ -0,0 +1 @@
+# Create your views here.