2009-03-07

Django page CMS 0.2.4 is released

A lot of time has passed since the last time I blogged about my Django CMS

Django page CMS has gained users and contributors since this moment. I takes me more time to checking out the new issues, keep them as low as possible and have an up to date documentation. It begins to be difficult to handle everything myself with my new job at Opera software. Thanks for the contributors that help me in this task.

The code base is now quite stable. I try to keep it clean and simple. It's not always easy with all the feature proposition and code contribution. Here is a brief summary of what have been achieved since the version 0.2.1 up to lastest 0.2.4 release :

  • I have completly revamped the page admin interface. Now the tree is collapsed by default. When you expand a node, the children are loaded via an XMLHttpRequest. This method reduce the amount of SQL request needed by default. Before the tree was reladed each time you move a page in the tree. Now only the relevants parts are reloaded.s
  • The site framework is now optional and disabled by default. It's also implemented in a simpler way.
  • I added a basic way to integrate third party application into the page edition form.
  • I also try to make things a little faster by using cache on page content. It's quite easy to invalidate the content cache of a page after modification. For nested pages and everything related to URLs it's a little bit more complicated.
  • The different languages support is now stronger and tested. You can use languages code up to 5 letters and redirect client special languages to others like "fr-ch" to "fr" (Swiss french to simple french).
  • I try to add a test everytime it was relevant and if the bug/feature was suggest to regression.
  • Finaly, I learned how to do a proper python package and published it on PyPi. Install django page cms is as simple as : $ sudo pip install django-page-cms.

My goal is to offer a very simple, easy to start with Django CMS. I try to add the important features to reduce the need of forks. But I know how it's not possible to please everyone. (django-cms 2.0 is one of the fork)

For the near future I plan to break some minor stuff so if you don't want any trouble, I recommend to stick to the 0.2.4 version.

Thanks again a lot to the contributors and users. I hope you will continue to use this CMS and occasionaly help me.

Cheers, Batiste

2008-06-20

3 things I would like to see in Django 1.0

I am using Django for more than 2 years now. During several projects I made up I found that some code snippets were missing. These snippets are a common need for me and I think it's maybe the case for others.

CallTag

Just like include, but can pass parameters to it.

This snippet is incredibly useful. Before I was doing things like that:

{% with "64x64" as image_size %}
    {% with 1 as display_birthday %}
        {% include "user.html" %}
    {% endwith %}
{% endwith %}

And for each variable needed the syntax becomes more complicated. With CallTag it would be like that:

{% call include "user.html" with image_size="64x64" display_birthday=1 %}

It's a lot more readable and debuggable. Additionaly, you can "easily" add smart caching using parameters.

Inclusion tags fill not this need because you can't choose the template. Includes with "with" keyword are ugly to read and to write, so IMHO, Django really need this template tag.

New forms output control

The newforms module save lot of time validating and generating automatic forms. But when it comes to control the generated output it another story.

One reason is because the newforms module don't use the Django templating language. IMHO it's the root of the problem. This abstraction is leaky in a bad way.

It will more powerful if the newforms module use templating to render its fields. This way we could override these templates and render the forms the the way we want. Today it's too complicated to take control of the renderer of the final form inputs. Costum Fields and Widgets objects could be created at this purpose but it's not a piece of cake.

One solution is to take the form auto rendering output as a scaffold and tweak it. It's not a bad solution but I think it's possible to do better.

Query several models with an Union

As far has I know it's not possible to create a SQL Union request on several models as if there is only one.

The possible solution is to create a SQL View into the database and a Django model that match this view but it feel strange to use a database trick to handle this (it's maybe a good idea after all).

It could be nice if Django ORM provide a way to create a Union QuerySet on several tables. Here is an exemple code of what I imagine:

class Post(models.Model):
    title = models.CharField(blank=False)
    author = models.ForeignKey(User, related_name='user_posts')
    body = models.TextField()
    
class Comment(models.Model):
    title = models.CharField(blank=False)
    author = models.ForeignKey(User, related_name='user_comments')
    body = models.TextField()
    
class Product(models.Model):
    name = models.CharField(blank=False)
    owner = models.ForeignKey(User, related_name='user_products')
    description = models.TextField()
    
class Content(models.Union):
    class Meta:
        union_models = (Post, Comment, Product, )
        union_fields = ('title', 'author', 'body' 'content_type',)
        union_fields_rename = ('name as title', 'owner as author',)
    
contents = Content.objects.filter(author_id=1)
2008-05-18

jQuery: Drag & Drop and Resize in an event delegation fashion

I don't see any drag & drop jQuery plugin that works in a event delegation way so I put some code together and it works pretty well. Event delegation is good for 2 things:

  1. Perfomance : Drag & Drop web app are to often too slow. Event delegation could help.
  2. Simplicity : It's painful to handle events for each elements you have and add during the life of the page.

If you plan to build a complex online webapp, you should take a look on event delegation technics. Hope it will help someone.

Example : Drag & Drop and Resize example.

Download the plug in.

Credits

Inspired by EasyDrag jQuery plug in.

2008-04-25

Problem: find the median value of 2 sorted lists

I came across an intereseting algorithmic problem on a web site lately. Apparently it the kind of problems you are supposed to solve on a black board at Google interview. The formal statement is simple: How to efficiently find the median value of two sorted lists.

It's easy to have a feeling on which way to take. But to actually find an algorithm and implement it in a efficient and bug free way it's another story.

The solution I found is a relatively simple dichotomic search alorithm with a logaritmic complexity in the worst case.

Here is an exemple of how it works. Imagine we have these two sorted lists:

l1 = [1, 4, 5, 7, 7, 8, 9, 9] 
l2 = [0, 1, 3, 6, 6, 6, 7, 8]

We calculate the median of both lists:

l1 median = 7
l2 median = 6

l1 median is greater than l2 median so we can safely remove the numbers on the right side of the median of l1 because the final median is somewhere between the median of both. We also have to remove the same amount of numbers at the left side of l2 to keep the stabilty of the final list. If one of the list is shorter the amount of removed numbers will the minimum between both median index. So we get:

l1 = [1, 4, 5, 7, 7]
l2 = [6, 6, 6, 7, 8]

We continue the process until have a list with 2 numbers (or less).

l1 median = 5
l2 median = 6

We remove 2 from left side of l1, we remove 2 from right side of l2.

l1 = [5, 7, 7]
l2 = [6, 6, 6]

l1 median = 7
l2 median = 6

We remove 1 from left side of l2, we remove 1 from right side of l1.

l1 = [5, 7]
l2 = [6, 6]

Remember that we have 2 list of the same size. In another case one could be an order of magnitude bigger than the other. Finaly we have to deal with some special case:

  1. The 2 remaining numbers have to be inserted on the left side of the median of the other list.
  2. The 2 remaining numbers have to be inserted on the right side of the median of the other list.
  3. One have to be inserted on the left side of the median and the other on the right side.

For each case we have to check if the number could change the median of the resulting list. If we decide to insert l1 into l2, there will be no implication on the initial median value of l2. But if we decide to insert l2 into l1, the median of l1 will be completly changed. In both case we optain this list [5, 6, 6, 7] and calcuate the median of it : 6.

Inserting the two remaining value into the other list is not the fastest thing possible but it's simple and understandable.

Here is my python implementation:

download median.py

from random import randint

def median(a, start=None, end=None):
    """find the median value of a sorted list or in a subset of it"""
    if start is None:
        start = 0
    if end is None:
        end = len(a)
    l = end-start
    if l < 1:
        raise "median value of a empty list? It make no sense"
    index = start + l/2
    if l % 2 == 0:
        return index, (a[index] + a[(index-1)]) / 2.0
    else:
        return index, float(a[index])

def median_2_lists(l1, l2):
    """find the median value of 2 sorted list"""
    iter = 0
    solution = None
    left1 = 0
    left2 = 0
    right1 = len(l1)
    right2 = len(l2)
    
    print l1[left1:right1], l2[left2:right2]
    
    while True:
        iter = iter+1
        index1, value1 = median(l1, left1, right1)
        index2, value2 = median(l2, left2, right2)
        
        minus1 = 1 if (right1-left1) % 2 == 0 else 0
        minus2 = 1 if (right2-left2) % 2 == 0 else 0
        remove_offset = max(1, min((right1-left1) / 
            2 - minus1, (right2-left2) / 2 - minus2))
        
        # to avoid special blocking case like this these
        # l1 = [-5,5]; l2 = [1,2] 
        # l1 = [5, 5]; l2 = [-11, -8, 20, 21]
        # I decided to stop the algorithm and insert the 
        # 2 last items into the big list
        if right1-left1 <= 2 or right2-left2 <= 2:
            break
    
        if value1 < value2:
            left1 += remove_offset
            print "removed of left side of l1 : %d" % remove_offset
            right2 -= remove_offset
            print "removed of right side of l2 : %d" % remove_offset
        elif value1 > value2:
            left2 += remove_offset
            print "removed of left side of l2 : %d" % remove_offset
            right1 -= remove_offset
            print "removed of right side of l1 : %d" % remove_offset
        else:
            print "solution found"
            solution = value1
            break
        
        print l1[left1:right1], l2[left2:right2]
    
    if right1-left1 > right2-left2:
        big = l1
        short = l2
        left = left1
        right = right1
        i = left2
        end = right2
    else:
        big = l2
        short = l1
        left = left2
        right = right2
        i = left1
        end = right1
    
    if solution is None:
        assert 0 < end-i < 3
        # we need to insert the remaining 1 or 2 number(s) of the short list
        # into the big list at the right place. It's done in a dichotomic way
        while i < end:
            l, r = left, right
            while True:
                index, value = median(big, l, r)
                if value == short[i] or index == l or index == r:
                    if short[i] > value:
                        # it's bad to modify the big list
                        big.insert(index+1, short[i])
                    else:
                        big.insert(index, short[i])
                    i += 1
                    right += 1
                    break
                else:
                    if short[i] > value:
                        l = index
                    if short[i] < value:
                        r = index
        solution = median(big, left, right)[1]
        
    return solution, iter

def test_median_2_lists():
    print "Generating random lists"
    l1 = []
    l2 = []
    i = 0
    n = randint(5,9)
    while i<n:
        l2.append(randint(0,9))
        i += 1
    i = 0
    n = randint(5,9)
    while i<n:
        l1.append(randint(0,9))
        i += 1
    
    l1.sort()
    l2.sort()
    
    merge = list(l1)
    merge.extend(l2)
    merge.sort()
    
    control_value = median(merge)[1]
    
    print "Start algorithm"
    solution, iter = median_2_lists(l1, l2)
    print "%s iteration(s) done" % iter
    print "control value : %f" % control_value
    print "solution value : % f" % solution
    assert control_value == solution
    print "Successful!"

test_median_2_lists()
2007-12-21

A tree view plugin for Kate

My text editor of choice is Kate from the Kde project. It's simple and fast and feature sufficient. The default list of opened document files is a little bit rude. Particuraly if you have to open a files with the same name : it's difficult to differienciate one from each other.

A tree view is ideal in this case because each opened file could be visualy located in the tree and differenciated easily. When I work in a project for a while I visualy memorize the pattern of files and directory and find them quicker time after time.

So I had the need to create a treeview plugin for kate. I used the source code of the current Kde4 tree. Kate is located now in the kdesdk directory. Getting a working plugin was not hard but something usable is another thing.

I ran into a strange bugs when the QDirModel displayed by a QTreeView is filtered with setNameFilters : file selection may be lost, some directory may be closed without any reasons. This annoying bug remain in this plugin.

Kate tree view plugin

I have posted this plugin to the Kwrite mailing list but it's hard to get some feedback. Kate hackers seems to be very busy these days.

I have also to mention Pâté, a nice Kate plugin that let you develop plugin with the python language. Sadly I have to give up using it because I can't get PyKde working on my linux distribution.