Repository: carpentries/amy

Issue:
Fails to return 404 at specific URLs
`workshops/person/example` should return 404, returns 500 instead.

Fix:

``` git
-    url(r'^person/(?P<person_id>[\w\.-]+)/?$', views.person_details, name='person_details'),
+    url(r'^person/(?P<person_id>\d+)/?$', views.person_details, name='person_details'),
```



Discussion:

Resolving patch:
diff --git a/workshops/urls.py b/workshops/urls.py
--- a/workshops/urls.py
+++ b/workshops/urls.py
@@ -1,4 +1,4 @@
-from django.conf.urls import url
+from django.conf.urls import url, include
 
 from workshops import views
 
@@ -9,98 +9,130 @@
 
     url(r'^log/$', views.changes_log, name='changes_log'),
 
-    url(r'^organizations/?$', views.all_organizations, name='all_organizations'),
-    url(r'^organization/(?P<org_domain>[\w\.-]+)/?$', views.organization_details, name='organization_details'),
-    url(r'^organization/(?P<org_domain>[\w\.-]+)/edit$', views.OrganizationUpdate.as_view(), name='organization_edit'),
-    url(r'^organization/(?P<org_domain>[\w\.-]+)/delete$', views.OrganizationDelete.as_view(), name='organization_delete'),
-    url(r'^organizations/add/$', views.OrganizationCreate.as_view(), name='organization_add'),
-
-    url(r'membership/(?P<org_domain>[\w\.-]+)/add', views.membership_create, name='membership_add'),
-    url(r'membership/(?P<membership_id>\d+)/edit', views.MembershipUpdate.as_view(), name='membership_edit'),
-    url(r'membership/(?P<membership_id>\d+)/delete', views.MembershipDelete.as_view(), name='membership_delete'),
-
-    url(r'^airports/?$', views.all_airports, name='all_airports'),
-    url(r'^airport/(?P<airport_iata>\w+)/?$', views.airport_details, name='airport_details'),
-    url(r'^airport/(?P<airport_iata>\w+)/edit$', views.AirportUpdate.as_view(), name='airport_edit'),
-    url(r'^airport/(?P<airport_iata>\w+)/delete$', views.AirportDelete.as_view(), name='airport_delete'),
-    url(r'^airports/add/$', views.AirportCreate.as_view(), name='airport_add'),
-
-    url(r'^persons/?$', views.all_persons, name='all_persons'),
-    url(r'^person/(?P<person_id>[\w\.-]+)/?$', views.person_details, name='person_details'),
-    url(r'^person/(?P<person_id>[\w\.-]+)/edit$', views.person_edit, name='person_edit'),
-    url(r'^person/(?P<person_id>\d+)/delete$', views.PersonDelete.as_view(), name='person_delete'),
-    url(r'^person/(?P<person_id>[\w\.-]+)/permissions$', views.PersonPermissions.as_view(), name='person_permissions'),
-    url(r'^person/(?P<person_id>[\w\.-]+)/password$', views.person_password, name='person_password'),
-    url(r'^persons/add/$', views.PersonCreate.as_view(), name='person_add'),
-    url(r'^persons/bulkadd/$',views.person_bulk_add, name='person_bulk_add'),
-    url(r'^persons/bulkadd/template',views.person_bulk_add_template, name='person_bulk_add_template'),
-    url(r'^persons/bulkadd/confirm$',views.person_bulk_add_confirmation, name='person_bulk_add_confirmation'),
-    url(r'^persons/merge/$', views.persons_merge, name='persons_merge'),
-    url(r'^persons/sync_usersocialauth/(?P<person_id>\d+)/?', views.sync_usersocialauth, name='sync_usersocialauth'),
-
-    url(r'^events/?$', views.all_events, name='all_events'),
-    url(r'^event/(?P<slug>[\w-]+)/?$', views.event_details, name='event_details'),
-    url(r'^event/(?P<slug>[\w-]+)/assign$', views.event_assign, name='event_assign'),
-    url(r'^event/(?P<slug>[\w-]+)/assign/(?P<person_id>[\w\.-]+)$', views.event_assign, name='event_assign'),
-    url(r'^event/(?P<slug>[\w-]+)/edit$', views.event_edit, name='event_edit'),
-    url(r'^event/(?P<slug>[\w-]+)/delete$', views.EventDelete.as_view(), name='event_delete'),
-    url(r'^events/add/$', views.EventCreate.as_view(), name='event_add'),
-    url(r'^event/(?P<slug>[\w-]+)/validate/?$', views.validate_event, name='validate_event'),
-    url(r'^events/metadata_changed/?$', views.events_metadata_changed, name='events_metadata_changed'),
-    url(r'^event/(?P<slug>[\w-]+)/review_metadata_changes/?$', views.event_review_metadata_changes, name='event_review_metadata_changes'),
-    url(r'^event/(?P<slug>[\w-]+)/review_metadata_changes/accept/?$', views.event_accept_metadata_changes, name='event_accept_metadata_changes'),
-    url(r'^event/(?P<slug>[\w-]+)/review_metadata_changes/dismiss/?$', views.event_dismiss_metadata_changes, name='event_dismiss_metadata_changes'),
-    url(r'^events/import/?$', views.event_import, name='event_import'),
-    url(r'^events/merge/?$', views.events_merge, name='events_merge'),
-
-    url(r'^event/(?P<slug>[\w-]+)/invoice$', views.event_invoice, name='event_invoice'),
-    url(r'^invoices/?$', views.AllInvoiceRequests.as_view(), name='all_invoicerequests'),
-    url(r'^invoice/(?P<request_id>\d+)/?$', views.InvoiceRequestDetails.as_view(), name='invoicerequest_details'),
-    url(r'^invoice/(?P<request_id>\d+)/edit$', views.InvoiceRequestUpdate.as_view(), name='invoicerequest_edit'),
-
-    url(r'^tasks/?$', views.all_tasks, name='all_tasks'),
-    url(r'^task/(?P<task_id>\d+)/?$', views.task_details, name='task_details'),
-    url(r'^task/(?P<task_id>\d+)/edit$', views.TaskUpdate.as_view(), name='task_edit'),
-    url(r'^task/(?P<task_id>\d+)/delete$', views.TaskDelete.as_view(), name='task_delete'),
-    url(r'^tasks/add/$', views.TaskCreate.as_view(), name='task_add'),
-
-    url(r'^sponsorship/(?P<pk>\d+)/delete$', views.SponsorshipDelete.as_view(), name='sponsorship_delete'),
-
-    url(r'^award/(?P<pk>\d+)/delete$', views.AwardDelete.as_view(), name='award_delete'),
-
-    url(r'^badges/?$', views.all_badges, name='all_badges'),
-    url(r'^badge/(?P<badge_name>[\w\.=-]+)/?$', views.badge_details, name='badge_details'),
-    url(r'^badge/(?P<badge_name>[\w\.=-]+)/award/?$', views.badge_award, name='badge_award'),
-
-    url(r'^trainings/?$', views.all_trainings, name='all_trainings'),
-
-    url(r'^workshop_staff/?$', views.workshop_staff, name='workshop_staff'),
-
-    url(r'^search/?$', views.search, name='search'),
-
-    url(r'^instructors_by_date/?$', views.instructors_by_date, name='instructors_by_date'),
-
-    url(r'^export/badges/?$', views.export_badges, name='export_badges'),
-    url(r'^export/instructors/?$', views.export_instructors, name='export_instructors'),
-    url(r'^export/members/?$', views.export_members, name='export_members'),
-
-    url(r'^reports/workshops_over_time/?$', views.workshops_over_time, name='workshops_over_time'),
-    url(r'^reports/learners_over_time/?$', views.learners_over_time, name='learners_over_time'),
-    url(r'^reports/instructors_over_time/?$', views.instructors_over_time, name='instructors_over_time'),
-    url(r'^reports/instructor_num_taught/?$', views.instructor_num_taught, name='instructor_num_taught'),
-    url(r'^reports/all_activity_over_time/?$', views.all_activity_over_time, name='all_activity_over_time'),
-    url(r'^reports/workshop_issues/?$', views.workshop_issues, name='workshop_issues'),
-    url(r'^reports/instructor_issues/?$', views.instructor_issues, name='instructor_issues'),
-    url(r'^reports/duplicates/?$', views.duplicates, name='duplicates'),
-
-    url(r'^revision/(?P<revision_id>[\d]+)/?$', views.object_changes, name='object_changes'),
+    url(r'^organizations/', include([
+        url(r'^$', views.all_organizations, name='all_organizations'),
+        url(r'^add/$', views.OrganizationCreate.as_view(), name='organization_add'),
+    ])),
+    url(r'^organization/(?P<org_domain>[\w\.-]+)/', include([
+        url(r'^$', views.organization_details, name='organization_details'),
+        url(r'^edit/$', views.OrganizationUpdate.as_view(), name='organization_edit'),
+        url(r'^delete/$', views.OrganizationDelete.as_view(), name='organization_delete'),
+        url(r'^membership/add/$', views.membership_create, name='membership_add'),
+    ])),
+
+    url(r'^membership/(?P<membership_id>\d+)/', include([
+        url(r'^edit/$', views.MembershipUpdate.as_view(), name='membership_edit'),
+        url(r'^delete/$', views.MembershipDelete.as_view(), name='membership_delete'),
+    ])),
+
+    url(r'^airports/', include([
+        url(r'^$', views.all_airports, name='all_airports'),
+        url(r'^add/$', views.AirportCreate.as_view(), name='airport_add'),
+    ])),
+    url(r'^airport/(?P<airport_iata>\w+)/', include([
+        url(r'^$', views.airport_details, name='airport_details'),
+        url(r'^edit/$', views.AirportUpdate.as_view(), name='airport_edit'),
+        url(r'^delete/$', views.AirportDelete.as_view(), name='airport_delete'),
+    ])),
+
+    url(r'^persons/', include([
+        url(r'^$', views.all_persons, name='all_persons'),
+        url(r'^add/$', views.PersonCreate.as_view(), name='person_add'),
+        url(r'^bulkadd/$',views.person_bulk_add, name='person_bulk_add'),
+        url(r'^bulkadd/template/$',views.person_bulk_add_template, name='person_bulk_add_template'),
+        url(r'^bulkadd/confirm/$',views.person_bulk_add_confirmation, name='person_bulk_add_confirmation'),
+        url(r'^merge/$', views.persons_merge, name='persons_merge'),
+    ])),
+    url(r'^person/(?P<person_id>\d+)/', include([
+        url(r'^$', views.person_details, name='person_details'),
+        url(r'^edit/$', views.person_edit, name='person_edit'),
+        url(r'^delete/$', views.PersonDelete.as_view(), name='person_delete'),
+        url(r'^permissions/$', views.PersonPermissions.as_view(), name='person_permissions'),
+        url(r'^password/$', views.person_password, name='person_password'),
+        url(r'^sync_usersocialauth/$', views.sync_usersocialauth, name='sync_usersocialauth'),
+    ])),
+
+    url(r'^events/', include([
+        url(r'^$', views.all_events, name='all_events'),
+        url(r'^add/$', views.EventCreate.as_view(), name='event_add'),
+        url(r'^import/$', views.event_import, name='event_import'),
+        url(r'^merge/$', views.events_merge, name='events_merge'),
+        url(r'^metadata_changed/$', views.events_metadata_changed, name='events_metadata_changed'),
+    ])),
+    url(r'^event/(?P<slug>[\w-]+)/', include([
+        url(r'^$', views.event_details, name='event_details'),
+        url(r'^assign/$', views.event_assign, name='event_assign'),
+        url(r'^assign/(?P<person_id>[\w\.-]+)/$', views.event_assign, name='event_assign'),
+        url(r'^edit/$', views.event_edit, name='event_edit'),
+        url(r'^delete/$', views.EventDelete.as_view(), name='event_delete'),
+        url(r'^validate/$', views.validate_event, name='validate_event'),
+        url(r'^review_metadata_changes/$', views.event_review_metadata_changes, name='event_review_metadata_changes'),
+        url(r'^review_metadata_changes/accept/$', views.event_accept_metadata_changes, name='event_accept_metadata_changes'),
+        url(r'^review_metadata_changes/dismiss/$', views.event_dismiss_metadata_changes, name='event_dismiss_metadata_changes'),
+        url(r'^invoice/$', views.event_invoice, name='event_invoice'),
+    ])),
+
+    url(r'^invoices/$', views.AllInvoiceRequests.as_view(), name='all_invoicerequests'),
+    url(r'^invoice/(?P<request_id>\d+)/', include([
+        url(r'^$', views.InvoiceRequestDetails.as_view(), name='invoicerequest_details'),
+        url(r'^edit/$', views.InvoiceRequestUpdate.as_view(), name='invoicerequest_edit'),
+    ])),
+
+    url(r'^tasks/', include([
+        url(r'^$', views.all_tasks, name='all_tasks'),
+        url(r'^add/$', views.TaskCreate.as_view(), name='task_add'),
+    ])),
+    url(r'^task/(?P<task_id>\d+)/', include([
+        url(r'^$', views.task_details, name='task_details'),
+        url(r'^edit/$', views.TaskUpdate.as_view(), name='task_edit'),
+        url(r'^delete/$', views.TaskDelete.as_view(), name='task_delete'),
+    ])),
+
+    url(r'^sponsorship/(?P<pk>\d+)/delete/$', views.SponsorshipDelete.as_view(), name='sponsorship_delete'),
+
+    url(r'^award/(?P<pk>\d+)/delete/$', views.AwardDelete.as_view(), name='award_delete'),
+
+    url(r'^badges/$', views.all_badges, name='all_badges'),
+    url(r'^badge/(?P<badge_name>[\w\.=-]+)/', include([
+        url(r'^$', views.badge_details, name='badge_details'),
+        url(r'^award/$', views.badge_award, name='badge_award'),
+    ])),
+
+    url(r'^trainings/$', views.all_trainings, name='all_trainings'),
+
+    url(r'^workshop_staff/$', views.workshop_staff, name='workshop_staff'),
+
+    url(r'^search/$', views.search, name='search'),
+
+    url(r'^instructors_by_date/$', views.instructors_by_date, name='instructors_by_date'),
+
+    url(r'^export/', include([
+        url(r'^badges/$', views.export_badges, name='export_badges'),
+        url(r'^instructors/$', views.export_instructors, name='export_instructors'),
+        url(r'^members/$', views.export_members, name='export_members'),
+    ])),
+
+    url(r'^reports/', include([
+        url(r'^workshops_over_time/$', views.workshops_over_time, name='workshops_over_time'),
+        url(r'^learners_over_time/$', views.learners_over_time, name='learners_over_time'),
+        url(r'^instructors_over_time/$', views.instructors_over_time, name='instructors_over_time'),
+        url(r'^instructor_num_taught/$', views.instructor_num_taught, name='instructor_num_taught'),
+        url(r'^all_activity_over_time/$', views.all_activity_over_time, name='all_activity_over_time'),
+        url(r'^workshop_issues/$', views.workshop_issues, name='workshop_issues'),
+        url(r'^instructor_issues/$', views.instructor_issues, name='instructor_issues'),
+        url(r'^duplicates/$', views.duplicates, name='duplicates'),
+    ])),
+
+    url(r'^revision/(?P<revision_id>[\d]+)/$', views.object_changes, name='object_changes'),
 
     url(r'^requests/$', views.all_eventrequests, name='all_eventrequests'),
-    url(r'^request/(?P<request_id>\d+)/?$', views.EventRequestDetails.as_view(), name='eventrequest_details'),
-    url(r'^request/(?P<request_id>\d+)/discard/?$', views.eventrequest_discard, name='eventrequest_discard'),
-    url(r'^request/(?P<request_id>\d+)/accept/?$', views.eventrequest_accept, name='eventrequest_accept'),
-    url(r'^request/(?P<request_id>\d+)/assign$', views.eventrequest_assign, name='eventrequest_assign'),
-    url(r'^request/(?P<request_id>\d+)/assign/(?P<person_id>[\w\.-]+)$', views.eventrequest_assign, name='eventrequest_assign'),
+    url(r'^request/(?P<request_id>\d+)/', include([
+        url(r'^$', views.EventRequestDetails.as_view(), name='eventrequest_details'),
+        url(r'^discard/$', views.eventrequest_discard, name='eventrequest_discard'),
+        url(r'^accept/$', views.eventrequest_accept, name='eventrequest_accept'),
+        url(r'^assign/$', views.eventrequest_assign, name='eventrequest_assign'),
+        url(r'^assign/(?P<person_id>[\w\.-]+)/$', views.eventrequest_assign, name='eventrequest_assign'),
+    ])),
     url(r'^swc/request/$', views.SWCEventRequest.as_view(), name='swc_workshop_request'),
     url(r'^swc/request/confirm/$', views.SWCEventRequestConfirm.as_view(), name='swc_workshop_request_confirm'),
     url(r'^dc/request/$', views.DCEventRequest.as_view(), name='dc_workshop_request'),
@@ -108,46 +140,59 @@
     url(r'^dc/request_selforganized/$', views.DCSelfOrganizedEventRequest.as_view(), name='dc_workshop_selforganized_request'),
     url(r'^dc/request_selforganized/confirm/$', views.DCSelfOrganizedEventRequestConfirm.as_view(), name='dc_workshop_selforganized_request_confirm'),
     url(r'^dc_selforganized_requests/$', views.AllDCSelfOrganizedEventRequests.as_view(), name='all_dcselforganizedeventrequests'),
-    url(r'^dc_selforganized_request/(?P<request_id>\d+)/?$', views.DCSelfOrganizedEventRequestDetails.as_view(), name='dcselforganizedeventrequest_details'),
-    url(r'^dc_selforganized_request/(?P<request_id>\d+)/edit$', views.DCSelfOrganizedEventRequestChange.as_view(), name='dcselforganizedeventrequest_edit'),
-    url(r'^dc_selforganized_request/(?P<request_id>\d+)/assign$', views.dcselforganizedeventrequest_assign, name='dcselforganizedeventrequest_assign'),
-    url(r'^dc_selforganized_request/(?P<request_id>\d+)/assign/(?P<person_id>[\w\.-]+)$', views.dcselforganizedeventrequest_assign, name='dcselforganizedeventrequest_assign'),
+    url(r'^dc_selforganized_request/(?P<request_id>\d+)/', include([
+        url(r'^$', views.DCSelfOrganizedEventRequestDetails.as_view(), name='dcselforganizedeventrequest_details'),
+        url(r'^edit/$', views.DCSelfOrganizedEventRequestChange.as_view(), name='dcselforganizedeventrequest_edit'),
+        url(r'^assign/$', views.dcselforganizedeventrequest_assign, name='dcselforganizedeventrequest_assign'),
+        url(r'^assign/(?P<person_id>[\w\.-]+)/$', views.dcselforganizedeventrequest_assign, name='dcselforganizedeventrequest_assign'),
+    ])),
 
     url(r'^submissions/$', views.AllEventSubmissions.as_view(), name='all_eventsubmissions'),
-    url(r'^submission/(?P<submission_id>\d+)/?$', views.EventSubmissionDetails.as_view(), name='eventsubmission_details'),
-    url(r'^submission/(?P<submission_id>\d+)/fix/?$', views.EventSubmissionFix.as_view(), name='eventsubmission_fix'),
-    url(r'^submission/(?P<submission_id>\d+)/discard/?$', views.eventsubmission_discard, name='eventsubmission_discard'),
-    url(r'^submission/(?P<submission_id>\d+)/accept/?$', views.eventsubmission_accept, name='eventsubmission_accept'),
-    url(r'^submission/(?P<submission_id>\d+)/assign$', views.eventsubmission_assign, name='eventsubmission_assign'),
-    url(r'^submission/(?P<submission_id>\d+)/assign/(?P<person_id>[\w\.-]+)$', views.eventsubmission_assign, name='eventsubmission_assign'),
+    url(r'^submission/(?P<submission_id>\d+)/', include([
+        url(r'^$', views.EventSubmissionDetails.as_view(), name='eventsubmission_details'),
+        url(r'^fix/$', views.EventSubmissionFix.as_view(), name='eventsubmission_fix'),
+        url(r'^discard/$', views.eventsubmission_discard, name='eventsubmission_discard'),
+        url(r'^accept/$', views.eventsubmission_accept, name='eventsubmission_accept'),
+        url(r'^assign/$', views.eventsubmission_assign, name='eventsubmission_assign'),
+        url(r'^assign/(?P<person_id>[\w\.-]+)/$', views.eventsubmission_assign, name='eventsubmission_assign'),
+    ])),
     url(r'^submit/$', views.EventSubmission.as_view(), name='event_submit'),
     url(r'^submit/confirm/$', views.EventSubmissionConfirm.as_view(), name='event_submission_confirm'),
 
     url(r'^profile_updates/$', views.AllProfileUpdateRequests.as_view(), name='all_profileupdaterequests'),
     url(r'^profile_updates/closed/$', views.AllClosedProfileUpdateRequests.as_view(), name='all_closed_profileupdaterequests'),
-    url(r'^profile_update/(?P<request_id>\d+)/?$', views.profileupdaterequest_details, name='profileupdaterequest_details'),
-    url(r'^profile_update/(?P<request_id>\d+)/fix/?$', views.ProfileUpdateRequestFix.as_view(), name='profileupdaterequest_fix'),
-    url(r'^profile_update/(?P<request_id>\d+)/discard/?$', views.profileupdaterequest_discard, name='profileupdaterequest_discard'),
-    url(r'^profile_update/(?P<request_id>\d+)/accept/?$', views.profileupdaterequest_accept, name='profileupdaterequest_accept'),
-    url(r'^profile_update/(?P<request_id>\d+)/accept/(?P<person_id>[\w\.-]+)/?$', views.profileupdaterequest_accept, name='profileupdaterequest_accept'),
+    url(r'^profile_update/(?P<request_id>\d+)/', include([
+        url(r'^$', views.profileupdaterequest_details, name='profileupdaterequest_details'),
+        url(r'^fix/$', views.ProfileUpdateRequestFix.as_view(), name='profileupdaterequest_fix'),
+        url(r'^discard/$', views.profileupdaterequest_discard, name='profileupdaterequest_discard'),
+        url(r'^accept/$', views.profileupdaterequest_accept, name='profileupdaterequest_accept'),
+        url(r'^accept/(?P<person_id>[\w\.-]+)/$', views.profileupdaterequest_accept, name='profileupdaterequest_accept'),
+    ])),
     url(r'^update_profile/$', views.profileupdaterequest_create, name='profileupdate_request'),
     url(r'^autoupdate_profile/$', views.autoupdate_profile, name='autoupdate_profile'),
 
     url(r'^request_training/$', views.trainingrequest_create, name='training_request'),
     url(r'^training_requests/$', views.all_trainingrequests, name='all_trainingrequests'),
     url(r'^training_requests/csv/$', views.download_trainingrequests, name='download_trainingrequests'),
-    url(r'^training_request/(?P<request_id>\d+)/?$', views.trainingrequest_details, name='trainingrequest_details'),
-    url(r'^training_request/(?P<pk>\d+)/edit/??$', views.TrainingRequestUpdate.as_view(), name='trainingrequest_edit'),
-
-    url(r'^trainees/?$', views.all_trainees, name='all_trainees'),
-
-    url(r'^training_progress/(?P<pk>\d+)/edit$', views.TrainingProgressUpdate.as_view(), name='trainingprogress_edit'),
-    url(r'^training_progress/(?P<pk>\d+)/delete$', views.TrainingProgressDelete.as_view(), name='trainingprogress_delete'),
-    url(r'^training_progresses/add/(?P<trainee_id>\d+)?$', views.TrainingProgressCreate.as_view(), name='trainingprogress_add'),
-
-    url(r'^todos/(?P<slug>[\w-]+)/add$', views.todos_add, name='todos_add'),
-    url(r'^todo/(?P<todo_id>\d+)/completed', views.todo_mark_completed, name='todo_mark_completed'),
-    url(r'^todo/(?P<todo_id>\d+)/incompleted', views.todo_mark_incompleted, name='todo_mark_incompleted'),
-    url(r'^todo/(?P<todo_id>\d+)/edit', views.TodoItemUpdate.as_view(), name='todo_edit'),
-    url(r'^todo/(?P<todo_id>\d+)/delete', views.TodoDelete.as_view(), name='todo_delete'),
+    url(r'^training_request/(?P<pk>\d+)/', include([
+        url(r'^$', views.trainingrequest_details, name='trainingrequest_details'),
+        url(r'^edit/$', views.TrainingRequestUpdate.as_view(), name='trainingrequest_edit'),
+    ])),
+
+    url(r'^trainees/$', views.all_trainees, name='all_trainees'),
+
+    url(r'^training_progresses/add/$', views.TrainingProgressCreate.as_view(), name='trainingprogress_add'),
+    url(r'^training_progresses/add/(?P<trainee_id>\d+)/$', views.TrainingProgressCreate.as_view(), name='trainingprogress_add'),
+    url(r'^training_progress/(?P<pk>\d+)/', include([
+        url(r'^edit/$', views.TrainingProgressUpdate.as_view(), name='trainingprogress_edit'),
+        url(r'^delete/$', views.TrainingProgressDelete.as_view(), name='trainingprogress_delete'),
+    ])),
+
+    url(r'^todos/(?P<slug>[\w-]+)/add/$', views.todos_add, name='todos_add'),
+    url(r'^todo/(?P<todo_id>\d+)/', include([
+        url(r'^completed/$', views.todo_mark_completed, name='todo_mark_completed'),
+        url(r'^incompleted/$', views.todo_mark_incompleted, name='todo_mark_incompleted'),
+        url(r'^edit/$', views.TodoItemUpdate.as_view(), name='todo_edit'),
+        url(r'^delete/$', views.TodoDelete.as_view(), name='todo_delete'),
+    ])),
 ]
diff --git a/workshops/views.py b/workshops/views.py
--- a/workshops/views.py
+++ b/workshops/views.py
@@ -3312,8 +3312,8 @@ def _accept_training_request(form, training_request, request):
 
 
 @admin_required
-def trainingrequest_details(request, request_id):
-    req = get_object_or_404(TrainingRequest, pk=int(request_id))
+def trainingrequest_details(request, pk):
+    req = get_object_or_404(TrainingRequest, pk=int(pk))
 
     if request.method == 'POST':
         form = AcceptTrainingRequestForm(request.POST)


Test patch:
diff --git a/workshops/test/test_event.py b/workshops/test/test_event.py
--- a/workshops/test/test_event.py
+++ b/workshops/test/test_event.py
@@ -323,7 +323,7 @@ def test_add_minimal_event(self):
             })
         if response.status_code == 302:
             url = response['location']
-            event_slug = url.rsplit('/', 1)[1]
+            event_slug = url.rstrip('/').rsplit('/', 1)[1]
             event = Event.objects.get(slug=event_slug)
             assert event.host == host, (
                 'New event has wrong host: {} != {}'.format(event.host, host))