scherfke / django-lastfm (http://stefan.sofa-rockers.org/django-lastfm/)

Access last.fm from your Django powered website.

Clone this repository (size: 52.4 KB): HTTPS / SSH
$ hg clone http://bitbucket.org/scherfke/django-lastfm/
commit 8: 5647e87b3e84
parent 7: 4f4a47c93179
branch: default
Wrote some documentation
Stefan Scherfke
4 months ago

Changed (Δ3.8 KB):

raw changeset »

lastfm/templatetags/lastfm_widget.py (51 lines added, 14 lines removed)

lastfm/tests.py (3 lines added, 0 lines removed)

lastfm/views.py (51 lines added, 0 lines removed)

Up to file-list lastfm/templatetags/lastfm_widget.py:

1
1
# encoding: utf-8
2
2
3
3
"""
4
Displays your recently listend tracks from last.fm.
4
This module defines a single template tag that will return a context object
5
for your Last.fm widget. It contains a title for the widget and a ``<div>`` container. This container includes the AJAX code that retrieves the data
6
from the ``lastfm`` view and generates an image for each item.
7
8
.. sourcecode:: html+django
5
9
6
10
    {% load lastfm_widget %}
7
11
    
8
12
    {% get_lastfm_widget as lastfm_widget %}
13
    <h2>{{ lastfm_widget.title }}</h2>
14
    {{ lastfm_widget.content }}
15
    
16
The generated code will roughly look like this:
17
18
.. sourcecode:: html
19
20
    <div>
21
        <div><a><img /></a></div>
22
        <div><a><img /></a></div>
23
        <!-- ... -->
24
    </div>
25
26
The surrounding ``<div>`` has the CSS class *lastfm*. You can use this to
27
customize the style of the widget. This could look like this:
28
29
.. sourcecode:: css
30
31
    #sidebar > #lastfm {
32
        min-height: 225px; /* required due to "float: left" in the next sec. */
33
    }
34
35
    #sidebar #lastfm div {
36
        width: 54px;
37
        height: 39px;
38
        overflow: hidden;
39
        float: left;
40
        border: 1px solid white;
41
        -moz-border-radius: 2px;
42
        -khtml-border-radius: 2px;
43
        border-radius: 2px;
44
        margin: 0px 2px 4px 2px;
45
    }
46
47
    #sidebar #lastfm div:active, #sidebar #lastfm div:hover {
48
        border-color: #9FC765;
49
    }
50
51
    #sidebar #lastfm img {
52
        width: 54px;
53
        min-height: 39px;
54
    }
9
55
"""
10
56
11
57
@@ -17,22 +63,13 @@ register = template.Library()
17
63
18
64
19
65
class LastfmWidgetNode(template.Node):
20
    """Renders the weblog’s sidebar widgets."""
66
    """This class will create a context object named ``var_name``. It will 
67
    contain the ``title`` and the ``content`` of the widget."""
21
68
    def __init__(self, var_name):
22
69
        self.var_name = var_name
23
        # self.chart_types = {
24
        #     'recent_tracks': self._get_recent_tracks,
25
        #     'weekly_top_artists': self._get_weekly_top_artists,
26
        #     'top_artists': self._get_top_artists,
27
        # }
28
        # self.url = 'http://ws.audioscrobbler.com/2.0/?'
29
70
        
30
71
    def render(self, context):
31
        # try:
32
            # data = self.chart_types[settings.LASTFM_CHART_TYPE]()
33
        # except:
34
        #     data = []
35
        
72
        """Load the template and render it into a context variable."""
36
73
        t = template.loader.get_template('lastfm_widget/_widget.html')
37
74
        lastfm_widget = {
38
75
            'title': settings.LASTFM_WIDGET_TITLE,
@@ -46,7 +83,7 @@ class LastfmWidgetNode(template.Node):
46
83
47
84
@register.tag
48
85
def get_lastfm_widget(parser, token):
49
    """Get the last.fm widget and store it in a template variable."""
86
    """Get the variable name for the widget from the template tag."""
50
87
    try:
51
88
        tagname, _as, var_name = token.split_contents()
52
89
    except ValueError:

Up to file-list lastfm/tests.py:

@@ -17,6 +17,8 @@ class TemplateTagsTestCase(TestCase):
17
17
        self.token = mock.Mock(spec=['split_contents'])
18
18
        
19
19
    def test_get_sidebar_widgets(self):
20
        """Test if ``lastfm_widget.get_lastfm_widget()`` returns a node with
21
        the correct variable name."""
20
22
        tag = 'get_lastfm_widget'
21
23
        var_name = 'widgets'
22
24
        self.token.split_contents.return_value = (tag, 'as', var_name)
@@ -27,6 +29,7 @@ class TemplateTagsTestCase(TestCase):
27
29
    @mock.patch('django.template.loader.get_template')
28
30
    @mock.patch('django.template.Context')
29
31
    def test_lastfm_widget_node(self, get_template, Context):
32
        """Test if the template node contains the correct template variables."""
30
33
        class ContextMock(dict):
31
34
            autoescape = object()
32
35
        context = ContextMock()

Up to file-list lastfm/views.py:

1
1
# encoding: utf-8
2
2
3
"""
4
The AJAX code generate by the template tag (see
5
:mod:`templatetags.lastfm_widget`) doesn’t contact Last.fm directly, but uses a
6
Django view as proxy. The advantage of this is, that you can exactly control
7
what data your site gets. Another advantage is, that visitors can’t see your
8
Last.fm username by inspecting the HTML source of your site.
9
10
In addition to the view itself this module also defines some helper classes that
11
are responsible for handling the different types of charts (e.g. top tracks or
12
top artists).
13
"""
14
3
15
import urllib
4
16
5
17
from django.conf import settings
@@ -8,6 +20,39 @@ from django.utils import simplejson as j
8
20
9
21
10
22
def lastfm_data(request):
23
    """This view retrievs the data from Last.fm and returns a JSON encoded list.         
24
    The AJAX code from the template tag will retrive this list and generate
25
    the chart from it.
26
    
27
    Each list entry is a with three elements:
28
    * ``title``: Contains the song title or artist name and can be displayed as
29
      alternative text or link title
30
    * ``url``: A url pointing to the track or artist on Last.fm
31
    * ``img_url``: A url pointing to the track’s cover or artist image
32
    
33
    For each chart type there is a class that handles its data. They are
34
    necessary to unify Last.fm’s different key names to those three explained
35
    above. Currently, there is no abstract base class which they must inherit
36
    from, but each class is expected to implement the following attributes and
37
    methods:
38
    
39
    ``params``: A dict that contains all parameters required by the `Last.fm API
40
    call <http://www.lastfm.de/api>`_
41
    
42
    ``get_data(data)``: Last.fm’s JSON data is quite nested. This method should
43
    extract the list with the actual items from the raw data.
44
    
45
    ``get_item_title(item)``: Return an item’s text for the ``title`` attribute
46
    in the data dict.
47
    
48
    ``get_default_image()``: Return a url to a default image that will be used
49
    if a track or artist has no image on its own.
50
    
51
    ``get_img_url(url, img_size, item)``: This method needs to be implemented
52
    only if an item has no ``image`` key (which is the case for e.g. the weekly
53
    top artist). It gets the Last.fm API url, the desired image size (small,
54
    medium, large, …) and the item. It might do another API call and extract a
55
    custom image URL for that item."""
11
56
    url = 'http://ws.audioscrobbler.com/2.0/?'
12
57
    chart_types = {
13
58
        'recent_tracks': RecentTracks,
@@ -50,6 +95,7 @@ def lastfm_data(request):
50
95
51
96
        
52
97
class RecentTracks(object):
98
    """This class handles the API call ``user.getRecentTracks``."""
53
99
    params = {
54
100
        'method': 'user.getRecentTracks',
55
101
        'user': settings.LASTFM_USER,
@@ -69,6 +115,7 @@ class RecentTracks(object):
69
115
70
116
        
71
117
class WeeklyTopArtists(object):
118
    """This class handles the API call ``user.getWeeklyArtistChart``."""
72
119
    params = {
73
120
        'method': 'user.getWeeklyArtistChart',
74
121
        'user': settings.LASTFM_USER,
@@ -88,6 +135,8 @@ class WeeklyTopArtists(object):
88
135
                'default_artist_large.png'
89
136
90
137
    def get_img_url(self, url, img_size, item):
138
        """A chart item of this class does not contain any images, so we need to 
139
        do another API call to get an image for artist in ``item``."""
91
140
        params = urllib.urlencode({
92
141
            'method': 'artist.getimages',
93
142
            'artist': item['name'],
@@ -105,6 +154,8 @@ class WeeklyTopArtists(object):
105
154
    
106
155
    
107
156
class TopArtists(object):
157
    """This class handles the API call ``user.getTopArtists``. The period must 
158
    be defined in the site’s settings module."""
108
159
    params = {
109
160
        'method': 'user.getTopArtists',
110
161
        'user': settings.LASTFM_USER,