0

I'm using Django 3 and Python 3.7. I have a model (MySql 8 backed table) that has integer primary keys. I have code that searches for such models like so

state = State.objects.get(pk=locality['state'])

The issue is if "locality['state']" contains an empty string, I get the below error

Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/django/db/models/fields/__init__.py", line 1768, in get_prep_value
    return int(value)
ValueError: invalid literal for int() with base 10: ''

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/davea/Documents/workspace/chicommons/maps/web/tests/test_serializers.py", line 132, in test_coop_create_with_incomplete_data
    assert not serializer.is_valid(), serializer.errors
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/rest_framework/serializers.py", line 234, in is_valid
    self._validated_data = self.run_validation(self.initial_data)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/rest_framework/serializers.py", line 433, in run_validation
    value = self.to_internal_value(data)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/rest_framework/serializers.py", line 490, in to_internal_value
    validated_value = field.run_validation(primitive_value)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/rest_framework/fields.py", line 565, in run_validation
    value = self.to_internal_value(data)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/rest_framework/relations.py", line 519, in to_internal_value
    return [
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/rest_framework/relations.py", line 520, in <listcomp>
    self.child_relation.to_internal_value(item)
  File "/Users/davea/Documents/workspace/chicommons/maps/web/directory/serializers.py", line 26, in to_internal_value
    state = State.objects.get(pk=locality['state'])
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/django/db/models/query.py", line 404, in get
    clone = self._chain() if self.query.combinator else self.filter(*args, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/django/db/models/query.py", line 904, in filter
    return self._filter_or_exclude(False, *args, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/django/db/models/query.py", line 923, in _filter_or_exclude
    clone.query.add_q(Q(*args, **kwargs))
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1337, in add_q
    clause, _ = self._add_q(q_object, self.used_aliases)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1362, in _add_q
    child_clause, needed_inner = self.build_filter(
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1298, in build_filter
    condition = self.build_lookup(lookups, col, value)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/django/db/models/sql/query.py", line 1155, in build_lookup
    lookup = lookup_class(lhs, rhs)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/django/db/models/lookups.py", line 22, in __init__
    self.rhs = self.get_prep_lookup()
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/django/db/models/lookups.py", line 72, in get_prep_lookup
    return self.lhs.output_field.get_prep_value(self.rhs)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/django/db/models/fields/__init__.py", line 1770, in get_prep_value
    raise e.__class__(
ValueError: Field 'id' expected a number but got ''.

Is there a more "Django" way to search for an object without an error being thrown if the object doesn't exist? I could do this

state = None if str(type(locality['state'])) != "<class 'int'>" else State.objects.get(pk=locality['state'])

but this seems unnecessarily wordy and not how Django was intended to be used.

Dave
  • 15,639
  • 133
  • 442
  • 830
  • You can validate the value of `locality['state']` before passing into Django ORM – JPG Jul 06 '20 at 14:40
  • could also use filter().first() instead of get(). or use an if statment like `if locality['state']:...` – hansTheFranz Jul 06 '20 at 14:51
  • I don't think you should let this invalid value go to the model query in first place – minglyu Jul 06 '20 at 14:55
  • @hansTheFranz, how would the filter().first() strategy work? – Dave Jul 06 '20 at 14:56
  • `state = State.objects.filter(pk=locality['state']).first()` filter will return a queryset and first takes the first one of that set. its similar to "get" but wont throw an error when nothing is found. – hansTheFranz Jul 06 '20 at 15:08
  • Thanks @hansTheFranz, unfortunately that also produces the error, 'ValueError: Field 'id' expected a number but got ''.' if the locality['state'] field evaluates to ''. – Dave Jul 06 '20 at 15:58

2 Answers2

2

I would choose Ask forgiveness not permission strategy

try:
    state = State.objects.get(pk=int(locality['state']))
except ValueError:
    state = None
JPG
  • 82,442
  • 19
  • 127
  • 206
  • I'll probably end up going with this ... I was just looking for some one line way of solving this. Not knowing Django too well, I wasn't sure if such a way existed, but it is starting to dawn on me that it does not. – Dave Jul 06 '20 at 17:32
  • There may exist a single-line solution, as you mentioned in OP, but [***readability counts***](https://www.python.org/dev/peps/pep-0020/#id2) – JPG Jul 06 '20 at 17:36
0

You could use a logical AND to validate the dict value before using it to look up the data.

state = locality['state'] and State.objects.get(pk=locality['state'])
schillingt
  • 13,493
  • 2
  • 32
  • 34
  • Thanks. Gave this a go but it's still throwing the same "ValueError: Field 'id' expected a number but got ''." error. – Dave Jul 06 '20 at 16:01
  • That means `locality['state'] == "."`. Check why your client is submitting invalid data. – schillingt Jul 06 '20 at 16:18
  • Sure. I was kind of hoping Django had a finder method that could deal with data that didn't match the primary key, regardless of object type, and just return None or something but maybe that was wishful thinking on my part. – Dave Jul 06 '20 at 17:31
  • You can make a validator to add to your serializer so that the view layer correctly handles this rather than hoping the data layer does. – schillingt Jul 06 '20 at 18:39
  • Ok. Is this what is done in traditional Django programming? I would like to conform as much as possible to what convention dictates without resorting to any hacks. Even if that means extra code. – Dave Jul 06 '20 at 19:43
  • Generally, yes. If you edit your question to include the view/viewset/serializers/forms, I can provide you with more direction. – schillingt Jul 06 '20 at 19:52
  • Also, picking up 2 Scoops of Django the book is a great way to learn the best practices of Django. I'm not affiliated with the author or have a vested interest in it, but Feldroy provides a ton of great information there. – schillingt Jul 06 '20 at 19:53