diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 32d37c1..6fcd7aa 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.34.0 +current_version = 0.34.1 [bumpversion:file:pyproject.toml] diff --git a/CHANGELOG.md b/CHANGELOG.md index 553a07d..6ed2835 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 # [Unreleased] +# [0.34.1] +- [PR 179](https://github.com/salesforce/django-declarative-apis/pull/179) Move the 204 response handling fix to resources. + # [0.34.0] - [PR 178](https://github.com/salesforce/django-declarative-apis/pull/178) Fix JSONEmitter for empty response bodies. diff --git a/django_declarative_apis/resources/emitters.py b/django_declarative_apis/resources/emitters.py index f26da52..d077176 100644 --- a/django_declarative_apis/resources/emitters.py +++ b/django_declarative_apis/resources/emitters.py @@ -183,11 +183,6 @@ def decode(self, data): def render(self, request): cb = request.GET.get("callback", None) assert cb is None, "JSONP Callbacks not supported" - seria = self.decode(self.construct()) - if isinstance(seria, list): - if len(seria) == 0 or (len(seria) == 1 and len(seria[0]) == 0): - # the body is empty, no need to run json.dumps - return "" # Callback # TODO: do we care about JSONP? @@ -195,7 +190,7 @@ def render(self, request): # return '%s(%s)' % (cb, seria) return json.dumps( - seria, + self.decode(self.construct()), cls=DjangoJSONEncoder, ensure_ascii=False, indent=4, diff --git a/django_declarative_apis/resources/resource.py b/django_declarative_apis/resources/resource.py index 902ce90..3a4a767 100644 --- a/django_declarative_apis/resources/resource.py +++ b/django_declarative_apis/resources/resource.py @@ -301,6 +301,10 @@ def __call__(self, request, *args, **kwargs): # noqa: C901 srl = emitter(result, handler, anonymous) try: + # If the status code is 204, we need to return an empty response. skip the emitter. + if status_code == http.HTTPStatus.NO_CONTENT: + return HttpResponse(b"", status=http.HTTPStatus.NO_CONTENT) + """ Decide whether or not we want a generator here, or we just want to buffer up the entire result diff --git a/docs/source/conf.py b/docs/source/conf.py index 9ec3d8e..85a212a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -75,7 +75,7 @@ # built documents. # The full version, including alpha/beta/rc tags. -release = "0.34.0" # set by bumpversion +release = "0.34.1" # set by bumpversion # The short X.Y version. version = release.rsplit(".", 1)[0] diff --git a/pyproject.toml b/pyproject.toml index 544a417..1cde7b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "django-declarative-apis" -version = "0.34.0" # set by bumpversion +version = "0.34.1" # set by bumpversion description = "Simple, readable, declarative APIs for Django" readme = "README.md" dependencies = [ diff --git a/tests/resources/test_emitters.py b/tests/resources/test_emitters.py index 6bd304f..e591b30 100644 --- a/tests/resources/test_emitters.py +++ b/tests/resources/test_emitters.py @@ -109,15 +109,6 @@ def test_decode(self): resp = em.render(django.test.RequestFactory().get("/")) self.assertEqual(json.loads(resp), ["foo", "bar"]) - def test_decode_empty_list(self): - em = emitters.JSONEmitter([""], lambda: None) - resp = em.render(django.test.RequestFactory().get("/")) - self.assertEqual(resp, "") - - em = emitters.JSONEmitter([], lambda: None) - resp = em.render(django.test.RequestFactory().get("/")) - self.assertEqual(resp, "") - class DjangoEmitterTestCase(unittest.TestCase): def test_render_http_response_succes(self): diff --git a/tests/resources/test_resource.py b/tests/resources/test_resource.py index 1e69154..3849cbf 100644 --- a/tests/resources/test_resource.py +++ b/tests/resources/test_resource.py @@ -13,6 +13,7 @@ import django.test from django.test.utils import override_settings from unittest import mock +from django.http import HttpResponse from django_declarative_apis.authentication.oauthlib import oauth_errors from django_declarative_apis.resources import resource @@ -212,3 +213,23 @@ def test_email_exception(self): (subj, body, _, __), ___ = mock_email.call_args_list[0] self.assertEqual(subj, "[Django] Django Declarative APIs crash report") self.assertEqual(body, traceback) + + def test_call_no_content_response(self): + """Test that HTTP 204 responses return empty content with content-length 0""" + + def handle_delete(request, *args, **kwargs): + # Handler returns an HttpResponse with 204 status as the result + return http.HTTPStatus.OK, HttpResponse(status=http.HTTPStatus.NO_CONTENT) + + class Handler: + allowed_methods = ("DELETE",) + method_handlers = {"DELETE": handle_delete} + + req = self.create_request(method="DELETE") + res = resource.Resource(lambda: Handler()) + resp = res(req) + + # Verify the response has 204 status and empty content + self.assertEqual(resp.status_code, http.HTTPStatus.NO_CONTENT) + self.assertEqual(resp.content, b"") + self.assertEqual(len(resp.content), 0)