This time around, I will try to take the logical next step with my exploration of REST and Django, and begin creating a Django contribution to implement the ideas I have been working on and to do so in a way that fits with Django’s philosophy. If you would like to get caught up, here are the previous posts in this series:
- Implementing Tagging in a Django Application
- Toward a RESTful Approach to Django Applications
- Django REST Redux
test driving a django component
Up to now, as I was blindly feeling my way around Django and REST, I haven’t been using a test driven approach, instead relying on Django’s development server and manual testing to try out these ideas. (After all, I originally thought I would only be tweaking a few URL patterns…) But now that I am trying to make a generalized Django contribution, I decided get my act together and use test driven development (TDD) to recode it the right way from the ground up. Exploring how to do TDD more fully in Django is another area I wanted to explore in this series anyway.
I decided to use standard unit tests mainly because I am familiar with JUnit clones like the standard Python unittest module, and I thought I would start out gently with my testing approach. I know there are a significant number of third party testing tools for Python that in various ways build upon or replace unittest with more functionality, and you can start using them with Django by consultng this documentation. However, I think there’s also something to be said for using the tools in the standard library, and by default, Django uses both unit tests or doc tests.
I will need to revisit testing a normal Django application in a future post, but I suspect that I will be using doc tests quite liberally, not only because it’s part of the standard library and Django supports it by default, but also because its a new approach for me, and that’s the running theme of this series. Also, I really like the idea that the tests will serve double-duty as API documentation and live close to the relevant code. Finally, it seems that much of coding a Django application involves gluing together existing components that are either part of the default framework or added on modules, so it seems like doc tests might be a more natural fit.
As for the RESTful contribution itself, it took some time for me to wrap my head around how to test this functionality outside a normal Django application, expecially the RESTful URL creation aspect. I couldn’t seem to find much in the way of tests for many of the standard contributed middleware applications in Django. Ironically, the approach I settled into is basically what I have written about before and involved essentially running a canned version of some of the Django machinery, configuring an in-memory database, then exercising the methods I needed.
So, testing middleware and other contributions seems to not really be so different from testing a normal Django application, although it may seem a little odd at first. In this case, because I would be using model subclasses, I would necessarily be firing up parts of Django behind the scenes that expect to find certain things in certain places, just like in a normal Django application. So, I replicated a few aspects of a normal Django environment to make Django happy and have access to the parts of Django I need.
So, I began to set up the contribution’s home directory, called ‘restful_model_views’ and figuring that it would eventually land inside django.contrib, I started a new PyDev project in Eclipse and created a symbolic link from django.contrib.restful_model_views to the relevant workspace folder. Because the django modules are themselves linked to my Python site-packages directory, I should now be able to reference the RESTful classes easily and avoid any nasty classpath issues.
All non-test classes are at the django.contrib.restful_model_views level. Also at that level is a ‘tests’ module, containing all the unit testing code, including a ‘test_app’ directory with my model and view modules, which will look like an expected Django application to my tests. To configure the canned Django session, I used this code in the tests from a module called InMemoryEnvironment:
import sysimport osdef configure(appName): try: # Change Settings from django.conf import settings, global_settings RESTFUL_APPS = (appName,) ROOT_URLCONF = appName + ‘.test_urls’ settings.configure(default_settings=global_settings, RESTFUL_APPS = RESTFUL_APPS, INSTALLED_APPS = RESTFUL_APPS, DATABASE_NAME=’:memory:’, DATABASE_ENGINE=’sqlite3′, ROOT_URLCONF = ROOT_URLCONF ) # Reload DB module from django import db reload(db) # Install Models from django.core import management # disable the “create a super user” question from django.contrib.auth.management import create_superuser from django.contrib.auth import models as auth_app from django.db.models import signals from django.dispatch import dispatcher dispatcher.disconnect(create_superuser, sender=auth_app, signal=signals.post_syncdb) management.syncdb() except Exception, e: print e
And then call it in the setUp method of the test suite before importing any model or view classes:
configure(‘django.contrib.restful_model_views.tests.test_app’)
Otherwise, the code looks largely the way it did before, except that because of the TDD approach, its more modular, and I think more comprehensible. Additionally:
- I incorporated the suggestions from last week’s post.
- I moved the ResourceDispatcher class out into its own module.
- I decided to initialize Resources and Resources_Member the same way, to be more consistent.
- Because Resources and Resources_Member are being called in fundamentally different ways, this is reflected in the code.
Beyond the simple test views I have already written, I still haven’t populated the default generic views, and when I do, I will explore the Test Client approach to testing these, as described in the Django testing documentation.
what’s next
The complete code for this contribution is available here under the GPL license. I have some basic installation and use instructions there as well.
To make this code available, I created my first project in Google Code, and for the first time, I have used Subversion to manage a project. Yes, I still use CVS and up to now, my knowledge of Subversion has been limited to using the checkout command for various projects like Django. Because I use Eclipse, its CVS integration takes away most of the painful points of CVS, but at the same time, the Subclipse plug-in works largely the same as the CVS plug-in so switching was also pretty painless.
I should point out that although I originally thought this contribution would be installed as middleware, I have stopped short of taking that step, at least for now, because I am not sure that is the correct fit for this code. Basically, middleware components registered for a site can define up to four methods to inject themselves into various stages in the request/response processing cycle, and because the bulk of what I am doing is generating urlpatterns that point to existing views, I don’t see a need to implement this approach as middleware per se.
I haven’t completely dismissed the idea either, however, as there are still things I want to do with the contribution, and at this point, it’s unclear if implementing the middleware methods can help me achieve those goals. Ideally, the minimum requirement for getting this contribution to work once installed would be creating the RESTFUL_APPS setting in settings.py. So, on the one hand, I would like to be able to use the default view functionality in the super class Resources without requiring code that explicitly imports and overrides this class. This could involve somehow dynamically adding Resources under the views at runtime.
On the other hand, it would also be nice not to have to inject any code into urls.py, but just automatically add the RESTful patterns somehow. A few months back, a change was made to Django to allow access to the ROOT_URLCONF setting through the request object so that middleware can override that setting. However, in my current approach, I want to silently slip additional patterns to be matched into the mix, not clobber the patterns already there. I suppose I may also find some other way to identify specific RESTful requests later on in the processing cycle and switch in the appropriate view before it gets called, essentially moving the ResourceDispatcher functionality into the middleware layer from the view layer where it currently resides.
resources
- source code for this post
- Django RESTful Model Views Contribution
- Django | Documentation | Testing Django applications
- Django | Documentation | Middleware
- How Django processes a request from The B-List
- Django source code, Changeset 4237 (“Django will now look for the root URLconf as an attribute of the request object, if available. This lets middleware override the urlconf as needed.”)