summaryrefslogtreecommitdiffstats
path: root/follower
diff options
context:
space:
mode:
authorEvgeny Fadeev <evgeny.fadeev@gmail.com>2011-05-02 04:24:09 -0400
committerEvgeny Fadeev <evgeny.fadeev@gmail.com>2011-05-02 04:24:09 -0400
commitbb33c7a97208626ad3d3696aa8aad5a9212bfb5f (patch)
tree6c4940f10aa551779010c6220a533cf0d9bd3264 /follower
parent641623332da36d70f43854981935b1e7b0360069 (diff)
downloadaskbot-bb33c7a97208626ad3d3696aa8aad5a9212bfb5f.tar.gz
askbot-bb33c7a97208626ad3d3696aa8aad5a9212bfb5f.tar.bz2
askbot-bb33c7a97208626ad3d3696aa8aad5a9212bfb5f.zip
created a simple "follower" app that allows users to follow any model
Diffstat (limited to 'follower')
-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
5 files changed, 193 insertions, 0 deletions
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.