mirror of
https://github.com/viq/NewsBlur.git
synced 2025-09-18 21:43:31 +00:00
Finishing up statistics graphs. Using Flot instead of Raphael. Added a migration to change how stories are counted.
This commit is contained in:
parent
56c4fd005c
commit
d4f30d8236
9 changed files with 2474 additions and 122 deletions
|
@ -104,10 +104,13 @@ def load_feeds(request):
|
|||
return data
|
||||
|
||||
user_subs = UserSubscription.objects.select_related('feed').filter(user=user)
|
||||
|
||||
updated_count = 0
|
||||
|
||||
for sub in user_subs:
|
||||
if sub.needs_unread_recalc:
|
||||
if updated_count < 200 and sub.needs_unread_recalc:
|
||||
# > 200 means that we counted enough, just move to refresh during live.
|
||||
sub.calculate_feed_scores()
|
||||
updated_count += 1
|
||||
feeds[sub.feed.pk] = {
|
||||
'id': sub.feed.pk,
|
||||
'feed_title': sub.feed.feed_title,
|
||||
|
|
124
apps/rss_feeds/migrations/0012_remove_storiespermonth.py
Normal file
124
apps/rss_feeds/migrations/0012_remove_storiespermonth.py
Normal file
|
@ -0,0 +1,124 @@
|
|||
# encoding: utf-8
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
|
||||
# Deleting model 'StoriesPerMonth'
|
||||
db.delete_table('rss_feeds_storiespermonth')
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
|
||||
# Adding model 'StoriesPerMonth'
|
||||
db.create_table('rss_feeds_storiespermonth', (
|
||||
('feed', self.gf('django.db.models.fields.related.ForeignKey')(related_name='stories_per_month', to=orm['rss_feeds.Feed'])),
|
||||
('month', self.gf('django.db.models.fields.IntegerField')()),
|
||||
('story_count', self.gf('django.db.models.fields.IntegerField')()),
|
||||
('beginning_of_month', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),
|
||||
('year', self.gf('django.db.models.fields.IntegerField')()),
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
))
|
||||
db.send_create_signal('rss_feeds', ['StoriesPerMonth'])
|
||||
|
||||
|
||||
models = {
|
||||
'rss_feeds.feed': {
|
||||
'Meta': {'object_name': 'Feed', 'db_table': "'feeds'"},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
|
||||
'average_stories_per_month': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'creation': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'days_to_trim': ('django.db.models.fields.IntegerField', [], {'default': '90'}),
|
||||
'etag': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
|
||||
'feed_address': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '255'}),
|
||||
'feed_link': ('django.db.models.fields.URLField', [], {'default': "''", 'max_length': '1000', 'null': 'True', 'blank': 'True'}),
|
||||
'feed_tagline': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
|
||||
'feed_title': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'fetched_once': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'last_load_time': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'last_modified': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'last_update': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'min_to_decay': ('django.db.models.fields.IntegerField', [], {'default': '15'}),
|
||||
'next_scheduled_update': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'num_subscribers': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'popular_authors': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}),
|
||||
'popular_tags': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
|
||||
'stories_last_month': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'stories_last_year': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'rss_feeds.feedfetchhistory': {
|
||||
'Meta': {'object_name': 'FeedFetchHistory'},
|
||||
'exception': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'feed': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'feed_fetch_history'", 'to': "orm['rss_feeds.Feed']"}),
|
||||
'fetch_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'message': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'status_code': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'rss_feeds.feedpage': {
|
||||
'Meta': {'object_name': 'FeedPage'},
|
||||
'feed': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'feed_page'", 'unique': 'True', 'to': "orm['rss_feeds.Feed']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'page_data': ('utils.compressed_textfield.StoryField', [], {'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'rss_feeds.feedupdatehistory': {
|
||||
'Meta': {'object_name': 'FeedUpdateHistory'},
|
||||
'average_per_feed': ('django.db.models.fields.DecimalField', [], {'max_digits': '4', 'decimal_places': '1'}),
|
||||
'fetch_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'number_of_feeds': ('django.db.models.fields.IntegerField', [], {}),
|
||||
'seconds_taken': ('django.db.models.fields.IntegerField', [], {})
|
||||
},
|
||||
'rss_feeds.feedxml': {
|
||||
'Meta': {'object_name': 'FeedXML'},
|
||||
'feed': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'feed_xml'", 'unique': 'True', 'to': "orm['rss_feeds.Feed']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'rss_xml': ('utils.compressed_textfield.StoryField', [], {'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'rss_feeds.pagefetchhistory': {
|
||||
'Meta': {'object_name': 'PageFetchHistory'},
|
||||
'exception': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'feed': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'page_fetch_history'", 'to': "orm['rss_feeds.Feed']"}),
|
||||
'fetch_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'message': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'status_code': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'rss_feeds.story': {
|
||||
'Meta': {'unique_together': "(('story_feed', 'story_guid_hash'),)", 'object_name': 'Story', 'db_table': "'stories'"},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'story_author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['rss_feeds.StoryAuthor']"}),
|
||||
'story_author_name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True', 'blank': 'True'}),
|
||||
'story_content': ('utils.compressed_textfield.StoryField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'story_content_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'story_date': ('django.db.models.fields.DateTimeField', [], {}),
|
||||
'story_feed': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'stories'", 'to': "orm['rss_feeds.Feed']"}),
|
||||
'story_guid': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
|
||||
'story_guid_hash': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
|
||||
'story_original_content': ('utils.compressed_textfield.StoryField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'story_past_trim_date': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
|
||||
'story_permalink': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
|
||||
'story_tags': ('django.db.models.fields.CharField', [], {'max_length': '2000', 'null': 'True', 'blank': 'True'}),
|
||||
'story_title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['rss_feeds.Tag']", 'symmetrical': 'False'})
|
||||
},
|
||||
'rss_feeds.storyauthor': {
|
||||
'Meta': {'object_name': 'StoryAuthor'},
|
||||
'author_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'feed': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['rss_feeds.Feed']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'rss_feeds.tag': {
|
||||
'Meta': {'object_name': 'Tag'},
|
||||
'feed': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['rss_feeds.Feed']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['rss_feeds']
|
116
apps/rss_feeds/migrations/0013_clear_storiespermonth.py
Normal file
116
apps/rss_feeds/migrations/0013_clear_storiespermonth.py
Normal file
|
@ -0,0 +1,116 @@
|
|||
# encoding: utf-8
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import DataMigration
|
||||
from django.db import models
|
||||
|
||||
class Migration(DataMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
from apps.rss_feeds.models import Feed
|
||||
for feed in Feed.objects.all():
|
||||
feed.stories_last_year = None
|
||||
feed.save()
|
||||
feed.count_stories(verbose=True)
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
"Write your backwards methods here."
|
||||
|
||||
|
||||
models = {
|
||||
'rss_feeds.feed': {
|
||||
'Meta': {'object_name': 'Feed', 'db_table': "'feeds'"},
|
||||
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
|
||||
'average_stories_per_month': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'creation': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'days_to_trim': ('django.db.models.fields.IntegerField', [], {'default': '90'}),
|
||||
'etag': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
|
||||
'feed_address': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '255'}),
|
||||
'feed_link': ('django.db.models.fields.URLField', [], {'default': "''", 'max_length': '1000', 'null': 'True', 'blank': 'True'}),
|
||||
'feed_tagline': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
|
||||
'feed_title': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'fetched_once': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'last_load_time': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'last_modified': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'last_update': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'min_to_decay': ('django.db.models.fields.IntegerField', [], {'default': '15'}),
|
||||
'next_scheduled_update': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'num_subscribers': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'popular_authors': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True', 'blank': 'True'}),
|
||||
'popular_tags': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
|
||||
'stories_last_month': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'stories_last_year': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'rss_feeds.feedfetchhistory': {
|
||||
'Meta': {'object_name': 'FeedFetchHistory'},
|
||||
'exception': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'feed': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'feed_fetch_history'", 'to': "orm['rss_feeds.Feed']"}),
|
||||
'fetch_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'message': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'status_code': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'rss_feeds.feedpage': {
|
||||
'Meta': {'object_name': 'FeedPage'},
|
||||
'feed': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'feed_page'", 'unique': 'True', 'to': "orm['rss_feeds.Feed']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'page_data': ('utils.compressed_textfield.StoryField', [], {'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'rss_feeds.feedupdatehistory': {
|
||||
'Meta': {'object_name': 'FeedUpdateHistory'},
|
||||
'average_per_feed': ('django.db.models.fields.DecimalField', [], {'max_digits': '4', 'decimal_places': '1'}),
|
||||
'fetch_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'number_of_feeds': ('django.db.models.fields.IntegerField', [], {}),
|
||||
'seconds_taken': ('django.db.models.fields.IntegerField', [], {})
|
||||
},
|
||||
'rss_feeds.feedxml': {
|
||||
'Meta': {'object_name': 'FeedXML'},
|
||||
'feed': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'feed_xml'", 'unique': 'True', 'to': "orm['rss_feeds.Feed']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'rss_xml': ('utils.compressed_textfield.StoryField', [], {'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'rss_feeds.pagefetchhistory': {
|
||||
'Meta': {'object_name': 'PageFetchHistory'},
|
||||
'exception': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'feed': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'page_fetch_history'", 'to': "orm['rss_feeds.Feed']"}),
|
||||
'fetch_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'message': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'status_code': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'rss_feeds.story': {
|
||||
'Meta': {'unique_together': "(('story_feed', 'story_guid_hash'),)", 'object_name': 'Story', 'db_table': "'stories'"},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'story_author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['rss_feeds.StoryAuthor']"}),
|
||||
'story_author_name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True', 'blank': 'True'}),
|
||||
'story_content': ('utils.compressed_textfield.StoryField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'story_content_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'story_date': ('django.db.models.fields.DateTimeField', [], {}),
|
||||
'story_feed': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'stories'", 'to': "orm['rss_feeds.Feed']"}),
|
||||
'story_guid': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
|
||||
'story_guid_hash': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
|
||||
'story_original_content': ('utils.compressed_textfield.StoryField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'story_past_trim_date': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
|
||||
'story_permalink': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
|
||||
'story_tags': ('django.db.models.fields.CharField', [], {'max_length': '2000', 'null': 'True', 'blank': 'True'}),
|
||||
'story_title': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['rss_feeds.Tag']", 'symmetrical': 'False'})
|
||||
},
|
||||
'rss_feeds.storyauthor': {
|
||||
'Meta': {'object_name': 'StoryAuthor'},
|
||||
'author_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'feed': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['rss_feeds.Feed']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'rss_feeds.tag': {
|
||||
'Meta': {'object_name': 'Tag'},
|
||||
'feed': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['rss_feeds.Feed']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['rss_feeds']
|
|
@ -97,38 +97,79 @@ class Feed(models.Model):
|
|||
'' if self.num_subscribers == 1 else 's',
|
||||
self.feed_title,
|
||||
),
|
||||
|
||||
|
||||
def count_stories(self, verbose=False, lock=None):
|
||||
month_ago = datetime.datetime.now() - datetime.timedelta(days=30)
|
||||
stories_last_month = Story.objects.filter(story_feed=self, story_date__gte=month_ago).count()
|
||||
self.stories_last_month = stories_last_month
|
||||
|
||||
# Save stories for this month count in granular StoriesPerMonth model
|
||||
today = datetime.datetime.now()
|
||||
beginning_of_month = datetime.datetime(today.year, today.month, 1)
|
||||
stories_this_month = Story.objects.filter(story_feed=self,
|
||||
story_date__gte=beginning_of_month).count()
|
||||
stories_per_month, created = StoriesPerMonth.objects.get_or_create(
|
||||
feed=self,
|
||||
year=today.year,
|
||||
month=today.month,
|
||||
defaults={
|
||||
'story_count': stories_this_month,
|
||||
'beginning_of_month': beginning_of_month,
|
||||
})
|
||||
if not created:
|
||||
stories_per_month.story_count = stories_this_month
|
||||
stories_per_month.save()
|
||||
|
||||
stories_last_year, average_stories_per_month = StoriesPerMonth.past_year(self)
|
||||
self.stories_last_year = json.encode(stories_last_year)
|
||||
self.average_stories_per_month = average_stories_per_month
|
||||
self.recount_feed(lock)
|
||||
|
||||
self.save(lock=lock)
|
||||
|
||||
if verbose:
|
||||
print " ---> %s [%s]: %s stories" % (self.feed_title, self.pk, self.stories_last_month)
|
||||
|
||||
|
||||
def recount_feed(self, lock=None):
|
||||
"""
|
||||
Fills in missing months between earlier occurances and now.
|
||||
|
||||
Save format: [('YYYY-MM, #), ...]
|
||||
Example output: [(2010-12, 123), (2011-01, 146)]
|
||||
"""
|
||||
d = defaultdict(int)
|
||||
now = datetime.datetime.now()
|
||||
min_year = now.year
|
||||
total = 0
|
||||
month_count = 0
|
||||
current_counts = self.stories_last_year and json.decode(self.stories_last_year)
|
||||
|
||||
if not current_counts:
|
||||
current_counts = []
|
||||
|
||||
# Count stories, aggregate by year and month
|
||||
stories = Story.objects.filter(story_feed=self).extra(select={
|
||||
'year': "EXTRACT(year FROM story_date)",
|
||||
'month': "EXTRACT(month from story_date)"
|
||||
}).values('year', 'month')
|
||||
for story in stories:
|
||||
year = int(story['year'])
|
||||
d['%s-%s' % (year, int(story['month']))] += 1
|
||||
if year < min_year:
|
||||
min_year = year
|
||||
|
||||
# Add on to existing months, always amending up, never down. (Current month
|
||||
# is guaranteed to be accurate, since trim_feeds won't delete it until after
|
||||
# a month. Hacker News can have 1,000+ and still be counted.)
|
||||
for current_month, current_count in current_counts:
|
||||
if current_month not in d or d[current_month] < current_count:
|
||||
d[current_month] = current_count
|
||||
year = re.findall(r"(\d{4})-\d{1,2}", current_month)[0]
|
||||
if year < min_year:
|
||||
min_year = year
|
||||
|
||||
# Assemble a list with 0's filled in for missing months,
|
||||
# trimming left and right 0's.
|
||||
months = []
|
||||
start = False
|
||||
for year in range(min_year, now.year+1):
|
||||
for month in range(1, 12+1):
|
||||
if datetime.datetime(year, month, 1) < now:
|
||||
key = '%s-%s' % (year, month)
|
||||
if d.get(key) or start:
|
||||
start = True
|
||||
months.append((key, d.get(key, 0)))
|
||||
total += d.get(key, 0)
|
||||
month_count += 1
|
||||
|
||||
self.stories_last_year = json.encode(months)
|
||||
if not total:
|
||||
self.average_stories_per_month = 0
|
||||
else:
|
||||
self.average_stories_per_month = total / month_count
|
||||
self.save(lock)
|
||||
|
||||
|
||||
def last_updated(self):
|
||||
return time.time() - time.mktime(self.last_update.timetuple())
|
||||
|
||||
|
@ -638,65 +679,3 @@ class PageFetchHistory(models.Model):
|
|||
self.message,
|
||||
self.exception[:50]
|
||||
)
|
||||
|
||||
class StoriesPerMonth(models.Model):
|
||||
feed = models.ForeignKey(Feed, related_name='stories_per_month')
|
||||
year = models.IntegerField()
|
||||
month = models.IntegerField()
|
||||
story_count = models.IntegerField()
|
||||
beginning_of_month = models.DateTimeField(default=datetime.datetime.now)
|
||||
|
||||
@classmethod
|
||||
def past_year(cls, feed):
|
||||
year_ago = datetime.datetime.now() - datetime.timedelta(days=365)
|
||||
story_counts = StoriesPerMonth.objects.filter(
|
||||
feed=feed,
|
||||
beginning_of_month__gte=year_ago
|
||||
).order_by('beginning_of_month')
|
||||
month_counts = [m.story_count for m in story_counts]
|
||||
average_per_month = sum(month_counts) / max(len(month_counts), 1)
|
||||
return month_counts, average_per_month
|
||||
|
||||
@classmethod
|
||||
def recount_feed(cls, feed, current_counts=None):
|
||||
d = defaultdict(int)
|
||||
now = datetime.datetime.now()
|
||||
min_year = now.year
|
||||
if not current_counts:
|
||||
current_counts = []
|
||||
|
||||
# Count stories, aggregate by year and month
|
||||
stories = Story.objects.filter(story_feed=feed).extra(select={
|
||||
'year': "EXTRACT(year FROM story_date)",
|
||||
'month': "EXTRACT(month from story_date)"
|
||||
}).values('year', 'month')
|
||||
for story in stories:
|
||||
year = int(story['year'])
|
||||
d['%s-%s' % (year, int(story['month']))] += 1
|
||||
if year < min_year:
|
||||
min_year = year
|
||||
|
||||
# Add on to existing months, always amending up, never down. (Current month
|
||||
# is guaranteed to be accurate, since trim_feeds won't delete it until after
|
||||
# a month. Hacker News can have 1,000+ and still be counted.)
|
||||
for current_month, current_count in current_counts:
|
||||
if current_month not in d or d[current_month] < current_count:
|
||||
d[current_month] = current_count
|
||||
year = re.findall(r"(\d{4})-\d{1,2}", current_month)[0]
|
||||
if year < min_year:
|
||||
min_year = year
|
||||
|
||||
# Assemble a list with 0's filled in for missing months,
|
||||
# trimming left and right 0's.
|
||||
months = []
|
||||
start = False
|
||||
for year in range(min_year, now.year+1):
|
||||
for month in range(1, 12+1):
|
||||
if datetime.datetime(year, month, 1) < now:
|
||||
key = '%s-%s' % (year, month)
|
||||
if d.get(key) or start:
|
||||
start = True
|
||||
months.append((key, d.get(key, 0)))
|
||||
from pprint import pprint
|
||||
pprint(months)
|
||||
|
|
@ -2591,16 +2591,15 @@ background: transparent;
|
|||
}
|
||||
|
||||
.NB-modal-statistics .NB-statistics-stat .NB-statistics-history-stat {
|
||||
float: left;
|
||||
width: 150px;
|
||||
text-align: center;
|
||||
margin: 0 0 12px 0;
|
||||
margin: 0 24px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.NB-modal-statistics .NB-statistics-stat .NB-statistics-history-chart {
|
||||
margin: -14px 0 0 0;
|
||||
float: right;
|
||||
width: 325px;
|
||||
margin: 0px 24px;
|
||||
width: 524px;
|
||||
height: 180px;
|
||||
}
|
||||
.NB-modal-statistics .NB-modal-feed-chooser-container {
|
||||
margin: 3px 0 12px;
|
||||
|
@ -2612,3 +2611,17 @@ background: transparent;
|
|||
.NB-modal-statistics .NB-modal-loading {
|
||||
margin: 6px 8px 0;
|
||||
}
|
||||
|
||||
.NB-modal-statistics .NB-statistics-subscribers {
|
||||
float: right;
|
||||
clear: both;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
color: #d0d0d0;
|
||||
text-shadow: 0 1px 0 #FFF;
|
||||
}
|
||||
|
||||
.NB-modal-statistics .NB-statistics-subscribers-count {
|
||||
color: #A8A8A8;
|
||||
padding-right: 4px;
|
||||
}
|
2119
media/js/jquery.flot.js
Normal file
2119
media/js/jquery.flot.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -3021,11 +3021,11 @@
|
|||
});
|
||||
$document.bind('keydown', 'j', function(e) {
|
||||
e.preventDefault();
|
||||
self.show_next_story(-1);
|
||||
self.show_next_story(1);
|
||||
});
|
||||
$document.bind('keydown', 'k', function(e) {
|
||||
e.preventDefault();
|
||||
self.show_next_story(1);
|
||||
self.show_next_story(-1);
|
||||
});
|
||||
$document.bind('keydown', 'left', function(e) {
|
||||
e.preventDefault();
|
||||
|
|
|
@ -155,41 +155,41 @@ NEWSBLUR.ReaderStatistics.prototype = {
|
|||
]),
|
||||
$.make('div', { className: 'NB-statistics-stat NB-statistics-history'}, [
|
||||
$.make('div', { className: 'NB-statistics-history-stat' }, [
|
||||
$.make('div', { className: 'NB-statistics-count' }, ''+data['subscriber_count']),
|
||||
$.make('div', { className: 'NB-statistics-label' }, 'subscribers'),
|
||||
$.make('div', { className: 'NB-statistics-count' }, ''+data['average_stories_per_month']),
|
||||
$.make('div', { className: 'NB-statistics-label' }, ' stories per month')
|
||||
$.make('div', { className: 'NB-statistics-label' }, 'Stories per month')
|
||||
]),
|
||||
$.make('div', { id: 'NB-statistics-history-chart', className: 'NB-statistics-history-chart' })
|
||||
])
|
||||
]);
|
||||
|
||||
var $subscribers = $.make('div', { className: 'NB-statistics-subscribers' }, [
|
||||
$.make('span', { className: 'NB-statistics-subscribers-count' }, ''+data['subscriber_count']),
|
||||
$.make('span', { className: 'NB-statistics-subscribers-label' }, 'subscriber' + data['subscriber_count']==1?'':'s')
|
||||
]);
|
||||
$('.NB-statistics-subscribers', this.$modal).remove();
|
||||
$('.NB-modal-subtitle', this.$modal).prepend($subscribers);
|
||||
|
||||
return $stats;
|
||||
},
|
||||
|
||||
make_charts: function(data) {
|
||||
var r = Raphael("NB-statistics-history-chart", 325, 170);
|
||||
var lines = r.g.linechart(20, 20, 290, 200,
|
||||
[[0, 2, 4, 6, 8, 10, 12],
|
||||
[0, 2, 4, 6, 8, 10, 12]],
|
||||
[[12, 12, 23, 15, 17, 27, 22],
|
||||
[10, 20, 30, 25, 15, 28, 2]], {
|
||||
nostroke: false,
|
||||
axis: false,
|
||||
symbol: "o",
|
||||
smooth: true
|
||||
}).hoverColumn(function () {
|
||||
this.tags = r.set();
|
||||
for (var i = 0, ii = this.y.length; i < ii; i++) {
|
||||
this.tags.push(r.g.tag(this.x, this.y[i], this.values[i], 160, 10).insertBefore(this).attr([{fill: "#fff"}, {fill: this.symbols[i].attr("fill")}]));
|
||||
}
|
||||
}, function () {
|
||||
this.tags && this.tags.remove();
|
||||
data['stories_last_year'] = _.map(data['stories_last_year'], function(date) {
|
||||
var date_matched = date[0].match(/(\d{4})-(\d{1,2})/);
|
||||
return [(new Date(parseInt(date_matched[1], 10), parseInt(date_matched[2],10)-1)).getTime(),
|
||||
date[1]];
|
||||
});
|
||||
lines.symbols.attr({r: 3});
|
||||
// lines.lines[0].animate({"stroke-width": 6}, 1000);
|
||||
// lines.symbols[0].attr({stroke: "#fff"});
|
||||
// lines.symbols[0][1].animate({fill: "#f00"}, 1000);
|
||||
var $plot = $(".NB-statistics-history-chart");
|
||||
var plot = $.plot($plot,
|
||||
[ { data: data['stories_last_year'], label: "Stories"} ], {
|
||||
series: {
|
||||
lines: { show: true },
|
||||
points: { show: true }
|
||||
},
|
||||
average: data['average_stories_per_month'],
|
||||
legend: { show: false },
|
||||
grid: { hoverable: true, clickable: true },
|
||||
yaxis: { tickDecimals: 0, min: 0 },
|
||||
xaxis: { mode: 'time' }
|
||||
});
|
||||
},
|
||||
|
||||
handle_change: function(elem, e) {
|
||||
|
|
|
@ -112,9 +112,7 @@ COMPRESS_JS = {
|
|||
'js/jquery.layout.js',
|
||||
'js/jquery.tinysort.js',
|
||||
'js/jquery.fieldselection.js',
|
||||
'js/raphael-1.4.7.js',
|
||||
'js/g.raphael.js',
|
||||
'js/g.line.js',
|
||||
'js/jquery.flot.js',
|
||||
'js/underscore.js',
|
||||
'js/newsblur/assetmodel.js',
|
||||
'js/newsblur/reader.js',
|
||||
|
|
Loading…
Add table
Reference in a new issue