Trackbacks mit Django empfangen

25.10.2007 9 Comments
Trackback-URL

Trackback-IconAls ich vor einiger Zeit den Artikel zu Blog-Features in Django geschrieben habe, waren natürlich die meisten Sachen an dieses Blog und meinen Erfahrungen mit ihm angelehnt. Damals gab es noch keine Möglichkeit Trackbacks an mein Blog zu schicken. Das ist mittlerweile anders und deshalb kann ich nun auch erklären wie es funktioniert, außerdem hat mich der Martin per Mail um diesen Artikel gebeten.

Da man zwangsläufig die Trackback-Funktionalität in Django selber schreiben muss ist es ganz gut vielleicht im Vorfeld einmal einen Blick in die Trackback-Spezifikation zu werfen. Erfunden wurden die Trackbacks von den Leuten von SixApart für ihre Blogging-Software MovableType. So wissentlich gestärkt ist die Implementierung der Trackbacks eigentlich ziemlich einfach.

Spezifikationen

Eigentlich ist ein Trackback nichts anderes als eine normale HTTP-Anfrage, die den Sinn hat das eigene Blog über die Verlinkung oder das "Bezug nehmen" auf einen bestimmten Beitrag zu informieren. Dementsprechend muss es für jeden einzelnen Beitrag eine eigene Trackback-Adresse geben, damit das eigene/empfangende Blog den Trackback auch einem Beitrag zuordnen kann. Diese besondere URL nennt sich Trackback-Ping-URL. Das Informieren eines Quellblogs nennt sich auch Ping.

Das pingende Blog sendet den Ping ganz einfach in der Form eines HTTP-POST-Request an die Trackback-Ping-URL. Dementsprechend hat man in Django also fast nichts anderes zu tun als eine einfache Formularbehandlung.

Folgende Parameter kann das pingende Blog mitschicken:

title
Mit dem Namen des Beitrags in dem man sich das pingende Blog auf den Trackback-Beitrag bezieht.

excerpt
Ein kurzer Ausschnit aus dem Beitrag in dem sich auf den Trackback-Beitrag bezogen wird.

url
Die URL des bezugsnehmenden Beitrags.

blog_name (optional)
Enthält den Namen des Blogs welches den Trackback sendet.

Abweichend von einer einfachen Auswertung von POST-Parametern ist die Antwort, die das gepingte Blog (also in diesen Fall Django) senden muss. Dabei sieht die Spezifikation zwei Antworttypen vor, jeweils als einfaches XML.

Bei erfolgreichem Ping und korrekter Annahme des Trackbacks:

<?xml version="1.0" encoding="utf-8"?>
<response>
<error>0</error>
</response>

Dementsprechend bei einem Fehler und fehlgeschlagendem Ping:

<?xml version="1.0" encoding="utf-8"?>
<response>
<error>1</error>
<message>The error message</message>
</response>

 

Implementierung in Django

Übersetzt man die Spezifikation in Django-Sprache bedeutet das folgendes: Man muss eine spezielle URL anlegen, die für jeden Beitrag als Trackback-URL existiert. Der hinter dieser URL liegende View muss die oben beschriebenen Daten entgegen nehmen und als Trackback speichern. Man brauch also ebenfalls ein extra Model um die Trackbacks zu speichern. Danach muss der View eine Antwort generieren, die je nach Ergebnis die entsprechende XML-Antwort gibt.

Zuerst ist es also sinnvoll das folgende Model 'Trackback' in der models.py anzulegen und Django entsprechend die passende Tabelle dazu anlegen zu lassen:

class Trackback(models.Model):
    blog = models.CharField("Blog",maxlength=200, core=True)
    title = models.CharField("Titel",maxlength=200, core=True)
    url = models.CharField("URL",maxlength=200, core=True)
    excerpt = models.TextField("Auszug",core=True)
    pub_date = models.DateTimeField("Datum",'date published')
    post = models.ForeignKey(Post,related_name='trackbacks')

    def __str__(self):
        return "%s" % (self.title)

    class Meta:
        ordering = ['-pub_date']
        verbose_name = "Trackback"
        verbose_name_plural = "Trackbacks"

    class Admin:
        list_display = ('blog', 'url', 'post', 'pub_date')
        date_hierarchy = 'pub_date'
        list_filter = ('pub_date','post')

Dabei verknüpft der ForeignKey den Trackback entsprechend mit dem Model, dass man für seine Posts verwendet.

Nun legt man die Trackback-URL in seiner urls.py an und weist ihr eine entsprechende View-Funktion zu. Das könnte z.B. so aussehen:

urlpatterns = patterns('blog.views',
    ...
    (r'^post/(?P<url_string>[0-9a-z_-]+)/$', 'post'), //URL für einen Post
    (r'^post/(?P<url_string>[0-9a-z_-]+)/trackback/$', 'trackback'), //Trackback-URL
    ...
)

Nun fehlt eigentlich nur noch die entsprechende View-Funktion in der views.py um den ankommenden Ping entsprechend auszuwerten. Diese lässt sich natürlich beliebig kompliziert anlegen. Mit Akismet oder ohne, wobei ich energisch empfehle immer Akismet mit einzubauen. Man ahnt nicht wie schnell man sich sonst Spam einfängt. Ich habe die Integration von Akismet im Blog-Features-Beitrag beschrieben, und dementsprechend verwendet auch der folgende Code Akismet.

def trackback(request, url_string):
    try:
        //Erstmal den Post finden
        post = Post.objects.get(url_string__iexact=url_string)
    except Post.DoesNotExist:
        //Wenn man ihn nicht finden konnte sollte man dem pingenden Blog das sagen
        return render_to_response('trackback-answer.xml',{'error':'1','errormsg':'Post not found, maybe you have the wrong trackback URL!'})

    param = ''
    try:
        //Dann die Parameter auslesen
        param = 'title'
        title = request.REQUEST[param]
        param = 'url'
        url = request.REQUEST[param]
        param = 'excerpt'
        excerpt = request.REQUEST[param]
        //das ist eigentlich unschön hier, weil der Parameter blog_name eigentlich optional ist
        //naja, mir sei etwas schludern erlaubt :)
        param = 'blog_name'
        blog = request.REQUEST[param]
       
        try:
            //Nun kommt zuerst der Check mit Akismet ob der Trackback Spam ist
            real_key = akismet.verify_key(AKISMET_API_KEY,BLOG_URL)
            if real_key:
                is_spam = akismet.comment_check(AKISMET_API_KEY,BLOG_URL,
                    request.META['REMOTE_ADDR'], request.META['HTTP_USER_AGENT'],
                    comment_author=blog,comment_content=excerpt,comment_type='trackback',comment_author_url=url)
                if is_spam:
                    //Sollte er Spam sein, dann sollte man das wiederum dem pingenden Blog mitteilen
                    return render_to_response('trackback-answer.xml',{'error':'1','errormsg': 'Your Trackback is spam!' })
                else:
                    //wenn alles OK ist dann den Trackback entsprechend speichern
                    trackback = Trackback()
                    trackback.blog = blog
                    trackback.title = title
                    trackback.url = url
                    trackback.excerpt = excerpt
                    trackback.pub_date = datetime.now()
                    trackback.post = post
                    trackback.save()
                    //und natürlich mit OK antworten
                    return render_to_response('trackback-answer.xml',{'error':'0'})
               
        except akismet.AkismetError, e:
            //Wird einfach nur geloggt, mehr kann man da nicht tun...
            error("%s - %s" % (e.response, e.statuscode))
            //Bescheid sagen
            return render_to_response('trackback-answer.xml',{'error':'1','errormsg': 'Internal error occured! Something is wrong with me.' })
       
    except Exception, e:
        //Es kann immer was Schiefgehen, das sollte man dem pingenden Blog mitteilen
        if(e.__class__.__name__ == 'KeyError'):
            //insbesondere könnten Parameter fehlen, die man aber erwartet
            //da kann man eine schöne Meldung stricken
            e = 'Parameter "%s" missing!' % param
        return render_to_response('trackback-answer.xml',{'error':'1','errormsg': e })
   
    //Im Zweifel ist halt alles gut. Aber diese Zeile dürfte fast immer unreachable sein.
    return render_to_response('trackback-answer.xml',{'error':'0'}) 

Nun fehlt nur noch der Code für die trackback-answer.xml die man wie üblich zu den anderen Templates ins 'templates'-Verzeichnis legt.

<?xml version="1.0" encoding="utf-8"?>
<response>
{% ifequal error '1' %}
<error>1</error>
<message>{{errormsg}}</message>
{% else %}
<error>0</error>
{% endifequal %}
</response>

Jetzt sollte Django mit Trackbacks von anderen Blogs problemlos umgehen können. Wichtig dafür ist natürlich, dass das andere Blog die entsprechende Trackback-URL des Beitrags kennt. Diese sollte man also dringend mit veröffentlichen.

Auto-Discovery für Trackback-URLs

In den Trackback-Spezifikationen ist aber auch der Fall berücksichtigt, dass das pingende Blog nur die Adresse des verlinkten Beitrags kennt und nicht die genaue Trackback-URL. Das pingende Blog versucht dann die Trackback-URL über eine automatische Erkennung herauszukriegen, indem es die Seite des Beitrags nach einem bestimmten RDF-Metaeintrag durchsucht.

Diese RDF-Metainformationen müssen wie folgt aussehen und man brauch sie eigentlich nur passend ein die Seite/das Template, die den Beitrag anzeigt, einzubauen:

...
<!--
    <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
             xmlns:dc="http://purl.org/dc/elements/1.1/"
             xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">
    <rdf:Description
        rdf:about="PERMALINK DES BEITRAGS"
        dc:title="TITEL DES BEITRAGS"
        dc:identifier="PERMALINK DES BEITRAGS" />
        trackback:ping="TRACKBACK-URL DES BEITRAGS"
    </rdf:RDF>
-->
...

Danach ist das selbstgebaute Django-Blog bereit für Trackbacks und Autodiscovery.

Trackbacks

Comments

Add a comment

Give your name to us, stranger!

This field is just for spam-detection!

Is the url written correctly?

Your opinion is still missing!

Sending comment
Sending comment