From 7889162420ccf579c920c996a2eb6e269c5a3a6f Mon Sep 17 00:00:00 2001 From: Philip Sargent Date: Sat, 22 Nov 2025 11:38:00 +0200 Subject: [PATCH] more progress to better serializer --- core/utils.py | 15 ++++-- core/views/logbooks.py | 103 ++++++++++++++++++++--------------------- 2 files changed, 61 insertions(+), 57 deletions(-) diff --git a/core/utils.py b/core/utils.py index 3dd1089..c865d59 100644 --- a/core/utils.py +++ b/core/utils.py @@ -326,9 +326,6 @@ def shared_use_machine(request): else: print(f" - shared use cookie exists, but has wrong value: '{cookie_txt}' not '{PUBLIC_LAPTOP_COOKIE_TEXT}'") return True - - - def get_cookie(request): """The initial idea of having a default turned out to be a bad idea as people just ignore the field. @@ -539,6 +536,18 @@ class WriteAndCommitError(Exception): def __str__(self): return f"WriteAndCommitError: {self.message}" +# Custom JSON Encoder to handle non-serializable types +class CustomJSONEncoder(json.JSONEncoder): + def default(self, obj): + # Convert datetime objects to ISO 8601 string format + if isinstance(obj, datetime): + return obj.isoformat() + # Convert Decimal objects to string + if isinstance(obj, Decimal): + # Use str() for perfect precision, or float() for numerical representation + return str(obj) + # Let the base class handle other types + return json.JSONEncoder.default(self, obj) """The following is a Bard converted version of Radosts's MIT copyrighted Javascript on 2023-10-27 with hand-editing. diff --git a/core/views/logbooks.py b/core/views/logbooks.py index 68b2c1e..d639dab 100644 --- a/core/views/logbooks.py +++ b/core/views/logbooks.py @@ -1,20 +1,24 @@ import json import re +from datetime import datetime +from decimal import Decimal + + +from django.contrib.auth.models import User from django.core.exceptions import ValidationError from django.core.serializers import serialize from django.db.models import Q +from django.forms.models import model_to_dict from django.shortcuts import redirect, render from django.views.generic.list import ListView -from django.contrib.auth.models import User - import troggle.settings as settings from troggle.core.models.logbooks import QM, LogbookEntry, PersonLogEntry, writelogbook from troggle.core.models.survex import SurvexBlock, SurvexFile from troggle.core.models.troggle import Expedition, Person from troggle.core.models.wallets import Wallet -from troggle.core.utils import TROG, current_expo, add_commit, git_commit, git_add, get_editor +from troggle.core.utils import TROG, current_expo, add_commit, git_commit, git_add, get_editor, ensure_dir_exists, CustomJSONEncoder from troggle.parsers.imports import import_logbook """These views are for logbook items when they appear in an 'expedition' page @@ -399,6 +403,8 @@ def write_entries(entries, year, editor): """ def write_json_file(): + # uses filepath, jsondict from context + try: with open(filepath, 'w', encoding='utf-8') as json_f: json.dump(jsondict, json_f, indent=1) @@ -408,80 +414,69 @@ def write_entries(entries, year, editor): ) except Exception as e: print(f"CANNOT write this file {filepath}. Exception dumping json. Ask a nerd to fix this: {e}") - raise e + raise e + + def serialize_logentry(): + # REPLACE this with hand-built serializer which includes .author, .who which were added to the entries but re not in the model Class directly + # see below for Gemini code to do that. Going to bed now. + jsondict = serialize("json", [le], fields=('slug', 'date', 'expedition', 'title', 'cave', 'place', 'other_people', 'time_underground', 'text')) + return jsondict dirpath = settings.EXPOWEB / "years" / year / LOGBOOK_ENTRIES - dirpath.mkdir(parents=True, exist_ok=True) for le in entries: filename = f"{le.slug}-{le.pk:03}.json" filepath = dirpath / filename # description = f" {le.slug} :: {le.date} - {le.title}" - - # REPLACE this with hand-built serializer which includes .author, .who which were added to the entries but re not in the model Class directly - # see below for Gemini code to do that. Going to bed now. - jsondict = serialize("json", [le], fields=('slug', 'date', 'expedition', 'title', 'cave', 'place', 'other_people', 'time_underground', 'text')) - + ensure_dir_exists(filepath) + + jsondict = serialize_logentry() write_json_file() git_add(filename, dirpath) - commit_msg = f"Exporting logbook entries as individual files" + commit_msg = f"Exporting {len(entries)} logbook entries as individual files." git_commit(dirpath, commit_msg, editor) return True # Gemini has the answer, get what I need from this: -# from django.http import JsonResponse, HttpResponse -# # from .models import LogbookEntry, PersonLogEntry, Person # Import your models -# from django.forms.models import model_to_dict -# from datetime import datetime -# import json -# from decimal import Decimal -# # Re-using the custom encoder from the previous suggestion for robust date/decimal handling -# class CustomJSONEncoder(json.JSONEncoder): - # def default(self, obj): - # if isinstance(obj, datetime): - # return obj.isoformat() - # if isinstance(obj, Decimal): - # return str(obj) - # return json.JSONEncoder.default(self, obj) -# def export_entry_with_author_details(request, entry_id): - # try: - # # 1. Get the LogbookEntry instance - # entry = LogbookEntry.objects.get(pk=entry_id) +def export_entry_with_author_details(request, entry_id): + try: + # 1. Get the LogbookEntry instance + entry = LogbookEntry.objects.get(pk=entry_id) - # # 2. Get the related PersonLogEntry and Person (Author) - # # Use .select_related() for efficiency - # author_link = PersonLogEntry.objects.select_related('person').get( - # entry=entry, - # is_author=True # Adjust filter based on your logic - # ) - # author = author_link.person + # 2. Get the related PersonLogEntry and Person (Author) + # Use .select_related() for efficiency + author_link = PersonLogEntry.objects.select_related('person').get( + entry=entry, + is_author=True # Adjust filter based on your logic + ) + author = author_link.person - # except (LogbookEntry.DoesNotExist, PersonLogEntry.DoesNotExist): - # return HttpResponse(f'Entry or Author not found for ID {entry_id}', status=404) + except (LogbookEntry.DoesNotExist, PersonLogEntry.DoesNotExist): + return HttpResponse(f'Entry or Author not found for ID {entry_id}', status=404) - # # 3. Manually create the nested dictionary structure - # # Use model_to_dict for easy extraction of the simple fields + # 3. Manually create the nested dictionary structure + # Use model_to_dict for easy extraction of the simple fields - # # Author data (specify fields you want to expose) - # author_data = model_to_dict(author, fields=['id', 'first_name', 'last_name', 'email']) + # Author data (specify fields you want to expose) + author_data = model_to_dict(author, fields=['id', 'first_name', 'last_name', 'email']) - # # Entry data (specify fields you want to expose) - # entry_data = model_to_dict(entry, fields=['id', 'title', 'content', 'date_created']) + # Entry data (specify fields you want to expose) + entry_data = model_to_dict(entry, fields=['id', 'title', 'content', 'date_created']) - # # Nest the author data inside the entry data - # entry_data['author'] = author_data + # Nest the author data inside the entry data + entry_data['author'] = author_data - # # Add data from the intermediate model if needed (e.g., the date the person was added) - # entry_data['author_assignment_date'] = author_link.date_assigned.isoformat() + # Add data from the intermediate model if needed (e.g., the date the person was added) + entry_data['author_assignment_date'] = author_link.date_assigned.isoformat() - # # 4. Return the custom dictionary using JsonResponse - # return JsonResponse( - # entry_data, - # encoder=CustomJSONEncoder, - # safe=False # Set to True if entry_data was a list/QuerySet - # ) \ No newline at end of file + # 4. Return the custom dictionary using JsonResponse + return JsonResponse( + entry_data, + encoder=CustomJSONEncoder, + safe=False # Set to True if entry_data was a list/QuerySet + ) \ No newline at end of file