djangodjango-webtest

NoReverseError in development environment but not in test


I have a view--called DevicesListView below--that executes without a problem in the test environment (in Webtest), but when I try to execute the same view in the development environment, I get a NoReverseMatch error.

devices/urls.py

from inventory.devices.views import DeviceCheckout
urlpatterns = patterns('',
    # NOTE: these are namespaced as 'devices'

    # ex: /devices
    url(r'^$', 
        DevicesListView.as_view(),
        name='index'),

    # ex: /devices/3/checkout
    url(r'^(?P<pk>\d)/checkout/$',
        DeviceCheckout.as_view(),
        name='checkout'),
)

views.py

class DevicesListView(ListView):
    '''Index view for devices.'''
    model = Device
    template_name = 'devices/index.html'
    context_object_name = 'all_devices'
    def get(self, request):
        #  ... 
    def post(self, request):
        """Process the action for the selected devices.
        """
        action = request.POST['action']  # the selected action
        # Get the IDs of the devices that had their checkboxes selected
        selected_pks = [int(v) for v in request.POST.getlist('device_select')]
        # Get the selected Device objects
        selected_devices = Device.objects.filter(pk__in=selected_pks)
        # ... Logic to handle selected action ...
        # redirect to lendee selection page
        device = selected_devices[0]
        if action == 'checkout_selected':
            return redirect('devices:checkout', pk=device.pk)  # throws NoReverseMatch in development
        elif action == 'checkin_selected':
            return redirect('devices:checkin', pk=device.pk)
        return redirect('devices:index')

webtest_tests.py

from django_webtest import WebTest
from inventory.user.tests.factories import UserFactory
from inventory.devices.tests.factories import DeviceFactory
class TestAUser(WebTest):
    def setUp(self):
        self.user = UserFactory()

    def test_can_checkout_device(self):
        # two devices are already created
        DeviceFactory()
        DeviceFactory()
        # goes to devices page
        res = self.app.get('/devices', user=self.user)
        # checks the first device
        form = res.forms['device_control']
        form.set('device_select', True, index=0)
        # Selects Checkout device
        form.set('action', 'checkout_selected')
        # Submits form
        res = form.submit().follow()
        assert_equal(res.status_code, 200) # PASSES

devices/index.html

  <form method="post" id="device_control" action="">
    {% csrf_token %}
    <label><span class="action-label">Action:</span>
      <select name="action">
        <option value="" selected="selected">---------</option>
        <option value="checkout_selected">Checkout</option>
        <option value="checkin_selected">Checkin</option>
        <option value="delete_selected">Delete selected</option>
      </select>
    </label>
    <input type="submit" name="_save" class="default btn" value="Go"/>
  </div><!-- end btn-toolbar -->
  <table class='table table-striped'>
    <thead>
      <th>Select</th>
      <th>Name</th>
    </thead>
    <tbody>
    {% for device in all_devices %}
      <tr>   
        <td><input type="checkbox" value="{{ device.pk }}" name='device_select' id="select_device{{ device.pk }}"></td>
        <td>{{ device.name }}</td>
      </tr>
    {% endfor %}
    </tbody>
  </table>
</form>

The template error I get is:

NoReverseMatch at /devices/
Reverse for 'checkout' with arguments '()' and keyword arguments '{'pk': 13}' not found.

I have made sure to syncdb and restart the development server. What would cause this different behavior between the test and development environments? I am using Django 1.5 on Mac OSX Lion.


Solution

  • A silly mistake indeed. The problem was in the url pattern regex. r'^(?P<pk>\d)/checkout/$' was only capturing 1-digit pk's. It was missing a + after \d. So it should be:

    ...
    # ex: /devices/13/checkout
    url(r'^(?P<pk>\d+)/checkout/$',
        DeviceCheckout.as_view(),
        name='checkout'),
    ...