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