Given some model
class Loan(models.Model):
time_of_loan = models.DateTimeField()
username = models.CharField()
I have attempted to use the ModelSerializer from Django's REST Framework to serialize the Loan.
class LoanSerializer(serializers.ModelSerializer):
time_of_loan = serializers.DateTimeField(
format=None, input_formats=['%Y-%m-%d %H:%M:%S',])
class Meta:
model = `Loan`
fields = ['time_of_loan', 'username']
On using the serializer.data to get the JSON format, when I save the first time, the first time the model is saved, the JSON is well-behaved.
{
'time_of_loan': '2016-06-20 00:00:00+08:00',
'username': 'doe'
}
However, when I attempt to update the model, it "misbehaves" and it appears in a python datetime format.
{
'time_of_loan': datetime.datetime(2016, 6, 20, 7, 55, tzinfo=<UTC>),
'username': 'doe'
}
What change do I need to do so that, whenever the model gets serialized, it remains as the first format that I want?
FIRST EDIT
Can you show what you're doing to update the object
The question asked was what I did to update the model. I actually am using this as an audit log and so it took from an actual Django Form. In forms.py:
id = forms.cleaned_data.get('id')
username = forms.cleaned_data.get('username')
loan = Loan.objects.filter(id=id) #Queryset with count() = 1
loan.update(username=username)
loan_obj = loan[0]
serializer = LoanSerializer(loan_obj)
print(serializer.data)
After so much finding, I finally got the answer.
from rest_framework.renderers import JSONRenderer
serializer = LoanSerializer(loan_obj)
serializer.data
json = JSONRenderer().render(serializer.data)
Related
I am trying to fetch the values from TypingTest model. The foreign key is referencing to the UserProfile model. While fetching the data I want the username to be displayed in the result of the query as well. So, after researching a bit I found out that select_related could be a way of doing this. But when I am trying to use select_related. I am not able to see the username, Only the value of the primary key is getting shown.
These are the two models that I have.
class UserProfile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
userName = models.CharField(max_length=26,unique=True)
email = models.EmailField(max_length=254,unique=True)
profilePhoto = models.ImageField(default="/default.png")
rank = models.CharField(max_length=26,default="Novice")
def __str__(self):
return self.userName
class TypingTest(models.Model):
user = models.ForeignKey(UserProfile,on_delete=models.CASCADE)
time = models.IntegerField()
wpm = models.IntegerField()
accuracy = models.IntegerField()
raw = models.IntegerField()
dateTaken = models.DateTimeField(auto_now_add=True)
Correspoding serializer class:
class TypingTestSerializer(serializers.ModelSerializer):
class Meta :
model = TypingTest
fields = ('id','user','time','wpm','accuracy','raw','dateTaken')
class UserSerializer(serializers.ModelSerializer):
class Meta :
model = UserProfile
fields = ('userName','email','profilePhoto','rank')
This is the view code that I have written.
#protected_resource()
#api_view(['GET'])
def getAllTests (request):
testListQuerySet = models.TypingTest.objects.all()
selectRelated = models.TypingTest.objects.select_related('user').only("userName")
print(selectRelated.values())
serializer=serializers.TypingTestSerializer(models.TypingTest.objects.select_related('user'),many=True)
return Response(serializer.data)
The output that I am getting is :
<QuerySet [{'id': 1, 'user_id': 3, 'time': 60, 'wpm': 60, 'accuracy': 91, 'raw': 79, 'dateTaken': datetime.datetime(2023, 2, 14, 21, 7, 32, 721899, tzinfo=datetime.timezone.utc)}, {'id': 2, 'user_id': 4, 'time': 30, 'wpm': 82, 'accuracy': 99, 'raw': 85, 'dateTaken': datetime.datetime(2023, 2, 14, 21, 33, 45, 326814, tzinfo=datetime.timezone.utc)}]>
Please let me know if this is the right way of doing the thing intended and if not what is?
To include the userName field in the output, you should use the UserSerializer to serialize the related UserProfile object instead. Currently, you should use the source argument to specify the related field name in the TypingTest model, and the UserSerializer to serialize the related object so:
class TypingTestSerializer(serializers.ModelSerializer):
userName = serializers.CharField(source='user.userName')
class Meta:
model = TypingTest
fields = ('id','user','userName','time','wpm','accuracy','raw','dateTaken')
Then in view, remove the select_related method call as it is not necessary so:
#protected_resource()
#api_view(['GET'])
def getAllTests (request):
testListQuerySet = models.TypingTest.objects.all()
serializer = serializers.TypingTestSerializer(testListQuerySet, many=True)
return Response(serializer.data)
if you want to get a username of all objects then you have to query like this
TypingTest.objects.select_related('user').values('userName')
select_related is just to fetch related in the same query and preventing to make a new DB hit for accessing FK. Read about it here.
In order to get user data, you need no get it, and select_related cause no more DB hit. You can get it by this way for example:
user = models.TypingTest.objects.select_related('user').first().user
And if you want to represent it in your response, you have to add user to your seriliazer fields:
class TypingTestSerializer(serializers.ModelSerializer):
class Meta:
model = TypingTest
fields = ("user", ...)
Using the Django REST Framework 2.2, I have a Person model as follows in models.py::
class Person(models.Model):
id = models.CharField(max_length = 20, primary_key = True, blank = True)
name = Models.CharField(max_length = 1024, blank = True)
values = {}
#staticmethod
def create_person(personData):
person = Person(
name = personData.get("name", "Unknown"),
values = personData.get("values", {}),
)
return person
All data is stored in a Firestore database for saving and retrieving data via the REST API. Before new entries are made into the database, a serializer is used to validate incoming POST data.
The route /person takes POST request data and runs it by the PersonCreateSerializer in
views.py:
def create_person(request):
"""
Route: /person
Method: POST
"""
try:
print(request.data)
# Above print outputs:
# <QueryDict: {'name': ['John Doe'], 'values': ['{ "height": 180 }']}>
serializer = PersonCreateSerializer(data = request.data)
serializer.is_valid(raise_exception = True)
person = Person.create_person(request.data)
...
except APIException as exception:
return JsonResponse(exception.APIError, status = exception.status)
serializers.py:
class PersonCreateSerializer(CreateModelSerializer):
class Meta:
model = Person
fields = "__all__"
def validate(self, data):
print(data)
# Above print outputs:
# OrderedDict([('name', 'John Doe')])
# Notice missing 'values' field.
if not data.get("values"): # Ensure we have a values field within the data.
raise APIException("ERROR_MISSING_FIELD", "Missing required field 'values'.", 400)
return data
The problem is however any value provided for the values dictionary is discarded when the serializer validate() function receives it.
POST Payload:
My question is why is the dictionary received from the POST request not received by the serializer so it can be parsed? What is the correcy way to create dictionary fields in Django?
Sent to Serializer:
<QueryDict: {'name': ['John Doe'], 'values': ['{ "height": 180 }']}>
Received by Serializer:
OrderedDict([('name', 'John Doe')])
The problem with JSONField and HStoreField
I have looked at alternatives mentioned such as HStoreField and JSONField however this data is being stored in a Firestore database and the key-value association needs to be retained rather than it being stored as a plain JSON string.
Because the data is being stored in Firestore, the structure of the dictionary array needs to be retained as a map in the database, this allows it to be indexed and queried with Firestore queries.
If we use JSONField, this simply converts the value to a string and removes this functionality.
I believe it is because values is not a field. It is just a class variable.
Serializer filters the data by fields you have pointed to. And you pointed to __all__ which means all fields in model.
You can try:
fields = ['name', 'values']
And if it didn't work, make a function and pass it "as a field":
# models.py
class Person(models.Model):
id = models.CharField(max_length = 20, primary_key = True, blank = True)
name = Models.CharField(max_length = 1024, blank = True)
values = {}
def get_values(self):
return self.values
# serializers.py
class PersonCreateSerializer(CreateModelSerializer):
class Meta:
model = Person
fields = ['name', 'get_values']
The solution I found was to make use of the django-dictionaryfield module, this provides a Dictionary field type that can be used for converting to and from all array types, such as dictionaries and lists.
Without a field declared in the model.py, the serializer ignores it since it isn't considered part of the model itself, therefore using a custom DictionaryField model allows it to be stored as a Django model field.
Django DictionaryField Setup
Install the module into your project:
$ pip install django-dictionaryfield
Add dictionaryfield into your INSTALLED_APPS in the Django configuration file:
INSTALLED_APPS = (
...
"dictionaryfield",
)
Model Class
Use the DictionaryField for fields that should be arrays.
from django.db import models
from dictionaryfield import DictionaryField
class Person(models.Model):
id = models.CharField(max_length = 20, primary_key = True, blank = True)
name = Models.CharField(max_length = 1024, blank = True)
values = DictionaryField(default = {})
#staticmethod
def create_person(personData):
person = Person(
name = personData.get("name", "Unknown"),
values = personData.get("values", {}),
)
return person
I have a Models like below
class Member(models.Model):
memberid = models.AutoField(primary_key=True, editable=False)
memberdate = models.DateTimeField(default=timezone.now)
fname = models.CharField(max_length=25)
mname = models.CharField(max_length=25)
lname = models.CharField(max_length=25)
mobile1 = models.CharField(max_length=15)
email = models.CharField(max_length=150)
dob = models.DateTimeField(default=timezone.now)
I am fetching data to display in the html template. For the view below is the code
def updateMemberView(request, id):
searchmem= id
member = Member.objects.filter(memberid=searchmem).values()
print(member[0])
return render(request, 'Member/member_update_form.html', {"member": member})
Now in print(member[0]) I am getting
{'memberid': 13, 'memberdate': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'fname': 'Akash', 'mname': 'chimanbhai', 'lname': 'khatri', 'mobile1': '', 'email': 'kashkhatri#yahoo.com', 'dob': datetime.datetime(2020, 4, 3, 0, 0, tzinfo=<UTC>)}
But when I try to print the value of dob in template using member.0.dob it gives me error.
Also when I try to execute command
print(member[0].dob)
this also give me error
'dict' object has no attribute 'dob'
So How could I get the dob value in view and also in template.
This is a dictionary. You access the value corresponding to a key in a dictionary by subscripting:
print(member[0]['dob'])
That being said, using .values(..) [Django-doc] should be used scarcely, especially since you erase the logical layer of the model. For example foreign keys are no longer retrieved lazily, you only have the value of the primary key.
You can simply pass the model object to the template:
from django.shortcuts import get_object_or_404
def update_member_view(request, id):
member = get_object_or_404(Member, memberid=id)
return render(request, 'Member/member_update_form.html', {'member': member})
Some common mistakes:
functions are normally written in slug_case, not in PerlCase, or camelCase;
it is better to use get_object_or_404 to return a HTTP 404 exception if the object can not be found; and
as said before, pass the Member object itself, that way you can add extra methods to the model that you can use in the tempate.
Is it possible to POST a new object while also specifying its id instead of auto incrementing? This is a one time import and the database id sequence would be corrected afterward.
class TestModelSerializer(serializers.ModelSerializer):
class Meta:
model = TestModel
fields = ('id', 'name')
class TestModelViewSet(viewsets.ModelViewSet):
queryset = TestModel.objects.all()
serializer_class = TestModelSerializer
import requests
def test_post(endpoint):
data = {
"id": 30,
"name": "Test",
}
r = requests.post(endpoint, data=data)
print(r.status_code, r.reason)
test_post('http://localhost:8000/api/test_model/30/')
>>> 405 Method Not Allowed
test_post('http://localhost:8000/api/test_model/')
>>> 201 Created
201 Created creates a new object but with the next id in sequence instead of the desired id.
I've also tried r = requests.put('http://localhost:8000/api/test_model/30/', data=data) but get 404 Not Found
This could work but i'm not sure, I believe django-rest is reading from your django model that id is an auto-increment field, and hence doesn't create with the id provided. But it could work if you specify it explicitly as an integer field.
class TestModelSerializer(serializers.ModelSerializer):
id = serializers.IntegerField()
class Meta:
model = TestModel
fields = ('id', 'name')
I have a model is pretty straight forward and I've created a property on the model to essentially return child data as JSON. The property is simple:
#property
def question_data(self):
from api.models import TemplateQuestion
questions = TemplateQuestion.objects.filter(template__id=self.template.id)
question_dict = [obj.as_dict() for obj in questions]
return(json.dumps(question_dict, separators=(',', ': ')))
Which does its job and outputs valid JSON. That said I'm at a total loss of how to add that property to the Serializer as JSON and not a string like
{
"questions": "[{\"sequence\": 2,\"next_question\": \"\",\"modified_at\": \"2016-01-27T19:59:07.531872+00:00\",\"id\": \"7b64784e-a41d-4019-ba6e-ed8b31f99480\",\"validators\": []},{\"sequence\": 1,\"next_question\": null,\"modified_at\": \"2016-01-27T19:58:56.587856+00:00\",\"id\": \"99841d91-c459-45ff-9f92-4f75c904fe1e\",\"validators\": []}]"
}
It's stringifying the JSON and which I need as proper JSON.
Serializer is probably too basic but I haven't worked with DRF in a while and have never tried to append JSON to the serialized output.
class BaseSerializer(serializers.ModelSerializer):
class Meta:
abstract = True
class SurveySerializer(BaseSerializer):
included_serializers = {
'landing_page': 'api.serializers.LandingPageSerializer',
'trigger': 'api.serializers.TriggerSerializer',
'template': 'api.serializers.TemplateSerializer'
}
questions = serializers.ReadOnlyField(source='question_data')
class Meta:
model = Survey
fields = ('id',
'name',
'slug',
'template',
'landing_page',
'trigger',
'trigger_active',
'start_date',
'end_date',
'description',
'fatigue_limit',
'url',
'questions',)
meta_fields = ('created_at', 'modified_at')
I'll add that I'm also bolting on the Django Rest Framework JSON API formatting but I think that in the end I'm just not getting how to append JSON to a model's serialization without it being returned as a string.
You should not be dumping the results of the method to JSON. Just return the dict; DRF's serializer will take care of converting it.