July 18, 2011

Django AJAX Tutorial Part 3 - ajax pagination #2

Hello. After long time not seen I'm back to finish the topic of pagination in django with AJAX and after few posts also django topic as a whole. The reason is simple - I've switched totally to javascript and ExtJs framework. So let's get started !

Ok, so we'll be using the same 'Friend' model, so I'll skip the code here. Next go the urls :

First address is for the main page of our sample project. We won't be using it anywhere in the code. Next is the address of view which generates the friends list. The index view as well as view for changing page will be sending requests there. Finally the last link is for the change page function. For sake of simplicity we'll be using named urls here.
Next - the views, and we'll start from the index view :

per_page = 4

def index(request):
    template_name = 'pagination/index2.html'
    html,pagination = load_friends_list(request)

    return render_to_response(template_name, {"html": html, 'pagination': pagination}, context_instance=RequestContext(request))

The per_page variable remains the same as previously. In the function itself not much to say about. We assign result of calling load_friends_list function to two variables and then include them in the rendered template.
Can we proceed to the next view ? If yes - the friends list generator comes into scene.

def load_friends_list(request):
    objects = Friend.objects.all()
    template_name = 'pagination/friends2.html'
    paginator = Paginator(objects, per_page)
    p = request.session.get("pagination_page", request.REQUEST.get("page", 1))

    pages = paginator.num_pages
    try:
        page = paginator.page(p)
    except EmptyPage:
        page = 0

    friends = render_to_string(template_name, RequestContext(request, {
        "page" : page,
    }))
    
    pagination = render_to_string('pagination/pagination.html', RequestContext(request, {
        "page" : page,
        "pages": pages        
    }))

    return (friends, pagination)

Now let's go through this code above. First two lines of the function don't need explenations I guess. Then we're creating the Paginator object, calling it's constructor with two arguments - our objects list that we want to paginate and the amount of items per page.
The next line is responsible for getting the current page number. As you'll see in a minute, the change page function can set a session variable for storing the current page value. If that didn't happen we're doing a lookup through REQUEST object ( which is added to each request ) for 'page' variable. And if it's not present - we're picking the first page.
Proceeding further we're storing total number of pages using the built-in Paginator function 'num_pages'(here's the link to docs concerning this topic ). Next we're doing a try-catch check to finally get our paginated objects. By calling the 'page' function of Paginator we're receiving the objects list from the desired page. If this page does not exist we're setting value 0.
Finally we need to render all those things to be used in templates. First goes paginated friends
list object. Next we're rendering pagination sending page object as well and amount of pages (which is not really needed but simplifies the template).
We could have had returned here friends list together with pagination in one template, but I didn't wanted to change the page structure.

Finally the last view, for setting the current page.

def set_friends_page(request):    
    request.session["pagination_page"] = request.GET.get("page", 1)    
    html = load_friends_list(request)[0]
    result = simplejson.dumps({"html" : html}, cls = LazyEncoder)
    return HttpResponse(result)

This view starts with getting the 'page' number from request and storing it in a session variable. If 'page' is not present in the request, we're setting it to the first page. Then we're using previously described function load_friends_list to generate list of paginated objects. As you can see we pick only the first argument from the returned value which is the friends list itself. Finally we can build the result response ( we're using the LazyEncoder described in the first tutorial concerning Ajax) and retunrn it via HttpResponse.

If we already have that we can get to the last part - the templates. First the templates for friends and pagination, as not much is going on there :

{% if page.objects_list %}
 <ul class="friends-list">
  {% for f in page.object_list  %}
   <li>
    <div class="friend-avatar">
     <img src="http://a2.twimg.com/profile_images/1116899230/mb_normal.png" />
    </div>
    <div class="friend-name">
     <p>{{ f.name }}</p>
    </div>
   </li>
  {% endfor %}
 </ul>
{% else %}
 <p>You have no friends.</p>
{% endif %}

'friends' template have basically the same structure as in the previous tutorial. We've just changed the source of paginated objects to objects_list parameter of the page objects (which was generated by Paginator as you already know) and moved here the test for friends existence

Now the pagination template :

<ul id="pagination">
    {% if pages %}
        {% ifequal pages 1 %}
            <li id="{{pages}}">{{pages}}</li>
        {% else %}
            {% for i in page.paginator.page_range %}
                <li id="{{i}}">{{i}}</li>
            {% endfor %}                        
        {% endifequal %}
    {% endif %}
</ul>

First we're checking if pagination will be present at all (if there were no objects to paginate, the amount of pages would be 0 and so there would be no pagination. We could have used page.paginator.num_pages here but it needs much more chars to describe as well as requires a lookup in the page object. So why bother ? :) )
If the amount of pages is one, we just insert one list element. If the amount is greater than one we'll iterate through page.paginator.page_range parameter which has the form of an array (ie. [1,2,3]). And that's all for the pagination. Finally we can get to the index tpl.

<div style=\"margin-top:15px; border-top:1px solid #E1E6EC\">
        <div class=\"friendsList\">
            <div id=\"loading\">
            </div>
            <h2 class=\"div-title\">
                Friends
            </h2>
            <div class=\"div-content\">
                <div id=\"friends-data\">                     
                    {{ html|safe }}
                </div>           
            </div>
            {{ pagination|safe }}
        </div>
    </div>   
<script type=\"text/javascript\">
</script>

First of all I've cut the script out as I want to describe it separately. As for the template itself basically nothing to write here about. Sctructure is mainly the same as previously. We have just inserted our two generated variables : the friends list and pagination. We're using 'safe' filter on them so that html tags won't be escaped. The last code belongs to the javascript required for handling ajax requests.

$(function(){  
 function showLoader(){
     $("#loading").fadeIn(500,0);
  $("#loading").html("");
 }

 function hideLoader(){
  $("#loading").fadeOut('slow');
 };
 
 $("#pagination li:first").addClass('selected');

 $("#pagination li").click(function(){   
  showLoader(); 
  $("#pagination li").removeClass();
  $(this).addClass('selected');
  var pageNum = this.id;

        $.ajax({
            type: "GET",
            url: "{% url set_friends_page %}",
            data: "page="+pageNum,
            async: false,
            dataType: 'json',
            success: function(data){
                el = $('#friends-data');
                $("#friends-data").html(data.html);
                hideLoader();
            }
        });
        return false;        
 });
});

As previously we're using jQuery for the sake of simplicity. Also first two functions are exactly the same - they either hide or show the loader div by changing it's display type. Next line sets the class of first pagination element to 'selected'. Finally the most important function - click event handler for pagination
list elements. So each time user clicks <li> element we call this function. First we show the loader, and remove class from all pagination list <li> elements. Next we add class 'selected' to the currently clicked element by using this variable. Before performing ajax request there's just one last thing to do which is storing the id of clicked element to pageNum variable.
Now the fun part - we will do a proper ajax request using jQuery's built in 'ajax' function. In a nutshell it all just comes down to doing a request at our change page view url and then handle the returned data.
Before doing a request we must set some initial parameters which are: the type of request ('GET', 'POST' etc.) url of request (which in this example is our set_friends_page view url), data that we will send (which is the 'page' variable that we're processing later in our views), asynchronousity (if the request blocks the UI or not. Here we do not need the request to be asynchronous as there's not much for the user to do during page load), type of returned data and callback function called when ajax calls ends with success.
The last parameter is the one responsible for performing the html update operation. As a result of our ajax request we're receiving json (of the following form {"html": "<ul>....</ul>"} ) object containing one parameter : html. This data.html contains the rendered list of friends for proper page number. So we select friends-data div from the DOM and in the next step insert the returned html inside this div with jquery's 'html' function. After that we hide the loader div once again.

So basically that's all for this episode of 'Ajax with Django' tutorial. Hope you liked it and if something is not clear or if you have some other questions, just leave a comment or find me on twitter. I'm aware of the fact, that this code has some bugs like the fact that stored session variable is not being reseted each time we're refreshing the example page which may lead that we have different than first page loaded but first page selected in the navigation but really I've tried to keep it simple.
I've created a demo so you can check how it works :
http://samples.fromzerotocodehero.x14.eu/pagination2/start
, as well as prepared the code for download:
http://ofcjs.x14.eu/pagination2.rar
I will try to put next tutorials in the next few days to finish the series. So stay tooned.

May 9, 2011

Break

Sorry for not updating my blog recently, but it's connected to a switch in my interests which now concern more javascript than django/python. Still in the next few days I'm gonna finish my pagination with django 'series' as well as post some nice small chat app which I think you will like. So stay tooned, and for now check those links :

microjs.com - forget about jquery. Use small (up to 5kb) libs containing only what you need.

mibbu.eu - cool small gamedev framework from me mate michalbe

{dev:unplugged} - competition from Microsoft for HTML5 games. Some of them look really nice.

February 3, 2011

Django AJAX Tutorial Part 2 - ajax pagination #1

Hello again. For some time now I will continue my tutorials concerning using AJAX with Django. I have received few requests to write about pagination next, so this is the topic for today's post. In the last months I went through few different methods of ajax pagination. I will try to show you at least three but each in different post.
The one I'm going to write about today was the first I've tried. It uses built-in django generic views.

Let's begin with views, as they're most important here :

#!/usr/bin/env python
# encoding: utf-8
#Python
import math

#Django
from django.conf import settings
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.views.generic.list_detail import object_list

#Project
from models import Friend

#we're importing LazyEncoder written in first part of Django - Ajax tutorial
from tutorial_utils import LazyEncoder

# 'global' variable storing amount of objects per page
per_page = 4

def index(request):
    
    #we take all object's that we want to be paginated
    friends = Friend.objects.all()
    
    #count them
    count = friends.count()
    
    #count amount of pages
    pages = math.ceil(count / float(per_page))
    pages = int(pages)
    
    #create list of pages
    pages = range(1, pages+1)

    #if amount of objects is smaller than per_page variable, we create only one page
    if count <= per_page:
        pages = 1

    return render_to_response('pagination/index1.html', {
            'pages': pages, 'count': count
            }, context_instance=RequestContext(request))

#this view will create us list of friends
def friends_list(request):

    #first take all objects       
    friends = Friend.objects.all()   
    template = 'pagination/friends1.html'
    
    #using generic view display list of objects paginated by 'pagenate_by' elements. Good part about using object_ilst generic view is that it automatically paginates elements and then allows showing the desired page by accessing 'page_with_objects_url' + ?page=number_of_page. we will take advantage of this in our example.

    return object_list(request, 
        queryset = friends,
        paginate_by = per_page,
        template_name = template,
        extra_context = {}
        )
Ok so idea is that we use generic view 'object_list' that paginates objects for us and creates links for each page. So now let's take a look at urls :
from django.conf.urls.defaults import *
from views import *

urlpatterns = patterns('',
    url(r'^start$', index, name="index"),
    url(r'^friends_list/$', friends_list),
)
Nothing complicated here. I'm not using named urls here to make it easier to understand in the javascript part. Next thing are the templates. First I'll present the one responsible for creating list of friends (props go to michalbe for lending me his twitter avatar :)) :
    {% for f in object_list %}


  • {{ f.name }}

  • {% endfor %}
And main template with the pagination part :

Friends

{% ifequal count 0 %}

You have no friends.

{% else %}
    {% if pages %} {% ifequal pages 1 %}
  • {{pages}}
  • {% else %} {% for i in pages %}
  • {{i}}
  • {% endfor %}{% endifequal %}{% endif %}
{% endifequal %}
As you can see here I've included pagination in this template (so that it won't be refreshed each time user changes page). If number of pages is none, we're showing appropriate alert. Else if there is only one page, we're not using loop because we would get two pages in fact (page 0 and 1). If there are more pages, we're just going through a for loop adding each of them and setting their id's to page number. we will need that in the js part which comes next. Once again I'm using jQuery here to keep it simple and take advantage of it's built in function 'load' that reads content under specified address and then includes it html of our page :

Ok, so that's the whole code for today. As previously I've created working example for you under this link : http://samples.fromzerotocodehero.x14.eu/pagination1/start And here's a downloadable package (soon I'll move all of this to my git account I promise :) ): http://ofcjs.x14.eu/pagination1.zip

January 31, 2011

Django AJAX Tutorial Part 1 - adding email to newsletter

This is something I really wanted to write about since some time - using ajax with django. The cause is simple - internet is lacking good materials concerning this matter. And I remember having many problems with ajax in the beginning. That's why in the next few days I will show typical usage of ajax with django, like form validation, pagination and simple shoutbox.

Ok - the basics. First thing you need to understand - when talking about ajax here we mean a request which is sent from the browser by javascript, usually asynchronously ( meaning user can do other things while we're processing it). So to keep it simple - ajax is the 'request' we're using in our django views. And instead of reloading the whole page after user clicks some link or submits form we can first process it in the background and then refresh only the needed parts of the page. Sounds pretty facebookish doesn't it ? :) And there's no magic in it, really.

Let's start from the simplest possible example - we will allow user to add email to some newsletter. Here is tho code for model, url and form :

class NewsletterForm(forms.Form):
    email = forms.EmailField(label=_("E-mail address"))

class NewsletterEmails(models.Model):
    email = models.EmailField(_(u"E-mail address"),)

urlpatterns = patterns('',
    url(r'^newsletter_add/$', newsletter_add, name="newsletter_add"),
)

Nothing unexpected here I guess. Simple model, simple form with just one field and a link to our view (remember - even though we're not redirecting user to this link, we still need a way to call our view function processing the email).
Next comes the view function :

from django.http import HttpResponse
from django.template.loader import render_to_string
from django.utils import simplejson
from django.utils.functional import Promise
from django.utils.encoding import force_unicode
from models import NewsletterEmails
from forms import NewsletterForm

#here is some magic. We need to write custom JSON encoder, since sometimes
#serializing lazy strings can cause errors. 
#You need to write it only once and then can reuse in any app

class LazyEncoder(simplejson.JSONEncoder):
    """Encodes django's lazy i18n strings.
    """
    def default(self, obj):
        if isinstance(obj, Promise):
            return force_unicode(obj)
        return obj

#here the view function starts
def newsletter_add(request):

    #as usual when processing forms we check if the method was POST/GET
    if request.method == "POST":

        #we get email address from request variables      
        adr = request.POST['email']
        e = None

        #we process address only if it's longer than 6 characters.
        #this assures us than we won't waste server time on
        #addresses like 'oeui' or 'u@u.u' 
        if len(adr) > 6:
            
            #create form instance and fill it with data. In this case it will only be the email address
            form = NewsletterForm(data=request.POST)
            
            #let django perform the validation
            if form.is_valid():

                #check if the email address is not already in our database
                try:
                    e = NewsletterEmails.objects.get(email = adr)
                    message = _(u"Email already added.")
                    type = "error"
                except NewsletterEmails.DoesNotExist:

                    if DoesNotExist exception was caught we add the address 'safe-mode' using try/catch
                    try:
                        e = NewsletterEmails(email = adr)
                    except DoesNotExist:
                        pass
                    message = _(u"Email added.")
                    type = "success"
                    e.save()
            else:
                message = _(u"Bad address.")
                type = "error"                
        else:
            message = _(u"Too short address.")
            type = "error"

    #we don't need to do this but it's good practice to check the source of request. If it comes from ajax call we build the response.

    if request.is_ajax():
        #let built in simplejson package serialize our data. It means we transform everything to simple strings.
        result = simplejson.dumps({
            "message": message,
            "type": type,
        }, cls=LazyEncoder)

        #return the response
        return HttpResponse(result, mimetype='application/javascript')

Now the form. This is really basic so I won't say anything more about it apart of the fact that we explicitly set the action of our form to address of view processing addition of emails :


and finally the javascript part. I'm using jQuery javascript library here as well as jQuery's form plugin for processing forms. It simply adds method that takes all the values from form's fields and sends it to specified address (in this case it'll be "newsletter_add"). We could do this by ourselves, but that's not what this tutorial is about.



And basically that will be all for this part. As you can see the key to using AJAX with django is that our views must return serialized strings with JSON so that we can process them with javascript.

For the first time I have prepared a working example under this link :

http://samples.fromzerotocodehero.x14.eu/newsletter1/start


as well as downloadable code :

http://ofcjs.x14.eu/ajax1.zip

If you have any questions feel free to ask in comments or on my twitter.

January 28, 2011

Synchronizing youtube user's videos filtered by tags

Today I will write about synchronizing your app with youtube and then filtering movies of specific user by tags. But let's get back to the beginning. I assume you already have django-syncr installed. I also assume that you're using video as attribute of some other object. Basic usage is pretty straightforward, so I'll proceed to some examples.

First the easiest one - we provide id of video as a string and as a result we receive Video instance :

from django-syncr.youtube import YoutubeSyncr

def sync_video_id(id):
    """ Synchronize single video by given YoutubeID    
    Takes youtube id as a string. Returns Video instance.   
    """
    
    y = YoutubeSyncr()
    try:
        video = y.syncVideo(id)
        return video
    except:    
        return video

That was easy. Let's try something more complicated then. Next there's a synchronization when provided url to video. Using python's built in urlparse module we divide url into components. Because my actual code needed to be compatible with version 2.4 of python, I'm using cgi module here (the same functions just stored in different place). Here's how this looks step by step. Everything is rather self-explanatory so I will not go through it line by line :

>>> url = 'http://www.youtube.com/watch?v=mPXxI1uyVEE&feature=rec-LGOUT-exp_fres
h+div-1r-3-HM'
>>> url
'http://www.youtube.com/watch?v=mPXxI1uyVEE&feature=rec-LGOUT-exp_fresh+div-1r-3
-HM'
>>> import urlparse
>>> url_data = urlparse.urlparse(url)
>>> url_data
ParseResult(scheme='http', netloc='www.youtube.com', path='/watch', params='', q
uery='v=mPXxI1uyVEE&feature=rec-LGOUT-exp_fresh+div-1r-3-HM', fragment='')
>>> query = urlparse.parse_qs(url_data[4])
>>> query
{'feature': ['rec-LGOUT-exp_fresh div-1r-3-HM'], 'v': ['mPXxI1uyVEE']}
>>> id = query["v"][0]
>>> id
'mPXxI1uyVEE'

and that's how we will do this in django :

from django-syncr.youtube import YoutubeSyncr

def sync_video_url(url):
    """ synchronize single video by it's url    
    Takes video url as a string, returns Video object.

    """
    import urlparse
    url_data = urlparse.urlparse(url)
    try:
        query = cgi.parse_qs(url_data[4])
    except:
        query = urlparse.parse_qs(url_data[4])
    id = query["v"][0]
    
    y = YoutubeSyncr()
    try:
        return video = y.syncVideo(id)
    except:    
        return none

When you're finished warm up It's time for more complex functionality. Now we will sync videos of specific users meeting specified tags requirements. First the core function that will do the work here. Because I tried to make it reusable django's ContentType module is used. :

from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django-syncr.youtube import YoutubeSyncr

def sync_video_user(parent,model_name,user,tags):
    """ Sync all videos of user matching given tags pattern .
    
    Takes parent object, object to sync videos (as a string), string with username and string with tags.
    Returns list of synchronized videos. Takes care of already existing videos as well as prevents duplication of slugs.
    """
    
    from django.template.defaultfilters import slugify
    
    #first we need to get the actual model for object holding video.
    model_class = ContentType.objects.get(model=model_name).model_class()
    
    #we create a queryset for searching for duplicates of video we're currently worknig on
    queryset = model_class._default_manager.all()
    
    #parse string of tags to get proper validating url string
    fmt_tags = parse_tags(tags)

    #request for youtube feed
    feed = get_youtube_feed_url(user, fmt_tags)
    
    #search for videos parsing returned feed
    sync = search_youtube(feed)

    #create a temporary instance (notice not using save() anywhere here) to get object methods available as well as queryset
    instance = model_class()

    for vid in sync:
        
        #if our queryset does not already contain object with this video id
        if not queryset.filter(**{"yt_video_id": vid.video_id}):

            #check if slug is free
            free = try_slug(instance, slugify(vid.title))
            if not free:
                
                #if not use function which we had already discussed some time ago - unqiue_slugify
                free = unique_slugify(instance, slugify(vid.title))
            else:
                free = slugify(vid.title)

            #create new object
            new = model_class(parent=parent,
                               slug=free,
                               name = vid.title,
                               yt_video_id=vid.video_id,
                               video=vid,
                               publication_date=vid.published, active=True,
                              )
            new.save()

    return 1

Now that we have the main part of the code, we can look on the smaller pieces :

def parse_tags(tags):
    """
    Parse received string of tags to tags list. Then append them to yt search API address.
    Return path to search api.    
    
    parse_tags(tags):
        url(string)        
    """

    tags_list = tags.split(',')
    tags_list = [tag.strip() for tag in tags_list]
    tags_list = [re.sub(r"\s+", "+", tag) for tag in tags_list
    parse_string = '&category=%s&v=2' % '%2C+'.join(tag.strip() for tag in tags_list)

    return parse_string

Not much magic going on here. We split string of tags and use simple regex to get single tags from it (tags are either separated by commas or more than two whitespaces. Multi-words tags allowed). We then put all tags to query url in the proper form.

Next function adds our tags to the rest of the query string containing youtube username :

def get_youtube_feed_url(user, url):
    """ Add username to youtube feed address.
    """
    
    feed = '/feeds/api/videos?author=%s&alt=rss' % user
    feed += url
    return feed

Finally when we have the whole query url we can perform the search using youtube api :

def search_youtube(path):
    """ Search videos on youtube
    
    If path is given, connects to youtube feed returns any videos matching query.
    All videos are then synched.
    
    """
    
    if path:
        import feedparser
        import urlparse
        
        YTSearchFeed = feedparser.parse("http://gdata.youtube.com" + path)
        videos = []
        for yt in YTSearchFeed.entries:

            #the only new part is here. Because returned feed is a really complex json file, we search for 'link' keys that store url to our desired videos       
            url_data = urlparse.urlparse(yt['link'])
            try:
                query = cgi.parse_qs(url_data[4])
            except:
                query = urlparse.parse_qs(url_data[4])
            id = query["v"][0]
            videos.append(id)
        
        synched = []
        for video in videos:
            try:
                sync = sync_video_id(video)
                synched.append(sync)
            except:
                pass
        return synched
    else:
        return []

And basically we're finished.

January 9, 2011

Using ProxyPass with django project

Once again I need to postpone my post about django-syncr and youtube since I came across interesting problem recently. Here's the deal : user has his django app running on some server under some link. Application itself is visible under different link. So apache conf got 'ProxyPass' and 'ProxyPassReverse' added. All seemed to work fine apart of links created with '{% url %}' templatetag (and from what I was able to test also get_absolute_urls could cause problems depending on the method used to create them). Url tag was adding original django application name to the link. Because I'm no good in setting apache I've came up with different solution. After skimming through django/template/defaulttags and django/core/urlesolvers I've realized that rewriting both 'reverse' function and 'url' tag would be tedious and probably unnecessary. Instead I've just added this 'try' block that replaces first unwanted occurence of our app name (which is defined in settings under PROJECT_NAME variable) with empty sign to 'url' templatetag and saved it as custom tag named 'urlc'. Not sure if overriding default tags works, gotta check it some day.

else:
                if self.asvar is None:
                    raise e
        
        #code starts here
        try:
            url = url.replace(settings.PROJECT_NAME+'/', '', 1)
        except:
            pass
        #code ends here    

        if self.asvar:
            context[self.asvar] = url
            return ''

Not sure if it works for OP, but worked at my test config without bigger problems.

January 8, 2011

Provide unique slugs for model class

Sorry my dear readers for this long break between posts, but I have had lots of work lately finishing previous jobs. Recently I've shown you how to synchronize your app with Twitter and also parse list of tweets changing hash tags to proper links. Today I wanted to show you how to synchronize application with youtube but for this purpose I need a way to provide unique slugs for models. So here's the whole code which I'll comment below :

import re

from django.template.defaultfilters import slugify

def try_slug(instance, slug):
    """Check if the slug is free for corresponding model instance
    """
    queryset = instance.__class__._default_manager.all()
    if not queryset.filter(**{"slug": slug}):
        return True
    else:
        return False

def unique_slugify(instance, value, slug_field_name='slug', queryset=None, slug_separator='-'):
    """Create unique slug
    
    Creates unique slug across model class.
    
    """
    slug_field = instance._meta.get_field(slug_field_name)
    slug_len = slug_field.max_length

    slug = slugify(value)
    slug = slug[:slug_len]
    slug = _slug_strip(slug, slug_separator)
    original_slug = slug
    
    if queryset is None:
        queryset = instance.__class__._default_manager.all()
    if instance.pk:
        queryset = queryset.exclude(pk=instance.pk)
 
    next = 2
    while not slug or queryset.filter(**{slug_field_name: slug}):
        slug = original_slug
        end = '%s%s' % (slug_separator, next)
        if slug_len and len(slug) + len(end) > slug_len:
            slug = slug[:slug_len-len(end)]
            slug = _slug_strip(slug, slug_separator)
        slug = '%s%s' % (slug, end)
        next += 1
    return slug

def _slug_strip(value, separator='-'):
    """
    Cleans up a slug by removing slug separator characters that occur at the
    beginning or end of a slug.
    """
    separator = separator or ''
    if separator == '-' or not separator:
        re_sep = '-'
    else:
        re_sep = '(?:-|%s)' % re.escape(separator)

    if separator != re_sep:
        value = re.sub('%s+' % re_sep, separator, value)

    if separator:
        if separator != '-':
            re_sep = re.escape(separator)
        value = re.sub(r'^%s+|%s+$' % (re_sep, re_sep), '', value)
    return value

So now some explanations to the code. First function gives us possibility to check if slug is free for specific model class. It takes object instance and slug and returns boolean value. Inside the code we take default 'all' manager of the model and filter queryset with slug.
Second function is the key to solving our problem. The unique_slugify function takes shown parameters. At first we check the max length of the slug field, so that we won't exceed its value. Then we slugify our slug with built in django function, cut it if needed and strip of sepparators different than '-' or 'separator' parameter received by function. Next step is to get the queryset on which we will be working, excluding our model instance. Finally the fun begins. In our while loop condition we check existence of newly created slug or result of filtering our queryset. If both are none we take the separator and add next integer to it.
If the length of concatenated slug and identifier is too long, we cut it off. Finally we're creating slug that will be used in while loop condition filter.
Last function is stripping our slug by separators appearing at the beginning and end of slug. Because it uses many regexes I will leave in your hands work of decyphering it :)

And now for some short sample usage :

free = try_slug(self.instance, self.cleaned_data['slug'])
if not free:
    self.cleaned_data['slug'] = unique_slugify(self.instance, self.cleaned_data['slug'])