Permalink
Browse files

pushing changes from tardis

  • Loading branch information...
1 parent 67b1f8e commit bdfc8d31b6b4475b0351c6c98aa2984a1f80e861 George Buckenham committed Mar 11, 2010
Showing with 306 additions and 66 deletions.
  1. +29 −8 common/models.py
  2. +46 −11 common/tests.py
  3. +70 −0 common/views.py
  4. +8 −0 feeds/forms.py
  5. +66 −13 feeds/models.py
  6. +2 −9 feeds/tests.py
  7. +5 −2 feeds/urls.py
  8. +73 −20 feeds/views.py
  9. +7 −3 todo/tests.py
View
@@ -1,21 +1,41 @@
from django.db import models
from django.contrib.auth.models import User
+from django.contrib import admin
+from datetime import datetime
+
+
+#stolen from http://codespatter.com/2009/07/01/django-model-manager-soft-delete-how-to-customize-admin/
+class SoftDeleteManager(models.Manager):
+ ''' Use this manager to get objects that have a deleted field '''
+ def get_query_set(self):
+ return super(SoftDeleteManager, self).get_query_set().filter(deleted=False)
+ def all_with_deleted(self):
+ return super(SoftDeleteManager, self).get_query_set()
+ def deleted_set(self):
+ return super(SoftDeleteManager, self).get_query_set().filter(deleted=True)
+
+class SoftDeleteAdmin(admin.ModelAdmin):
+ list_display = ('id', '__unicode__', 'deleted',)
+ list_filter = ('deleted',)
+
class Building(models.Model):
+ objects = SoftDeleteManager() #hello, soft deleting
+ deleted = models.BooleanField(default=False)
+
name = models.CharField(max_length=50)
owner = models.ForeignKey(User)
slug = models.CharField(max_length=20)
-
def __unicode__(self):
return self.name
def current_score(self):
scores = Score.objects.filter(parent=self)
try:
- latest_score = scores.order_by("-time")[0]
- except IndexError: #catching the case where no score's been made yet. in which case, create one
- latest_score = self.create_initial_score()
+ latest_score = scores.order_by("-time", "-points")[0]
+ except IndexError: #catching the case where no score's been made yet. TODO
+ latest_score = Score(parent = self, points = 0)
return latest_score
def create_initial_score(self):
@@ -24,12 +44,14 @@ def create_initial_score(self):
score.save()
return score
-
class Score(models.Model):
+ objects = SoftDeleteManager() #hello, soft deleting
+ deleted = models.BooleanField(default=False)
+
parent = models.ForeignKey(Building)
points = models.IntegerField()
- time = models.DateTimeField(auto_now_add=True)
-
+ time = models.DateTimeField(default=datetime.now())
+
class Meta:
ordering = ['parent', 'time']
@@ -38,4 +60,3 @@ def __unicode__(self):
def __init__(self, *args, **kwargs):
super(Score, self).__init__(*args, **kwargs)
-
View
@@ -6,18 +6,53 @@
"""
from django.test import TestCase
+from common.models import Building, Score
+from common.views import *
+from django.contrib.auth.models import User
+from datetime import datetime, timedelta
-class SimpleTest(TestCase):
- def test_basic_addition(self):
- """
- Tests that 1 + 1 always equals 2.
- """
- self.failUnlessEqual(1 + 1, 2)
-__test__ = {"doctest": """
-Another way to test that 1 + 1 is equal to 2.
+class scores_in_daterange_Test(TestCase):
+ def setUp(self):
+ user = User.objects.create_user(username="test", email="test@test.com", password="pwtest")
+ self.building = Building.objects.create(owner=user, name="user's default")
+ user.save()
+ self.building.save()
+ self.old_score = Score(parent=self.building, points=23, time=datetime(1987, 1, 2, 1, 1, 1))
+ self.old_score.save()
+ time_yesterday = datetime.now() - timedelta(days=1)
+ self.recent_score = Score(parent=self.building, points=42, time=time_yesterday)
+ self.recent_score.save()
+ time_day_before_yesterday = datetime.now() - timedelta(days=2)
+ self.other_recent_score = Score(parent=self.building, points=52, time=time_day_before_yesterday)
+ self.other_recent_score.save()
->>> 1 + 1 == 2
-True
-"""}
+ def test_with_given_daterange(self):
+ scores = scores_in_daterange(self.building, (datetime(1987,1,1,0,0,0),datetime(1988,1,1,1,1,1)))
+ self.assertEqual(len(scores), 1)
+ self.assertEqual(scores[0], self.old_score)
+ def test_with_no_daterange(self):
+ scores = scores_in_daterange(self.building)
+
+ self.assertEqual(len(scores), 3)
+# self.assertContains(scores, self.recent_score)
+# self.assertContains(scores, self.other_recent_score)
+
+
+
+class scores_in_daterange_with_no_scores_Test(TestCase):
+ def setUp(self):
+ user = User.objects.create_user(username="test", email="test@test.com", password="pwtest")
+ self.building = Building.objects.create(owner=user, name="user's default")
+ user.save()
+ self.building.save()
+
+
+ def test_with_given_daterange(self):
+ scores = scores_in_daterange(self.building, (datetime(1987,1,1,0,0,0),datetime(1988,1,1,1,1,1)))
+ self.assertEqual(len(scores), 0)
+
+ def test_with_no_daterange(self):
+ scores = scores_in_daterange(self.building)
+ self.assertEqual(len(scores), 1)
View
@@ -1,7 +1,11 @@
from django.contrib.auth.views import redirect_to_login
from django.template import RequestContext, loader
from django.http import Http404, HttpResponse, HttpResponseRedirect
+from django.utils.dateformat import format as human_date
+import pygooglechart
+from models import Score
+import time, datetime
def access_control(request, owner, callback):
'''
@@ -21,3 +25,69 @@ def access_control(request, owner, callback):
raise Http404 #replace with "not authed" page?
else:
return redirect_to_login(request.path)
+
+
+def scores_in_daterange(building, daterange=None):
+ """
+ returns a list of scores that occured for a building within a given daterange
+ without startdate and enddate it defaults to showing the last week
+ """
+ if daterange == None:
+ daterange = (datetime.datetime.today() - datetime.timedelta(weeks=1), datetime.datetime.today() + datetime.timedelta(days=1)) #i thought __range was inclusive, not exclusive
+ scores = list(Score.objects.filter(parent=building).filter(time__range = daterange))
+ if daterange[1] >= datetime.datetime.today(): #if the chart goes up to today, incluse an extra point at the current time, with current_score
+ scores.append(building.current_score())
+
+ return scores
+
+def google_graph(scores, dim_x=600, dim_y=400):
+ '''
+ returns a google chart api url for a png graph of the scores for building. chart has dimensions dim_x by dim_y
+ '''
+ if len(scores) < 2: #scores has to have at least two values to be graphed nicely.
+ raise ValueError
+
+ tupled_list = [(score.points, score.time) for score in scores]
+
+ try:
+ points_list, time_list = zip(*tupled_list)
+ except ValueError: #catching there being nothing matching
+ return None
+
+ epoched_time_list = map(lambda t : int(time.mktime(t.timetuple())), time_list)
+
+ #to be clear here: time is x, points is y
+
+ x_min = min(epoched_time_list)
+ x_max = max(epoched_time_list)
+
+ #plus a little padding
+ x_diff = x_max - x_min
+ x_max += x_diff/20
+
+ y_min = min(min(points_list), 0)
+ y_max = max(points_list) + 10
+
+ #plus a little padding
+ y_diff = y_max - y_min
+ y_max += y_diff/20
+
+
+ lchart = pygooglechart.XYLineChart(dim_x, dim_y, x_range = [x_min, x_max], y_range=[y_min, y_max])
+ lchart.add_data(epoched_time_list)
+ lchart.add_data(points_list)
+
+ lchart.set_axis_range(pygooglechart.Axis.LEFT, y_min, y_max)
+
+
+ date_format = 'n/j/y P' #this isn't the prettiest it could be...
+ try:
+ x_labels = [human_date(datetime.datetime.utcfromtimestamp(d), date_format) for d in range(x_min, x_max, x_diff/5)]
+ except ValueError: #breaks on only one score
+ x_labels = human_date(datetime.datetime.utcfromtimestamp(x_min), date_format)
+
+ lchart.set_axis_labels(pygooglechart.Axis.BOTTOM, x_labels)
+
+ return lchart.get_url()
+
+
View
@@ -0,0 +1,8 @@
+from feeds.models import FeedBuilding
+from django.forms import *
+
+class FeedBuildingForm(ModelForm):
+ class Meta:
+ model=FeedBuilding
+ fields = ['name', 'feedurl', 'score_per_item']
+
View
@@ -3,32 +3,85 @@
from datetime import datetime
import feedparser
import time
+from itertools import groupby
+
class FeedBuilding(Building):
feedurl = models.CharField(max_length=255)
- last_checked = models.DateTimeField(auto_now_add=True)
+ last_checked = models.DateTimeField(default=datetime.now())
score_per_item = models.IntegerField(default=10) #do we need a more complex representaton?
-
+ def save(self, *args, **kwargs):
+ super(FeedBuilding, self).save(*args, **kwargs)
+ if not self.id:
+ self.create_initial_score()
def __unicode__(self):
return self.name
+ def get_absolute_url(self):
+ return "/feeds/id/%i" % self.id
def fetch_new(self):
-
+
feed = feedparser.parse(self.feedurl)
new_items= []
for e in feed.entries:
- if datetime(*e.published_parsed[:6]) > self.last_checked: #this seems broken.
+ try:
+ feed_timestamp = e.published_parsed[:6]
+ except AttributeError: #unless it doesn't have that.
+ try:
+ feed_timestamp = e.updated_parsed[:6]
+ except AttributeError: #or that: we buggered.
+ break
+
+ if datetime(*feed_timestamp) > self.last_checked: #this triggers when there's new items. it adds a new score.
+ e.timestamp = feed_timestamp
new_items += [e]
- else:
- pass #break #cuz theyre chronological, we can do this. yes?
-
- new_points = self.score_per_item * len(new_items) + self.current_score().points
- score = Score(points = new_points, parent = self)
- score.save()
- self.last_checked = datetime.now()
+
+ self.last_checked = datetime(*feed_timestamp)
self.save()
+
return new_items
- def __init__(self, *args, **kwargs):
- super(FeedBuilding, self).__init__(*args, **kwargs)
+ def scores_from_new(self, new_items):
+ new_items.sort(key = lambda i : i.timestamp)
+ items_by_timestamp = [list(g) for k,g in groupby(new_items, key = lambda i: i.timestamp)] #whew! so this converts new_items into a list that contains lists of simultaneous events
+
+ scores = []
+ for event in items_by_timestamp:
+ points = self.current_score().points
+ for item in event:
+ points += self.score_per_item
+ score = Score(points = points, parent = self, time = datetime(*event[0].timestamp))
+ score.save()
+ scores += [score]
+ return scores
+
+'''
+a list which we want to convert into a list of Scores
+
+any simultaneous entries wanna be summed, done as a single score.
+
+and the non-simulatenous get their own Scores.
+
+so first: a list of lists. each list contains all the new_items that happen at any one time.
+
+
+'''
+
+
+'''
+ if datetime(*feed_timestamp) == self.last_checked: #this checks to see whether this update is made at exactly the same time as the last update. in which case it's one big bonanza update - update the score with the new points instead.
+ try:
+ score = Score.objects.filter(time=self.last_checked)[0]
+ score.points += self.score_per_item
+ score.save()
+ except IndexError:
+ pass #TODO denormalization has gone wrong. dogs are fornicating with cats. self.current_score().time differs from self.last_checked
+
+
+ new_points = self.score_per_item + self.current_score().points
+ score = Score(points = new_points, parent = self)
+ score.save()
+ score.time = datetime(*feed_timestamp)
+ score.save()
+'''
View
@@ -1,10 +1,3 @@
-"""
-This file demonstrates two different styles of tests (one doctest and one
-unittest). These will both pass when you run "manage.py test".
-
-Replace these with more appropriate tests for your application.
-"""
-
from django.contrib.auth.models import User
from django.test import TestCase
from datetime import datetime
@@ -24,10 +17,10 @@ def test_fetch(self):
user.save()
feed = FeedBuilding(name="test",owner=user, feedurl="/home/v21/projx/feeds/test.atom", last_checked=datetime(2009, 12, 3, 15, 26, 47), score_per_item=10)
feed.save()
- feed.last_checked = datetime(2009, 12, 3, 15, 26, 47) #bloody auto_now_add
- feed.save()
new_posts = feed.fetch_new()
self.assertEquals(3, len(new_posts))
+ feed.scores_from_new(new_posts)
+ self.assertEquals(30, feed.current_score().points)
"""
from common.models import Score
View
@@ -1,5 +1,8 @@
from django.conf.urls.defaults import *
-urlpatterns = patterns('',
- (r'^$', 'feeds.views.all'),
+urlpatterns = patterns('feeds.views',
+ (r'^$', 'all'),
+ (r'^id/(\d+)$', 'detail'),
+ (r'^add', 'add_feed'),
+ (r'^edit/(\d+)$', 'edit_feed'),
)
Oops, something went wrong.

0 comments on commit bdfc8d3

Please sign in to comment.