Repository: codingjoe/joeflow

Issue:
ValueError: Field 'id' expected a number but got a string
The following error occurs if someone tries to access a URL workflow detail view with a non integer primary key in the URL.
We should adjust the URL pattern to only accept integers to avoid this error and raise the expected 404 response.

```
ValueError: invalid literal for int() with base 10: 'some-string'
  File "django/db/models/fields/__init__.py", line 1774, in get_prep_value
    return int(value)
ValueError: Field 'id' expected a number but got 'some-string'.
  File "django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "newrelic/hooks/framework_django.py", line 554, in wrapper
    return wrapped(*args, **kwargs)
  File "django/views/generic/base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "newrelic/hooks/framework_django.py", line 944, in wrapper
    return wrapped(*args, **kwargs)
  File "django/views/generic/base.py", line 98, in dispatch
    return handler(request, *args, **kwargs)
  File "django/views/generic/detail.py", line 106, in get
    self.object = self.get_object()
  File "django/views/generic/detail.py", line 36, in get_object
    queryset = queryset.filter(pk=pk)
  File "django/db/models/query.py", line 942, in filter
    return self._filter_or_exclude(False, *args, **kwargs)
  File "django/db/models/query.py", line 962, in _filter_or_exclude
    clone._filter_or_exclude_inplace(negate, *args, **kwargs)
  File "django/db/models/query.py", line 969, in _filter_or_exclude_inplace
    self._query.add_q(Q(*args, **kwargs))
  File "django/db/models/sql/query.py", line 1358, in add_q
    clause, _ = self._add_q(q_object, self.used_aliases)
  File "django/db/models/sql/query.py", line 1377, in _add_q
    child_clause, needed_inner = self.build_filter(
  File "django/db/models/sql/query.py", line 1319, in build_filter
    condition = self.build_lookup(lookups, col, value)
  File "django/db/models/sql/query.py", line 1165, in build_lookup
    lookup = lookup_class(lhs, rhs)
  File "django/db/models/lookups.py", line 24, in __init__
    self.rhs = self.get_prep_lookup()
  File "django/db/models/fields/related_lookups.py", line 117, in get_prep_lookup
    self.rhs = target_field.get_prep_value(self.rhs)
  File "django/db/models/fields/__init__.py", line 1776, in get_prep_value
    raise e.__class__(
```

Comments:

Patch:
diff --git a/.bandit b/.bandit
index fba0d25..b7df176 100644
--- a/.bandit
+++ b/.bandit
@@ -1,2 +1,2 @@
 [bandit]
-exclude: tests
+exclude: ./tests
diff --git a/joeflow/models.py b/joeflow/models.py
index df067fa..266091f 100644
--- a/joeflow/models.py
+++ b/joeflow/models.py
@@ -137,7 +137,7 @@ class Workflow(models.Model, metaclass=WorkflowBase):
                 if isinstance(node, BaseCreateView):
                     route = "{name}/".format(name=name)
                 else:
-                    route = "{name}/<pk>/".format(name=name)
+                    route = "{name}/<int:pk>/".format(name=name)
                 urls.append(
                     path(
                         route + node.path,
@@ -147,12 +147,12 @@ class Workflow(models.Model, metaclass=WorkflowBase):
                 )
         if cls.detail_view:
             urls.append(
-                path("<pk>/", cls.detail_view.as_view(model=cls), name="detail")
+                path("<int:pk>/", cls.detail_view.as_view(model=cls), name="detail")
             )
         if cls.override_view:
             urls.append(
                 path(
-                    "<pk>/override",
+                    "<int:pk>/override",
                     cls.override_view.as_view(model=cls),
                     name="override",
                 )


Test patch:
diff --git a/tests/test_models.py b/tests/test_models.py
index dc82286..4ecad08 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -249,6 +249,10 @@ class TestWorkflow:
             "override",
         }
 
+    def test_urls__none_int_pk_mismatch(self, client):
+        response = client.get("/shipment/test/")
+        assert response.status_code == 404
+
 
 class TestTaskQuerySet:
     def test_scheduled(self, db):