Dan Carroll avatar Dan Carroll committed ea70ea2

Initial checkin of code moved from dancarrollorg

Comments (0)

Files changed (41)

+syntax: glob
+
+*.pyc
+*.swp
+
Add a comment to this file

activitysync/__init__.py

Empty file added.

activitysync/admin.py

+# Activity admin page
+from activitysync.models import Provider, Activity
+from django.contrib import admin
+
+admin.site.register(Provider)
+
+class ActivityAdmin(admin.ModelAdmin):
+    list_display = ('title', 'provider', 'published', 'link', 'pub_date')
+    list_filter = ['pub_date', 'provider', 'username', 'published']
+    search_fields = ['title', 'comments']
+    date_hierarchy = 'pub_date'
+
+admin.site.register(Activity, ActivityAdmin)
+
Add a comment to this file

activitysync/management/__init__.py

Empty file added.

Add a comment to this file

activitysync/management/commands/__init__.py

Empty file added.

activitysync/management/commands/updateactivities.py

+from django.conf import settings
+from django.core import exceptions
+from django.core.management.base import BaseCommand, CommandError
+from django.core.management.color import no_style
+from django.core.mail import mail_admins
+from optparse import make_option
+from activitysync.models import Provider, Activity
+from activitysync.providers import ActivityProvider, ActivityInfo
+
+import os
+import sys
+import time
+import datetime
+import feedparser
+import twitter
+
+class Command(BaseCommand):
+    option_list = BaseCommand.option_list + (
+        make_option('--send-result', '-s', default=False, action='store_true', dest='sendmail',
+            help='Send email with new activities to site admins'),
+        make_option('--dry-run', '-d', default=False, action='store_true', dest='dryrun',
+            help='Gather activities, but do not create items in database'),
+    )
+    help = "Update activities by depositing them into the blog database."
+
+    def handle(self, *args, **options): 
+        self.style = no_style()
+        if len(args) != 0:
+            raise CommandError("Command does not accept any arguments")
+
+        send_email = options.get('sendmail')
+        dry_run = options.get('dryrun')
+
+        email_status_info = []
+        items_added = False
+        try:
+            # Go through provider list
+            provider_list = getattr(settings, 'ACTIVITYSYNC_PROVIDERS', [])
+            for provider_path in provider_list:
+                try:
+                    dot = provider_path.rindex('.')
+                except ValueError:
+                    raise exceptions.ImproperlyConfigured('%s is not an activity provider' % provider_path)
+                provider_module, provider_classname = provider_path[:dot], provider_path[dot+1:]
+                try:
+                    mod = __import__(provider_module, {}, {}, [''])
+                except ImportError, e:
+                    raise exceptions.ImproperlyConfigured('Error importing provider %s: %s' % (provider_module, e))
+                try:
+                    provider_class = getattr(mod, provider_classname)
+                except AttributeError:
+                    raise exceptions.ImproperlyConfigured('Provider module "%s" does not define a "%s" class' % (provider_module, provider_classname))
+
+                provider_instance = provider_class()
+                email_status_info.append('\n\n%s\n\n' % provider_instance.name())
+
+                # Create Provider model object if it does not exist
+                try:
+                    providerModelObject = Provider.objects.get(sourceid=provider_instance.sourceid())
+                except Provider.DoesNotExist:
+                    print 'First time seeing provider with sourceid: %s' % provider_instance.sourceid()
+                    providerModelObject = Provider.objects.create(
+                        name=provider_instance.name(),
+                        prefix=provider_instance.prefix(),
+                        link=provider_instance.link(),
+                        sourceid=provider_instance.sourceid()
+                    )
+
+                for activity_item in provider_instance.get_activity():
+                    try:
+                        Activity.objects.get(guid=activity_item.guid)
+                    except Activity.DoesNotExist:
+                        print "Created item: %s (%s)" % (activity_item.title, activity_item.link)
+                        email_status_info.append("Created item: %s (%s)\n" % (activity_item.title, activity_item.link))
+                        items_added = True
+                        
+                        if dry_run:
+                            print 'Dry run, not creating item'
+                        else:
+                            Activity.objects.create(title=activity_item.title, link=activity_item.link, username=activity_item.username, author=activity_item.author, comments=activity_item.comments, pub_date=activity_item.pub_date, published=activity_item.published, guid=activity_item.guid, provider=providerModelObject)
+
+        except:
+            ### DEBUGGING CODE
+            raise
+            ### END DEBUGGING
+            items_added = True
+            print "Unexpected error:", sys.exc_info()[0]
+            email_status_info.append("Unexpected error: %s\n\n" % sys.exc_info()[0])    
+        finally:
+            if items_added:
+                mailBody = u""
+                for itemString in email_status_info:
+                    try:
+                        mailBody = mailBody.encode('utf-8') + itemString.encode('utf-8')
+                    except UnicodeDecodeError:
+                        mailBody = mailBody + "\n\nFAILED TO PARSE ACTIVITY\n\n"
+                if send_email:
+                    mail_admins('Update Activities command completed', mailBody, fail_silently=False)
+                    print 'Mail sent to admins'
+

activitysync/managers.py

+from django.db.models import Manager
+import datetime
+
+class ActivityManager(Manager):
+    def get_query_set(self):
+        return super(ActivityManager, self).get_query_set().select_related('provider')
+   
+    def published(self):
+        """Returns published posts that are not in the future."""
+        return self.get_query_set().filter(published=True, pub_date__lte=datetime.datetime.now())
+

activitysync/media/README

+Social network icons courtesy of Komodo Media
+---------------------------------------------
+http://www.komodomedia.com/download/
+Social Network Icon Pack by Rogie King is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License (http://creativecommons.org/licenses/by-nc-sa/3.0/)
+
Add a comment to this file

activitysync/media/networks/delicious.png

Added
New image
Add a comment to this file

activitysync/media/networks/digg.png

Added
New image
Add a comment to this file

activitysync/media/networks/email.png

Added
New image
Add a comment to this file

activitysync/media/networks/facebook.png

Added
New image
Add a comment to this file

activitysync/media/networks/flickr.png

Added
New image
Add a comment to this file

activitysync/media/networks/google.png

Added
New image
Add a comment to this file

activitysync/media/networks/googlereader.png

Added
New image
Add a comment to this file

activitysync/media/networks/hulu.png

Added
New image
Add a comment to this file

activitysync/media/networks/linkedin.png

Added
New image
Add a comment to this file

activitysync/media/networks/picasa.png

Added
New image
Add a comment to this file

activitysync/media/networks/posterous.png

Added
New image
Add a comment to this file

activitysync/media/networks/reddit.png

Added
New image
Add a comment to this file

activitysync/media/networks/rss.png

Added
New image
Add a comment to this file

activitysync/media/networks/tumblr.png

Added
New image
Add a comment to this file

activitysync/media/networks/twitter.png

Added
New image
Add a comment to this file

activitysync/media/networks/windowslive.png

Added
New image
Add a comment to this file

activitysync/media/networks/youtube.png

Added
New image

activitysync/migrations/0001_initial.py

+# 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):
+        
+        # Adding model 'Activity'
+        db.create_table('activitysync_activity', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('title', self.gf('django.db.models.fields.CharField')(max_length=200)),
+            ('link', self.gf('django.db.models.fields.URLField')(max_length=500)),
+            ('source', self.gf('django.db.models.fields.CharField')(max_length=2)),
+            ('username', self.gf('django.db.models.fields.CharField')(max_length=20, blank=True)),
+            ('author', self.gf('django.db.models.fields.CharField')(max_length=20, blank=True)),
+            ('comments', self.gf('django.db.models.fields.TextField')(blank=True)),
+            ('pub_date', self.gf('django.db.models.fields.DateTimeField')()),
+            ('published', self.gf('django.db.models.fields.BooleanField')(default=True)),
+            ('guid', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255, db_index=True)),
+        ))
+        db.send_create_signal('activitysync', ['Activity'])
+
+
+    def backwards(self, orm):
+        
+        # Deleting model 'Activity'
+        db.delete_table('activitysync_activity')
+
+
+    models = {
+        'activitysync.activity': {
+            'Meta': {'ordering': "('-pub_date',)", 'object_name': 'Activity'},
+            'author': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'guid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'link': ('django.db.models.fields.URLField', [], {'max_length': '500'}),
+            'pub_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'published': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'source': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+            'username': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'})
+        }
+    }
+
+    complete_apps = ['activitysync']

activitysync/migrations/0002_auto__add_provider__add_field_activity_provider.py

+# 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):
+        
+        # Adding model 'Provider'
+        db.create_table('activitysync_provider', (
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=50)),
+            ('prefix', self.gf('django.db.models.fields.CharField')(max_length=50)),
+            ('link', self.gf('django.db.models.fields.URLField')(max_length=500)),
+            ('sourceid', self.gf('django.db.models.fields.CharField')(unique=True, max_length=20, primary_key=True, db_index=True)),
+        ))
+        db.send_create_signal('activitysync', ['Provider'])
+
+        # Adding field 'Activity.provider'
+        db.add_column('activitysync_activity', 'provider', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['activitysync.Provider'], null=True), keep_default=False)
+
+
+    def backwards(self, orm):
+        
+        # Deleting model 'Provider'
+        db.delete_table('activitysync_provider')
+
+        # Deleting field 'Activity.provider'
+        db.delete_column('activitysync_activity', 'provider_id')
+
+
+    models = {
+        'activitysync.activity': {
+            'Meta': {'ordering': "('-pub_date',)", 'object_name': 'Activity'},
+            'author': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'guid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'link': ('django.db.models.fields.URLField', [], {'max_length': '500'}),
+            'provider': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['activitysync.Provider']", 'null': 'True'}),
+            'pub_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'published': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'source': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+            'username': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'})
+        },
+        'activitysync.provider': {
+            'Meta': {'ordering': "('name',)", 'object_name': 'Provider'},
+            'link': ('django.db.models.fields.URLField', [], {'max_length': '500'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'prefix': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'sourceid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'primary_key': 'True', 'db_index': 'True'})
+        }
+    }
+
+    complete_apps = ['activitysync']

activitysync/migrations/0003_convert_to_provider_objects.py

+# 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):
+        for activity in orm.Activity.objects.all():
+            print "Activity: %s" % activity.title
+            provider = self.get_or_create_provider(orm, activity)
+            print "   - Has provider: %s" % provider.name
+            activity.provider = provider
+            activity.save()
+
+    def backwards(self, orm):
+        for activity in orm.Activity.objects.all():
+            provider = activity.provider
+            print 'Activity: %s' % activity.title
+            print '   - Has source: %s' % self.sourceid_to_sourcechoice(provider)
+            activity.source = self.sourceid_to_sourcechoice(provider)
+            activity.save()   
+
+    def get_or_create_provider(self, orm, activity):
+        sourceid = self.get_provider_sourceid(activity)
+
+        try:
+            provider = orm.Provider.objects.get(sourceid=sourceid)
+            return provider
+        except orm.Provider.DoesNotExist:
+            name = self.get_provider_name(activity)
+            prefix = self.get_activity_prefix(activity)
+            link = self.get_network_link(activity)
+
+            provider = orm.Provider.objects.create(name=name, prefix=prefix, link=link, sourceid=sourceid)
+            return provider
+
+    def get_provider_sourceid(self, activity):
+        if activity.source == 'T':
+            return u"twitter"
+        elif activity.source == 'DL':
+            return u"delicious"
+        elif activity.source == 'DG':
+            return u"digg"
+        elif activity.source == 'FB':
+            return u"facebook"
+        elif activity.source == 'HU':
+            return u"hulu"
+        elif activity.source == 'RD':
+            return u"reddit"
+        elif activity.source == 'GR':
+            return u"googlereader"
+
+    def get_provider_name(self, activity):
+        if activity.source == 'T':
+            return u"Twitter"
+        elif activity.source == 'DL':
+            return u"Delicious"
+        elif activity.source == 'DG':
+            return u"Digg"
+        elif activity.source == 'FB':
+            return u"Facebook"
+        elif activity.source == 'HU':
+            return u"Hulu"
+        elif activity.source == 'RD':
+            return u"Reddit"
+        elif activity.source == 'GR':
+            return u"Google Reader"
+ 
+    def get_activity_prefix(self, activity):
+        if activity.source == 'DL':
+            return u'Bookmarked '
+        elif activity.source == 'DG':
+            return u'Dugg '
+        elif activity.source == 'HU':
+            return u'Watched '
+        elif activity.source == 'RD':
+            return u'Liked '
+        elif activity.source == 'GR':
+            return u'Shared '
+        else:
+            return u''
+    
+    def get_network_link(self, activity):
+        if activity.source == 'T':
+            return u"http://twitter.com/"
+        elif activity.source == 'DL':
+            return u"http://delicious.com/"
+        elif activity.source == 'DG':
+            return u"http://www.digg.com"
+        elif activity.source == 'FB':
+            return u"http://www.facebook.com/"
+        elif activity.source == 'HU':
+            return u"http://www.hulu.com/"
+        elif activity.source == 'RD':
+            return u"http://www.reddit.com/"
+        elif activity.source == 'GR':
+            return u"http://www.google.com/reader/"
+
+# HELPER METHODS FOR BACKWARDS MIGRATION
+    def sourceid_to_sourcechoice(self, provider):
+        if provider.sourceid == 'twitter':
+            return u"T"
+        elif provider.sourceid == 'delicious':
+            return u"DL"
+        elif provider.sourceid == 'digg':
+            return u"DG"
+        elif provider.sourceid == 'facebook':
+            return u"FB"
+        elif provider.sourceid == 'hulu':
+            return u"HU"
+        elif provider.sourceid == 'reddit':
+            return u"RD"
+        elif provider.sourceid == 'googlereader':
+            return u"GR"
+
+
+    models = {
+        'activitysync.activity': {
+            'Meta': {'ordering': "('-pub_date',)", 'object_name': 'Activity'},
+            'author': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'guid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'link': ('django.db.models.fields.URLField', [], {'max_length': '500'}),
+            'provider': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['activitysync.Provider']", 'null': 'True'}),
+            'pub_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'published': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'source': ('django.db.models.fields.CharField', [], {'max_length': '2'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+            'username': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'})
+        },
+        'activitysync.provider': {
+            'Meta': {'ordering': "('name',)", 'object_name': 'Provider'},
+            'link': ('django.db.models.fields.URLField', [], {'max_length': '500'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'prefix': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'sourceid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'primary_key': 'True', 'db_index': 'True'})
+        }
+    }
+
+    complete_apps = ['activitysync']

activitysync/migrations/0004_auto__del_field_activity_source.py

+# 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 field 'Activity.source'
+        db.delete_column('activitysync_activity', 'source')
+
+
+    def backwards(self, orm):
+        
+        # Adding field 'Activity.source'
+        db.add_column('activitysync_activity', 'source', self.gf('django.db.models.fields.CharField')(default='?', max_length=2), keep_default=False)
+
+
+    models = {
+        'activitysync.activity': {
+            'Meta': {'ordering': "('-pub_date',)", 'object_name': 'Activity'},
+            'author': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
+            'comments': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'guid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'link': ('django.db.models.fields.URLField', [], {'max_length': '500'}),
+            'provider': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['activitysync.Provider']", 'null': 'True'}),
+            'pub_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'published': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+            'username': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'})
+        },
+        'activitysync.provider': {
+            'Meta': {'ordering': "('name',)", 'object_name': 'Provider'},
+            'link': ('django.db.models.fields.URLField', [], {'max_length': '500'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+            'prefix': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}),
+            'sourceid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '20', 'primary_key': 'True', 'db_index': 'True'})
+        }
+    }
+
+    complete_apps = ['activitysync']
Add a comment to this file

activitysync/migrations/__init__.py

Empty file added.

activitysync/models.py

+from django.db import models
+from activitysync.managers import ActivityManager
+
+class Provider(models.Model):
+    """Provider represents a particular social network"""
+    name = models.CharField(max_length=50)
+    prefix = models.CharField(max_length=50, blank=True)
+    link = models.URLField(max_length=500)
+    sourceid = models.CharField(max_length=20, primary_key=True, unique=True, db_index=True)
+
+    class Meta:
+        verbose_name = 'provider'
+        verbose_name_plural = 'providers'
+        ordering = ('name',)
+    
+    def __unicode__(self):
+        return u'%s' % self.name
+
+
+class Activity(models.Model):
+    """Activity from social network (Twitter, Flickr, etc)."""
+    
+    title = models.CharField('title', max_length=200)
+    link = models.URLField(max_length=500)
+    username = models.CharField(max_length=20, blank=True)
+    author = models.CharField(max_length=20, blank=True)
+    comments = models.TextField(blank=True)
+    pub_date = models.DateTimeField('Date published')
+    published = models.BooleanField(default=True)
+    guid = models.CharField(max_length=255, unique=True, db_index=True)
+
+    provider = models.ForeignKey(Provider, null=True)
+
+    objects = ActivityManager()
+    
+    class Meta:
+        verbose_name = 'activity'
+        verbose_name_plural = 'activities'
+        ordering = ('-pub_date',)
+        get_latest_by = 'pub_date'
+    
+    def __unicode__(self):
+        return u'%s' % self.title
+       

activitysync/paginator.py

+from django.core.paginator import Paginator, Page, PageNotAnInteger, EmptyPage
+from django.core.urlresolvers import reverse
+
+# Code borrowed from django-pagination.  I ripped it out since I didn't want the
+# rest of the functionality it provided (auto-pagination).
+class InfinitePaginator(Paginator):
+    """
+    Paginator designed for cases when it's not important to know how many total
+    pages.  This is useful for any object_list that has no count() method or can
+    be used to improve performance for MySQL by removing counts.
+
+    The orphans parameter has been removed for simplicity and there's a link
+    template string for creating the links to the next and previous pages.
+    """
+
+    def __init__(self, object_list, per_page, allow_empty_first_page=True):
+        orphans = 0 # no orphans
+        super(InfinitePaginator, self).__init__(object_list, per_page, orphans,
+            allow_empty_first_page)
+        # no count or num pages
+        del self._num_pages, self._count
+
+    def validate_number(self, number):
+        """
+        Validates the given 1-based page number.
+        """
+        try:
+            number = int(number)
+        except ValueError:
+            raise PageNotAnInteger('That page number is not an integer')
+        if number < 1:
+            raise EmptyPage('That page number is less than 1')
+        return number
+
+    def page(self, number):
+        """
+        Returns a Page object for the given 1-based page number.
+        """
+        number = self.validate_number(number)
+        bottom = (number - 1) * self.per_page
+        top = bottom + self.per_page
+        page_items = self.object_list[bottom:top]
+        # check moved from validate_number
+        if not page_items:
+            if number == 1 and self.allow_empty_first_page:
+                pass
+            else:
+                raise EmptyPage('That page contains no results')
+        return InfinitePage(page_items, number, self)
+
+    def _get_count(self):
+        """
+        Returns the total number of objects, across all pages.
+        """
+        raise NotImplementedError
+    count = property(_get_count)
+
+    def _get_num_pages(self):
+        """
+        Returns the total number of pages.
+        """
+        raise NotImplementedError
+    num_pages = property(_get_num_pages)
+
+    def _get_page_range(self):
+        """
+        Returns a 1-based range of pages for iterating through within
+        a template for loop.
+        """
+        raise NotImplementedError
+    page_range = property(_get_page_range)
+
+
+class InfinitePage(Page):
+    def __init__(self, object_list, number, paginator):
+        self.object_list = object_list
+        self.number = number
+        self.paginator = paginator
+        self.cached_has_next = None
+
+    def __repr__(self):
+        return '<Page %s>' % self.number
+
+    def has_next(self):
+        """
+        Checks for one more item than last on this page.
+        """
+        if self.cached_has_next != None:
+            return self.cached_has_next
+
+        try:
+            next_item = self.paginator.object_list[
+                self.number * self.paginator.per_page]
+        except IndexError:
+            self.cached_has_next = False
+            return False
+
+        self.cached_has_next = True
+        return True
+
+    def end_index(self):
+        """
+        Returns the 1-based index of the last object on this page,
+        relative to total objects found (hits).
+        """
+        return ((self.number - 1) * self.paginator.per_page +
+            len(self.object_list))
+
+    #Bonus methods for creating links
+
+    def next_link(self):
+        if self.has_next():
+            return reverse('activity_paged', args=[self.number + 1])
+        return None
+
+    def previous_link(self):
+        if self.has_previous():
+            if self.number == 2:
+                return reverse('main_activity')
+            else:
+                return reverse('activity_paged', args=[self.number - 1])
+        return None
+
+    def page_title(self):
+        if self.number > 1:
+            return 'Page %s' % self.number
+        return None
+
+    def create_template_context(self):
+        return {
+            'object_list': self.object_list,
+            'page_title': self.page_title(),
+            'has_next': self.has_next(),
+            'has_previous': self.has_previous(),
+            'next': self.next_link(),
+            'previous': self.previous_link(),
+        }

activitysync/providers/__init__.py

+"""Base ActivityProvider class"""
+
+class ActivityProvider(object):
+    """
+    Base class for activity providers (networks that provide social activity)
+    """
+
+    def get_activity(self):
+        """Returns a list of ActivityInfo objects representing the provider's activity"""
+        raise NotImplementedError
+
+    def name(self):
+        """Name of the network the provider connects to"""
+        raise NotImplementedError
+
+    def prefix(self):
+        """Prefix to put before activity item representing the action of the activity (i.e. "shared", "liked", etc)"""
+        raise NotImplementedError
+
+    def link(self):
+        """Link to the network or user profile page on the network"""
+        raise NotImplementedError
+
+    def sourceid(self):
+        """String representing the name or identification for the network. Used to associate items with the provider."""
+        raise NotImplementedError
+
+class ActivityInfo(object):
+    def __init__(self, title, link, pub_date, guid, username=None, author=None, comments='', published=True):
+        self.title = title
+        self.link = link
+        self.username = username
+        self.author = author
+        self.comments = comments
+        self.pub_date = pub_date
+        self.published = published
+        self.guid = guid
+

activitysync/providers/googlereader.py

+from django.conf import settings
+from activitysync.providers import ActivityProvider, ActivityInfo
+
+import time
+import datetime
+import feedparser
+
+class GoogleReaderProvider(ActivityProvider):
+    """
+    Provider for accessing shared Google Reader items for one user.
+    """
+#class ActivityInfo(object):
+#    def __init__(self, title=None, link=None, username=None, author=None, comments=None, pub_data=None, published=True, guid=None)
+
+    def get_activity(self):
+        item_list = []
+
+        print 'Attempting to parse Google Reader feed'
+        parsed_feed = feedparser.parse(settings.GOOGLEREADER_SHARED_RSS)
+    
+        for entry in parsed_feed.entries:
+            title = entry.title.encode(parsed_feed.encoding, "xmlcharrefreplace")
+            guid = entry.get("id", entry.link).encode(parsed_feed.encoding, "xmlcharrefreplace")
+            link = entry.link.encode(parsed_feed.encoding, "xmlcharrefreplace")
+
+            shared_by = u"Dan Carroll"
+            comments =u""
+                
+            if not guid:
+                guid = link
+                    
+            try:
+                if entry.has_key('published_parsed'):
+                    date_published = datetime.datetime.fromtimestamp(time.mktime(entry.published_parsed) - time.timezone)
+                elif entry.has_key('updated_parsed'):
+                    date_published = datetime.datetime.fromtimestamp(time.mktime(entry.updated_parsed) - time.timezone)
+                elif entry.has_key('modified_parsed'):
+                    date_published = datetime.datetime.fromtimestamp(time.mktime(entry.modified_parsed) - time.timezone)
+                else:
+                    date_published = datetime.datetime.now()
+            except TypeError:
+                date_published = datetime.datetime.now()
+                        
+            if entry.has_key('content'):        
+                if len(entry.content) == 2:
+                    comments = entry.content[1].value.encode(parsed_feed.encoding, "xmlcharrefreplace")
+            
+            activity_info = ActivityInfo(title=title, link=link, pub_date=date_published, guid=guid, username=shared_by, author=shared_by, comments=comments)
+            item_list.append(activity_info)
+
+        return item_list
+
+
+    def name(self):
+        return 'Google Reader'
+
+    def prefix(self):
+        return 'Shared'
+
+    def link(self):
+        return settings.GOOGLEREADER_PUBLIC_URL
+
+    def sourceid(self):
+        return 'googlereader'
+

activitysync/providers/redditprovider.py

+from django.conf import settings
+from activitysync.providers import ActivityProvider, ActivityInfo
+
+import time
+import datetime
+import feedparser
+
+class RedditProvider(ActivityProvider):
+    """
+    Provider for accessing liked Reddit items for one user.
+    """
+    
+    def get_activity(self):
+        item_list = []
+
+        print 'Attempting to parse Reddit feed'
+        username = settings.REDDIT_USERNAME
+        parsed_feed = feedparser.parse("http://www.reddit.com/user/%s/liked/.rss" % username)
+
+        for entry in parsed_feed.entries:
+            title = entry.title.encode(parsed_feed.encoding, "xmlcharrefreplace")
+            guid = entry.get("id", entry.link).encode(parsed_feed.encoding, "xmlcharrefreplace")
+            link = entry.link.encode(parsed_feed.encoding, "xmlcharrefreplace")
+
+            if not guid:
+                guid = link
+
+            if entry.has_key('author'):
+                author = entry.author.encode(parsed_feed.encoding, "xmlcharrefreplace")
+            else:
+                author = u''
+
+            try:
+                if entry.has_key('published_parsed'):
+                    date_published = datetime.datetime.fromtimestamp(time.mktime(entry.published_parsed) - time.timezone)
+                elif entry.has_key('updated_parsed'):
+                    date_published = datetime.datetime.fromtimestamp(time.mktime(entry.updated_parsed) - time.timezone)
+                elif entry.has_key('modified_parsed'):
+                    date_published = datetime.datetime.fromtimestamp(time.mktime(entry.modified_parsed) - time.timezone)
+                else:
+                    date_published = datetime.datetime.now()
+            except TypeError:
+                date_published = datetime.datetime.now()
+                    
+            activity_info = ActivityInfo(title=title, link=link, pub_date=date_published, guid=guid, username=username, author=author)
+            item_list.append(activity_info)
+
+        return item_list
+
+
+    def name(self):
+        return 'Reddit'
+
+    def prefix(self):
+        return 'Liked'
+
+    def link(self):
+        return 'http://www.reddit.com/user/%s/' % settings.REDDIT_USERNAME
+
+    def sourceid(self):
+        return 'reddit'
+

activitysync/providers/twitterprovider.py

+from django.conf import settings
+from activitysync.providers import ActivityProvider, ActivityInfo
+
+import time
+import datetime
+import twitter as TwitterLibrary
+
+class TwitterProvider(ActivityProvider):
+    """
+    Provider for accessing Twitter status updates for one user.
+    """
+    
+    def get_activity(self):
+        item_list = []
+
+        print 'Attempting to obtain Twitter items'
+        api = TwitterLibrary.Api()
+        username = settings.TWITTER_USERNAME
+        statuses = api.GetUserTimeline(username, count=50)
+
+        for status in statuses:
+            title = status.text
+            guid = "twitter:%s" % status.id
+            link = "http://twitter.com/%s/statuses/%s" % (status.user.screen_name, status.id)
+            author = status.user.name
+                
+            date_published = datetime.datetime.fromtimestamp(status.created_at_in_seconds)
+                 
+            # Don't show @replies
+            if not status.in_reply_to_user_id:
+                activity_info = ActivityInfo(title=title, link=link, pub_date=date_published, guid=guid, username=username, author=author)
+                item_list.append(activity_info)
+
+        return item_list
+
+
+    def name(self):
+        return 'Twitter'
+
+    def prefix(self):
+        return ''
+
+    def link(self):
+        return 'http://twitter.com/%s' % settings.TWITTER_USERNAME
+
+    def sourceid(self):
+        return 'twitter'
+

activitysync/templates/activitysync/activities_tag.html

+{% if not use_date_headers %}<div id="activity-list"><ul>{% endif %}
+{% for activity in activities %}
+{% if use_date_headers %}    
+    {% ifchanged activity.pub_date.date %}
+        {% if not forloop.first %}</ul></div>{% endif %}
+        <h2 class="section-title">{{ activity.pub_date|date:"F j" }}</h2>
+        <div id="activity-list"><ul>
+    {% endifchanged %}
+{% endif %}
+    <li><a href="{{ activity.provider.link }}"><img src="{{ MEDIA_URL }}networks/{{ activity.provider.sourceid }}.png" alt="{{ activity.provider.name }}" /></a>{{ activity.provider.prefix }} <a href="{{ activity.link }}">{{ activity.title }}</a> <span class="time">{{ activity.pub_date|timesince|upper }} AGO</span></li>
+    
+{% endfor %}
+</ul></div>
Add a comment to this file

activitysync/templatetags/__init__.py

Empty file added.

activitysync/templatetags/activitysync_extras.py

+from django import template
+from django.conf import settings
+from activitysync.models import Activity
+
+register = template.Library()
+
+@register.inclusion_tag('activitysync/activities_tag.html')
+def render_activities(activities):
+    return {
+        'activities': activities,
+        'use_date_headers': False,
+        'MEDIA_URL': settings.MEDIA_URL,
+    }
+
+@register.inclusion_tag('activitysync/activities_tag.html')
+def render_activities_with_date_headers(activities):
+    return {
+        'activities': activities,
+        'use_date_headers': True,
+        'MEDIA_URL': settings.MEDIA_URL,
+    }
+

activitysync/tests.py

+"""
+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.test import TestCase
+
+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.
+
+>>> 1 + 1 == 2
+True
+"""}
+

activitysync/views.py

+from django.http import Http404
+from django.shortcuts import redirect, render_to_response
+from django.template import RequestContext
+
+from activitysync.models import Activity
+from activitysync.paginator import InfinitePaginator
+
+def activity(request, page="1", explicit_page_request=False):
+    # Make sure page parameter is an integer
+    try:
+        page = int(page)
+    except:
+        raise Http404
+
+    # Make sure we only have one canonical first page
+    if explicit_page_request and page == 1:
+        return redirect('main_activity')
+
+    # Previous URL uses GET parameter 'page', so let's check
+    # for that and redirect to new view if necessary
+    if not explicit_page_request:
+        try:
+            requestNum = request.GET['page']
+            if requestNum != None and requestNum.isdigit():
+                return redirect('activity_paged', page=requestNum)
+        except KeyError:
+            pass
+    
+    activity_list = Activity.objects.published().defer("username", "author", "comments", "guid")
+    paginator = InfinitePaginator(activity_list, 25)
+    
+    try:
+        activities = paginator.page(page)
+    except:
+        raise Http404
+        
+    return render_to_response('activity.html',
+                activities.create_template_context(),
+                context_instance=RequestContext(request))
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.