So below I have some code that tests the functionality where someone creates a post and that post has a hash_tag
which is "#video" in this case. The code takes the Post
body
and uses regex to find any word that starts with "#". If it does then it creates or gets that HashTag
from the HashTag
table. Then sets that list of HashTag
to the hash_tags
attribute under Post
.
For some reason the CreatePostSerializer
serializer is throwing an exception that doesn't make sense. The serializer is throwing the exception ValidationError({'hash_tags': [ErrorDetail(string='Invalid pk "[\'video\']" - object does not exist.', code='does_not_exist')]})
. The reason this doesn't make sense is because when I debug and set a breakpoint right after except Exception as e
under views.py
this is what I get
>>>e
ValidationError({'hash_tags': [ErrorDetail(string='Invalid pk "[\'video\']" - object does not exist.', code='does_not_exist')]})
>>>HashTag.objects.get(pk='video')
<HashTag: HashTag object (video)>
>>>request.data['hash_tags']
['video']
So the >>>
represents what I input into the debugger. I'm essentially stopped at the line return Response...
and we can see e
is the ValidationError
I mentioned, but we can see that the object it claims doesn't exist does indeed exist. Why is the serializer throwing a "ValidationError - object does not exist" when it does?
Note: I have another test that does exactly the same thing and passes except no video file is being passed this leads me to believe that Django is doing something different in the case that the incoming body is multi-part
. I also tried in the instance that there is only one hash tag to set hash_tags
=<single hash tag> rather than a list and it worked. This is a hack though and cleaner solution is preferred.
helpers.py
import re
def extract_hashtags(text):
regex = "#(\w+)"
return re.findall(regex, text)
test.py
def test_real_image_upload_w_hash_tag(self):
image_file = retrieve_test_image_upload_file()
hash_tag = 'video'
response = self.client.post(reverse('post'),
data={'body': f'Some text and an image #{hash_tag}',
'images': [image_file]},
**{'HTTP_AUTHORIZATION': f'bearer {self.access_token}'})
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
views.py
def set_request_data_for_post(request, user_uuid: str):
request.data['creator'] = user_uuid
post_text = request.data['body']
hash_tags_list = extract_hashtags(post_text)
hash_tags = [HashTag.objects.get_or_create(hash_tag=ht)[0].hash_tag for ht in hash_tags_list]
if len(hash_tags) > 0:
request.data['hash_tags'] = hash_tags
return request
def create_post(request):
user_uuid = str(request.user.uuid)
request = set_request_data_for_post(request=request, user_uuid=user_uuid)
try:
serializer = CreatePostSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
post_obj = serializer.save()
except Exception as e:
return Response(dict(error=str(e),
user_message=error_message_generic),
status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.data, status=status.HTTP_201_CREATED)
serializer.py
from rest_framework import serializers
from cheers.models import Post
class CreatePostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('creator', 'body', 'uuid', 'created', 'updated_at', 'hash_tags')
model.py
class Post(models.Model):
# ulid does ordered uuid creation
uuid = models.UUIDField(primary_key=True, default=generate_ulid_as_uuid, editable=False)
created = models.DateTimeField('Created at', auto_now_add=True)
updated_at = models.DateTimeField('Last updated at', auto_now=True, blank=True, null=True)
creator = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="post_creator")
body = models.CharField(max_length=POST_MAX_LEN, validators=[MinLengthValidator(POST_MIN_LEN)])
hash_tags = models.ManyToManyField(HashTag, blank=True)
class HashTag(models.Model):
hash_tag = models.CharField(max_length=HASH_TAG_MAX_LEN, primary_key=True, validators=[
MinLengthValidator(HASH_TAG_MIN_LEN)])
under your test/__init__.py
you have to add these lines
from django.db.backends.postgresql.features import DatabaseFeatures
DatabaseFeatures.can_defer_constraint_checks = False
There's some weird internal bug where if you operate on one table a lot with a lot of different TestCase
classes then it'll do a DB check at the end after it's been torn down and it'll cause an error.
I'm also using factory boy (https://factoryboy.readthedocs.io/en/stable/orms.html) to generate my test DB, which is the main reason this issue arises. The reason I believe this is because I switched out factory boy for just using <model>.objects.create()
and my tests stopped failing.