Category Archives: Developer Tips and Tricks

So You Want To Make Your Own Extension (Part 2)

Alrighty, so you made it through Part 1! Congrats! By now, you should be able to play around with some kind of front-end prototype for your extension. Here comes the hard part: bridging to the back-end. Just a reminder, I’m going to be referring to the extension I wrote, which you can find here for a better reference.

For this, Reviewboard has something nice called the WebResourceAPI. In short it facilitates REST API calls to your back-end. Once you’ve got all your configurations set up, you basically have very little to do except call their API to do the stuff you want, like instantiating new objects, modifying them, or deleting them.

Models

But before we even get to that, we should make the models for our extension. For my case, I only needed one model, which is declared in models.py. It’s pretty simple, because I only needed to keep track of a few things for each checklist. Its methods are also pretty basic; each one implements each functionality that I want the checklist to do.

Here is my models.py file.

from django.contrib.auth.models import User
from django.db import models
from djblets.util.fields import JSONField
from reviewboard.reviews.models import ReviewRequest

class ReviewChecklist(models.Model):
    """A checklist is a list of items to keep track of during a review. """

    user = models.ForeignKey(User)
    review_request = models.ForeignKey(ReviewRequest)
    items_counter = models.IntegerField(default=0)
    checklist_items = JSONField()

    def add_item(self, item_description):
        ...

    def edit_item_desc(self, itemID, item_description):
        ...

    def toggle_item_status(self, itemID):
        ...

    def delete_item(self, itemID):
        ...

Just to avoid cluttering up the tutorial, you can view the full implementation here. As a design decision, I made the checklist items a JSON instead of creating a different class for it. It ended up simpler that way, but of course, you are free to make decisions about your extension’s models. You can make as many as you need.

Notice that my foreign keys for this model is the user id and review request id. This is why I needed {{user.pk}} and {{review_request.id}} from the template. I needed to be able to pass them in as keys when instantiating / accessing my checklist.

WebAPIResource

WebAPIResource Class

Create a python class that subclasses reviewboard.webapi.base.WebAPIResource. For my case, my file is called checklistResource.py. For now, we’ll add these lines:

from reviewboard.webapi.base import WebAPIResource

from checklist.models import ReviewChecklist

class ChecklistResource(WebAPIResource):
    name = 'checklist'
    model = ReviewChecklist
    uri_object_key = 'checklist_id'
    allowed_methods = ('GET', 'POST', 'PUT', 'DELETE')
    fields = {
        'id': {
            'type': int,
            'description': 'The numeric ID of the checklist review.'
        },
        'items_counter': {
            'type': int,
            'description': 'Number of items added in the checklist.'
        },
        'checklist_items': {
            'type': str,
            'description': 'Items in checklist.'
        }
    }

Remember to import your extension as well (and just to clarify, this file should be in the same directory as your models.py). Anyway, here’s a breakdown of what these attributes mean:

  1. name: This is needed for the API calls from the front-end. Your URL would end up being something like this — api/extensions/checklist.extension.Checklist/checklists/. The extension id is checklist.extension.Checklist. You don’t define this. It will be defined for you. The last part is the plural form of the name defined here. See, I had a bug where I thought the name itself had to be plural, and my url ended up having checklistss, and needless to say, it didn’t work. So the URL takes the name and adds an ‘s’ to it.
  2. model: This is the model that you want the API to work with.
  3. uri_object_key: If you’re familiar with the way Django does its regex for the url, you might have seen something like this before: r’^path/(?P<var>\d{3}), or something similar. Basically this allows values in the URL to be captured, but you have to specify that variable. This is what you put on the uri_object_key. For example, I put checklist_id. Basically, I’m telling Django to capture the id in the URL and assign that to checklist_id.
  4. allowed_methods: This defines which REST API calls you would allow through this class.
  5. fields: This dictionary defines the variables that would be sent back to the client side after each API call. So if you have something on the front-end that depends on the values of your object on the back-end, this would be a good place to send those values over.

By the way, these aren’t the only attributes you can define. Make sure you check out djblets.webapi.resources.py for the full documentation.

Next, we add methods.

For GET and DELETE, unless you need to do something really special, getting and deleting just require two functions to be implemented.

    def has_access_permissions(self, request, checklist, *args, **kwags):
        return checklist.user == request.user

    def has_delete_permissions(self, request, checklist, *args, **kwags):
        return checklist.user == request.user

These two functions should define the criteria for who / when a GET or a DELETE is acceptable. In my case, I only need to know whether the user who is performing these actions are the owner of the checklist. The actual fetching and deleting are handled by the ResourceAPI.

Now, onto more complicated methods. I’ll only show you how to do one, and hopefully you’ll have enough primer to sink your teeth on your own functions.

    @webapi_request_fields(
        required={
            'user_id': {
                'type': int,
                'description': 'The id of the user creating the checklist.'
            },
            'review_request_id': {
                'type': int,
                'description': 'The id of the review request.'
            }
        }
    )
    @webapi_login_required
    @webapi_check_local_site
    def create(self, request, user_id=None, *args, **kwargs):
        """Creates a new checklist."""
        user = User.objects.get(pk=user_id)
        review_request = resources.review_request.get_object(request,
                                                             args, **kwargs)

        new_checklist, _ = ReviewChecklist.objects.get_or_create(
            user=user,
            review_request=review_request)

        return 201, {self.item_result_key: new_checklist}

So this is a function that creates a new instance of your model. It is automatically mapped to the POST request, so it gets called whenever the front-end does a POST. Now let’s break this down.

@webapi_request_fields
This is a dictionary containing data from the client-side. If any of your function is expecting data from the client-side, you should have this decorator sitting on top of your function. You can see that it’s called required. You can also have a dictionary (inside the same decorator) called optional, and here you can write all the data that you might expect some of the time, but not others.

If you pass in data that’s not declared in either a required or optional dictionary, you would experience some 404 errors. If you try to call a function and you do not pass in one of the fields inside required, you would also get 404 errors. So make sure you define these correctly.

There are other types of decorators like @webapi_login_required and @webapi_check_local_site. These enforce certain things when the function is called. I have to admit I don’t know much about them, but there are a lot of them on hand, so you should check out which ones would ensure the integrity of your data. Don’t be afraid to ask which ones you need or what something means. That’s usually the best way to find out.

You can access these fields either through **kwargs, using the variable name as the key. Or you can include them in the function signature, like I did here for user_id.

Class Registration

Now, we need to connect this resource API to the model and the extension. So at the end of checklistResource.py, instantiate a new resource:

checklist_resource = ChecklistResource()

And then in your extension.py, register the model to your resource. So add these lines:

from checklist.checklistResource import checklist_resource

class Checklist(Extension):
    ...
    resources = [checklist_resource]

    def __init__(self, *args, **kwargs):
        ...
        register_resource_for_model(ReviewChecklist, checklist_resource)

    def shutdown(self, *args, **kwargs):
        unregister_resource_for_model(ReviewChecklist)

So, also notice that I added a new method called shutdown. It’s responsible for unregistering the resource.

BaseResource

There is a corresponding resource for the client side which works with the WebAPIResource. It’s called RB.BaseResource and you can find it in reviewboard/static/rb/js/resources/models/baseResourceModel.js. It’s a very big Backbone.js model, because it does a lot of things for you. But this also means that it might take you a while to learn how to use it. I’ll cover the main things here, but you should definitely still look into it, because I’m sure there would be functionalities you’d need which I won’t cover.

The good news is that after you extend RB.BaseResource, there’s only a few things you have to override.

Your BaseResource File

Create a Backbone.js model that extends RB.BaseResource. i called mine checklistAPI.js, because I’m using it as an interface for my main Backbone.js views. (I’ll show you later how to do this.) For now, we can start off like this:

Checklist.ChecklistAPI = RB.BaseResource.extend({
    rspNamespace: 'checklist',

     url: function () {
        var baseURL = SITE_ROOT + 'api/extensions/checklist.extension.Checklist/checklists/';
        return this.isNew() ? baseURL : (baseURL + this.id + '/');
    },
});

Alright. So in this little tidbit, we define our rspNamespace. This is very important, so don’t forget to define it. I named mine like my extension.

The url function builds the URL that we’re going to be using to get to the server-side. The API Resource normally does this automatically for RB apps, but at the time of writing, this wasn’t yet supported for extensions. So this function just defines the URL that I use, which must follow the pattern api/extensions/extensionID/extensionName + ‘s’. So remember the name we had to define in our WebAPIResource? This is where it’s needed.

Now we’ll need to add a few more attributes and functions.

parseResourceData

    parseResourceData: function(rsp) {
        this.checklist_items = rsp.checklist_items;
        return {
            id: rsp.id,
            links: rsp.links,
            checklist_item_id: rsp.items_counter,
            loaded: true
        };
    }

This function takes the data (denoted by rsp here) you return from your server side, and parses it. Here, I return it as a JSON to my Backbone.js view in a form I can work with. I also assign the checklist_items variable to the checklist_items that was given back to me. Basically this is the function where you can initially get the data the server returns to you, so this is where you get to handle it in any way you like.

toJSON

    toJSON: function () {
        return {
            user_id: this.get('user_id') || undefined,
            review_request_id: this.get('review_request_id') || undefined,
            checklist_item_id: this.get('checklist_item_id') || undefined,
            item_description: this.get('item_description') || undefined,
            toggle: this.get('toggle') || undefined
        };
    },

This function organizes the data you want to send into a JSON that the BaseResource will send over to the back-end. So, remember those required or optional dictionaries we created inside the @webapi_request_fields decorator? The variables here should correspond to the variables there. So, for example, in our create function, we needed user_id and review_request_id. Make sure that the corresponding variables here is not NULL when you do a POST. (I’ll be showing you how to do that shortly.)  Here, I’m assigning them to the values of the class attributes. I set the class attributes in my Backbone.js view.

Just so you know, it’s entirely up to you how you extend BaseResource, whether as a model directly related to your extension, or as an interface. I just saw that treating BaseResource as an interface from my view was a better choice for me. It’s really your call.

How to Use the BaseResource

So let’s take a look at my view. My view is responsible for making the API calls. For example, suppose I want to add a new checklist item to the checklist. Here is my function for it:

    /* Add the new item to the backend. */
    addItemDB: function (event) {
        if (event.keyCode === 13) {
            var item_desc = $('input[name=checklist_itemDesc]').val();
            $('input[name=checklist_itemDesc]').val('');

            if (item_desc === '') {
                alert("Please type a description");
                return;
            }

            this.checklistAPI.set({
                item_description: item_desc,
                checklist_item_id: null,
            });

            var self = this;
            var saveOptions = {
                success: function (data) {
                    var item_id = data.attributes.checklist_item_id;
                    self.addItem(item_id, item_desc, false);
                },
            };

            this.checklistAPI.save(saveOptions, this);
        }
    },

Let’s break this down.

L12 – L15
Here, I refer to a variable called checklistAPIchecklistAPI is just an instance of ChecklistAPI, and I have made it an attribute of my view. I am setting the value of checklistAPI’s instance variables. This is so that when toJSON is called, item_description and checklist_item_id are set properly. (Note that I purposely set checklist_item_id to null; this is to let the server side know that I’m adding a new item, rather than editing the description of an already existing item. Again, designing your API calls is up to you. This is merely procedure stuff.)

L19 – L23
Here we define a saveOptions variable, which we will pass in to our API call. It contains functions that defines what we expect would be done upon completion of the API call. For this example, I have defined a ‘success’ function. The back-end sends me the item id for the new item, so I use that to create my front-end Backbone.js model for the new item. You may also define an error function, which is useful for debugging.

L26
This line is virtually the only line you’ll ever need to fire off your POST or PUT call. For a delete, you can call destroy rather than save. You pass your saveOptions variable, along with the object you want to be referred to as ‘this’ in your success / error functions. In this case, I want the success function to operate on my view whenever it uses ‘this’, so I just pass in ‘this’, because my current object is the view.

…And, that’s about it!

So, so, so. This would be as far as this tutorial would cover. I believe I covered the most major points about using the WebAPIResource and the BaseResource. If there’s anything that’s not clear to you, feel free to leave comments, and I’ll try to help as much as I can; re-explain things in a different way, or clarify some stuff that might not have crossed as well as I hoped. But more importantly, check out the documentation, and never forget to ask for help!! When I was going through this, the help I got from the mentors were so valuable, and it’s mostly because of them that this tutorial is up in the first place.

So I’ll leave you guys with that, and good luck with your extensions!

Advertisements

So You Want To Make Your Own Extension (Part 1)

That’s great! Extensions are a great way to add functionality to an existing web app. This blog post is going to help you through some of the initial stages of creating your extension for ReviewBoard.

Oh, but I’ll make it clear that this is not supposed to replace the [Extensions Documentation].

The documentation is meant to be a general outline on how one creates Extensions. Actually, why don’t you go take a look at it right now?

…Alright, are you back? So you might be wondering now, “Well, if there’s already a documentation, what is this tutorial for?” The documentation covers a lot of scope and is very general in the way it approaches extension creation. My purpose in this blog post is slightly more particular to getting your extension’s front-end up and running, and bridging that with your back-end. Specifically, we will look at:

Part 1:
– Starting your extension
– Managing your static files

Part 2:
– WebAPIResource
– BaseResource

Basically, I am hoping that this tutorial will let you do more of this:

And less of this:

But seriously, if you feel like this at any point in the semester, you should probably talk to one of the mentors ASAP. Trust me. It helps.

Example Extension

For this tutorial, I’ll show you how I setup my Checklist extension. The Checklist extension allows users to create a list of items for a review so that they’ll remember what kind of things to mention for a review request. I will be posting snippets of my code here, but if you want to check out the entire thing, here is the GitHub page for it.

I know some of you might be total newbies to Django, which is perfectly fine; however, for this tutorial, I’m hoping you know at least how Django implements the MVC framework more or less. Also, while I want to be as helpful as possible, I know for sure that this tutorial may not be 100% of what you need. Maybe some things will be missing, and maybe some stuff I say here may be completely useless to you. It’s a testament to Reviewboard’s flexibility that so many different types of extensions can be created. The extension we’re going to be working with is heavy on the front-end. And this is why we will be starting there. It’s nice to get a prototype up and running, just to get a feel for what you really want your extension to do.

Extension Boilerplate Generator

The good news is that a lot of the startup code you need would be generated for you automatically using the generate_extension command.

cd reviewboard
./contrib/tools/generate_extension.py checklist

You can follow the directions from there. Mostly it’s going to ask you about some preliminary configurations. After that, you should have checklist directory inside reviewboard. The checklist directory should look something like this:

checklist
— setup.py
— checklist
—— admin_urls.py
—— extension.py
—— __init__.py

RB-Extension-Pack

You will need a place to put your extension. Even though RB generated it in reviewboard, we have to put it somewhere else. We can’t squeeze it into the Reviewboard directory, because that’s for the main RB code. For extensions, clone the rb-extension-pack from Github.

git clone https://github.com/reviewboard/rb-extension-pack.git
cd rb-extension-pack

Then copy over the checklist directory into rb-extension-pack.

Extension.py

Alright, so this is where we start to get some meat. Extension.py is where you put the bulk of the configuration for your extensions. At this point, it should look like this:

# checklist Extension for Review Board.
from reviewboard.extensions.base import Extension
from reviewboard.extensions.hooks import TemplateHook

class Checklist(Extension):
    def __init__(self, *args, **kwargs):
        super(Checklist, self).__init__(*args, **kwargs)

Here are some more things you should add in your class:

    metadata = {
        'Name': 'Review Checklist',
    }

    js_model_class = 'Checklist.Extension'

We will be coming back periodically to the extension.py file, but for now, this is all we have to do.

Managing Your Static Files

The Template Hook

So, for Extensions, there are these things called “hooks”. These allow your extension to hook into certain parts of ReviewBoard, hence the name. Here is the list of hooks that you can use for your extension: [Extension Hooks]. The important part for checklist extension is the Template Hook, because in order for us to proceed with our prototype, we want our front-end to show up where it needs to be. For example, for our checklist, we want it to show up on pages where creating a review is possible, such as any of the Review UI templates, or the diff templates. Basically, anywhere that a user can comment on code, images, pdf files, etc. we want a checklist to be created.

We can tell our extension to do this by adding a Template Hook in the __init__ function.

    def __init__(self, *args, **kwargs):
        super(Checklist, self).__init__(*args, **kwargs)
        TemplateHook(self, "base-scripts-post", "checklist/template.html",
                     apply_to=["view_diff", "view_diff_revisions",
                               "file_attachment"])

Alright, so let’s dissect this a bit.

We’re instantiating a TemplateHook. “checklist/template.html” is the template that we want to inject into the other pages. The template.html defines how your extension looks like on the front end; it would also include links to your extension’s Javascript and CSS files. I will talk more about these later.

The apply_to list is a list of template names defining the pages into which we would like to inject our extension. If you look at reviewboard/reviews/urls.py, you will see that the views corresponding to the urls have names. You can use these names to specify where you want your extension to show.

For my case, since the checklist should show up anywhere a user can comment on a review request, we want it to show up on the diff pages of the request, as well as any file attachments included with the request.

Configuring your Static Files

So now that we have configured our extension to show up where we want it to, the next thing to do is to actually code up how it should appear. If you have done web programming before, you’ll know that we will need an html page at the very least. To add functionality on the front-end, we will need a Javascript file, and to make it look nice, we will need a CSS file.

First, we have to add a bit to our extension.py file again, to let RB know how to access our static files.

Add these just after the assignment to ‘js_model_class’:

    css_bundles = {
        'css_default': {
            'source_filenames': ['css/index.css']
        }
    }

    js_bundles = {
        'js_default': {
            'source_filenames': ['js/models/checklist.js',
                                 'js/models/checklistAPI.js',
                                 'js/views/checklistView.js']
        }
    }

Alright. So in the source_filenames list, we have to add the path to our static files. In the css_bundles list, we add the path to our CSS file, and in the js_bundles list, we add the path to our Javascript files. Pretty simple. The strings ‘css_default’ and ‘js_default’ are the names of the bundles. You can put whatever you want here, but remember them, because we’d need these names in our HTML template.

Now, we will take a look at template.html. You should put this file in a templates directory. More specifically, the path to template.html should be:

templates/checklist/template.html’

Django recommends that within the templates directory, the html files should be organized according to the apps. Even though we only have one app — the checklist app — we should still be organized.

You should still add the links to your static files in the template.html file. Here is where the bundles we declared in extension.py will help us. So instead of linking your static files the normal, html way, we can just do it like this:

{% load djblets_extensions %}
{% ext_js_bundle extension "js_default" %}
{% ext_css_bundle extension "css_default" %}

Note that we refer to the bundle we want to use by the name we gave it earlier.

If you’re using Backbone.js (which you really should), you don’t need to link the files to Backbone and its dependencies, because Reviewboard does that already.

In this file, you can put whatever you need for your extension’s front end. I used a Backbone.js View to instantiate my checklist.

<div id="checklist"></div>

<script>
    $(document).ready(function() {
        new Checklist.ChecklistView({
            user_id: "{{user.pk}}",
            review_request_id: "{{review_request.id}}"
        });
    });
</script>

The div will be where I’d add all my DOM elements. The script instantiates a new view.

But what’s {{user.pk}} and {{review_request.id}}? Respectively, they’re the id of the user creating the checklist, and the id of the review request. There are variables accessible through the Django templates, so make sure to check out if the ones you need are available right from the template. 

Alright. Now go to the Admin page of your localhost account. Click Extensions in the menu bar. You should see your extension listed there.

Click ‘Enable’.

Namespaces

One final tip! It’s recommended to use namespaces in your Javascript files. Basically, suppose you have a class called MyClass. It’s a good idea to append your extension name at the beginning of every single one of your class names, like so: MyExtension.MyClass.

Then in your Javascript file, make sure to instantiate your namespace object. As you can see in my models/checklist.js file, I instantiated a variable called ‘Checklist’.

var Checklist = {}

Whenever I refer to my javascript classes, I use Checklist.ClassName. Using namespaces prevents confusion, because there may be variables with the same name as yours from other apps.

Now, you can go to the pages where you injected your template. You are now free to play around with the front-end of your extension!

How to Configure PyCharm for Review Board Development

So you’ve just spent a few hours going through the Getting Started guide and have finally gotten your development environment configured (and hopefully all the unit tests passed!). Getting the dev server up and running and seeing Review Board load up in your web browser for the first time is a tremendous accomplishment, and you should be proud to have gotten this far! But greater challenges lie ahead as you start hacking. If you’ve just started working on Review Board like me, you’ll want to take advantage of all the tools at your disposal to make the learning curve as painless and enjoyable as possible as you try to figure out how the pieces in the project fit together. For me, an IDE with a good GUI debugger is a must because it allows me to dynamically step through the program’s runtime execution, as opposed to having to figure it out statically through code inspection.

PyCharm is my IDE of choice. Compared to Eclipse with PyDev, I’ve found PyCharm to be much more friendly to set up, and it doesn’t make unnecessary modifications to your Git repository when you import the project, leaving you in a clean state before you start making changes to the code. PyCharm’s built-in support for Django is also a nice feature to have.

Configuring PyCharm with your Review Board project is simple (based on PyCharm 2.7 on OS X 10.8.5) :

  1. Open up PyCharm. Under the Quick Start pane, click Open Directory and select the top-level Review Board directory (the one that you cloned with Git). e.g. ~/reviewboard.
  2. Once the project is loaded, open the Project Settings window by going to PyCharm > Preferences.
  3. Under Project Interpreter > Python Interpreters, add the python interpreter you are using. If you’re using virtualenv, PyCharm is also smart enough to recognize it. You should see a list of python packages associated with the interpreter you have selected.Image
  4. Go to Django Support. Check Enable Django Support and set the Django project root directory to the top-level Review Board source directory. For example: /Users/Edward/reviewboard. Settings and Manage Script should be set to reviewboard/settings.py and reviewboard/manage.py, respectively.Image
  5. Save your changes and open the Run/Debug Configurations window by going to Run > Edit Configurations…
  6. Add a new Django server configuration and set Host to 127.0.0.1 and Port to 8080 (the default settings for Review Board’s development server when you run ./contrib/internal/devserver.py). Make sure Python interpreter is also set to the one you have previously configured.Image
  7. Click the Run or Debug button to start the development web server. Happy debugging!

UCOSP Blog Post — Surya

I’m Surya Nallu, a 3rd year Computer Science specialist student at the University of Toronto. Past summer I was excited to be a part of Google’s Summer of Code where in I got the opportunity to work on a project; that also introduced me to Django. Django’s elegant way of representing the components within the MVC framework is hands down awesome. I liked the experience so much that I wanted to continue working on a similar project during the school term. Fast forward and it’s been the same fun ride with ReviewBoard so far.

To get a feel of the code-base and to grasp the workflow, we were encouraged to find and fix some easy bugs during the three day long sprint at Facebook. I started off with checking some bugs on the backend and fixed a simple bug involving truncation of whitespace characters in an email field. How easy was it? As easy as calling .strip() on a variable. I sent the code for a review and it was pushed to the repository. But that’s not the point. It was my first bug fix and it does, indeed feel awesome to realize that your one line change will be implicitly used by a wide array of people.

The Extension Browser
While going through the potential list of projects the one that I was most interested was in developing an extension browser module for ReviewBoard. Given that I was comfortable with Django and preferred the back-end, I was stuck on pursuing the same. What is it? It’s a module on the ReviewBoard administration end, which lets you browse extensions (from a yet to be store — which will serve as a directory of extensions available) and install them right away. It’s analogous to WordPress’ automatic plugin installation. Why is it needed? First, since anyone can make an extension for ReviewBoard as a potential administrator you don’t possibly know all the extensions that exist. Second, as of now, you have to manually install the extension (which is ultimately a Python egg, given that Django runs on Python) on the server — which involves you to SSH into the server and perform the installation. This isn’t ideal. By creating an in-application extension manager, the administrators have the convnience of finding and installing an extension of their choice on-the-fly. No SSH, direct access to the server or technical know hows are required. Just find an extension and install.

Under the hood, the module would communicate with an API on the (yet to be) extension store and expect JSON responses. You get to specify what kind of extensions you’re looking for and are presented with the search results. You then have the option to look into more details for a particular extension or to install it. Installation is handled on the back-end through invoking easy_install within RevewBoard. If everything goes well, your new extension is up for use.

The progress so far: YouTube screencast

Starting trouble:

Initially upon taking up the project, one of the bugs that had been already reported needed attention. The bug was that upon installing an extension, you had to re-start the server (the development server or the appropriate WSGI platform) for the installation to take effect. You can see why this was something that needed to be fixed right away — on-the-fly installations from the extension browser module won’t take effect immediately. I started looking into it during the sprint and it took a while for us (Christian, Steven and I) to figure out what exactly was causing it in the first place. What was the problem?

Aside: When easy_install is used to install a package (in our context – the extension), the package/extension is made exposed to ReviewBoard with the help of entrypoints. The way I see it, it’s a registry of installed Python modules with each package associating itself with one or more entrypoints. The entrypoint associated with ReviewBoard is “reviewboard.extensions”; so any package that advertises itself to this entrypoint is accessible within ReviewBoard (and can be treated as an extension).

The issue technically here was that upon installing an extension, the extension wasn’t visible within the ReviewBoard’s extension entrypoint immediately. If we restarted the test server, the entrypoints got updated. Okay, so we figured (after a couple of searches and reading up resources) that the problem was with the package not being activated within the entry point and hence not being accessible instantly. We ended up reading through this thread and figured that’s the exact problem we were facing. We went ahead and applied the fix only to find it still not working. It became clear eventually that something specific to ReviewBoard was further causing the problem. How did we figure where?

We had to narrow down the problem to its root. Since we were calling easy_install directly within a python script, our first guess was that doing so was creating a problem; since usually easy_install is invoked through the shell. We created a test python script emulating exactly what we wanted and it worked; the entrypoints were getting updated as it was stated in the StackOverflow thread. Alright, narrowed down a little deeper. Next point was to see if Django itself was inhibiting something. So I emulated the same process within a test Django app and it worked fine. Okay, so Python’s good, so is Django. This confirmed that the ReviewBoard codebase was doing something special (or rather weird!). ReviewBoard (Djblets to be specific) provides a convenience function that lets you iterate through its extensions entrypoint.  I then manually iterated and printed through all the packages that were present in this entrypoint (post an installation) and the new extension didn’t show up. To confirm again, I accessed the entrypoint directly myself (this time, not through the convenience function) using Python’s pkg_resources module and voila the new extension did show up now. So we clearly realized that the problem was the way the class holding the convenience function was handling the entrypoint (Djblets/extensions/base.py). It turned out that a stale line of import had no effect and it was creating the problem. Removing it, everything worked well.

Tips

Here are some tips that I personally find effective to be productive with working on ReviewBoard (or generally UCOSP itself):

  • Find a good chunk of time to work on it. Having a continuous chunk of hours is way better than having small blocks of them split across the week. I typically prefer to spend a whole day in a week as a result and it helps me keep the momentum. It’s especially bad when you’re having all these nice ideas fly through your head about your implementation and you’ve to quickly stop and switch to some other work.
  • When in doubt, ask! Though I’m fairly comfortable with Django, I often find myself not remembering the exact specifics of it. Django has pretty awesome documentation on everything you’d need. Moreover, if the problem is too specific chances are someone else has encountered the same problem before and it’s up on StackOverflow (that’s how we found the thread linked above in “Starting trouble” — I just Google’d “easy_install without restart”). Lastly, ping your mentors whenever you’re stuck. I often think of approaches that seem very elegant at first but after a quick discussion, it turns out there are better ways.
  • Although this can be contested, printing for debugging is okay. While I love debuggers, it’s not all that easy (and often not necessary, especially) to quickly inspect a variable or a function you wrote on the server side with a debugger for Django. I add a raw_input() statement immediately followed by a print statement so that the values I’m interested in show up on the console running the test server and wait for my go (raw_input() will block) to continue processing ahead.

That’s all I have for now! I’m excited that the extension browser module would soon be a useful addition to ReviewBoard.

A few words about IRC

Hello new Review Board student! Along with our code reviews and mailing lists, IRC is one of the primary communication mechanisms that we use in the project. I thought I’d jot down a few notes on the following:

  1. What is IRC? And how do I start using it?
  2. How to talk to people on IRC (etiquette and protocol)

So here goes.

What is IRC? And how do I start using it?

Briefly, IRC (Internet Relay Chat) allows you to talk in chat-rooms. Chat-rooms are a little antiquated now – but if you’ve ever used instant messaging, it’s like that – except there can be any number of people in the chat at once.

An IRC chat in progress

The basic idea is this: there is an IRC server that users connect to via IRC clients. On the IRC server are any number of channels (“channel” is synonymous with chat-room). Users can join any number of channels, and talk back and forth in there. That’s basically it.

IRC Clients

So you’re definitely going to need an IRC client of some kind in order to connect to an IRC server. Here’s a big list of IRC clients. Here are popular ones for each major platform:

Windows

OSX

Linux

  • XChat
  • irssi (works on OSX and Windows too – though I rarely see people using it there)

Other

Connecting to Freenode

Once you have a client installed, you need to instruct it to connect to an IRC server. The IRC server that the Review Board project uses is called Freenode.

The path to connecting to a server is different from client to client, but here are the need-to-knows:

Hostname: chat.freenode.net
Open ports: 6665, 6666, 6667, 6697 (SSL only), 7000 (SSL only), 7070 (SSL only), 8000, 8001 and 8002.
Here’s additional information on Freenode servers.

You’ll also need to choose a unique nickname – but again, this is different from client to client.

Once you’ve connected to a server, you need to join a channel. By convention, channels start with the # character, and the channel we work in is:

#reviewboard-students

Your chat client might have a built-in way to connect to channels. If not, the command you need to send to the server to join is:

/join #reviewboard-students

And that will put you in the channel. If all goes well, you should see a list of names of the people who are in the channel, and you can start chatting.

Who’s who?

Your mentors have the following IRC nicknames:

Christian Hammond: ChipX86
David Trowbridge:  purple_cow
Steven MacLeod: smacleod
Mike Conley: m_conley

How to talk to people on IRC (etiquette and protocol)

That list of people you see in the room represent connections to the server – it doesn’t necessarily mean that they’re actively chatting; it just means that they’re connected via a client somehow.

Oftentimes, people connect and just idle while they do other things. That’s normal behaviour.

If you’d like to talk to someone, one of the established ways of opening connections is “pinging” them.

Pinging

Pinging somebody is a way of getting their attention. Most IRC clients have a mechanism that alerts the user when their IRC nickname is mentioned, and pinging is writing somebody’s handle in the message, like this:

m_conley> ChipX86: ping(30 seconds elapse)
ChipX86> m_conley: pong

In this scenario, ChipX86 ponged (though he could have said anything else, like “hey”, or “hello”), to let me know that he’s there. I can start a conversation with him now.

But I can also ask questions when the person I’m asking is not there…

IRC is beautifully asynchronous

IRC is an awesome place to ask questions when working on Review Board. However, we’re not always at our computers, or we’re super busy, and so we won’t respond to pings right away.

Instead of waiting for us to respond to a ping, just ask your question after a ping attempt.

We might not answer right away, but you can be sure that when we get a free moment, we’ll notice that we were pinged, and then we’ll read the back-scroll. And then we’ll respond to you if you’re in the room.

Being always there

You’ll notice that some people are always in the channel. Some people do this by just never / rarely shutting their computers off.

Most people do this by having a bouncer / proxy set up. Their bouncer is hosted on a remote machine, and it stays connected (and re-connects if it loses the connection). The user then connects to the bouncer, and the bouncer just forwards messages to the client.

This is really handy if you never want to miss what’s going on in IRC – your client will re-connect, and you’ll get a dump of the conversation that elapsed while you were disconnected.

For the term, we can set you up a bouncer – just talk to ChipX86.

Private messaging

Sometimes, you just want to talk to a single person privately. Questions about Review Board should probably be in the #reviewboard-students channel so that other people can listen and chip in, but questions about things like grades or performance can / should be done via private messaging. Each client does this differently, but generally, right-clicking on a nickname in the channel user list allows you to open up private chat.

That’s all for now. Happy chatting!

TL;DR Useful Tips All-In-One Index

There are lots of useful information about review board out there but sometimes it’s hard and costly to dig for them. Wouldn’t it be nice if there’s an index for them to save all the random access cost to search for them?

Well, here it is. If we all contribute to this list with tips or links to useful information, then this can be very useful for newcomers to review board and a great ‘how-do-I-do-that-again’ reference for everyone.

Getting Started on Review Board

  • Apply a patch the git way
  • 1. download the diff (from a review request etc)
    2. put it in the rb repo, then:
    3. $ git apply rb****.patch
  • Apply a patch the cool johns187 way
  • $ rb patch <rid>

Testing Review Board

  • To test review board in local test environment
Add new repository on localhost:
name: Review Board
hosting service: GitHub
project owner: reviewboard
project name: reviewboard

post-review --server=http://localhost:8080

Link to post: https://reviewboardstudents.wordpress.com/2012/03/03/how-to-set-up-local-test-environment-for-review-board/

  • To run a specific unit test case (subset of test cases):
./reviewboard/manage.py test -- reviewboard.scmtools.tests:GitTests.testFilemodeWithFollowingDiff

Link to post: https://reviewboardstudents.wordpress.com/2011/09/24/running-subsets-of-review-board-tests/

CSS tips

  • Use the 4 CSS rules of multiplicity well
1. Multiple declarations can live in a single rule.
2. Multiple selectors can preface the same rule set.
3. Multiple rules can be applied to the same selector.
4. Multiple classes can be set on a single element.

Link to external post: http://www.cssnewbie.com/css-rules-multiplicity/

  • Good practice: Use the multiplicity rules instead of mixins (less css)
  • "Mixins are neat and all, but my preference is to avoid them in cases like this (when you want two classes of basically the same thing but only a slight difference, such as one floats left and one floats right). The code should be:
    
    .class1, .class2 {
       // All the common stuff here.
    }
    
    And then have the .class1 and .class2 rules define their own specific stuff (like their own floats and 'a' rules).
    
    The reason is that, while it's neat to be able to include data like this in lesscss, in the end it's really just turning into even more CSS code the browser has to load in and keep separate, and it's more work to debug. It's equivalent to copy/paste.
    
    Similarly, this situation (class inheritance) shouldn't use a mixin, as it brings in a lot of code. Instead, the elements using the various "child" classes should be also specifying "parent" as a class. More reuse, less debugging and less output."
    
                                                                      - ChipX86

We are using LESS for reviewboard, more about LESS: http://lesscss.org/