diff --git a/django_dump_die/templates/django_dump_die/index.html b/django_dump_die/templates/django_dump_die/index.html index a408f294129521af8b4cae7d574f9c252b9321b8..af96b409c5baadbfc931301812c05864678da245 100644 --- a/django_dump_die/templates/django_dump_die/index.html +++ b/django_dump_die/templates/django_dump_die/index.html @@ -40,6 +40,9 @@ <br> + <p><a href="{% url 'django_dump_die:django-request-response-cycle-example' %}"> + Django Request Response Cycle Example + </a></p> <p><a href="{% url 'django_dump_die:edge-case-example' %}">Edge Case Example</a></p> </body> diff --git a/django_dump_die/urls.py b/django_dump_die/urls.py index ffe531e97746780e81aee8ba1149e559c327ac68..56900f5af99f95923b3191969220af968a4cfcae 100644 --- a/django_dump_die/urls.py +++ b/django_dump_die/urls.py @@ -22,6 +22,7 @@ from .views import ( system_path_example, full_purpose_example, + django_request_response_cycle_example, edge_case_example, ) @@ -46,6 +47,11 @@ urlpatterns = [ path('full-purpose-example/', full_purpose_example, name='full-purpose-example'), + path( + 'django-request-response-cycle-example/', + django_request_response_cycle_example, + name='django-request-response-cycle-example', + ), path('edge-case-example/', edge_case_example, name='edge-case-example'), path('', index, name='index'), diff --git a/django_dump_die/utils.py b/django_dump_die/utils.py index 07d83347f69ae90236e6fa90af5e206e8f48864d..76b55745ca2a4fb4601a4a055ce0459ebb1c9db5 100644 --- a/django_dump_die/utils.py +++ b/django_dump_die/utils.py @@ -22,6 +22,7 @@ from decimal import Decimal from enum import EnumMeta from django.core.exceptions import ObjectDoesNotExist +from django.http import QueryDict from django_dump_die.constants import COLORIZE_DUMPED_OBJECT_NAME, INCLUDE_FILENAME_LINENUMBER, PYTZ_PRESENT @@ -335,11 +336,19 @@ def get_members(obj): # Get initial member set or empty list. members = inspect.getmembers(obj) - # Add type specific members that will not be included from the use of the - # inspect.getmembers function. + # Add type specific members that will not be included from the use of the inspect.getmembers function. if is_dict(obj): - # Dictionary members. - members.extend(obj.items()) + if isinstance(obj, QueryDict): + # Django QueryDict members. Has handling for multiple unique values referencing the same key. + # https://docs.djangoproject.com/en/dev/ref/request-response/#querydict-objects + obj_copy = copy.deepcopy(obj) + for key in obj_copy.keys(): + item = obj.pop(key) + members.append((key, item)) + else: + # Standard dictionary members. + members.extend(obj.items()) + elif is_iterable(obj): # Lists, sets, etc. if _is_indexable(obj): diff --git a/django_dump_die/views/__init__.py b/django_dump_die/views/__init__.py index 4b66d5da907a2b83647b041d6e35fd965bf966ef..73199eaf77dc84b0a2fd5cd8385874fde10f0c03 100644 --- a/django_dump_die/views/__init__.py +++ b/django_dump_die/views/__init__.py @@ -17,5 +17,6 @@ from .example_views import ( system_path_example, full_purpose_example, + django_request_response_cycle_example, edge_case_example, ) diff --git a/django_dump_die/views/example_helpers.py b/django_dump_die/views/example_helpers.py index 743aac38c972d9b07205cccbc991122d86c09600..cc0dab1f9be017d89271ce78e559466d0acafc19 100644 --- a/django_dump_die/views/example_helpers.py +++ b/django_dump_die/views/example_helpers.py @@ -5,14 +5,18 @@ Helps prevent package errors relating to example view load from propagating to general package usage. """ -from enum import Enum +import copy import os from datetime import datetime, timedelta from decimal import Decimal from django.core.files import File from django.db import models from django.forms import ModelForm +from django.http import QueryDict +from django.shortcuts import render +from django.template.response import TemplateResponse from django.utils import timezone +from enum import Enum from pathlib import Path, PosixPath, PurePath, WindowsPath from types import ModuleType @@ -716,6 +720,43 @@ def dump_syspath_types(): dump(windows_path) +def dump_django_request_response_cycle_types(request): + """Dump Django request-response cycle Types.""" + + # Generate variables to dump. + sample_query_dict = QueryDict('one_val=test&two_vals=one&two_vals=2') + sample_query_dict = copy.deepcopy(sample_query_dict) + sample_query_dict.appendlist('example_field_list', 'username') + sample_query_dict.appendlist('example_field_list', 'first_name') + sample_query_dict.appendlist('example_field_list', 'last_name') + sample_query_dict.appendlist('example_types', None) + sample_query_dict.appendlist('example_types', True) + sample_query_dict.appendlist('example_types', 5) + sample_query_dict.appendlist('example_types', 3.0) + + sample_request = request + sample_http_response = render(request, 'django_dump_die/sample.html', {}) + sample_template_response = TemplateResponse(request, 'django_dump_die/sample.html', {}) + sample_template_response.render() + + # Call dump on generated variables. + dump('') + dump('QueryDict object (GET and POST are instances of this):') + dump(sample_query_dict) + + dump('') + dump('Request object:') + dump(sample_request) + + dump('') + dump('HttpResponse object:') + dump(sample_http_response) + + dump('') + dump('TemplateResponse object:') + dump(sample_template_response) + + def dump_edgecase_types(): """Dump Edge Case Types""" # Call dump on problem children. diff --git a/django_dump_die/views/example_views.py b/django_dump_die/views/example_views.py index 70d2cfbdfe881e53411f4796ac7710dd2c84bfd9..cbdba553dc61e97229e81c29d69c94f3bf8bdcb9 100644 --- a/django_dump_die/views/example_views.py +++ b/django_dump_die/views/example_views.py @@ -292,6 +292,24 @@ def full_purpose_example(request): return render(request, 'django_dump_die/sample.html', {}) +def django_request_response_cycle_example(request): + """""" + from .example_helpers import dump_django_request_response_cycle_types + + # Output desired dump values. + dump('Displaying Django request-response-cycle example output.') + dump('') + dump_django_request_response_cycle_types(request) + dump('') + dump('') + + # Force dd to prevent further view parsing. + dd('done') + + # Show that any calls after dd() end up ignored. + return render(request, 'django_dump_die/sample.html', {}) + + def edge_case_example(request): """Example view, rendering various edge-case output. diff --git a/tests/test_views.py b/tests/test_views.py index 5d09059e37f8cb09a393ff6405fa6fe321c4d6b1..f8a25a94f4f92e46f37e5f870d2444e8fea6b7ad 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -3896,3 +3896,533 @@ class DumpDieViewFunctionTestCase(GenericViewTestCase): """, content_ends_before='done', ) + + +class DumpDieDjangoRequestResponseCycleTestCase(GenericViewTestCase): + """Verify handling of dumped "Django request-response cycle" types.""" + url = 'django_dump_die:django-request-response-cycle-example' + + def test_page_descriptor_display(self): + """Verify initial page descriptor output.""" + self.assertGetResponse( + self.url, + expected_title='DD', + expected_header='Django DumpDie', + expected_content=[ + # Check page descriptor. + """ + <div class="dump-wrapper"> + <span class="dumped_object" title="Dumped Object"> + <span class="string">'Displaying Django request-response-cycle example output.'</span> + </span>: + <span class="type" title="str">str</span> + <code class="string">'Displaying Django request-response-cycle example output.'</code> + </div> + """, + '<hr>', + # Check visual-padding lines. + """ + <div class="dump-wrapper"> + <span class="dumped_object" title="Dumped Object"> + <span class="string">''</span> + </span>: + <span class="type" title="str">str</span> + <code class="string">''</code> + </div> + """, + '<hr>', + """ + <div class="dump-wrapper"> + <span class="dumped_object" title="Dumped Object"> + <span class="string">''</span> + </span>: + <span class="type" title="str">str</span> + <code class="string">''</code> + </div> + """, + '<hr>', + ], + content_starts_after='<div class="static-padding"></div>', + content_ends_before='QueryDict object', + ) + + # TODO/NOTES: + # Due to the number of "unique" values being generated in a Request object, it seems to error out with these + # tests. Either we get a StopIteration error, due to an insufficient mock range, or we get a RecursionError + # due to too many calls when we raise this mock value. + # + # At the moment, I'm unsure of if there's a way around this to physically test this page logic. + # + # Note: The below QueryDict test technically passes (at least when this was written, as of early Aug 2022) IF you + # edit the view itself to stop further dd output after displaying the QueryDict. But as soon as you include the + # Request object, then the above described errors occur. + # + # And at least for now, I'd rather have all output types displayed for manual user-examination when physically + # loading this page in a browser, rather than comment out the "Request object dump", just for the sake of + # UnitTests. + # + # + # @patch('django_dump_die.templatetags.dump_die._generate_unique') + # def test_querydict_display(self, mocked_unique_generation): + # """Verify dumping a "QueryDict" type has expected output.""" + # # Override default "unique" generation logic, for reproduce-able tests. + # # This generates enough uniques to guarantee mock does not raise errors. + # # Unlike above tests, we need to give each object custom uniques, to ensure child-objects display. + # side_effects = [] + # for index in range(5000): + # side_effects += [ + # (f'data_900{index}', ''), + # ] + # mocked_unique_generation.side_effect = side_effects + # + # # Test object display. + # self.assertGetResponse( + # self.url, + # expected_title='DD', + # expected_header='Django DumpDie', + # expected_content=[ + # '<hr>', + # + # # Object descriptor. + # """ + # <div class="dump-wrapper"> + # <span class="dumped_object" title="Dumped Object"> + # <span class="string">'QueryDict object (GET and POST are instances of this):'</span> + # </span>: + # <span class="type" title="str">str</span> + # <code class="string">'QueryDict object (GET and POST are instances of this):'</code> + # </div> + # """, + # + # '<hr>', + # # Object opening tags. + # """ + # <div class="dump-wrapper"> + # <span class="dumped_object" title="Dumped Object"> + # <span class="dumped_name">sample_query_dict</span> + # </span>: + # <span class="type" title="QueryDict">QueryDict:0</span> + # <span class="braces">{</span> + # <a + # class="arrow-toggle collapsed" + # title="[Ctrl+click] Expand all children" + # data-toggle="collapse" + # data-target=".data_9008" + # data-dd-type="type" + # data-object-depth="1" + # aria-label="Close" + # aria-expanded="false" + # > + # <span class="unique" data-highlight-unique="data_9008">data_9008</span> + # <span id="arrow-data_9008" class="arrow arrow-data_9008">â–¶</span> + # </a> + # <div class="dd-wrapper collapse data_9008 " data-unique="data_9008"> + # <ul class="attribute-list"> + # <a + # class="arrow-toggle show always-show" + # title="[Ctrl+click] Expand all children" + # data-target=".data_9008-attributes" + # data-dd-type="attr" + # aria-label="Open/Close" + # aria-expanded="" + # > + # <span class="section_name">Attributes</span> + # <span id="arrow-data_9008-attributes" class="arrow"> + # </span> + # </a> + # <div + # class="li-wrapper collapse data_9008-attributes show" + # data-unique-attributes="data_9008-attributes" + # > + # """, + # + # # Object child elements. + # """ + # <li> + # <span class="key" title="Key">'one_val'</span>: + # <span class="type" title="list">list:1</span> + # <span class="braces">[</span> + # <a + # class="arrow-toggle collapsed" + # title="[Ctrl+click] Expand all children" + # data-toggle="collapse" + # data-target=".data_90010" + # data-dd-type="type" + # data-object-depth="2" + # aria-label="Close" + # aria-expanded="false" + # > + # <span class="unique" data-highlight-unique="data_90010">data_90010</span> + # <span id="arrow-data_90010" class="arrow arrow-data_90010">â–¶</span> + # </a> + # <div class="dd-wrapper collapse data_90010 " data-unique="data_90010"> + # <ul class="attribute-list"> + # <a + # class="arrow-toggle show always-show" + # title="[Ctrl+click] Expand all children" + # data-target=".data_90010-attributes" + # data-dd-type="attr" + # aria-label="Open/Close" + # aria-expanded="" + # > + # <span class="section_name">Attributes</span> + # <span id="arrow-data_90010-attributes" class="arrow"></span> + # </a> + # <div + # class="li-wrapper collapse data_90010-attributes show" + # data-unique-attributes="data_90010-attributes" + # > + # <li> + # <span class="index" title="Index">0</span>: + # <span class="type" title="str">str</span> <code class="string">'test'</code> + # </li> + # </div> + # </ul> + # <ul class="attribute-list"></ul> + # </div> + # <span class="braces">]</span> + # </li> + # """, + # """ + # <li> + # <span class="key" title="Key">'two_vals'</span>: + # <span class="type" title="list">list:2</span> + # <span class="braces">[</span> + # <a + # class="arrow-toggle collapsed" + # title="[Ctrl+click] Expand all children" + # data-toggle="collapse" + # data-target=".data_90012" + # data-dd-type="type" + # data-object-depth="2" + # aria-label="Close" + # aria-expanded="false" + # > + # <span class="unique" data-highlight-unique="data_90012">data_90012</span> + # <span id="arrow-data_90012" class="arrow arrow-data_90012">â–¶</span> + # </a> + # <div class="dd-wrapper collapse data_90012 " data-unique="data_90012"> + # <ul class="attribute-list"> + # <a + # class="arrow-toggle show always-show" + # title="[Ctrl+click] Expand all children" + # data-target=".data_90012-attributes" + # data-dd-type="attr" + # aria-label="Open/Close" + # aria-expanded="" + # > + # <span class="section_name">Attributes</span> + # <span id="arrow-data_90012-attributes" class="arrow"></span> + # </a> + # <div + # class="li-wrapper collapse data_90012-attributes show" + # data-unique-attributes="data_90012-attributes" + # > + # <li> + # <span class="index" title="Index">0</span>: + # <span class="type" title="str">str</span> <code class="string">'one'</code> + # </li> + # <li> + # <span class="index" title="Index">1</span>: + # <span class="type" title="str">str</span> <code class="string">'2'</code> + # </li> + # </div> + # </ul> + # <ul class="attribute-list"></ul> + # </div> + # <span class="braces">]</span> + # </li> + # """, + # """ + # <li> + # <span class="key" title="Key">'example_field_list'</span>: + # <span class="type" title="list">list:3</span> + # <span class="braces">[</span> + # <a + # class="arrow-toggle collapsed" + # title="[Ctrl+click] Expand all children" + # data-toggle="collapse" + # data-target=".data_90015" + # data-dd-type="type" + # data-object-depth="2" + # aria-label="Close" + # aria-expanded="false" + # > + # <span class="unique" data-highlight-unique="data_90015">data_90015</span> + # <span id="arrow-data_90015" class="arrow arrow-data_90015">â–¶</span> + # </a> + # <div class="dd-wrapper collapse data_90015 " data-unique="data_90015"> + # <ul class="attribute-list"> + # <a + # class="arrow-toggle show always-show" + # title="[Ctrl+click] Expand all children" + # data-target=".data_90015-attributes" + # data-dd-type="attr" + # aria-label="Open/Close" + # aria-expanded="" + # > + # <span class="section_name">Attributes</span> + # <span id="arrow-data_90015-attributes" class="arrow"></span> + # </a> + # <div + # class="li-wrapper collapse data_90015-attributes show" + # data-unique-attributes="data_90015-attributes" + # > + # <li> + # <span class="index" title="Index">0</span>: + # <span class="type" title="str">str</span> <code class="string">'username'</code> + # </li> + # <li> + # <span class="index" title="Index">1</span>: + # <span class="type" title="str">str</span> <code class="string">'first_name'</code> + # </li> + # <li> + # <span class="index" title="Index">2</span>: + # <span class="type" title="str">str</span> <code class="string">'last_name'</code> + # </li> + # </div> + # </ul> + # <ul class="attribute-list"></ul> + # </div> + # <span class="braces">]</span> + # </li> + # """, + # """ + # <li> + # <span class="key" title="Key">'example_types'</span>: + # <span class="type" title="list">list:4</span> + # <span class="braces">[</span> + # <a + # class="arrow-toggle collapsed" + # title="[Ctrl+click] Expand all children" + # data-toggle="collapse" + # data-target=".data_90019" + # data-dd-type="type" + # data-object-depth="2" + # aria-label="Close" + # aria-expanded="false" + # > + # <span class="unique" data-highlight-unique="data_90019">data_90019</span> + # <span id="arrow-data_90019" class="arrow arrow-data_90019">â–¶</span> + # </a> + # <div class="dd-wrapper collapse data_90019 " data-unique="data_90019"> + # <ul class="attribute-list"> + # <a + # class="arrow-toggle show always-show" + # title="[Ctrl+click] Expand all children" + # data-target=".data_90019-attributes" + # data-dd-type="attr" + # aria-label="Open/Close" + # aria-expanded="" + # > + # <span class="section_name">Attributes</span> + # <span id="arrow-data_90019-attributes" class="arrow"></span> + # </a> + # <div + # class="li-wrapper collapse data_90019-attributes show" + # data-unique-attributes="data_90019-attributes" + # > + # <li> + # <span class="index" title="Index">0</span>: + # <span class="type" title="null">null</span> <code class="none">None</code> + # </li> + # <li> + # <span class="index" title="Index">1</span>: + # <span class="type" title="bool">bool</span> <code class="bool">True</code> + # </li> + # <li> + # <span class="index" title="Index">2</span>: + # <span class="type" title="int">int</span> <code class="number">5</code> + # </li> + # <li> + # <span class="index" title="Index">3</span>: + # <span class="type" title="float">float</span> <code class="number">3.0</code> + # </li> + # </div> + # </ul> + # <ul class="attribute-list"></ul> + # </div> + # <span class="braces">]</span> + # </li> + # """, + # + # # Object closing tags. + # """ + # </div> + # </ul> + # <ul class="attribute-list"></ul> + # </div> + # <span class="braces">}</span> + # </div> + # """, + # + # '<hr>', + # ], + # content_starts_after='QueryDict object', + # content_ends_before='Request object', + # ) + + # @patch('django_dump_die.templatetags.dump_die._generate_unique') + # def test_request_display(self, mocked_unique_generation): + # """Verify dumping a "Request" type has expected output.""" + # # Override default "unique" generation logic, for reproduce-able tests. + # # This generates enough uniques to guarantee mock does not raise errors. + # # Unlike above tests, we need to give each object custom uniques, to ensure child-objects display. + # side_effects = [] + # for index in range(5000): + # side_effects += [ + # (f'data_900{index}', ''), + # ] + # mocked_unique_generation.side_effect = side_effects + # + # # Test object display. + # self.assertGetResponse( + # self.url, + # expected_title='DD', + # expected_header='Django DumpDie', + # expected_content=[ + # '<hr>', + # + # # Object opening tags. + # """ + # <div class="dump-wrapper"> + # """, + # + # # Object child elements. + # """ + # <li> + # </li> + # """, + # """ + # <li> + # </li> + # """, + # """ + # <li> + # </li> + # """, + # + # # Object closing tags. + # """ + # </div> + # </ul> + # <ul class="attribute-list"></ul> + # </div> + # <span class="braces">]</span> + # </div> + # """, + # + # '<hr>', + # ], + # content_starts_after='', + # # content_ends_before='', + # ) + + # @patch('django_dump_die.templatetags.dump_die._generate_unique') + # def test_http_response_display(self, mocked_unique_generation): + # """Verify dumping a "HttpResponse" type has expected output.""" + # # Override default "unique" generation logic, for reproduce-able tests. + # # This generates enough uniques to guarantee mock does not raise errors. + # # Unlike above tests, we need to give each object custom uniques, to ensure child-objects display. + # side_effects = [] + # for index in range(5000): + # side_effects += [ + # (f'data_900{index}', ''), + # ] + # mocked_unique_generation.side_effect = side_effects + # + # self.assertGetResponse( + # self.url, + # expected_title='DD', + # expected_header='Django DumpDie', + # expected_content=[ + # '<hr>', + # + # # Object opening tags. + # """ + # <div class="dump-wrapper"> + # """, + # + # # Object child elements. + # """ + # <li> + # </li> + # """, + # """ + # <li> + # </li> + # """, + # """ + # <li> + # </li> + # """, + # + # # Object closing tags. + # """ + # </div> + # </ul> + # <ul class="attribute-list"></ul> + # </div> + # <span class="braces">]</span> + # </div> + # """, + # + # '<hr>', + # ], + # content_starts_after='', + # # content_ends_before='', + # ) + + # @patch('django_dump_die.templatetags.dump_die._generate_unique') + # def test_template_response_display(self, mocked_unique_generation): + # """Verify dumping a "TemplateResponse" type has expected output.""" + # # Override default "unique" generation logic, for reproduce-able tests. + # # This generates enough uniques to guarantee mock does not raise errors. + # # Unlike above tests, we need to give each object custom uniques, to ensure child-objects display. + # side_effects = [] + # for index in range(5000): + # side_effects += [ + # (f'data_900{index}', ''), + # ] + # mocked_unique_generation.side_effect = side_effects + # self.assertGetResponse( + # self.url, + # expected_title='DD', + # expected_header='Django DumpDie', + # expected_content=[ + # '<hr>', + # + # # Object opening tags. + # """ + # <div class="dump-wrapper"> + # """, + # + # # Object child elements. + # """ + # <li> + # </li> + # """, + # """ + # <li> + # </li> + # """, + # """ + # <li> + # </li> + # """, + # + # # Object closing tags. + # """ + # </div> + # </ul> + # <ul class="attribute-list"></ul> + # </div> + # <span class="braces">]</span> + # </div> + # """, + # + # '<hr>', + # ], + # content_starts_after='', + # # content_ends_before='', + # )