From ae727f27659953dcc9d04345cc5f8f5e847c42d2 Mon Sep 17 00:00:00 2001
From: Brandon Rodriguez <brodriguez8774@gmail.com>
Date: Wed, 7 Feb 2024 03:39:04 -0500
Subject: [PATCH] Add some api testing views and corresponding logic to django
 v4 project

---
 django_v4/test_app/models.py |  18 +++++-
 django_v4/test_app/urls.py   |   4 ++
 django_v4/test_app/views.py  | 108 +++++++++++++++++++++++++++++++++++
 3 files changed, 128 insertions(+), 2 deletions(-)

diff --git a/django_v4/test_app/models.py b/django_v4/test_app/models.py
index e819e71..5d21fff 100644
--- a/django_v4/test_app/models.py
+++ b/django_v4/test_app/models.py
@@ -11,6 +11,14 @@ from localflavor.us.models import USStateField, USZipCodeField
 MAX_LENGTH = 255
 
 
+class BaseAbstractModel(models.Model):
+    """Expanded version of the default Django model."""
+
+    # Self-setting/Non-user-editable fields.
+    date_created = models.DateTimeField(auto_now_add=True)
+    date_modified = models.DateTimeField(auto_now=True)
+
+
 class User(AbstractUser):
     """Custom user model definition.
     Defined as per the Django docs. Not yet directly used.
@@ -38,7 +46,7 @@ class User(AbstractUser):
             UserProfile.objects.create(user=self)
 
 
-class UserProfile(models.Model):
+class UserProfile(BaseAbstractModel):
     """Basic model to act as a test fk to user model."""
 
     # Relationship Keys.
@@ -52,7 +60,7 @@ class UserProfile(models.Model):
     zipcode = USZipCodeField()
 
 
-class FavoriteFood(models.Model):
+class FavoriteFood(BaseAbstractModel):
     """Basic model to act as a test m2m relation to user model."""
 
     # Relationship Keys.
@@ -61,3 +69,9 @@ class FavoriteFood(models.Model):
     # Model fields.
     name = models.CharField(max_length=MAX_LENGTH)
 
+
+class ApiRequestJson(BaseAbstractModel):
+    """Used to retain data for API testing views."""
+
+    # Model fields.
+    json_value = models.JSONField(default=dict)
diff --git a/django_v4/test_app/urls.py b/django_v4/test_app/urls.py
index a71f531..b7db94c 100644
--- a/django_v4/test_app/urls.py
+++ b/django_v4/test_app/urls.py
@@ -16,6 +16,10 @@ urlpatterns = [
     path('view_with_permission_check/', views.view_with_permission_check, name='view_with_permission_check'),
     path('view_with_group_check/', views.view_with_group_check, name='view_with_group_check'),
 
+    # Test API views.
+    path('api/parse/', views.api_parse, name='api_parse'),
+    path('api/display/', views.api_display, name='api_display'),
+
     # App root.
     path('', views.index, name='index')
 ]
diff --git a/django_v4/test_app/views.py b/django_v4/test_app/views.py
index 52747fa..7d5f08b 100644
--- a/django_v4/test_app/views.py
+++ b/django_v4/test_app/views.py
@@ -2,10 +2,20 @@
 Views for Django v4.1 test project app.
 """
 
+# System Imports.
+import json
+import html
+
 # Third-Party Imports.
 from django.contrib.auth.decorators import login_required, permission_required
+from django.http import JsonResponse, QueryDict
+from django.views.decorators.csrf import csrf_exempt
+from django.views.decorators.http import require_http_methods
 from django.shortcuts import redirect, render, reverse
 
+# Internal Imports.
+from test_app.models import ApiRequestJson
+
 
 # region Index/Root Views
 
@@ -47,3 +57,101 @@ def view_with_group_check(request):
     return render(request, 'test_app/group_check.html')
 
 # endregion Login/Permission Test Views
+
+
+# region API Views
+
+@csrf_exempt
+@require_http_methods(["GET", "POST"])
+def api_parse(request):
+    """Takes in JSON ping, and saves incoming value to web cookies.
+
+    Then if api_display view is called after, will display the saved cookie value to web page.
+
+    Allows quick debugging to make sure the expected, correct data is being sent.
+    """
+    # Get data from response.
+    data = {'data': 'No data found in request.'}
+    if request.GET:
+        data = request.GET
+    elif request.POST:
+        data = dict(request.POST)
+    elif request.body:
+        # Attempt to escape. Limited functionality so may not work.
+        data = html.unescape(request.body.decode('UTF-8'))
+
+    data = _recurisive_json_parse(data)
+
+    # Save api data to database.
+    model_instance = ApiRequestJson.objects.first()
+    if not model_instance:
+        model_instance = ApiRequestJson.objects.create()
+    model_instance.json_value = data
+    model_instance.save()
+
+    # Generate response.
+    return JsonResponse({'success': True})
+
+
+def _recurisive_json_parse(data_item):
+    """Helper function to ensure all sub-items of response are properly read-in."""
+
+    # Convert from potentially problematic types, for easier handling.
+    if isinstance(data_item, QueryDict):
+        data_item = dict(data_item)
+    if isinstance(data_item, tuple):
+        data_item = list(data_item)
+
+    # Process some known types.
+    if isinstance(data_item, dict):
+        # Is dictionary. Iterate over each (key, value) pair and attempt to convert.
+        for key, value in data_item.items():
+            data_item[key] = _recurisive_json_parse(value)
+
+    elif isinstance(data_item, list):
+        # Is iterable. Iterate over each item and attempt to convert.
+        for index in range(len(data_item)):
+            sub_item = data_item[index]
+            data_item[index] = _recurisive_json_parse(sub_item)
+
+    else:
+        # For all other types, just attempt naive conversion
+        try:
+            data_item = json.loads(data_item)
+        except:
+            # On any failure, just skip. Leave item as-is.
+            pass
+
+    # Return parsed data.
+    return data_item
+
+
+def api_display(request):
+    """After a JSON ping to api_parse view, this displays parsed value to web page.
+
+    Allows quick debugging to make sure the expected, correct data is being sent.
+    """
+
+    # Grab api data from session, if any.
+    model_instance = ApiRequestJson.objects.first()
+    if model_instance:
+        content = {
+            'payload_data': model_instance.json_value,
+            'payload_sent_at': model_instance.date_created,
+        }
+    else:
+        content = {
+            'payload_data': {},
+            'payload_sent_at': 'N/A',
+        }
+
+    # Attempt to output api data to browser.
+    response = JsonResponse(content, safe=False)
+
+    # Delete all existing instances of saved API data.
+    ApiRequestJson.objects.all().delete()
+
+    # Return data view to user.
+    return response
+
+# endregion API Views
-- 
GitLab