Permalink
Browse files

Merge branch 'track-related-object' into one-off-plus-objects

Conflicts:
	agon/models.py
	agon/signals.py
	agon/tests.py
  • Loading branch information...
2 parents 1f17f91 + c6cc293 commit 947bd627de6b4f638094c84fee631291b46caa52 @paltman paltman committed Oct 30, 2010
Showing with 160 additions and 24 deletions.
  1. +73 −20 agon/models.py
  2. +1 −1 agon/signals.py
  3. +86 −3 agon/tests.py
View
@@ -35,19 +35,29 @@ class AwardedPointValue(models.Model):
"""
# object association (User is special-cased as it's a common case)
- target_user = models.ForeignKey(User, null=True)
- target_content_type = models.ForeignKey(ContentType, null=True)
+ target_user = models.ForeignKey(User, null=True, related_name="awardedpointvalue_targets")
+
+ target_content_type = models.ForeignKey(ContentType, null=True, related_name="awardedpointvalue_targets")
target_object_id = models.IntegerField(null=True)
target_object = generic.GenericForeignKey("target_content_type", "target_object_id")
value = models.ForeignKey(PointValue, null=True)
points = models.IntegerField()
+
+ source_content_type = models.ForeignKey(ContentType, null=True, related_name="awardedpointvalue_sources")
+ source_object_id = models.IntegerField(null=True)
+ source_object = generic.GenericForeignKey("source_content_type", "source_object_id")
+
timestamp = models.DateTimeField(default=datetime.datetime.now)
@property
def target(self):
return self.target_user or self.target_object
-
+
+ @property
+ def source(self):
+ return self.source_object
+
def __unicode__(self):
val = self.value
if self.value is None:
@@ -62,17 +72,27 @@ class TargetStat(models.Model):
"""
# object association (User is special-cased as it's a common case)
- target_user = models.OneToOneField(User, null=True)
- target_content_type = models.ForeignKey(ContentType, null=True)
+ target_user = models.OneToOneField(User, null=True, related_name="targetstat_targets")
+
+ target_content_type = models.ForeignKey(ContentType, null=True, related_name="targetstat_targets")
target_object_id = models.IntegerField(null=True)
target_object = generic.GenericForeignKey("target_content_type", "target_object_id")
+ source_content_type = models.ForeignKey(ContentType, null=True, related_name="targetstat_sources")
+ source_object_id = models.IntegerField(null=True)
+ source_object = generic.GenericForeignKey("source_content_type", "source_object_id")
+
points = models.IntegerField(default=0)
position = models.PositiveIntegerField(null=True)
level = models.PositiveIntegerField(default=1)
class Meta:
- unique_together = [("target_content_type", "target_object_id")]
+ unique_together = [(
+ "target_content_type",
+ "target_object_id",
+ "source_content_type",
+ "source_object_id"
+ )]
@classmethod
def update_points(cls, given, lookup_params):
@@ -115,9 +135,16 @@ def target(self):
return self.target_user
else:
return self.target_object
+
+ @property
+ def source(self):
+ """
+ Match the ``target`` abstraction so the interface is consistent.
+ """
+ return self.source_object
-def award_points(target, key):
+def award_points(target, key, source=None):
"""
Awards target the point value for key. If key is an integer then it's a
one off assignment and should be interpreted as the actual point value.
@@ -150,6 +177,13 @@ def award_points(target, key):
"target_content_type": apv.target_content_type,
"target_object_id": apv.target_object_id,
}
+ if source is not None:
+ apv.source_object = source
+
+ lookup_params.update({
+ "source_content_type": apv.source_content_type,
+ "source_object_id": apv.source_object_id
+ })
apv.save()
if not TargetStat.update_points(points, lookup_params):
@@ -167,28 +201,47 @@ def award_points(target, key):
sender=target.__class__,
target=target,
key=key,
- points=points
+ points=points,
+ source=source
)
- new_points = points_awarded(target)
+ new_points = points_awarded(target, source=source)
old_points = new_points - points
TargetStat.update_positions((old_points, new_points))
return apv
-def points_awarded(target):
- if isinstance(target, User):
- lookup_params = {
- "target_user": target,
- }
- else:
- lookup_params = {
- "target_content_type": ContentType.objects.get_for_model(target),
- "target_object_id": target.pk,
- }
+def points_awarded(target=None, source=None):
+ """
+ Lookup points awarded either by target, by source, or by both
+ """
+
+ if target is None and source is None:
+ return 0
+
+ lookup_params = {}
+
+ if target is not None:
+ if isinstance(target, User):
+ lookup_params.update({
+ "target_user": target,
+ })
+ else:
+ lookup_params.update({
+ "target_content_type": ContentType.objects.get_for_model(target),
+ "target_object_id": target.pk,
+ })
+
+ if source is not None:
+ lookup_params.update({
+ "source_content_type": ContentType.objects.get_for_model(source),
+ "source_object_id": source.pk
+ })
+
try:
- return TargetStat.objects.get(**lookup_params).points
+ return TargetStat.objects.filter(**lookup_params).aggregate(models.Sum("points"))["points__sum"] or 0
except TargetStat.DoesNotExist:
return 0
+
View
@@ -1,4 +1,4 @@
from django.dispatch import Signal
-points_awarded = Signal(providing_args=["target", "key", "points"])
+points_awarded = Signal(providing_args=["target", "key", "points", "source"])
View
@@ -79,7 +79,7 @@ def test_user_one_off_point_award(self):
user = self.users[0]
award_points(user, 500)
self.assertEqual(points_awarded(user), 500)
-
+
def test_generic_one_off_point_award(self):
group = Group.objects.create(name="Dwarfs")
award_points(group, 500)
@@ -91,13 +91,13 @@ def test_user_one_off_point_award_value_is_null(self):
award_points(user, 500)
apv = AwardedPointValue.objects.all()[0]
self.assertTrue(apv.value is None)
-
+
def test_generic_one_off_point_award_value_is_null(self):
group = Group.objects.create(name="Dwarfs")
award_points(group, 500)
apv = AwardedPointValue.objects.all()[0]
self.assertTrue(apv.value is None)
-
+
def test_unicode_simple_user_point_award(self):
self.setup_users(1)
self.setup_points({
@@ -141,6 +141,17 @@ def test_unicode_generic_one_off_point_award(self):
unicode(apv),
u"%s points awarded to %s" % (500, unicode(group))
)
+
+ def test_simple_generic_point_award_with_source(self):
+ self.setup_points({
+ "ATE_SOMETHING": 5,
+ })
+ dwarfs = Group.objects.create(name="Dwarfs")
+ elves = Group.objects.create(name="Elves")
+ award_points(dwarfs, "ATE_SOMETHING", source=elves)
+ award_points(dwarfs, "ATE_SOMETHING")
+ self.assertEqual(points_awarded(dwarfs), 10)
+ self.assertEqual(points_awarded(dwarfs, source=elves), 5)
class PointsTransactionTestCase(BasePointsTestCase, TransactionTestCase):
@@ -186,6 +197,25 @@ def test_no_range(self):
[(1, 100), (2, 90), (3, 85), (4, 70), (4, 70), (6, 60), (7, 50), (8, 10), (9, 5)]
)
+ def test_no_args_with_target_objects(self):
+ self.setup_points({
+ "ATE_SOMETHING": 5,
+ "DRANK_SOMETHING": 10,
+ "WENT_TO_SLEEP": 4
+ })
+ points = PointValue.objects.all()
+
+ TargetStat.objects.create(target_object=points[0], points=100)
+ TargetStat.objects.create(target_object=points[1], points=90)
+ TargetStat.objects.create(target_object=points[2], points=90)
+
+ TargetStat.update_positions()
+
+ self.assertEqual(
+ [(p.position, p.points) for p in TargetStat.objects.order_by("position")],
+ [(1, 100), (2, 90), (2, 90)]
+ )
+
def test_up_range(self):
self.setup_users(9)
@@ -207,6 +237,25 @@ def test_up_range(self):
[(1, 100), (2, 90), (3, 85), (4, 70), (4, 70), (6, 61), (7, 60), (8, 10), (9, 5)]
)
+ def test_up_range_with_target_objects(self):
+ self.setup_points({
+ "ATE_SOMETHING": 5,
+ "DRANK_SOMETHING": 10,
+ "WENT_TO_SLEEP": 4
+ })
+ points = PointValue.objects.all()
+
+ TargetStat.objects.create(target_object=points[0], points=100, position=1)
+ TargetStat.objects.create(target_object=points[1], points=90, position=2)
+ TargetStat.objects.create(target_object=points[2], points=95, position=3)
+
+ TargetStat.update_positions((90, 95))
+
+ self.assertEqual(
+ [(p.position, p.points) for p in TargetStat.objects.order_by("position")],
+ [(1, 100), (2, 95), (3, 90)]
+ )
+
def test_down_range(self):
self.setup_users(9)
@@ -227,6 +276,40 @@ def test_down_range(self):
[(p.position, p.points) for p in TargetStat.objects.order_by("position")],
[(1, 100), (2, 90), (3, 70), (3, 70), (5, 69), (6, 60), (7, 50), (8, 10), (9, 5)]
)
+
+ def test_down_range_with_target_objects(self):
+ self.setup_points({
+ "ATE_SOMETHING": 5,
+ "DRANK_SOMETHING": 10,
+ "WENT_TO_SLEEP": 4
+ })
+ points = PointValue.objects.all()
+
+ TargetStat.objects.create(target_object=points[0], points=100, position=1)
+ TargetStat.objects.create(target_object=points[1], points=90, position=2)
+ TargetStat.objects.create(target_object=points[2], points=95, position=3)
+
+ TargetStat.update_positions((95, 90))
+
+ self.assertEqual(
+ [(p.position, p.points) for p in TargetStat.objects.order_by("position")],
+ [(1, 100), (2, 95), (3, 90)]
+ )
+
+
+class TargetObjectsTestCase(BasePointsTestCase, TestCase):
+
+ def test_exception_assiging_object_to_user(self):
+ self.setup_points({
+ "ATE_SOMETHING": 5,
+ "DRANK_SOMETHING": 10
+ })
+ points = PointValue.objects.all()
+
+ self.assertRaises(
+ ValueError,
+ lambda: TargetStat.objects.create(target_user=points[0], points=100)
+ )
class TopObjectsTagTestCase(TestCase):

0 comments on commit 947bd62

Please sign in to comment.