Repository: carpentries/amy
Issue: Can't assign person to something when lookup fails

This is direct cause for this error:

```
Internal Server Error: /workshops/request/65/assign
Traceback (most recent call last):
  File "/home/amy/amy_site/venv/lib/python3.4/site-packages/django/core/handlers/base.py", line 132, in get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/amy/amy_site/venv/lib/python3.4/site-packages/django/contrib/auth/decorators.py", line 22, in _wrapped_view
    return view_func(request, *args, **kwargs)
  File "/home/amy/amy_site/venv/lib/python3.4/site-packages/django/contrib/auth/decorators.py", line 22, in _wrapped_view
    return view_func(request, *args, **kwargs)
  File "./workshops/views.py", line 1989, in eventrequest_assign
    assign(request, event_req, person_id)
  File "./workshops/util.py", line 737, in assign
    person = Person.objects.get(pk=person_id)
  File "/home/amy/amy_site/venv/lib/python3.4/site-packages/django/db/models/manager.py", line 127, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/home/amy/amy_site/venv/lib/python3.4/site-packages/django/db/models/query.py", line 325, in get
    clone = self.filter(*args, **kwargs)
  File "/home/amy/amy_site/venv/lib/python3.4/site-packages/django/db/models/query.py", line 679, in filter
    return self._filter_or_exclude(False, *args, **kwargs)
  File "/home/amy/amy_site/venv/lib/python3.4/site-packages/django/db/models/query.py", line 697, in _filter_or_exclude
    clone.query.add_q(Q(*args, **kwargs))
  File "/home/amy/amy_site/venv/lib/python3.4/site-packages/django/db/models/sql/query.py", line 1310, in add_q
    clause, require_inner = self._add_q(where_part, self.used_aliases)
  File "/home/amy/amy_site/venv/lib/python3.4/site-packages/django/db/models/sql/query.py", line 1338, in _add_q
    allow_joins=allow_joins, split_subq=split_subq,
  File "/home/amy/amy_site/venv/lib/python3.4/site-packages/django/db/models/sql/query.py", line 1209, in build_filter
    condition = self.build_lookup(lookups, col, value)
  File "/home/amy/amy_site/venv/lib/python3.4/site-packages/django/db/models/sql/query.py", line 1102, in build_lookup
    return final_lookup(lhs, rhs)
  File "/home/amy/amy_site/venv/lib/python3.4/site-packages/django/db/models/lookups.py", line 101, in __init__
    self.rhs = self.get_prep_lookup()
  File "/home/amy/amy_site/venv/lib/python3.4/site-packages/django/db/models/lookups.py", line 139, in get_prep_lookup
    return self.lhs.output_field.get_prep_lookup(self.lookup_name, self.rhs)
  File "/home/amy/amy_site/venv/lib/python3.4/site-packages/django/db/models/fields/__init__.py", line 727, in get_prep_lookup
    return self.get_prep_value(value)
  File "/home/amy/amy_site/venv/lib/python3.4/site-packages/django/db/models/fields/__init__.py", line 985, in get_prep_value
    return int(value)
ValueError: invalid literal for int() with base 10: ''
```

There needs to be a "get-or-404" mechanism in `workshops.util.assign` - it will prevent this error.


Changed files:
workshops/util.py, workshops/test/test_util.py

diff --git a/workshops/test/test_util.py b/workshops/test/test_util.py
index d4e33f88..ad4d0637 100644
--- a/workshops/test/test_util.py
+++ b/workshops/test/test_util.py
@@ -5,8 +5,9 @@ from io import StringIO
 
 from django.contrib.auth.models import Group
 from django.contrib.sessions.serializers import JSONSerializer
-from django.test import TestCase, RequestFactory
 from django.core.urlresolvers import reverse
+from django.http import Http404
+from django.test import TestCase, RequestFactory
 
 from ..models import Host, Event, Role, Person, Task, Badge, Award
 from ..util import (
@@ -23,6 +24,7 @@ from ..util import (
     create_username,
     InternalError,
     Paginator,
+    assign,
 )
 
 from .base import TestBase
@@ -1008,3 +1010,62 @@ class TestPaginatorSections(TestCase):
             # None is a break, it appears as '...' in the paginator widget
             [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
         )
+
+
+class TestAssignUtil(TestCase):
+    def setUp(self):
+        """Set up RequestFactory for making fast fake requests."""
+        Person.objects.create_user('test_user', 'User', 'Test', 'user@test')
+        self.factory = RequestFactory()
+        self.event = Event.objects.create(
+            slug='event-for-assignment', host=Host.objects.first())
+
+    def test_no_integer_pk(self):
+        """Ensure we fail with 404 when person PK is string, not integer."""
+        tests = [
+            (self.factory.get('/'), 'alpha'),
+            (self.factory.post('/', {'person_1': 'alpha'}), None),
+        ]
+        for request, person_id in tests:
+            with self.subTest(method=request.method):
+                with self.assertRaises(Http404):
+                    assign(request, self.event, person_id=person_id)
+
+                # just reset the link, for safety sake
+                self.event.assigned_to = None
+                self.event.save()
+
+    def test_assigning(self):
+        """Ensure that with assignment is set correctly."""
+        first_person = Person.objects.first()
+        tests = [
+            (self.factory.get('/'), first_person.pk),
+            (self.factory.post('/', {'person_1': first_person.pk}), None),
+        ]
+        for request, person_id in tests:
+            with self.subTest(method=request.method):
+                # just reset the link, for safety sake
+                self.event.assigned_to = None
+                self.event.save()
+
+                assign(request, self.event, person_id=person_id)
+                self.event.refresh_from_db()
+                self.assertEqual(self.event.assigned_to, first_person)
+
+    def test_removing_assignment(self):
+        """Ensure that with person_id=None, the assignment is removed."""
+        first_person = Person.objects.first()
+        tests = [
+            (self.factory.get('/'), None),
+            (self.factory.post('/'), None),
+        ]
+        for request, person_id in tests:
+            with self.subTest(method=request.method):
+                # just re-set the link to first person, for safety sake
+                self.event.assigned_to = first_person
+                self.event.save()
+
+                assign(request, self.event, person_id=person_id)
+
+                self.event.refresh_from_db()
+                self.assertEqual(self.event.assigned_to, None)
diff --git a/workshops/util.py b/workshops/util.py
index 3aee0556..ad5d1d7d 100644
--- a/workshops/util.py
+++ b/workshops/util.py
@@ -762,5 +762,5 @@ def assign(request, obj, person_id):
 
         obj.save()
 
-    except Person.DoesNotExist:
+    except (Person.DoesNotExist, ValueError):
         raise Http404("No person found matching the query.")