Commits

Stefan Scherfke committed 2a2de46

Moved everything that was to "lastfm/" and added some basic documentation.

Comments (0)

Files changed (20)

+The primary author of django-lastfm is Stefan Scherfke, who may be found online 
+at http://stefan.sofa-rockers.org/.
+
+Changelog for django-lastfm
+===========================
+
+Version 0.1 – 2009-11-24:
+-------------------------
+
+- [NEW] Initial release

LICENSE

-Copyright (c) 2009, Stefan Scherfke
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification,
-are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice,
-      this list of conditions and the following disclaimer.
-
-    * Redistributions in binary form must reproduce the above copyright notice,
-      this list of conditions and the following disclaimer in the documentation
-      and/or other materials provided with the distribution.
-
-    * Neither the name of the <ORGANIZATION> nor the names of its contributors
-      may be used to endorse or promote products derived from this software
-      without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
-ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+Copyright (c) 2009, Stefan Scherfke
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+
+    * Neither the name of the <ORGANIZATION> nor the names of its contributors
+      may be used to endorse or promote products derived from this software
+      without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Django Last.fm access
+=====================
+
+This is a small Django application that allows you to access Last.fm and
+embed e.g. your recently listened tracks or you top artists on your website.
+
+Currently you can get your
+* recently listened tracks
+* weekly artist chart
+* top artists (for a definable period)
+
+The application consists of two main parts: The first one is a view, that
+retrieves the data from Last.fm, processes it and returns it as a JSON encoded
+dict. The second one is a template tag that embeds some AJAX code into your
+website. This code will call the view and display the artist images or album
+covers. This mechanism prevents the Last.fm servers from slowing down your
+blazingly fast Django site … ;-)
+
+
+Requirements
+------------
+
+This app is tested with Django 1.1. It might also work with older versions, but
+I haven’t tested it.
+
+If you want to run the tests, you’ll need to install `Mock <http://www.voidspace.org.uk/python/mock/>`_.
+
+There are no other requirements.
+
+
+Installation
+------------
+
+If you read this, you have probably managed to extract the archive containing
+these files. Next, open a Terminal and `cd` to the directory containing this
+file (e.g. ``cd ~/Downloads/django-lastfm``). Then execute::
+
+    python setup.py install
+    
+If you checked out the repository and always want to use the newest version,
+type::
+
+    python setup.py develop
+    
+
+Usage
+-----
+
+Documentation on how to use this app can be found in the *docs/* directory.

__init__.py

Empty file removed.

lastfm/__init__.py

Empty file added.

lastfm/models.py

Empty file added.

lastfm/templates/lastfm_widget/_widget.html

+<div id="lastfm">
+<script type="text/javascript" charset="utf-8">
+    $.getJSON("{% url lastfm %}",
+        function(data) {
+            $.each(data, function(i, item) {
+                img = $("<img />").attr({
+                        'src': item.img_url, 
+                        'alt': item.title, 
+                        'title': item.title
+                })
+                a = $("<a />").attr('href', item.url ).append(img)
+                div = $("<div />").append(a)
+                div.appendTo('#lastfm')
+            });
+        });
+</script>
+</div>

lastfm/templatetags/__init__.py

Empty file added.

lastfm/templatetags/lastfm_widget.py

+# encoding: utf-8
+
+"""
+Displays your recently listend tracks from last.fm.
+
+    {% load lastfm_widget %}
+    
+    {% get_lastfm_widget as lastfm_widget %}
+"""
+
+
+from django import template
+from django.conf import settings
+
+
+register = template.Library()
+
+
+class LastfmWidgetNode(template.Node):
+    """Renders the weblog’s sidebar widgets."""
+    def __init__(self, var_name):
+        self.var_name = var_name
+        # self.chart_types = {
+        #     'recent_tracks': self._get_recent_tracks,
+        #     'weekly_top_artists': self._get_weekly_top_artists,
+        #     'top_artists': self._get_top_artists,
+        # }
+        # self.url = 'http://ws.audioscrobbler.com/2.0/?'
+        
+    def render(self, context):
+        # try:
+            # data = self.chart_types[settings.LASTFM_CHART_TYPE]()
+        # except:
+        #     data = []
+        
+        t = template.loader.get_template('lastfm_widget/_widget.html')
+        lastfm_widget = {
+            'title': settings.LASTFM_WIDGET_TITLE,
+            'content': t.render(template.Context({},
+                    autoescape=context.autoescape)),
+        }
+        
+        context[self.var_name] = lastfm_widget
+        return ''
+
+
+@register.tag
+def get_lastfm_widget(parser, token):
+    """Get the last.fm widget and store it in a template variable."""
+    try:
+        tagname, _as, var_name = token.split_contents()
+    except ValueError:
+        raise template.TemplateSyntaxError('%r tag requires two arguments.' % 
+                token.contents.split()[0])
+    return LastfmWidgetNode(var_name)
+# encoding: utf-8
+"""
+Tests for the last.fm app.
+"""
+
+from django.test import TestCase
+import mock
+
+from lastfm.templatetags import lastfm_widget
+
+
+class TemplateTagsTestCase(TestCase):
+    """Test the template tag for the last.fm widget."""
+    
+    def setUp(self):
+        self.parser = mock.Mock(spec=['compile_filter'])
+        self.token = mock.Mock(spec=['split_contents'])
+        
+    def test_get_sidebar_widgets(self):
+        tag = 'get_lastfm_widget'
+        var_name = 'widgets'
+        self.token.split_contents.return_value = (tag, 'as', var_name)
+        
+        node = lastfm_widget.get_lastfm_widget(self.parser, self.token)
+        self.assertEqual(node.var_name, var_name)
+        
+    @mock.patch('django.template.loader.get_template')
+    @mock.patch('django.template.Context')
+    def test_lastfm_widget_node(self, get_template, Context):
+        class ContextMock(dict):
+            autoescape = object()
+        context = ContextMock()
+        template = get_template.return_value
+    
+        var_name = 'widgets'
+        node = lastfm_widget.LastfmWidgetNode(var_name)
+        node.render(context)     
+        
+        self.assertTrue(var_name in context)
+        widget = context[var_name]
+        self.assertTrue('title' in widget)
+        self.assertTrue('content' in widget)
+# encoding: utf-8
+
+import urllib
+
+from django.conf import settings
+from django.http import HttpResponse
+from django.utils import simplejson as json
+
+
+def lastfm_data(request):
+    url = 'http://ws.audioscrobbler.com/2.0/?'
+    chart_types = {
+        'recent_tracks': RecentTracks,
+        'weekly_top_artists': WeeklyTopArtists,
+        'top_artists': TopArtists,
+    }
+    img_size = 'large'
+    if hasattr(settings, 'LASTFM_IMG_SIZE'):
+        img_size = settings.LASTFM_IMG_SIZE
+        
+    chart = chart_types[settings.LASTFM_CHART_TYPE]()
+    
+    params = urllib.urlencode(chart.params)
+    data = json.loads(urllib.urlopen(url + params).read())
+    
+    items = []
+    for i, d in enumerate(chart.get_data(data)):
+        # Not every last.fm method supports the ``limit`` parameter so we have
+        # to take care of this ourselves.
+        if i == int(settings.LASTFM_NUM_IMAGES):
+            break
+        
+        item = {
+            'title': chart.get_item_title(d),
+            'url': d['url'],
+            'img_url': chart.get_default_image(),
+        }
+        
+        if 'image' in d:
+            for img in d['image']:
+                if img['size'] == img_size and img['#text']:
+                    item['img_url'] = img['#text']
+                    break
+        else:
+            item['img_url'] = chart.get_img_url(url, img_size, d)
+        
+        items.append(item)
+        
+    return HttpResponse(json.dumps(items), mimetype='application/json')
+
+        
+class RecentTracks(object):
+    params = {
+        'method': 'user.getRecentTracks',
+        'user': settings.LASTFM_USER,
+        'limit': settings.LASTFM_NUM_IMAGES,
+        'api_key': settings.LASTFM_API_KEY,
+        'format': 'json',
+    }
+    
+    def get_data(self, data):
+        return data['recenttracks']['track']
+        
+    def get_item_title(self, item):
+        return u'%s – %s' % (item['artist']['#text'], item['name'])
+        
+    def get_default_image(self):
+        return 'http://cdn.last.fm/depth/catalogue/noimage/cover_85px.gif'
+
+        
+class WeeklyTopArtists(object):
+    params = {
+        'method': 'user.getWeeklyArtistChart',
+        'user': settings.LASTFM_USER,
+        'limit': settings.LASTFM_NUM_IMAGES,
+        'api_key': settings.LASTFM_API_KEY,
+        'format': 'json',
+    }
+    
+    def get_data(self, data):
+        return data['weeklyartistchart']['artist']
+        
+    def get_item_title(self, item):
+        return '%s (%s plays)' % (item['name'], item['playcount'])
+        
+    def get_default_image(self):
+        return 'http://cdn.last.fm/flatness/catalogue/noimage/2/' + \
+                'default_artist_large.png'
+
+    def get_img_url(self, url, img_size, item):
+        params = urllib.urlencode({
+            'method': 'artist.getimages',
+            'artist': item['name'],
+            'limit': 1,
+            'api_key': settings.LASTFM_API_KEY,
+            'format': 'json',
+        })
+        
+        img_data = json.loads(urllib.urlopen(url + params).read())['images']
+        if 'image' in img_data:
+            img_data = img_data['image']['sizes']['size']
+            for img in img_data:
+                if img['name'] == img_size and img['#text']:
+                    return img['#text']
+    
+    
+class TopArtists(object):
+    params = {
+        'method': 'user.getTopArtists',
+        'user': settings.LASTFM_USER,
+        'api_key': settings.LASTFM_API_KEY,
+        'period': settings.LASTFM_TOP_ARTISTS_PERIOD,
+        'format': 'json'
+    }
+    
+    def get_data(self, data):
+        return data['topartists']['artist']
+        
+    def get_item_title(self, item):
+        return '%s (%s plays)' % (item['name'], item['playcount'])
+        
+    def get_default_image(self):
+        return 'http://cdn.last.fm/flatness/catalogue/noimage/2/' + \
+                'default_artist_large.png'

models.py

Empty file removed.

setup.py

Empty file added.

templates/lastfm_widget/_widget.html

-<div id="lastfm">
-<script type="text/javascript" charset="utf-8">
-    $.getJSON("{% url lastfm %}",
-        function(data) {
-            $.each(data, function(i, item) {
-                img = $("<img />").attr({
-                        'src': item.img_url, 
-                        'alt': item.title, 
-                        'title': item.title
-                })
-                a = $("<a />").attr('href', item.url ).append(img)
-                div = $("<div />").append(a)
-                div.appendTo('#lastfm')
-            });
-        });
-</script>
-</div>

templatetags/__init__.py

Empty file removed.

templatetags/lastfm_widget.py

-# encoding: utf-8
-
-"""
-Displays your recently listend tracks from last.fm.
-
-    {% load lastfm_widget %}
-    
-    {% get_lastfm_widget as lastfm_widget %}
-"""
-
-
-from django import template
-from django.conf import settings
-
-
-register = template.Library()
-
-
-class LastfmWidgetNode(template.Node):
-    """Renders the weblog’s sidebar widgets."""
-    def __init__(self, var_name):
-        self.var_name = var_name
-        # self.chart_types = {
-        #     'recent_tracks': self._get_recent_tracks,
-        #     'weekly_top_artists': self._get_weekly_top_artists,
-        #     'top_artists': self._get_top_artists,
-        # }
-        # self.url = 'http://ws.audioscrobbler.com/2.0/?'
-        
-    def render(self, context):
-        # try:
-            # data = self.chart_types[settings.LASTFM_CHART_TYPE]()
-        # except:
-        #     data = []
-        
-        t = template.loader.get_template('lastfm_widget/_widget.html')
-        lastfm_widget = {
-            'title': settings.LASTFM_WIDGET_TITLE,
-            'content': t.render(template.Context({},
-                    autoescape=context.autoescape)),
-        }
-        
-        context[self.var_name] = lastfm_widget
-        return ''
-
-
-@register.tag
-def get_lastfm_widget(parser, token):
-    """Get the last.fm widget and store it in a template variable."""
-    try:
-        tagname, _as, var_name = token.split_contents()
-    except ValueError:
-        raise template.TemplateSyntaxError('%r tag requires two arguments.' % 
-                token.contents.split()[0])
-    return LastfmWidgetNode(var_name)

tests.py

-# encoding: utf-8
-"""
-Tests for the last.fm app.
-"""
-
-from django.test import TestCase
-import mock
-
-from lastfm.templatetags import lastfm_widget
-
-
-class TemplateTagsTestCase(TestCase):
-    """Test the template tag for the last.fm widget."""
-    
-    def setUp(self):
-        self.parser = mock.Mock(spec=['compile_filter'])
-        self.token = mock.Mock(spec=['split_contents'])
-        
-    def test_get_sidebar_widgets(self):
-        tag = 'get_lastfm_widget'
-        var_name = 'widgets'
-        self.token.split_contents.return_value = (tag, 'as', var_name)
-        
-        node = lastfm_widget.get_lastfm_widget(self.parser, self.token)
-        self.assertEqual(node.var_name, var_name)
-        
-    @mock.patch('django.template.loader.get_template')
-    @mock.patch('django.template.Context')
-    def test_lastfm_widget_node(self, get_template, Context):
-        class ContextMock(dict):
-            autoescape = object()
-        context = ContextMock()
-        template = get_template.return_value
-    
-        var_name = 'widgets'
-        node = lastfm_widget.LastfmWidgetNode(var_name)
-        node.render(context)     
-        
-        self.assertTrue(var_name in context)
-        widget = context[var_name]
-        self.assertTrue('title' in widget)
-        self.assertTrue('content' in widget)

views.py

-# encoding: utf-8
-
-import urllib
-
-from django.http import HttpResponse
-from django.utils import simplejson as json
-
-from django_site import settings
-
-
-def lastfm_data(request):
-    url = 'http://ws.audioscrobbler.com/2.0/?'
-    chart_types = {
-        'recent_tracks': RecentTracks,
-        'weekly_top_artists': WeeklyTopArtists,
-        'top_artists': TopArtists,
-    }
-    img_size = 'large'
-    if hasattr(settings, 'LASTFM_IMG_SIZE'):
-        img_size = settings.LASTFM_IMG_SIZE
-        
-    chart = chart_types[settings.LASTFM_CHART_TYPE]()
-    
-    params = urllib.urlencode(chart.params)
-    data = json.loads(urllib.urlopen(url + params).read())
-    
-    items = []
-    for i, d in enumerate(chart.get_data(data)):
-        # Not every last.fm method supports the ``limit`` parameter so we have
-        # to take care of this ourselves.
-        if i == int(settings.LASTFM_NUM_IMAGES):
-            break
-        
-        item = {
-            'title': chart.get_item_title(d),
-            'url': d['url'],
-            'img_url': chart.get_default_image(),
-        }
-        
-        if 'image' in d:
-            for img in d['image']:
-                if img['size'] == img_size and img['#text']:
-                    item['img_url'] = img['#text']
-                    break
-        else:
-            item['img_url'] = chart.get_img_url(url, img_size, d)
-        
-        items.append(item)
-        
-    return HttpResponse(json.dumps(items), mimetype='application/json')
-
-        
-class RecentTracks(object):
-    params = {
-        'method': 'user.getRecentTracks',
-        'user': settings.LASTFM_USER,
-        'limit': settings.LASTFM_NUM_IMAGES,
-        'api_key': settings.LASTFM_API_KEY,
-        'format': 'json',
-    }
-    
-    def get_data(self, data):
-        return data['recenttracks']['track']
-        
-    def get_item_title(self, item):
-        return u'%s – %s' % (item['artist']['#text'], item['name'])
-        
-    def get_default_image(self):
-        return 'http://cdn.last.fm/depth/catalogue/noimage/cover_85px.gif'
-
-        
-class WeeklyTopArtists(object):
-    params = {
-        'method': 'user.getWeeklyArtistChart',
-        'user': settings.LASTFM_USER,
-        'limit': settings.LASTFM_NUM_IMAGES,
-        'api_key': settings.LASTFM_API_KEY,
-        'format': 'json',
-    }
-    
-    def get_data(self, data):
-        return data['weeklyartistchart']['artist']
-        
-    def get_item_title(self, item):
-        return '%s (%s plays)' % (item['name'], item['playcount'])
-        
-    def get_default_image(self):
-        return 'http://cdn.last.fm/flatness/catalogue/noimage/2/' + \
-                'default_artist_large.png'
-
-    def get_img_url(self, url, img_size, item):
-        params = urllib.urlencode({
-            'method': 'artist.getimages',
-            'artist': item['name'],
-            'limit': 1,
-            'api_key': settings.LASTFM_API_KEY,
-            'format': 'json',
-        })
-        
-        img_data = json.loads(urllib.urlopen(url + params).read())['images']
-        if 'image' in img_data:
-            img_data = img_data['image']['sizes']['size']
-            for img in img_data:
-                if img['name'] == img_size and img['#text']:
-                    return img['#text']
-    
-    
-class TopArtists(object):
-    params = {
-        'method': 'user.getTopArtists',
-        'user': settings.LASTFM_USER,
-        'api_key': settings.LASTFM_API_KEY,
-        'period': settings.LASTFM_TOP_ARTISTS_PERIOD,
-        'format': 'json'
-    }
-    
-    def get_data(self, data):
-        return data['topartists']['artist']
-        
-    def get_item_title(self, item):
-        return '%s (%s plays)' % (item['name'], item['playcount'])
-        
-    def get_default_image(self):
-        return 'http://cdn.last.fm/flatness/catalogue/noimage/2/' + \
-                'default_artist_large.png'