Review Board Roadmap and Donations

Roadmap

With the upcoming release of Django 1.0 in the next few weeks, we decided it was time to formalize a roadmap for Review Board 1.0. The roadmap provides a good overview of what users can expect for our release, and what it will take to get there.

At this point we’re asking for people to contribute wherever possible. The big thing is fixing bugs targeted for the 1.0 release. We’d also like some help in finalizing unit tests.

Quality Control

We’re doing what we can to improve quality control in Review Board. For a lot of people, Review Board works great, though setups often differ and some users hit issues that others never see. For this, we’re trying to improve our unit tests to catch these various cases. When people submit patches, we’d greatly appreciate unit tests to cover the new code, and in some cases will require them for the code submission.

Selenium

We will soon start using Selenium in our unit test process to simulate user action in various web browsers. Selenium allows for remote-controlling a web browser, simulating clicks, text input, and other user actions and checking the results. Over time, when our Selenium test suite is more complete, we should be able to catch browser-specific problems a lot more easily.

Buildbot Server

Another issue users have hit lately is breakages due to changes in Django for the 1.0 release. As things calm down there, this will become less of an issue, but we’ve put things in place to catch these problems before users do.

We have just set up a buildbot server that will perform a full build and run the test suite whenever there’s a code check-in to Review Board, Django, Djblets or Django-Evolution. It will then notify us when there’s a new breakage. Users can check the build page before updating just to make sure they won’t hit a major problem. Later on, our buildbot server will generate nightly builds and handle Selenium tests.

We have a limited number of servers to test with. If you have server space and resources to donate and would like to run a BuildBot Slave server, let me know. We’re looking to set up slaves to test various combinations of the following:

  • Python 2.5
  • Python 2.4
  • Django SVN trunk
  • Django 1.0
  • Windows 2000, XP and Vista
  • Internet Explorer 6 and 7 (for Selenium tests)
  • Opera (for Selenium tests)
  • Firefox 2 and 3 (for Selenium tests)

Sandbox

Our BuildBot server is also set up to allow us to test code changes before we submit the code. Running a sandbox build of our pending code will cause all build slaves to run the entire test suite. This ensures that we don’t break things accidentally.

If you’re a contributor working on large patches for Review Board and would like to have access to the sandbox, please post to the mailing list and we can work with you on getting an account set up.

Installation Improvements

We’re working to make the installation experience much easier. I’m in the process of creating Python easy_install packages for Review Board and Djblets. Soon, users will be able to simply easy_install ReviewBoard to get going instead of checking out the development tree. I’m hoping to create both nightly builds and release builds.

Code will soon go in to move the entire project configuration into the administration interface. Modifying settings_local.py and restarting the server will be a thing of the past. All that will be left there will be a few site-specific settings and the database settings. Expect this to go in real soon.

A tool is in development for helping to generate the initial Review Board server tree based on an installed reviewboard Python module (using easy_install) and generating the web server configuration files. This will hopefully take care of a lot of problems people hit when trying to get their server configuration right the first time.

And last but not least, before 1.0 we will have a first-time installation page that handles the creation of the initial settings_local.py and the adding and checking of repositories.

So in the end, the installation process will be something along the lines of:

  1. sudo easy_install ReviewBoard
  2. sudo rb-install-site /var/www/reviews.mycompany.com
  3. Fill out the fields presented.
  4. Hand-tweak the configuration files if needed.
  5. Go to the page for the new Review Board server, fill out the fields and finish the install.

This is the goal, anyway. We’re going to try to get as close to this as possible for 1.0.

Donations

Review Board has become a full-time project for us. Though it got its start at VMware, it’s really a personal project developed in our spare time, not a project run by VMware. As the project grows, we’ve been putting more time, energy and money into it.

Hosting fees have started to become large, given that we’re now hosting the main project website, the main Review Board server for our code reviews, the demo server, the Google Summer of Code review server and the BuildBot server and slaves. Down the road, we have many plans that will also require funding.

To help cover our costs, we’re now made it easy to donate to the project. If Review Board has helped you, your company, team, or project and saved you money or time over alternative solutions, maybe you’d like to help give back to keep our project going. Every bit helps.

Review Board Roadmap and Donations Read More »

Django Development with Djblets: Dynamic Site Configuration

Django’s a powerful toolkit and offers many things to ease the creation of websites and web applications. Features such as the automatically built Administration UI are just awesome and save developers from having to reinvent the wheel on every project. It does fall short in a few areas, however, and one of them is site configuration.

Now, Django’s settings support isn’t bad. You get a nice little settings.py file to put settings in for your site/webapp, and since it’s just a Python module, your settings can really hold whatever data you want and can be well documented with comments. That’s all great, but when you want to actually change a setting, you must SSH in, edit the file, save it, and restart the server.

This is fine for small websites with a few visitors, or sites with almost no custom settings, but for large sites it can be a problem. Bringing down the site however briefly could interrupt users, generate errors, causing worries during a checkout process or when editing a post on a site. Doing anything more complex and preventing downtime really means rolling your own thing.

So we rolled our own thing. Review Board has operated with the standard Django settings module since day 1, but it’s become obvious over time that it wasn’t good enough for us. So we wrote the dynamic Site Configuration app for Djblets.

djblets.siteconfig

siteconfig is a relatively small Django app that stores setting data in the database, along with project versioning data in case you need it to handle migrations of some sort down the road. There’s a lot of useful things in this app, so I’ll summarize what it provides before I jump into details:

  • Saving/Retrieving settings.

    Setting values can be simple strings, integers, booleans, or something more complex like an array of dictionaries of key/value pairs.

  • Default values for settings.

    These default values could be provided by your application directory or could be based off the contents in your settings.py file.

  • Syncing to/from settings.py.

    Just because you’re using djblets.siteconfig doesn’t mean you can’t still support settings.py. Settings can be pulled from your old settings and saved back there when modified.

  • Compatibility with standard Django settings.

    Django offers a lot of useful settings that you may want to customize. We do the hard work of mapping these and keeping them in sync so you can customize them dynamically.

  • Auto-generated settings pages.

    Much like the existing admin UI, we offer an easy way to provide settings pages for your application. Go ahead and stick these in the admin UI if you like.

Getting Started

To start out, you’ll want to add some code in your app that creates a SiteConfiguration model, perhaps as a post_syncdb management hook. This is project-specific, since you’ll be storing version information in here and linking it with your existing Site. Here’s one way to do it. We’ll use this file as a template in later examples:

# myapp/siteconfig.py
# NOTE: Import this file in your urls.py or some place before
#       any code relying on settings is imported.
from django.contrib.sites.models import Site

from djblets.siteconfig.models import SiteConfiguration


def load_site_config():
    """Sets up the SiteConfiguration, provides defaults and syncs settings."""
    try:
        siteconfig = SiteConfiguration.objects.get_current()
    except SiteConfiguration.DoesNotExist:
        # Either warn or just create the thing. Depends on your app
        siteconfig = SiteConfiguration(site=Site.objects.get_current(),
                                       version="1.0")

    # Code will go here for settings work in later examples.


load_site_config()

Saving/Retrieving Settings

Settings exist in a SiteConfiguration model inside a JSONField. Anything that Django’s native JSON serializer/deserializer code can handle, you can store. Usually you’ll want to store primitive values (strings, booleans, integers, etc.), but you’re free to do something more complex.

from djblets.siteconfig.models import SiteConfiguration


siteconfig = SiteConfiguration.objects.get_current()
siteconfig.set("mystring", "foobar")
siteconfig.set("mybool", True)
siteconfig.set("myarray", [1, 2, 3, 4])

mybool = siteconfig.get("mybool")

It’s pretty straightforward. The set/get functions are just simple accessors for the SiteConfiguration.settings JSON field, but you’ll want to use them because they’ll handle the registered defaults for settings, which I will get to in a moment.

Since this is just a Djblets JSONField, which is a glorified dictionary, you can do a lot of things, such as iterate through the keys and values:

for key, value in siteconfig.settings:
    print "%s: %s" % (key, value)

And so on. Let’s go a step further.

Default Values

Say you’re starting fresh and just created your SiteConfiguration instance, or you’ve introduced a new setting into an existing site. The setting won’t be in the saved settings, so what value is returned when you do a get call? Well, that depends on your setup a bit.

In the first case, with a fresh new setting:

>>> print siteconfig.get("mynewsetting")
None

Any setting without a default value and not in the saved settings will return None. Good ol’ reliable None.. If you want to make sure you return something sane at this specific callpoint, you can specify a default value in the call to get, like so:

>>> print siteconfig.get("mynewsetting", default=123)
123

This can be really useful at times, but it’s a pain to have to do this in every call and keep the values in sync. So we provide a third option: Registered default values.

# myapp/siteconfig.py


defaults = {
    'mystring':     'Foobar',
    'mybool':       False,
    'mynewsetting': 123,
}


def load_site_config():
    ...

    if not siteconfig.get_defaults():
        siteconfig.add_defaults(defaults)

That’s all it takes. Now calling get with just the setting name will return the default value, if the setting hasn’t been saved in the database.

Down the road we’re going to have support for automatic querying of apps to grab their settings, giving third party apps an easier way to integrate into codebases using siteconfig.

Syncing Settings with settings.py

Django’s settings.py is and will continue to be an important place for some settings to go. If you’re developing a reusable application for other projects, you may not want to fully ditch settings.py. Or maybe you’re using a third party application that uses settings.py and want to make the settings dynamic.

siteconfig was designed from the beginning to handle syncing settings in both places. Now, when I say syncing, I don’t mean we write out to settings.py, since that would require enforcing certain permissions on the file. What we do instead is to load in the values from settings.py if not set in the siteconfig settings, and to write out to the settings object, an in-memory version of settings.py.

Let’s revisit our example above, but in this case we want to be able to dynamically control Django’s EMAIL_HOST setting.

# myapp/siteconfig.py
from django.conf import settings

from djblets.siteconfig.django_settings import apply_django_settings,
                                               generate_defaults


settings_map = {
    # siteconfig key    settings.py key
    'mail_host':        'EMAIL_HOST',
}

defaults = {
    ...
}
defaults.update(generate_defaults(settings_map))


def load_site_config():
    ...

    apply_django_settings(siteconfig, settings_map)

What we did here was to generate a mapping table of our custom settings to Django settings.py settings. We can then generate a set of defaults using the generate_defaults function. This goes through the Django settings keys, pulls out the values if set in settings.py, and returns a dictionary similar to the one we wrote above. This guarantees that our default values will be what you have configured in settings.py, handling the first part of our synchronization.

The second part is handled through use of the apply_django_settings function. This will take a siteconfig and a settings map and write out all values that have actually changed. So if “mail_host” above is never modified, apply_django_settings won’t write it out to the database, allowing the default value to change without having to do anything too fancy.

Compatibility with Django’s Settings

The above example dealt with the settings.EMAIL_HOST setting, but in reality we won’t have to cover this at all. There’s a few other goodies in the django_settings module that handle all the Django settings for you.

First off is get_django_settings_map. You’ll rarely need to call this directly, but it returns a settings map for all the Django settings sites are likely to want to change.

Then there’s get_django_defaults, which you’ll want to either merge into your existing defaults table or add directly. We don’t do it for you because you may very well not care about these settings.

The third is one you just saw, apply_django_settings. In the above example, we passed in our settings map, but if you don’t specify one it will use get_django_settings_map.

Let’s take a look at how this works.

# myapp/siteconfig.py

from djblets.siteconfig.django_settings import apply_django_settings,
                                               get_django_defaults


def load_site_config():
    ...

    if not siteconfig.get_defaults():
        siteconfig.add_defaults(defaults)
        siteconfig.add_defaults(get_django_defaults())

    apply_django_settings(siteconfig, settings_map)
    apply_django_settings(siteconfig)

This is all it takes.

If you take a look at django_settings.py, you’ll notice that we don’t reuse the existing Django settings names and instead provide our own. This is an attempt to cleanly namespace the settings used. You’ll also notice that there’s several settings maps and defaults functions. This is so you can be more picky if you really want to.

Auto-generated Settings Pages

Oh, this is where it gets fun.

Dynamic settings are all well and good, but if you can’t set them through your browser, why bother?

We wanted to make sure that not only could these be modified through a browser, but that it was dead simple to put up pages for changing settings. All you have to do is add a URL mapping and one form per page.

The form doesn’t even need much more than fields.

Let’s start out with some code. It should be pretty self-explanatory.

# urls.py
from myapp.forms import GeneralSettingsForm

urlpatterns = patterns('',
    (r'^admin/settings/general/$', 'djblets.siteconfig.views.site_settings',
     {'form_class': GeneralSettingsForm}),
    (r'^admin/(.*)', admin.site.root),
)
# myapp/forms.py
from django import forms
from djblets.siteconfig.forms import SiteSettingsForm


class GeneralSettingsForm(SiteSettingsForm):
    mystring = forms.CharField(
        label="My String",
        required=True)

    mybool = forms.BooleanField(
        label="My Boolean",
        required=False)

    mail_host = forms.CharField(
        label="Mail Server",
        required=False)


    class Meta:
        title = "General Settings"

That’s it. Now just go to /admin/settings/general/ and you’ll see your brand new settings form. It will auto-load existing or default settings and save modified settings. Like any standard form, it will handle help text, field validation and error generation.

Example Settings Page

Proxy Fields

For a lot of sites, this will be sufficient. For more complicated settings, we have a few more things we can do.

Let’s take the array above. Django’s forms doesn’t have any concept of array values, but we can fake it with custom load and save methods and a proxy field.

# myapp/forms.py
import re

class GeneralSettingsForm(SiteSettingsForm):
    ...

    my_array = forms.CharField(
        label="My Array",
        required=False,
        help_text="A comma-separated list of anything!")

    def load(self):
        self.fields['my_array'].initial =
            ', '.join(self.siteconfig.get('myarray'))

        super(GeneralSettingsForm, self).load()

    def save(self):
        self.siteconfig.set('myarray',
            re.split(r',s*', self.cleaned_data['my_array']))

        super(GeneralSettingsForm, self).save()

    class Meta:
        save_blacklist = ('my_array',)

What we’re essentially doing here is to come up with a new field not in the settings database (notice “my_array” versus “myarray”) and to use this as a proxy for the real setting. We then handle the serialization/deserialization in the save and load methods.

When doing this, it’s very important to add the proxy field to the save_blacklist tuple in the Meta class so that the form doesn’t save your proxy field in the database!

Fieldsets

Much like Django’s own administration UI, the SiteSettingsForm allows for placing fields into custom fieldsets, allowing settings to be grouped together under a title and optional description. This is done by filling out the fieldsets variable in the Meta class, like so:

class GeneralSettingsForm(SiteSettingsForm):
    class Meta:
        fieldsets = (
            {
                'title':   "General",
                'classes': ('wide',),
                'fields':  ('mystring', 'mybool', 'my_array',),
            },
            {
                'title':       "E-Mail",
                'description': "Some description of e-mail settings.",
                'classes':     ('wide',),
                'fields':      ('mail_host',),
            },
        )

This will result in two groups of settings, each with a title, and one with a description. The description can span multiple paragraphs by inserting newlines (“n“). All fields in the dictionary except for 'fields' are optional.

Re-applying Django settings

When you modify a field that corresponds to a setting in settings.py, you want to make sure you re-apply the setting or you could break something. This is pretty simple. Again override save as follows:

from myapp.siteconfig import load_site_config


class GeneralSettingsForm(SiteSettingsForm):
    def save(self):
        super(GeneralSettingsForm, self).save()
        load_site_config()

This will re-synchronize the settings, making sure everything is correct. Note that down the road, this will likely be automatic.

There’s one last thing to show you…

Disabled Fields

Sometimes it’s handy to disable fields. Maybe a particular setting requires a third party module that isn’t installed. The SiteSettingsForm has two special dictionaries, disabled_fields and disabled_reasons, that handle this. Just override your load function again.

class GeneralSettingsForm(SiteSettingsForm):
    def load(self):
        if not myarray_supported():
            self.disabled_fields['my_array'] = True
            self.disabled_reasons['my_array'] =
                "This cannot be modified because we don't support arrays today."

        super(GeneralSettingsForm, self).load()

The resulting fields in the HTML will be disabled, and the error message will be displayed below the field.

Depending on whether or not you have a custom administration UI, you may want to tweak the CSS file or completely override it. Note that this all assumes that the djblets/media/ directory exists in your MEDIA_URL as the “djblets” directory. If you have something custom, you can always override siteconfig/settings.html and pass the new template to the view along with your settings form.

More Coming Soon!

There’s some new stuff in the works for the siteconfig app. Auto-detection of defaults and settings maps is the big one. I also have a few more useful tricks for doing advanced things with settings pages that I’ll demonstrate.

If you’re a user of Djblets, let us know, and feel free to blog about it! I’ll link periodically to other blogs talking about Djblets and projects using Djblets.

And as always, suggestions, constructive criticism and patches are always welcome 🙂

Django Development with Djblets: Dynamic Site Configuration Read More »

Django Development with Djblets: Data Grids

It’s been a while since my last post in this series, but this one’s a good one.

A common task in many web applications is to display a grid of data, such as rows from a database. Think the Inbox from GMail, the model lists from the Django administration interface, or the Dashboard from Review Board. It’s not too hard to write something that displays a grid by doing something like:


{% for user in users %}
 
{% endfor %}
Username First Name Last Name
{{user.username}} {{user.first_name}} {{user.last_name}}

This works fine, so long as you don’t want anything fancy, like sortable columns, reorderable columns, or the ability to let users specify which columns they want to see. This requires something a bit more complex.

djblets.datagrids

We wrote a nifty little set of classes for making data grids easy. Let’s take the above example and convert it over.

# myapp/datagrids.py
from django.contrib.auth.models import User
from djblets.datagrid.grids import Column, DataGrid

class UserDataGrid(DataGrid):
    username = Column("Username", sortable=True)
    first_name = Column("First Name", sortable=True)
    last_name = Column("Last Name", sortable=True)

    def __init__(self, request):
        DataGrid.__init__(self, request, User.objects.filter(is_active=True), "Users")
        self.default_sort = ['username']
        self.default_columns = ['username', 'first_name', 'last_name']
# myapp/views.py
from myapp.datagrids import UserDataGrid

def user_list(request, template_name='myapp/datagrid.html'):
    return UserDataGrid(request).render_to_response(template_name)

Now while this may look a bit more verbose, it offers many benefits in return. First off, users will be able to reorder the columns how they like and choose which columns to see. You could extend the above DataGrid to add some new column but leave it out of default_columns so that only users who really care about it would see it.

Custom columns

Let’s take this a step further. Say we want our users datagrid to optionally show staff members with a special badge. This might be useful to some users, but not all, so we’ll leave it out by default. We can implement this by creating a custom column with image data:

class StaffBadgeColumn(Column):
    def __init__(self, *args, **kwargs):
        Column.__init__(self, *args, **kwargs)

        # These define what will appear in the column list menu.
        self.image_url = settings.MEDIA_URL + "images/staff_badge.png"
        self.image_width = 16
        self.image_height = 16
        self.image_alt = "Staff"

        # Give the entry in the columns list menu a name.
        self.detailed_label = "Staff"

        # Take up as little space as possible.
        self.shrink = True

    def render_data(self, user):
        if user.is_staff:
            return '%s' %
                (self.image_url, self.image_width, self.image_height, self.image_alt)

        return ""

class UserDataGrid(DataGrid):
    ...
    staff_badge = StaffBadgeColumn()
    ...

This will add a new entry to the datagrid’s column customization menu showing the staff badge with a label saying “Staff.” If users enable this column, their datagrid will update to show an staff icon for any users who are marked as staff.

Custom columns can render data in a couple of different ways.

The first is to bind the column to a field in the model. By default, a Column instance added to a DataGrid will use its own name (such as “first_name” above) as a lookup in the object. If you want to use a custom field, set the db_field attribute on the column. This field can span relationships as well. For example:

class UserDataGrid(DataGrid):
    ...
    # Uses the field name as the lookup.
    username = Column("Username")

    # Spans relationships, looking up the age in the profile.
    age = Column("profile__age")

The second is the way shown above, by overriding the render_data function. This takes an object, which is the object the datagrid represents, and outputs HTML. The logic in this can be quite complicated, depending on what’s needed.

Linking to objects

A datagrid is pretty worthless if you can’t link entries for the object to a URL. We have a couple of ways to do this.

To link a column on a datagrid to a URL, set the link parameter on the DataGrid to True. By default, the URL used will be the result of calling get_absolute_url() on the object represented by the datagrid, but you can override this:

# myapp/datagrids.py

class UserDataGrid(DataGrid):
    username = Column("Username", sortable=True, link=True)
    ...
    @staticmethod
    def link_to_object(user, value):
        return "/users/%s/" % user

Then

link_to_object takes an object and a value. The object is the object being represented by the datagrid, and the value is the rendered data in the cell. The result is a valid path or URL. In the above example, we’re explicitly defining a path, but we could use Django’s reverse function.

Usually the value is ignored, but it can be useful. Imagine that your Column represents a field that is a ForeignKey of an object. The contents of the cell is the string version of that object (implemented by the __str__ or __unicode__ function), but it’s just an object and you want to link to it. This is where value comes in handy, as instead of being HTML output, it’s actually an object you can link to.

If you intend to link a particular column to value.get_absolute_url(), we provide a handy utility function called link_to_value which exists as a static method on the DataGrid. You can pass it (or any function) to the Column’s constructor using the link_func parameter.

class UserDataGrid(DataGrid):
    ...
    myobj = Column(link=True, link_func=link_to_value)

Custom columns can hard-code their own link function by explicitly setting these values in the constructor.

Saving column settings

If a user customizes his column settings by adding/removing columns or rearranging them, he probably expects to see his customizations the next time he visits the page. DataGrids can automatically save these settings in the user’s profile, if the application supports it. Handling this is as simple as adding some fields to the app’s Profile model and setting a couple options in the DataGrid. For example:

# myapp/models.py

class Profile(models.Model):
    user = models.ForeignKey(User, unique=True)

    # Sort settings. One per datagrid type.
    sort_user_columns = models.CharField(max_length=256, blank=True)

    # Column settings. One per datagrid type.
    user_columns = models.CharField(max_length=256, blank=True)
# myapp/datagrids.py

class UserDataGrid(DataGrid):
    ...
    def __init__(self, request):
        self.profile_sort_field = "sort_user_columns"
        self.profile_columns_field = "user_columns"

The DataGrid code will handle all the loading and saving of customizations in the Profile. Easy, isn’t it?

Requirements and compatibility

Right now, datagrids require both the Yahoo! UI Library and either yui-ext (which is hard to find now) or its replacement, ExtJS.

Review Board, which Djblets was mainly developed for, still relies on yui-ext instead of ExtJS, so datagrids today by default are built with that in mind. However, the JavaScript portion of datagrids should work with ExtJS today. The default template for rendering the datagrids (located in djblets/datagrids/templates/datagrids/) assumes both YUI and yui-ext are present in the media path, but sites using datagrids are encouraged to provide their own template to reflect the actual locations and libraries used.

We would like to work with more JavaScript libraries, so if anyone wants to provide an implementation for their favorite library or help to dumb down our implementation to work with any and all, we’d welcome patches.

See it in action

We make extensive use of datagrids on Review Board. You can get a sense of it and play around by looking at the users list and the dashboard (which requires an account).

That’s it for now!

There’s more to datagrids than what I’ve covered here, but this should give you a good understanding of how they work. Later on I’d like to do a more in-depth article on how datagrids work and some more advanced use cases for them.

For now, take a look at the source code and Review Board’s datagrids for some real-world examples.

Django Development with Djblets: Data Grids Read More »

Django Tips: PIL, ImageField and Unit Tests

I recently spent a few days trying to track down a bug in Review Board with our unit tests. Basically, unit tests involving uploading a file to a models.ImageField would fail with a validation error specifying that the file wasn’t an image. This worked fine when actually using the application, just not during unit tests.

The following code would cause this problem. (Note that this isn’t actual code, just a demonstration.)

# myapp/models.py
from django.db import models

class MyModel(models.Model):
    image = models.ImageField()
# myapp/forms.py
from django import forms
from myapp.models import MyModel

class UploadImageForm(forms.Form):
    image_path = forms.ImageField(required=True)

    def create(self, file):
        mymodel = MyModel()
        mymodel.save_image_file(file.name, file, save=True)
        return mymodel
# myapp/views.py
from myapp.forms import UploadImageForm

def upload(request):
    form = UploadImageForm(request.POST, request.FILES)

    if not form.is_valid():
        # Return some error
        pass

    # Return success
# myapp/tests.py
from django.test import TestCase

class MyTests(TestCase):
    def testImageUpload(self):
        f = open("testdata/image.png", "r")
        resp = self.client.post("/upload/", {
            'image_path': f,
        })
        f.close()

After a good many hours going through the Django code, expecting there to be a problem with file handles becoming invalidated or the read position not being reset to the beginning of the file, I finally decided to look at PIL, the Python Imaging Library. It turns out that at this point, due to some weirdness with unit tests, PIL hadn’t populated its known list of file formats. All it knew was BMP, and so my perfectly fine PNG file didn’t work.

The solution? Be sure to import PIL and call init() before the unit tests begin. If you have a custom runner (as we do), then putting it in here is appropriate. Just add the following:

from PIL import Image
Image.init()

Hopefully this helps someone else with the problems I’ve dealt with the past few days.

Django Tips: PIL, ImageField and Unit Tests Read More »

I just wanted a hamburger

What was supposed to be a quick 10 minute trip to the nearby Carl’s Jr. for a hamburger ended up with blood, police and arrests.

I’m back home visiting my family for a few days, and my brothers and I decided to make a quick run to grab a couple hamburgers. It was about 1 AM, but we’ve done this before, hundreds of times, and never had a problem. It was pretty quiet out, and trouble is the last thing we expected.

We got our burgers and began to walk home. Across the street from the burger joint is an AM/PM. There’s an alley way right behind that that takes us to a small parking area off a street that connects to our street. It’s a nice quick shortcut, well lit, and safe enough. Usually.

As we were heading through the parking area, we heard footsteps behind us. I turned around and saw three guys, but they were a distance aways. I had a bad feeling, but thought I was being paranoid. As we continued to walk, they continued to keep their distance, until we walked onto the street in an unlit area.

One of the guys (they were all teens) pulled his shirt or jacket over his face and grabbed my food. He said, “Give me the food. Give me the food!” I held onto the bag and he pulled hard, breaking the bag in two. He took it and started to laugh. Without hesitation, my brother yelled at him to give it back, and threw his little Dixie cup of water at him. The guy turned around and started to punch him in the face.

There was blood everywhere.

This all happened so fast that I couldn’t react in time, but immediately after I saw another one of the guys start to go over to my brother as if he was about to join in the fight. I jumped between them and said, “Hey, it’s cool. Just take the food and go.” I was frightened that they would pull a knife or gun on my brother and wanted to just end it right then.

They turned around and started to leave. The guy who punched my brother said something like, “I sure was hungry!” and they laughed and continued down the street. We saw them turn onto this one fairly long street. I immediately called my parents, waking them up, as at this point I really wanted more people nearby in case those guys realized they could get our wallets instead of just our food.

I then called the police. They were here within two minutes, faster than my parents even. And not just a couple police. Two police cars sped past us, down the street those guys went (as I described to the operator on the phone), and then two police cars stopped where we were. A third came by later. And a fire truck. And an ambulance.

It turned out that they were expecting something big a block away and thought this might be it. So they sent in the cavalry. Fine by us. They took our statements, had us each identify the suspects (who they had nabbed almost immediately). I was able to positively identify one of them, though he wasn’t the attacker. My brother, however, was able to give a positive identification for the attacker. We were told he’d likely be in jail before the night is up, and maybe his accomplices as well.

So at least we got them off the street. My brother, though, is currently at the hospital getting stitches, and I’m feeling guilty as hell for talking them into coming with me for a burger. I just wanted a hamburger, and instead I got my brother a trip to the hospital, and some guys off the street. What a night.

I just wanted a hamburger Read More »

Happy birthday Review Board!

One whole year…

While attending SuperHappyDevHouse, David and I realized that it was the one year anniversary of Review Board‘s public announcement. (This was the evening of May 17th. I’m just a few hours late in getting this post up.)

It’s been a pretty awesome year for Review Board. What begun as a small project intended for use in our team at VMware and in our personal open source projects turned into a large project with a great community of developers and users. It’s now being used by dozens of companies and projects (some of which have given us permission to list them publicly) and has had code contributions from over 35 users. To everyone who has contributed to the project, we’d like to give our thanks. Review Board wouldn’t be the tool it is today without your help.

I’d like to list some of the major things that have happened in the past year.

  • Review Board has been adopted by one or more teams at around 40 companies (including Yahoo! Search, VMware, and Tripwire).
  • Received contributions from over 35 users.
  • Added support for CVS, Subversion, Perforce, Git, Mercurial, and Bazaar.
  • Added support for useful features such as interdiffs, screenshot commenting, syntax highlighted diffs, customizable dashboard for keeping track of review requests, status reports, a full JSON API, and more.
  • Several presentations were given at various companies and conferences on Review Board by third parties.
  • One public presentation was given by us at LugRadio Live USA 2008 (view the presentation)
  • We’ve started hosting projects for this year’s Google Summer of Code on our Summer of Code server.

That’s just a small sampling. There have been many changes made to stabilize the codebase, improve usability, and generally make the product more awesome.

And of course there’s a number of things happening in the near future:

  • Monotone support.
  • A new, improved administration UI.
  • Extensions support, allowing Review Board to be extended in a variety of ways and integrated into other products.
  • A new set of command-line tools and a Python library for working with Review Board servers.
  • Support for P4V (for Windows Perforce users).

Database Migration and Parent Diffs

Today, a change went in that improves Review Board in two fundamental ways.

First, we now have a fast, powerful way of doing database migrations in-place, without dumping and loading the database. While this should make life easier for users and allow us to modify the database without fear of breaking people’s installs as often, it will also save administrators of large Review Board servers a lot of time. Before this change, the VMware database (of over 25,000 review requests) took at least 30 minutes to do a full migration. Now it shouldn’t take more than a minute.

Second, we’ve just landed the initial support for diffs based on parent diffs. If you’re a user of a distributed version control system (such as Git or Mercurial) you’ll appreciate this. Before, your change had to apply against revisions in the master repository, making it impossible to put up a review request for a change on a sub-branch of a branch when the parent branch didn’t exist on the server. Now, a diff of the parent branch can be uploaded along with your sub-branch’s diff.

Confused? Maybe an example will help. Say you’re working on a large restructuring change in your “code-restructure” branch in your local Git checkout. You have a topic branch off of your “code-restructure” branch with changes you want to put up for review. This used to be impossible without including the whole “code-restructure” branch’s changes in your diff as well, but now, you can put up a diff of the topic branch along with a diff of the “code-restructure” branch, and the topic branch’s diff will appear on Review Board, ready for review.

The backend code for this is now in Subversion. We’ll be adding support to the post-review tool shortly, making this accessible to anyone. Review Board will become fully usable for distributed version control systems.

Are you using Review Board?

Review Board is gaining popularity, and more companies are beginning to use it. If you work for a company or an open source project that’s using Review Board and can give us permission to list you on our Happy Users page, please let us know!

And here’s to an even better year for Review Board.

Happy birthday Review Board! Read More »

Free Review Board Hosting for Summer of Code

Google Summer of Code 2008 is on, and soon students will begin coding, which means mentors will be reviewing code. While the Review Board project is not a participating project in this year’s Summer of Code, we felt Review Board could help with the whole process, improving things for both the students and the mentors.

Starting today, and for the duration of this year’s Summer of Code, we at the Review Board project would like to supply up to 30 projects with free Review Board hosting at gsoc.review-board.org. We’ll handle maintenance of the server, support, and provide assistance to get students and mentors set up.

Doing code reviews with Review Board is simple and saves time over reviewing standard raw diffs, with features such as a powerful diff viewer with syntax highlighting and inline commenting, interdiffs, status reports, and support for a wide variety of revision control systems. We believe it can be as effective a tool for open source development as Bugzilla and Trac have become.

Th Summer of Code server is intended for Summer of Code-related changes only. While we’d love to provide hosting for projects in general, we have limited resources. However, should your project decide to set up its own Review Board server in the future, we’ll be able to assist in migrating your Review Board history to your new server.

If you’re interested in trying out Review Board for your Summer of Code project, you can find out how to apply on our Summer of Code Hosting page.

To learn more about Review Board and how it works, you can look at our project website or watch our presentation from this year’s LugRadio Live USA.

Free Review Board Hosting for Summer of Code Read More »

Review Board presentation at LugRadio Live 2008

This year’s LugRadio Live USA conference was a blast. There were some great talks, interesting booths, and fun swag.

The Review Board presentation went pretty well. I think in my next presentation I’ll demo more often instead of just at the end, but all in all people seemed to find it interesting. The presentation focused on what Review Board is, how it works, and how it could benefit open source. With luck, it will encourage projects to begin using a real code review system, making life easier for all involved.

For those that missed it, the slides and the presentation video are now available. Enjoy!

Review Board presentation at LugRadio Live 2008 Read More »

Django Development with Djblets: Unrooting your URLs

Typically, Django sites are designed with the assumption that they’ll have a domain or subdomain to themselves. Often times this is fine, but if you’re developing a web application designed for redistribution, sometimes you can’t make that assumption.

During development of Review Board, many of our users wanted the ability to install Review Board into a subdirectory of one of their domains, rather than a subdomain.

There’s a few rules that are important when making your site relocatable:

  • Always use MEDIA_URL when referring to your media directory, so that people can customize where they put their media files.
  • Don’t hard-code URLs in templates. Use the {% url %} tag or get_absolute_url() when possible.

These solve some of the issues, but doesn’t address relocating a Django site to a subdirectory.

Djblets fills in this gap by providing a special URL handler designed to act as a prefix for all your projects’ URLs. To make use of this, you need to modify your settings.py file as follows:

settings.py

TEMPLATE_CONTEXT_PROCESSORS = (
    ...
    'djblets.util.context_processors.siteRoot',
)

SITE_ROOT_URLCONF = 'yourproject.urls'
ROOT_URLCONF = 'djblets.util.rooturl'

SITE_ROOT = '/'

SITE_ROOT specifies where the site is located. By default, this should be “/”, but this can be changed to point to any path. For example, “/myproject/”. Note that it should always have a leading and a trailing slash.

The custom template context processor (djblets.util.context_processors.siteRoot) will make SITE_ROOT accessible to templates.

SITE_ROOT should be used in templates when you need to refer to URLs that aren’t designed to respect SITE_ROOT (such as User.get_absolute_url). Your own custom applications should always respect SITE_ROOT whenever providing a URL.

ROOT_URLCONF is typically what you would set to point to your project’s URL. However, in this case, you’ll be pointing it to djblets.util.rooturl. This in turn will forward all URLs to your project’s handler, defined in SITE_ROOT_URLCONF.

This is all you need to have a fully relocatable Django site!

To sum up:

  1. Add djblets.util.context_processors.siteRoot to your TEMPLATE_CONTEXT_PROCESSORS.
  2. Set SITE_ROOT_URLCONF to your project’s URL handler.
  3. Set ROOT_URLCONF to ‘djblets.util.rooturl’
  4. Prefix any URLs with SITE_ROOT in your templates, unless the URL would already take SITE_ROOT into account.

This is functionality that will hopefully make its way into Django at some point. For now, you have an easy way of unrooting your Django project.

Django Development with Djblets: Unrooting your URLs Read More »

Workstation 6.5 Beta 1 – Now with 100% more Unity!

I talked a little while ago about working outside the box with Unity. At that time I gave a sneak peak into what I’ve been working here at VMware the past few months. Well, now everyone can see.

We just announced VMware Workstation 6.5 beta 1, the first public beta for Workstation 6.5. Among many other awesome features is Unity, a feature we introduced in our Fusion product (for MacOS X) which allows you to run your applications from your virtual machine on your desktop without needing to be confined to a big box representing the VM’s monitor.

Unity is available in both our Linux and Windows releases of Workstation 6.5 beta 1, and there’s currently support for Windows guests (Windows 2000 and up). However, it’s a beta so you can expect some problems. To help people get started, here’s a rundown on what you can expect from Unity in beta 1.

Features Overview:

  • Shaped windows
  • Guest mouse cursors
  • Proper window types for most windows (Menu, Dialog, Tooltip, etc.)
  • Special effects with Compiz
  • Virtual desktops
  • Copy and paste between host and guest
  • Start menu integration
  • Window borders and badges

Seamless window integration

With the press of a button, the applications in your virtual machine will pop out and appear on your desktop, intermixed with all your native applications. These windows can stack in any order along with your native windows and will maximize, minimize, and close as you’d expect any normal window to. They’ll appear just like they would in the guest, aside from any borders or badges you have set to help identify the guest windows (more on that in a minute).

We do our best to set the window types on these windows to best reflect their type in the guest. This means that a tooltip from the guest will look and act like a tooltip in the host, as will a dialog, menu, etc. This is important for supporting the special effects provided by a window manager.

Special effects

If your window manager has any special effects set for the windows, they’ll apply to guest windows. For example, users of Compiz will be glad to know that their wobbly windows will work for such applications as Office 2008 or Minesweeper, and your guest menus will still burst into flames when they appear.

There are a few cases where the effect isn’t as strong as with native windows. Due to the way we receive window updates and events, the display of a window will often update before we receive open, close or minimize events. We plan to make this work better for some event types in the next beta, but for now, I recommend choosing special effects that modify a window in-place (fire, fade-in, etc.) instead of one that zooms a window to a location for opening/closing windows.

Wobbly Windows

Virtual desktops

Windows may not natively have virtual desktop support, but Linux does, so we felt it was important to make virtual desktops with Unity just work. You can place your guest applications across your virtual desktops. Maximize Office on one desktop, play a game of Solitaire on another, and reserve a third for your Internet Explorer debugging session.

Unity with Virtual Desktops

Copy and paste

Copy and paste is an important part of any user’s daily work. We currently have support for copying and pasting text between host and guest. You can’t yet copy and paste images or other data, though.

Start menu integration

Helper’s Head

A desktop environment isn’t useful without the ability to get to your programs. We provide a little tool called Unity Helper that runs automatically and provides start menu integration. Simply move your mouse to the top-left corner of your primary monitor and the menu will pop down, providing a start button for each of your VMs in Unity. Click the button and your start menu’s contents will appear.

The start button will match the color of the Unity badges and borders that are set to help you quickly identify your VM.

This functionality is pretty new so there are some kinks to work out. For example, if you don’t have a top panel or your top panel is larger than 24 pixels, you might notice the window in a wrong location. This is a bug that will be fixed in beta 2. We’re also hoping to add more options for the location of this window.

Applications Menu

Another useful tip is that you can use Unity Helper to launch applications in a guest via the panel or command line. Simply run:

 $ vmware-unity-helper --run /path/to/vm.vmx c:\path\to\program.exe arguments

This only works if your VM is currently powered on and in Unity or if the VM is not open anywhere. It’s not a supported feature at this point.

Borders and badges

In order to help identify a window belonging to a particular VM, we have color-coded badges and borders on the Unity windows. The border goes around the window and fades from corner to corner, and the badge is a little VMware logo sitting on your titlebar. Both are purely decorative and optional. You can turn them on or off in VM Settings or change the color. The color will also match the start button.

Badges and Borders

Known bugs (and workarounds)

As with any beta, there are of course bugs that you may hit. Pay special attention to the first item on the list.

  • Start menu problems after a crash. If there’s a crash, sometimes the start menu integration won’t work the next session. The trick is to exit Workstation (leave the VM running in the background), delete /tmp/vmware-$USER/unity-helper-ipc-*, and bring Workstation back up.
  • Occasionally Unity may crash. This is a known bug when a guest window changes its type when we don’t expect it. If you hit this, don’t worry! Your VM is still running in the background. Just re-launch Workstation or Player and go back into Unity mode.
  • Graphics glitches. Sometimes you’ll notice the background appearing when you close or minimize a window. We hope to fix this up for the next beta.
  • Multiple monitors are not supported in beta 1.
  • Drag and drop is not supported in beta 1.
  • Due to a recent regression just before beta 1, there are graphical glitches for applications not on the current desktop.
  • Some applications behave badly. Photoshop and Flash (the creation program, not the plugin) (ab)use windows all over the place, and so you’ll see windows where you wouldn’t expect them. Sometimes they don’t even get proper updates, making the UI unusable. We’re looking into solutions for this.

There’s more, but those are the main ones I can think of that people may hit.

Give it a try and feel free to report bugs in the user forums.

Workstation 6.5 Beta 1 – Now with 100% more Unity! Read More »

Scroll to Top