Tag code

Django with virtualenv on Webfaction

I really like the Django web framework and I like virtualenv enabling me to use different python configs and packages for different projects. And I have to say I like Webfaction, so easy, so good! (No, I don't get paid by them) This is a quick guide on how to setup Django in virtualenv using mod_wsgi with a Webfaction account.

  • Create an "mod_wsgi" application and create a website to use this application
  • connect to your accounts shell using ssh
  • enable Python 2.6.x as default Python interpreter (as written in the Webfaction documentation)

echo "alias python=python2.6" >> ~/.bash_profile
source ~/.bash_profile
python -V
  • install "pip" and "virtualenv" (be sure to use easy_install-2.6 to use the setuptools of Python 2.6.x):

easy_install-2.6 -U pip
easy_install-2.6 -U virtualenv
  • I like yolk, that enables you to list the installed Python packages, install it using pip if you like

pip install yolk
  • Note that these packages (pip, virtualenv, yolk) are installed in your global Python. I recommend to not install any more packages to the global installation as virtualenv enables use to install all packages we need into the virtualenv Python lib.
  • navigate to your recently create mod_wsgi webapp, there should be 2 folders, "apache2" and "htdocs"
  • create the virtualenv for your project in the webapp folder, name it like you want, I'll use "ve" here

virtualenv --no-site-packages --distribute ve
  • a new folder "ve" with the virtualenv was created. Please refer to the virtualenv documentation for more information and usage of virtualenv.
  • Now let's install yolk into the virtualenv to see which packages are in there
    • either activate the virtualenv and install or use pip magic to install into an virtualenv without activating it:

source ve/bin/activate
pip install yolk
// or
pip -E ve install yolk
  • now you activate the virtualenv and execute yolk:

source ve/bin/activate
yolk -l
deactivate
  • Install Django and any requirements the same way as demonstrated for yolk.
  • Assuming you installed Django into the virtualenv and created a Django project, we now must adjust the Apache config
    • backup the httpd.conf from /webapps/<yourdjangoapp>/apache2/conf
    • create a django.wsgi file somewhere in your webapp directory (I use the conf folder of apache)
    • scan the httpd.conf for "Listen XXX", XXX ist the webapp port
    • remove the <Directory>...</Directory> segment
    • append the following telling the Apache to use the wsgi config we create in the next step:

NameVirtualHost *:<webapp-port>
<VirtualHost *:<webapp-port>> ServerName <SomeServerName> WSGIScriptAlias / /home/<your-username>/webapps/<your-webapp-name>/apache2/conf/django.wsgi </VirtualHost>
  • Edit the django.wsgi file:

#!/usr/bin/python
import os, sys, site
# add virtualenv python libs
site.addsitedir('/home/<your-username>/webapps/<your-webapp-name>/ve/lib/python2.6/site-packages')
# append the project path to system path
sys.path.append('/home/<your-username>/webapps/<your-webapp-name>/ve/')
sys.path.append('/home/<your-username>/webapps/<your-webapp-name>/ve/<your-django-project-name>')
# set the settings module
os.environ['DJANGO_SETTINGS_MODULE'] = '<your-django-project-name>.settings'
# init the wsgi handler
from django.core.handlers.wsgi import WSGIHandler
application = WSGIHandler()
  • Restart the Apache (/home/<your-username>/webapps/<your-webapp-name>/apache2/bin/restart) and everything should be fine!

Show Django AdminSite fields depending on the current user

Introduction

The auto-created AdminSite in Django is a nifty feature for creating an administrative view of your model classes. Somehow it is not perfect, even if you want to customized it. To guide you to my problem and solution you've to know, that I implemented the SoftDelete behaviour for model classes as described by Greg Allard. It simply adds a 'deleted' field to each model marking it as deleted if its deleted in the admin site (or in some application) but never really gets deleted from the database. So in the admin site there is this field 'deleted' displayed, if you have configured it.

The Problem

My project has 2 different 'usergroups', the editors and the superusers. Superusers should see the 'deleted' fields, the others not. Unfortunatly that is not possible out of the box with Django.

The solution

I'm not the first one struggling with the user dependent display of model fields. Luke Plant found a solution for an older version of Django dealing with the field display in the add/change view. Later he updated the thing as the Django version grew. I adapted some lines of his last code for the add/change view and wrote something new to also handle the history view (that lists all model objects). I divided it into 2 subclasses of admin.ModelAdmin for better abstraction: one for the handling the user dependent queryset for SoftDelete and one for the display of the fields.

SoftDeleteModelAdmin

As the superusers should see the deleted field and the others not, we'll only query for the field if there is a superuser. Quite logical. The class uses the proper queryset and also returns a deleted objects if directly requested:

class SoftDeleteModelAdmin(admin.ModelAdmin):
    def queryset(self, request):
        if request.user.is_superuser:
            qs = self.model._default_manager.all_with_deleted()
        else:
            qs = self.model._default_manager.all()
        ordering = self.ordering or ()
        if ordering:
            qs = qs.order_by(*ordering)
        return qs

    def get(self, *args, **kwargs):
        """
        If a specific record was requested, return it even if it's deleted
        """
        return self.all_with_deleted().get(*args, **kwargs)

    def filter(self, *args, **kwargs):
        """
        If pk was specified as a kwarg, return even if it's deleted
        """
        if 'pk' in kwargs:
            return self.all_with_deleted().filter(*args, **kwargs)
        return self.get_query_set().filter(*args, **kwargs)

UserDependentModelAdmin

For the display of the field depending on the current user you have to inherit from the UserDependentModelAdmin class as listed below. Adjust the ModelAdmin class and add list_display_editors (for all users != superuser) and list_display_superuser (for superusers) tuples with the field names for the appropriate user (I'll only use a field called name and the deleted field):

class SomeAdmin(SoftDeleteModelAdmin, UserDependentModelAdmin):
    list_display = ()

    # editor fields and fieldsets
    list_display_editor = ('name',)
    fieldsets_editor = [(None, { 'fields': ['name'] })]

    # superuser fields and fieldsets
    list_display_superuser = list_display_editor + ('deleted',)
    fieldsets_superuser = [(None, { 'fields': ['name', 'deleted'] })]

All other magic is done by the UserDependentModelAdmin class. It simply changes the list_display field in the history view depending on the user and sets some prerequisites. The get_fieldset method returns the fields for the add/change view, this is nearly the same code Luke Plant wrote.

csrf_protect_m = method_decorator(csrf_protect)

class UserDependentModelAdmin(admin.ModelAdmin):
    """
    User dependent admin model.
    This class allows to specify tuples of the listed fields and displayed fieldsets
    for the editors and superusers.
    """
    def __init__(self, model, admin_site):
        """
        Class constructor, adding actions an/or links for the user fields.
        :param model: the model the admin site is created for
        :param admin_site: the admin site
        """
        # this is performed regularly on the list_display tuple
        # do the same for the additional list_display fields
        self._add_actions_and_links_for_list_display('editor')
        self._add_actions_and_links_for_list_display('superuser')
        super(UserDependentModelAdmin, self).__init__(model, admin_site)

    ## Adds actions and/or links for the list_display tuple of the given user
    # definid list_type.
    # @param list_type  the list_display type, currently 'editor' for editors or 'superuser' for superuser
    def _add_actions_and_links_for_list_display(self, list_type):
        if 'action_checkbox' not in getattr(self, 'list_display_' + list_type) and self.actions is not None:
            setattr(
                self,
                'list_display_' + list_type,
                ['action_checkbox'] +list(getattr(self, 'list_display_' + list_type))
            )
        if not self.list_display_links:
            for name in getattr(self, 'list_display_' + list_type):
                if name != 'action_checkbox':
                    self.list_display_links = [name]
                    break

    @csrf_protect_m
    def changelist_view(self, request, extra_context=None):
        """
        View for listing the models.
        Adjusts the list_display tuple according to the current user.
        :param request: the sent request
        :param extra_content: an additional context
        """
        if request.user.is_superuser:
            self.list_display = self.list_display_superuser
        else:
            self.list_display = self.list_display_editor
        return super(UserDependentModelAdmin, self).changelist_view(request, extra_context=extra_context)

    def get_fieldsets(self, request, obj=None):
        """
        Returns the fieldsets depending on the user.
        :param request: the sent request
        :param obj: the model object if already persisted
        """
        if request.user is None or request.user.is_anonymous():
            # never get here normally
            return ()
        else:
            if request.user.is_superuser:
                return self.fieldsets_superuser
            else:
                return self.fieldsets_editor

The Bug Genie 2 SVN Integration on Windows with VisualSVN Server

In my local development environment at home I use The Bug Genie 2 for bugtracking. The tool is quiet cool, although the german translation is totally broken (I fixed it and send it to them, let's see what happens). It comes with a module called "svn_integration" for integrating SVN into the tracker to automatically have updated issues when the SVN comments contain special keywords. That's quiet cool, too, but unfortunatly does not work for me.

I'm using Windows Vista Business x64 as os and for SVN the wonderful, free and easy VisualSVN Server. Next ugly thing on The Bug Genie is that there is no documentation for the modules. I found an entry in the forums how to use the integration in windows, but that did not work out of the box. But the code is already within the module, what is good.

So open modules/svn_integration/post_commit.phpfrom your buggenie installation directory and adjust this line with your path to the bugtracker installation dir:

define('BUGS2_INCLUDE_PATH', 'D:\\xampp\\htdocs\\bugs\\');

Then create a batch file in the same directory called post-commit.bat. You can see the source below, copy it and adjust the following variables. I used the code from the forum post and adjusted it a little. Remember that urls with whitespaces do not work, create a symlink or rename your directories:

  • Path to svnlook.exe (you see my VisualSVN Server resides in D:\VisualSVN_Server, the default location C:\Program Files (x86)\VisualSVN Server does not work because of the whitespaces) VisualSVN Server has all the svn tools in its bin directory.

SET SVNLOOK=D:\VisualSVN_Server\bin\svnlook.exe
  • The path to your php executable. I used XAMPP, so it is in D:\xampp\php\php.exe if installed in root on drive D.

SET PHP=D:\xampp\php\php.exe
  • The path to your SVN directory. This is where svn stores its data.

SET SVN_PATH=D:\svn
  • The post-commit hook we create later takes two arguments from VisualSVN Server, the path and the revision. Unfortunatly VisualSVN Server uses the url as path which collides with the svnlook command we use to determine if a bug was mentioned in a revision comment, as svnlook wants the real file path.
    So we have to adjust the url to a path. As there is no real substring functionality for windows batch (or I did not search long enough) we simply count the length of our SVN url. In my case VisualSVN Server listens at https://amanda:8443/svn/, so a repository would be at https://amanda:8443/svn/my_repos. The server root (https://amanda:8443/svn/)has a length length of 24. Change the 24 to the length of your url. The batch then combines the SVN_PATH and your repository path to the local path, D:\svn\myrepos in my case here.

SET REPOS=%REPOS:~24%
  • Once again, set the path to your installation directory of the bugtracker (replace D:\xampp\htdocs\bugs with your path):

%PHP% -f "D:\xampp\htdocs\bugs\modules\svn_integration\post-commit.php" "%AUTHOR%" "%REV%" "%COMMIT_MSG%" "%CHANGED%"

After your saved the file, open VisualSVN Server and the properties of your repository. Select the the Hooks tab and doubleclick the Post-commit hook. Enter the path to the batch file with the 2 arguments path and revision, in my case I entered "D:\xampp\htdocs\bugs\modules\svn_integration\post-commit.bat %1 %2", where %1 is the repository path and %2 the revision number.

Finally you have to install either WebSVN (which I prefer) or ViewVC and specify the path to your repository (in my case it is http://localhost/websvn/listing.php?repname=my_repos&) in The Bug Genie module settings for svn_integration.

One final and important hint: your user in the bugtracker has to have the same username as in SVN. By default, the first user in the tracker has the username "Administrator". My SVN user does not have the name "Administrator", so I logged out of the tracker, changed the property "uname" of the MySQL table "bugs2_users" for the admin to my SVN username, logged in and then everything worked fine.

As always, feel free to add comments and spread the word

And here the post-commit.bat code:

@echo off
SET REV=%2
SET REPOS=%1

REM path to svnlook (remember that paths with whitespace do not work e.g. C:\Program Files --> use mklink to create a path without)
SET SVNLOOK=D:\VisualSVN_Server\bin\svnlook.exe

REM path to php executable
SET PHP=D:\xampp\php\php.exe

REM path to svn (not url!)
SET SVN_PATH=D:\svn

REM maps the svn url to the svn path - straightforward but the easiest way
REM replace with the length of your svn url e.g. 24 for https://amanda:8443/svn/
SET REPOS=%REPOS:~24%

SET REPOS=%SVN_PATH%%REPOS:/=\%

%SVNLOOK% log -r %REV% "%REPOS%" > COMMIT_MSG
REM SET THE COMMIT_MSG FROM THE FILE. The file is expected to contain only one line with this value
FOR /F "delims=" %%A IN (COMMIT_MSG) DO SET COMMIT_MSG=%%A
echo COMMIT_MSG=%COMMIT_MSG%

%SVNLOOK% changed -r %REV% "%REPOS%" > CHANGED
REM SET THE CHANGED FROM THE FILE. The file is expected to contain only one line with this value
FOR /F %%A IN (CHANGED) DO SET CHANGED=%%A
echo COMMIT_MSG=%CHANGED%

%SVNLOOK% author -r %REV% "%REPOS%" > AUTHOR
REM SET THE AUTHOR FROM THE FILE. The file is expected to contain only one line with this value
FOR /F %%A IN (AUTHOR) DO SET AUTHOR=%%A
echo COMMIT_MSG=%AUTHOR%

%PHP% -r "echo urlencode($argv[1]);" "%COMMIT_MSG%" > URL_COMMIT_MSG
REM SET THE URL_COMMIT_MSG FROM THE FILE. The file is expected to contain only one line with this value
FOR /F %%A IN (URL_COMMIT_MSG) DO SET URL_COMMIT_MSG=%%A
echo COMMIT_MSG=%URL_COMMIT_MSG%

%PHP% -f "D:\xampp\htdocs\bugs\modules\svn_integration\post-commit.php" "%AUTHOR%" "%REV%" "%COMMIT_MSG%" "%CHANGED%"