Django serializing all objects in JSONB - json

I am trying to serialize a model for displaying on an existing front end interface. The model is setup as such:
class Timevalue(models.Model):
time = models.FloatField(blank=True, null=True)
values = JSONField(blank=True, null=True)
The nature of the values is that it has no defined keys, therefore it is using JSON rather than a structured schema. As an end result, I need to RestAPI to output a list of timevalue objects that is flattened so that each element contains the time key as well as all the keys for the values.
So far I have written the following serializer that can return the data in the format of [{'time': 0.01, 'values': {'value1': 1, 'value2': 2, 'value3': 3}}]
class TimevalueSerializer(serializers.Serializer):
time = serializers.FloatField()
values = serializers.JSONField()
However I cannot achieve getting the output in the necessary format: [{'time': 0.01, 'value1': 1, 'value2': 2, 'value3': 3}].
I have tried the following serializer setup:
class TimevaluechildSerializer(serializers.Serializer):
fields = '*'
class TimevalueSerializer(serializers.Serializer):
time = serializers.FloatField()
values = TimevaluechildSerializer('*')
but I cannot work out what to pass to the child serializer in order for it to return all of the key-value pairs.
As this model is used for other views, I prefer to use a Serializer rather than a ModelSerializer.
Hopefully the answer isn't too difficult.
Stu

Maybe using serializer will be hard for this, rather than that, you can send this response manually. For example:
from rest_framework import status
from rest_framework.response import Response
class SomeApiView(ApiView):
resp_list = list()
for i in Timevalues.objects.all():
t = {'time': i.time}
t.update(i.values)
resp_list.append(t)
return Response(resp_list, status=status.HTTP_200_OK)

Related

Django model with value as a special HTML character code

I am confused on how to implement a Django model with the value being a special html character code for making a chessboard.
As a reference I'm looking at a sudoku board model:
class Board(models.Model):
name = models.CharField(max_length=4, primary_key=True)
value = models.SmallIntegerField()
The value for sudoku is easy, since the table will only be filled with numbers.
For reference here's a snippet from the sudoku page_data dictionary in views.py giving each table cell its appropriate value:
{"r1c1": 6, "r1c2": 7, "r1c3": 0, ...}
I don't know what to put for my model's value variable:
class Board(models.Model):
name = models.CharField(max_length=2, primary_key=True)
value =
Here's a snippet of where I assign the name/value pairs in my views.py with the special HTML chess piece character codes in my own page_data dictionary:
{"a8": html.unescape('♖'), "b8": html.unescape('♘'), "c8": html.unescape('♗'), ...}
Any help is appreciated.
So you are looking for a way to store your json data in a django model field.
One option would be to convert your json data to a string and store it in a Charfield.
class Board(models.Model):
name = models.CharField(max_length=4, primary_key=True)
value = models.CharField(blank=True, null=True)
Then convert your json data to a string with the following code:
import json
data = {"r1c1": 6, "r1c2": 7, "r1c3": 0}
Board.objects.create(name="bo", value=json.dumps(data))
You can use the json data in the template by using the safe tag:
from django.utils.safestring import SafeString
def view(request):
return render(request, 'template.html', {'board': SafeString(my_board.value)})
If you do not want to convert your json to string, you can take a look at the JSONField.

DRF: can you deserialize one JSON key:value pair into multiple fields

In my API I have a module, which collects JSON objects obtained via POST request. JSON objects I'm receiving look more or less like this:
{
"id": "bec7426d-c5c3-4341-9df2-a0b2c54ab8da",
"data": {
"temperature": -2.4,
// some other stuff is here as well ...
}
}
The problem is requirement that I have to save both: records from data dictionary and whole data dictionary as a JSONField. My ORM model looks like this:
class Report(BaseModel):
id = models.BigAutoField(primary_key=True)
data = JSONField(verbose_name=_("Data"), encoder=DjangoJSONEncoder, blank=True, null=True)
temperature = models.DecimalField(
max_digits=3,
decimal_places=1,
)
# other properties
Is there any neat way to get both properties in one attempt to deserialize JSON object? Currently I use nested serializers formed like this:
class DataSerializer(serializers.ModelSerializer):
temperature = serializers.DecimalField(
source="temperature", write_only=True, max_digits=3, decimal_places=1
)
class Meta:
model = Report
fields = ("temperature")
class ReportSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source="uuid", read_only=True)
data = DataSerializer(source="*")
class Meta:
model = Report
fields = ("id", "data")
which obviously does not pass whole data dictionary to validated_data, which means I have to update the JSON field elsewhere (not good since I would like to have both data and temperature at null=False in ORM model). Any good ideas on how to fix this using DRF serializers would be appreciated.
I believe you should be able to override validate method for your serializer where you can "store initial data JSON field" and do the default validation by calling super()... method.
More info https://www.django-rest-framework.org/api-guide/serializers/#validation
Also, there are object-level validation functions available, you can take a look there as well for the initial posted data
https://www.django-rest-framework.org/api-guide/serializers/#object-level-validation
Also, you can override the method run_validation to access the initially passed data object.

Django View: Return Queryset in JSON Format

i am trying to make the following view with return JsonResponse() at the end work correctly:
def get_data(request):
full_data = Fund.objects.all()
data = {
"test2": full_data.values('investment_strategy').annotate(sum=Sum('commitment')),
}
return JsonResponse(data)
However, I get an error message saying "Object of type QuerySet is not JSON serializable".
When I put the above Queryset in a view with return render() at the end:
def get_more_data(request):
full_data = Fund.objects.all()
data = {"test2": full_data.values('investment_strategy').annotate(sum=Sum('commitment'))}
return render (request, 'test.html', data)
I get the the following result: <QuerySet [{'investment_strategy': 'Buyout', 'sum': 29}, {'investment_strategy': 'Growth', 'sum': 13}, {'investment_strategy': 'Miscellaneous', 'sum': 14}, {'investment_strategy': 'Venture Capital', 'sum': 23}, {'investment_strategy': 'n/a', 'sum': 36}]>
So the queryset works fine, I just have no clue how to return the data in proper Json format (which I would need to use the data charts.js)
I looked through answers for similar questions such as:
TypeError: object is not JSON serializable in DJango 1.8 Python 3.4
Output Django queryset as JSON
etc.
but could not find a meaningful solution for my problem.
Any help would be much appreciated!
So I managed to find a solution, that worked for me - in case anyone else has the same problem. I changed my view to the following:
def get_data(request):
full_data = Fund.objects.all()
full_data_filtered = full_data.values('investment_strategy').annotate(sum=Sum('commitment'))
labels = []
values = []
for d in full_data_filtered:
labels.append(d['investment_strategy'])
values.append(d['sum'])
data = {
"labels": labels,
"values": values,
}
return JsonResponse(data)
So basically I iterate over the Queryset and assign the values I need to lists, which can be passed to JsonResponse. I don't know if this is the most elegant way to do this (sure not), but it works and I can render my data in charts.js
JsonResponse(list(data)) will evaluate the Queryset (actually perform the query to the database) and turn it into a list that can be passed to JsonResponse.
This works because you use values and annotate, so the list is a list of dictionaries containing serializable fields.
In the example you mentioned, it didn't work because the Queryset was just returning a list of model instances, so wrapping in list() isn't enough. If you hadn't added values, you'd have had a list of Fund instances which are not serializable.
The best way I found was to create a custom QuerySet and Manager, it's not a lot of code and it is reusable!
I began by creating the custom QuerySet:
# managers.py but you can do that in the models.py too
from django.db import models
class DictQuerySet(models.QuerySet):
def dict(self):
values = self.values()
result = {}
for value in values:
id = value['id']
result[id] = value # you can customize the content of your dictionary here
return result
Then I created a custom Manager, it is optional but i prefer this way.
# managers.py, also optional
class DictManager(models.Manager):
def get_queryset(self):
return DictQuerySet(self.model, using=self._db)
Change the default manager in your model:
# models.py
from .managers import DictManager
class Fund(models.Model):
# ...
objects = DictManager()
# ...
And now you can call the dict() method from the query
# views.py
def get_data(request):
full_data = Fund.objects.all().dict()
return JsonResponse(full_data)
The response will be the full_data as a dictionary of dictionaries and each key is the primary key of the corresponding object.
If you intend to keep the same format for your JSONs, then you can use the same custom manager for all your models.

Return proper JSON from Django JsonResponse

My Model:
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
phone = models.CharField(max_length=20)
email = models.EmailField()
My View:
def users(request):
people = Person.objects.all()
data = serializers.serialize('json', people)
return JsonResponse(data, safe=False)
All I want back is the data in JSON format. What I'm getting back is this:
"[{\"model\": \"myapp.person\", \"pk\": 1, \"fields\": {\"first_name\": \"ahmet\", \"last_name\": \"arsan\", \"phone\": \"xxx-xxx-xxxx\", \"email\": \"aarsan#xxxxxxxx.com\"}}]"
While technically that is valid JSON, there are 2 problems (for me) with this response:
I don't want those double quotes escaped.
I don't need the model name (myapp.person).
I don't know if/what I'm doing wrong, but it seems like something is off here. Perhaps my query should be returning a dict but I don't know how to get it to do that. I am using Django 1.10.1, Python 3.4.
You already have an answer, but what you're doing wrong is double encoding. JsonResponse serialises to json, but you already have json as that's what's returned from the serialiser.
Either serialise to "python" or use a standard HttpResponse.
I am assuming you are asking this question for an API response. I would suggest using Rest Framework for that as it makes things very easy. You can select your own fields by writing your own serializers for the model.
from rest_framework import serializers
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = ('first_name', 'last_name', 'phone', 'email')

How to save a dictionary of objects?

I have a Python 3.5 program that creates an inventory of objects. I created a class of Trampolines (color, size, spring, etc.). I constantly will create new instances of the class and I then save a dictionary of them. The dictionary looks like this:
my_dict = {name: instance} and the types are like so {"string": "object"}
My issue is that I want to know how to save this inventory list so that I can start where I left off the last time I closed the program.
I don't want to use pickle because I'm trying to learn secure ways to do this for more important versions in the future.
I thought about using sqlite3, so any tips on how to do this easily would be appreciated.
My preferred solution would state how to do it with the json module. I tried it, but the error I got was:
__main__.Trampoline object at 0x00032432... is not JSON serializable
Edit:
Below is the code I used when I got the error:
out_file = open(input("What do you want to save it as? "), "w")
json.dump(my_dict, out_file, indent=4)
out_file.close()
End of Edit
I've done a good amount of research, and saw that there's also an issue with many of these save options that you can only do one object per 'save file', but that the work around to this is that you use a dictionary of objects, such as the one I made. Any info clarifying this would be great, too!
What you might be able to do is saving the instance's attributes to a CSV-file and then just create it when starting up. This might be a bit too much code and is possible not the best way. One obvious problem is that it doesn't work if you don't have the same amount of attributes as parameters, which should be possible to fix if necessary I believe. I just thought I might try and post and see if it helps :)
import json
class Trampoline:
def __init__(self, color, size, height, spring):
self.color = color
self.size = size
self.height = height
self.spring = spring
def __repr__(self):
return "Attributes: {}, {}, {}, {}".format(self.color, self.size, self.height, self.spring)
my_dict = {
"name1": Trampoline('red', 100, 2.3, True),
"name2": Trampoline('blue', 50, 2.1, False),
"name3": Trampoline('green', 25, 1.8, True),
"name5": Trampoline('white', 10, 2.6, False),
"name6": Trampoline('black', 0, 1.4, True),
"name7": Trampoline('purple', -33, 3.0, True),
"name8": Trampoline('orange', -999, 2.5, False),
}
def save(my_dict):
with open('save_file.txt', 'w') as file:
temp = {}
for name, instance in my_dict.items():
attributes = {}
for attribute_name, attribute_value in instance.__dict__.items():
attributes[attribute_name] = attribute_value
temp[name] = attributes
json.dump(temp, file)
def load():
with open('save_file.txt', 'r') as file:
my_dict = {}
x = json.load(file)
for name, attributes in x.items():
my_dict[name] = Trampoline(**attributes)
return my_dict
# CHECK IF IT WORKS!
save(my_dict)
my_dict = load()
print("\n".join(["{} | {}".format(name, instance) for name, instance in sorted(my_dict.items())]))
Here is an example of a class that handles datetime objects.
class CustomEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.datetime):
if obj.tzinfo:
obj = obj.astimezone(isodate.tzinfo.UTC).replace(tzinfo=None)
return obj.isoformat()[:23] + 'Z'
return json.JSONEncoder.default(self, obj)
when you encode to json the default function of the cls is called with object you passed. If you want to handle a type that is not part of the standard json.JSONEncoder.default you need to intercept it and return how you want it handled as a valid json type. In this example I turned the datetime into a str and returned that. If its not one of the types I want to special case, I just pass it along to the standard json.JSONEncoder.default handler.
To use this class you need to pass it in the cls param of json.dump or json.dumps:
json.dumps(obj, cls=CustomEncoder)
Decoding is done the same way but with json.JSONDecoder, json.load, and json.loads. However you can not match on type, so you will need to either add an 'hint' in encoding for decoding or know what type it needs to decode.
For a simple class, you can make an easy serializer as below. This will take all of the properties of your Trampoline object and put them into a dictionary and then into JSON.
class Trampoline(object):
...
def serialize(self):
return json.dumps(vars(self))
If your class is a bit more complicated, then write a more complicated serializer :)