Colin Copeland avatar Colin Copeland committed 068de63

add flickr app stub; use css3 transitions on homepage

Comments (0)

Files changed (12)

lib/copelco/apps/default/views.py

 from django.template import Context, RequestContext, loader
 from django.shortcuts import render_to_response, get_object_or_404
 
+from copelco.apps.aggregator.models import FeedItem
+from copelco.apps.flickr.models import PhotoSize
+
 
 def home(request):
-    context = {}
+    feed_items = FeedItem.objects.order_by('-date_modified')[:10]
+    photos = PhotoSize.objects.filter(type='medium',
+                                      width__gt=500).order_by('?')[:10]
+    context = {'feed_items': feed_items, 'photos': photos}
     return render_to_response('home.html', context,
                               context_instance=RequestContext(request))
 
Add a comment to this file

lib/copelco/apps/flickr/__init__.py

Empty file added.

lib/copelco/apps/flickr/admin.py

+from django.contrib import admin
+
+from copelco.apps.flickr import models as flickr
+
+
+class UserAdmin(admin.ModelAdmin):
+	list_display = ('username', 'nsid')
+admin.site.register(flickr.User, UserAdmin)
+
+
+class PhotoAdmin(admin.ModelAdmin):
+	list_display = ('id', 'title', 'date_uploaded', 'date_taken',
+	                'date_synced', 'user')
+	date_hierarchy = 'date_uploaded'
+	list_filter = ('user', 'date_taken', 'date_synced')
+	ordering = ('-date_uploaded',)
+	search_fields = ('title', 'user__username', 'user__nsid')
+admin.site.register(flickr.Photo, PhotoAdmin)
+
+
+class PhotoSizeAdmin(admin.ModelAdmin):
+	list_display = ('id', 'type', 'photo', 'width', 'height')
+	list_filter = ('type',)
+	ordering = ('-photo__date_uploaded',)
+	search_fields = ('photo__title', 'heigh', 'width')
+admin.site.register(flickr.PhotoSize, PhotoSizeAdmin)

lib/copelco/apps/flickr/api/__init__.py

+import time
+import json
+import urllib
+import urllib2
+import urlparse
+import logging
+
+import oauth2 as oauth
+
+
+__all__ = ('Flickr',)
+
+
+logger = logging.getLogger('flickr')
+logging.basicConfig(level=logging.DEBUG)
+
+
+class Flickr(object):
+    """
+    Simple Flickr API wrapper
+    """
+
+    url = 'http://api.flickr.com/services/rest/'
+
+    def __init__(self, key, secret):
+        self._consumer_key = key
+        self._consumer_secret = secret
+
+    def _call(self, **kwargs):
+        """
+        Call Flickr REST service and parse JSON response
+        """
+        settings = {
+            'format': 'json',
+            'api_key': self._consumer_key,
+            'nojsoncallback': 1,
+        }
+        kwargs.update(settings)
+        logger.debug('REST call: {0}'.format(kwargs))
+        response = urllib2.urlopen(self.url, urllib.urlencode(kwargs))
+        data = json.loads(response.read())
+        return data
+
+    def search(self, **kwargs):
+        """
+        Helper method to call flickr.photos.search
+        """
+        return self._call(method='flickr.photos.search', **kwargs)
+
+    def user_photos(self, username, **kwargs):
+        """
+        Iterate through user photos, page by page
+        """
+        if 'per_page' not in kwargs:
+            kwargs['per_page'] = 50
+        page, pages = 1, 1
+        logger.debug('Fetching page 1 of user photos')
+        while page <= pages:
+            if page > 1:
+                msg = 'Fetching page {0} of {1} of user photos'
+                logger.debug(msg.format(page, pages))
+            kwargs['page'] = page
+            response = self.search(user_id=username, **kwargs)
+            pages = response['photos']['pages']
+            for photo in response['photos']['photo']:
+                yield photo
+            page += 1
+
+    def get_nsid(self, username):
+        """
+        Return a user's NSID, given their username
+        """
+        response = self._call(method='flickr.people.findByUsername',
+                              username=username)
+        return response['user']['nsid']

lib/copelco/apps/flickr/api/auth.py

+# requires oauth2==1.5.170
+def _authenticate(self):
+    consumer = oauth.Consumer(key=self._consumer_key,
+                              secret=self._consumer_secret)
+    # get request token
+    params = {
+        'oauth_timestamp': str(int(time.time())),
+        'oauth_signature_method': "HMAC-SHA1",
+        'oauth_version': "1.0",
+        'oauth_callback': ' ',
+        'oauth_nonce': oauth.generate_nonce(),
+        'oauth_consumer_key': self._consumer_key,
+    }
+    req = oauth.Request('GET', self.request_token_url, params)
+    req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), consumer, None)
+    response = urllib2.urlopen(req.to_url())
+    data = dict(urlparse.parse_qsl(response.read()))
+    self.request_token = oauth.Token(data['oauth_token'],
+                                     data['oauth_token_secret'])
+    # authorize
+    auth_params = {
+        'perms': 'read',
+        'oauth_token': self.request_token.key,
+    }
+    req = oauth.Request('GET', self.authorize_url, auth_params)
+    req.sign_request(oauth.SignatureMethod_HMAC_SHA1(), consumer, None)
+    response = urllib2.urlopen(req.to_url())

lib/copelco/apps/flickr/models.py

+import datetime
+
+from django.db import models
+
+
+class User(models.Model):
+	username = models.CharField(max_length=255)
+	nsid = models.CharField(max_length=255)
+
+	def __unicode__(self):
+		return self.nsid
+
+
+class Photo(models.Model):
+
+	user = models.ForeignKey(User)
+	id = models.BigIntegerField('ID', primary_key=True)
+	title = models.CharField(max_length=255, blank=True, default='')
+	date_uploaded = models.DateTimeField()
+	date_taken = models.DateTimeField()
+	date_synced = models.DateTimeField()
+
+	def __unicode__(self):
+		return unicode(self.id)
+
+
+class PhotoSize(models.Model):
+
+	SIZE_CHOICES = (
+		('', 'Unknown'),
+		('square', 'Square'),
+		('thumbnail', 'Thumbnail'),
+		('small', 'Small'),
+		('medium', 'Medium'),
+		('original', 'Original'),
+	)
+
+	photo = models.ForeignKey(Photo, related_name='sizes')
+	type = models.CharField(max_length=16, choices=SIZE_CHOICES, blank=True,
+	                        db_index=True)
+	width = models.PositiveSmallIntegerField()
+	height = models.PositiveSmallIntegerField()
+	source = models.URLField(verify_exists=False)
+
+	def __unicode__(self):
+		return self.source

lib/copelco/apps/flickr/sync.py

+import time
+import datetime
+import logging
+
+from django.conf import settings
+
+from copelco.apps.flickr import api
+from copelco.apps.flickr import models as flickr
+
+
+PHOTO_EXTRAS = (
+	'url_t',
+	'url_s',
+	'url_m',
+	'url_z',
+	'date_upload',
+	'date_taken',
+)
+PHOTO_SIZES = {
+	't': 'thumbnail',
+	's': 'square',
+	'm': 'small',
+	'z': 'medium',
+}
+logger = logging.getLogger('flickr.sync')
+
+
+def sync_photos():
+	session = api.Flickr(key=settings.FLICKR_KEY,
+	                     secret=settings.FLICKR_SECRET)
+	photo_extras = ','.join(PHOTO_EXTRAS)
+	date_synced = datetime.datetime.now()
+	for user in flickr.User.objects.all():
+		logger.debug('Syncing user {0}'.format(user.nsid))
+		for result in session.user_photos(user.nsid, extras=photo_extras):
+			logger.debug('Inserting photo {0}'.format(result))
+			save_photo(user, result, date_synced)
+
+
+def save_photo(user, result, date_synced):
+	date_uploaded = time.gmtime(int(result['dateupload']))
+	photo, _ = flickr.Photo.objects.get_or_create(
+		user=user,
+		id=result['id'],
+		title=result['title'],
+		date_uploaded=datetime.datetime(*date_uploaded[:6]),
+		date_taken=result['datetaken'],
+		defaults={'date_synced': date_synced},
+	)
+	urls = [key for key in result.keys() if key.startswith('url')]
+	for url in urls:
+		_, suffix = url.split('_')
+		type_ = PHOTO_SIZES.get(suffix, '')
+		photo.sizes.get_or_create(
+			type = type_,
+			width=result['width_{0}'.format(suffix)],
+			height=result['height_{0}'.format(suffix)],
+			source=result[url],
+		)

lib/copelco/settings.py

     'copelco.apps.default',
     'copelco.apps.estate',
     'copelco.apps.aggregator',
+    'copelco.apps.flickr',
 ]
 
 STATICFILES_DIRS = (

lib/copelco/static/css/layout.css

 ul#site-navigation li:first-child {
     border-left: none;
 }
+
+ul#slideshow {
+   margin: 0;
+   width: 640px;
+   height: 460px;
+   position: relative;
+   border: 4px solid black;
+   border-radius: 3px;
+   overflow: hidden;
+   box-shadow: 2px 2px 4px #888888;
+}
+
+ul#slideshow li {
+    list-style: none;
+    margin: 0;
+    padding: 0;
+    position: absolute;
+    top: 0;
+    left: 0;
+}
+
+ul#slideshow li img {
+    margin: 0;
+    opacity: 0;
+    overflow: hidden;
+    -moz-transition: opacity 3s ease;
+    -webkit-transition: opacity 3s ease;
+    -o-transition: opacity 3s ease;
+    transition: opacity 3s ease;
+}
+
+ul#slideshow li.target img {
+    opacity: 1;
+}

lib/copelco/templates/base_site.html

         <link rel="stylesheet" type="text/css" media="all" href="{{ STATIC_URL }}css/960.css" />
         <link rel="stylesheet" type="text/css" media="all" href="{{ STATIC_URL }}css/layout.css" />
         {% block css %}{% endblock %}
+        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
+        <script type="text/javascript" src="{{ STATIC_URL }}js/slideshow.js"></script>
         {% block javascript %}{% endblock %}
     </head>
     <body>

lib/copelco/templates/home.html

 {% extends 'base_columns.html' %}
 
+{% block content %}
 
+<p>I'm a Python/Django web developer in Durham, NC. I work at <a href='http://www.caktusgroup.com/'>Caktus Group</a>.
+
+
+
+<ul id='slideshow'>
+{% for photo in photos %}
+	<li {% if forloop.first %}class='target'{% endif %}>
+		<img src='{{ photo.source }}' width='{{ photo.width }}' height='{{ photo.height }}' />
+	</li>
+{% endfor %}
+</ul>
+
+{% endblock %}
+
+{% block sidebar %}
+{% for item in feed_items %}
+    <div id='item-{{ item.pk }}'>{{ item.summary|safe }}</div>
+{% endfor %}
+{% endblock %}

requirements/apps.txt

 BeautifulSoup==3.2.0
 celery==2.2.6
 django-celery==2.2.4
-feedparser==5.0.1
+feedparser==5.0.1
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.