Django Generic XSS Safe Tags
Adding generic cross site scripting (XSS) safe tags to any site in Django is a painless process once you have the code. Tags are an amazing new concept that stems directly from our efforts to organize a bloated and cluttered online world. This blog is a great example, there are tags at the bottom to help people find this article and others like it. Tags differ from plain web text in that they are an intentional active effort by a human being to categorize something. Search engines and page scraping have historically been passive non-intuitive algorithms. Tags add a human element like Django does for development…
On my recent projects I’ve built a basic tagging mechanism that has served me well. I use this exact code in JoostList and Joosthot. The tagging feature lets all online visitors tag the site content while preventing XSS, allowing searches, and even multi-tag entry at the same time. This artivle will cover in random order…
- Tag submit form and display
- Tag handler and validation
- Tag integration into a Django model
- Tag search and filter implementation
Template
Presenting tags to the user is made relatively simple by the Django framework. Loop through a list of tags and display them, note that the "urlencode" filter helps prevent XSS attacks by converting any html characters into safe encoded values. The "urlencode" is probably not necessary because the back end handler will filter all improper tags but its nice to have in case the back end is ever modified.
New tags are added to the system via a basic form. The form does not currently use AJAX, instead it submits directly to a handler that re-display’s the page. Note that the handler is just a generic video handler in this case, the tags handler is a generic component than can be embedded in any other handler. See the integration section below for details.
{% for tag in tags %}
{{ tag.title }}
{% endfor %}
Tag Code
All the functionality of tagging is handled within one file. This handler receives input from the html above and verifies it before saving it to the model. XSS is filtered out by only allowing letters, numbers, space, and a comma. Those are the only characters that should be in a tag anyway. Furthermore we use "self.model.objects" to get a generic reference to the object being tagged, this makes the tag handler super generic. If you use this code you probably do not need to modify anything except "joosthot.joosthot.models import Tag", which is the model location.
import sys
import re
from django import forms
from django.core import validators
from django.core.exceptions import ObjectDoesNotExist
from django.template import RequestContext
from string import Template
from joosthot.joosthot.models import Tag
from datetime import datetime
class TagManipulator(forms.Manipulator):
def __init__(self, request, model):
self.fields = (
forms.TextField(field_name='tags', length=20, maxlength=40, is_required=False, validator_list=[self.isTag, validators.hasNoProfanities]),
)
self.request = request
self.model = model
def isTag(self, field_data, all_data):
if re.search('[^A-Za-z0-9, ]', field_data):
raise validators.ValidationError, "No XSS"
def post(self):
new_data = {}
errors = {}
tags = {}
if self.request.POST and self.request.POST.has_key('tags'):
new_data = self.request.POST.copy()
errors = self.get_validation_errors(new_data)
self.do_html2python(new_data)
if not errors:
tags = new_data['tags'].split(',')
new_data['tags'] = ''
for token in tags:
tag = Tag()
tag.title=token.strip().lower()
tag.content_object = self.model.objects.get(id=new_data['id'])
tag.save()
return forms.FormWrapper(self, new_data, errors)
Integration Code
To get the code to work with your site, copy and paste the above handler as is (modify only the model), put the template snippet in your front end somewhere, then modify your back end as follows. The code below is a quick way to tie tags to any object, simply call the tag manipulator, pass the returned form to the front end, and return a list of tags. That’s it, you can now gather and display tags on any item on your site. Well almost, remember to modify your model, next section.
def video(request, id = '0'):
tags = {}
tag_manipulator = TagManipulator(request, Video)
tag_form = tag_manipulator.post();
video = Video.objects.get(id=id)
tags = video.tags.all()
return render_to_response('video_details.html', { 'id':id, 'video':video, 'tags':tags, 'tag_form':tag_form })
Model
Modifying the model is quick and easy. Simply copy and paste the entire Tag class, then add a generic relation to each object you want to tag. Again the Tag class should remain unmodified.
class Tag(models.Model):
title = models.SlugField()
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = models.GenericForeignKey()
class Meta:
ordering = ["title"]
class Admin:
pass
def __str__(self):
return self.title
class Video(models.Model):
title = models.CharField(maxlength=50)
description = models.TextField()
tags = models.GenericRelation(Tag)
Search
What is the point of having tags if you cannot search them. here is the snippet of code that lets users filter your objects based on tags. Its a simple search because it uses "and" vs. "or". Personally I like and because its simple to build, maintain, and use. If you would like to modify the search to use "or" feel free to do so, be warned of the complications.
from django.shortcuts import render_to_response
from django.core.exceptions import ObjectDoesNotExist
from django.contrib.contenttypes.models import ContentType
from joosthot.joosthot.models import Channel
from joosthot.joosthot.models import Video
from joosthot.joosthot.models import Tag
def list(request):
videos = {}
tags = []
search = ''
if request.POST:
new_data = request.POST.copy()
search = new_data['search']
tags = [tag.strip() for tag in new_data['search'].split(',')]
try:
videos = [ t.content_object for t in Tag.objects.filter(title__in=tags, content_type=ContentType.objects.get_for_model(Video))]
except ObjectDoesNotExist:
pass
return render_to_response('list.html', { 'videos':videos, 'search':search })
def channel(request, id):
videos = Video.objects.filter(channel__id=id)
channel = Channel.objects.get(id=id).title
return render_to_response('list.html', { 'videos':videos, 'channel':channel })
Conclusion
The steps seem daunting at first but once I went through the process it boils down to modifying only 1 or 2 lines of code for each new object in my model I wish to tag. Working examples of this code can be found at JoostList and JoostHot.
More from Aware Labs
- Everything A Django Developer Needs To Create Logins
- Goodbye WebFaction Django Hosting – A Reflection
- Django Generic Relations Made Easier
- Implementing CNN style votes in Django, Episode I
- Custom Actions In Django Admin Object Editor
Aware Labs Recommends
- Popularizing Django — Or Reusable apps considered harmful. (USwaretech)
- Django’s tipping point (Antonio Cangiano)
- An Interview with Jacob Kaplan-Moss – Creator of Django (USwaretech)
-
sighs

![Recommend [AwareLabs]](http://s3.amazonaws.com/arkayne-media/img/badge/logo-recommend-badge-medium.png)