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
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)
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 |
""" |
|
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 |
|
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, |
