I have django app that handle restAPIs with following JSON response, also i have ember app with django-ember-adapter. The problem is i can not get related items in the sub route(http://localhost:8000/api/projects/4/tasks) of API. I can get projects by http://localhost:8000/api/projects/4. Am i have to include task in project's django model in API?, I have tried using links in serializer but seems it's not working.
How do i get project's tasks successfully?
json response on http://localhost:8000/api/projects/4 i can get this by this.store.findRecord('project', params.project_id):
{
"id": 4,
"title": "test1 project",
"description": "some project",
"owner": "test1"
}
json response on http://localhost:8000/api/projects/4/tasks:
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"id": 5,
"title": "4th",
"description": "4tg description"
},
{
"id": 6,
"title": "",
"description": ""
}
]
}
django models:
class Project(models.Model):
"""Canara project """
title = models.CharField(max_length=50, blank=True, default='')
created = models.DateTimeField(auto_now_add=True)
description = models.CharField(max_length=255, blank=True)
active = models.BooleanField(default=True)
owner = models.ForeignKey(User, related_name='projects', null=True)
def __unicode__(self):
return self.title
class Task(models.Model):
title = models.CharField(max_length=55, blank=True)
description = models.CharField(max_length=255, blank=True)
created = models.DateTimeField(auto_now_add=True)
due_date = models.DateField(blank=True, null=True)
assign = models.ForeignKey(User, null=True)
project = models.ForeignKey(Project, null=True)
serializer:
class ProjectViewSet(ModelViewSet):
"""
API endpoint for project
"""
authentication_classes = [TokenAuthentication, SessionAuthentication]
permission_classes = (IsAuthenticated, IsOwner)
serializer_class = ProjectSerializer
def get_queryset(self):
"""
Get user's project
"""
return Project.objects.filter(members__member=self.request.user)
def perform_create(self, serializer):
"""
Set user as an owner when create project
"""
serializer.save(owner=self.request.user)
class TaskViewSet(ModelViewSet):
"""
API for task
"""
authentication_classes = [TokenAuthentication, SessionAuthentication]
permission_classes = [IsAuthenticated,]
serializer_class = TaskSerializer
queryset = Task.objects.all()
ember app's project model
export default Model.extend({
title: attr(),
description: attr(),
});
ember app's task model
export default Model.extend({
title: attr(),
description: attr(),
due_date: attr('date'),
assign: belongsTo('user'),
project: belongsTo('project')
});
Update
I have tried embedded record mixin but no luck
in the serializer:
class ProjectSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = Project
fields = ('id', 'title', 'description', 'owner', 'tasks')
def create(self, validated_data):
"""
Create project using given validated data
"""
return Project.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
Update project title description
"""
instance.title = validated_data.get('title', instance.title)
instance.description = validated_data.get(
'description', instance.description)
instance.save()
class TaskSerializer(serializers.ModelSerializer):
project = serializers.PrimaryKeyRelatedField(queryset=Project.objects.all())
class Meta:
model = Task
fields = ('id', 'title', 'description', 'due_date', 'assign', 'project')
def create(self, validated_data):
return Task.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.title = validated_data.get('title', instance.title)
instance.description = validated_data.get('description', instance.description)
instance.due_date = validated_data.get('due_date', instance.due_date)
instance.assign = validated_data.get('assign', instance.assign)
i've tried links in this way:
import DRFSerializer from './drf';
export default DRFSerializer.extend({
normalizeFindAllResponse(store, primaryModelClass, payload, id, requestType) {
console.log(payload)
payload.links: {
tasks: tasks
};
});
return this._super(...arguments);
}
});
Related
I am pretty new to DRF/Django and want to create an endpoint that returns nested json from multiple models in the format:
{
"site": {
"uuid": "99cba2b8-ddb0-11eb-bd58-237a8c3c3fe6",
"domain_name": "hello.org"
},
"status": "live",
"configuration": {
"secrets": [
{
"name": "SEGMENT_KEY", # Configuration.name
"value": [...] # Configuration.value
},
{
"name": "CONFIG_KEY",
"value": [...]
},
"admin_settings": {
'tier'='trail',
'subscription_ends'='some date',
'features'=[]
}
Here are the models:
class Site(models.Model):
uuid = models.UUIDField(
default=uuid.uuid4,
editable=False,
unique=True)
domain_name = models.CharField(max_length=255, unique=True)
created = models.DateTimeField(editable=False, auto_now_add=True)
modified = models.DateTimeField(editable=False, auto_now=True)
class AdminConfiguration(models.Model):
TRIAL = 'trial'
PRO = 'pro'
TIERS = [
(TRIAL, 'Trial'),
(PRO, 'Professional'),
]
site = models.OneToOneField(
Site,
null=False,
blank=False,
on_delete=models.CASCADE)
tier = models.CharField(
max_length=255,
choices=TIERS,
default=TRIAL)
subscription_ends = models.DateTimeField(
default=set_default_expiration)
features = models.JSONField(default=list)
class Configuration(models.Model):
CSS = 'css'
SECRET = 'secret'
TYPES = [
(CSS, 'css'),
(SECRET, 'secret')
]
LIVE = 'live'
DRAFT = 'draft'
STATUSES = [
(LIVE, 'Live'),
(DRAFT, 'Draft'),
]
site = models.ForeignKey(Site, on_delete=models.CASCADE)
name = models.CharField(max_length=255, blank=False)
type = models.CharField(
max_length=255,
choices=TYPES)
value = models.JSONField(
null=True)
status = models.CharField(
max_length=20,
choices=STATUSES)
Logic behind serializer/viewset to achieve mentioned json:
retrieves lookup_field: uuid
filters query param: Configuration.status (either live or draft
filters AdminConfiguration on site id (something like AdminConfiguration.objects.get(Site.objects.get(uuid))
filters Configuration on type = secret
Here are my serializers:
class SiteSerializer(serializers.ModelSerializer):
class Meta:
model = Site
fields = [
'uuid',
'domain_name'
]
class AdminSerializer(serializers.ModelSerializer):
class Meta:
model = AdminConfiguration
fields = [
'tier',
'subscription_ends',
'features'
]
class ConfigurationSubSerializer(serializers.ModelSerializer):
class Meta:
model = Configuration
fields = [
'name',
'value',
]
class SecretsConfigSerializer(serializers.ModelSerializer):
site = SiteSerializer()
admin_settings = AdminSerializer()
status = serializers.CharField()
configuration = ConfigurationSubSerializer(many=True, source='get_secret_config')
class Meta:
model = Configuration
fields = [
'site',
'admin_settings',
'status'
'configuration'
]
def get_secret_config(self, uuid):
site = Site.objects.get(uuid=self.context['uuid'])
if self.context['status'] == 'live' or self.context['status'] == 'draft':
return Configuration.objects.filter(
site=site,
status=self.context['status'],
type='secret'
)
Viewset:
class SecretsViewSet(viewsets.ReadOnlyModelViewSet):
model = Site
lookup_field = 'uuid'
serializer_class = SecertsConfigSerializer
filter_backends = (DjangoFilterBackend,)
filterset_fields = ['status'] #query params
def get_serializer_context(self):
return {
'status': self.request.GET['status'],
'uuid': self.request.GET['uuid']
}
def get_serializer(self, *args, **kwargs):
kwargs['context'] = self.get_serializer_context()
return CombinedConfigSerializer(*args, **kwargs)
What am I missing to achieve the desired output?
output from django shell:
from site_config.models import Site, AdminConfiguration, Configuration
from site_config.serializers import SecretsConfigSerializer
site = Site.objects.get(id=2)
s = SecretsConfigSerializer(site)
s.data
### OUTPUT ###
AttributeError: Got AttributeError when attempting to get a value for field `site` on serializer `SecretsConfigSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Site` instance.
Original exception text was: 'Site' object has no attribute 'site'.
Why you don't try something more general and build your response separating the serializers like this (maybe you can use the same serializers in somewhere else):
def get(self, request, *args, **kwargs):
resp = {
'site': None,
'status': None,
'configuration': None,
'admin_settings': None,
}
sites = models.Site.objects.all()
resp['site'] = serializers.SitesSerializer(sites, many=True).data
admin_settings = models.AdminConfiguration.objects.all()
resp['admin_settings'] = serializers.AdminConfigurationSerializer(admin_settings, many=True).data
# and so
return Response(resp, status=status.HTTP_200_OK)
** You can try like this. This will also help to findout errors**
def get(self, request, *args, **kwargs):
resp = {
"site": None,
"status": None,
"configuration": None,
"admin_settings": None
}
sites = models.Site.objects.all()
resp['site'] = serializers.SitesSerializer(sites, many=True).data
if resp['site'].is_valid():
admin_settings = models.AdminConfiguration.objects.all()
resp['admin_settings'] = serializers.AdminConfigurationSerializer(admin_settings, many=True).data
if resp['admin_settings'].is_valid():
return Response(resp, status=status.HTTP_200_OK)
return Response(resp['admin_settings'].errors, status=status.HTTP_404_NOT_FOUND)
return Response(resp['site'].errors, status=status.HTTP_404_NOT_FOUND)
Im working on a django project, and I have a model that looks like this:
class Comment(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1,on_delete=models.CASCADE)
slug = models.SlugField(unique=True,null=True, blank=True)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
parent = models.ForeignKey("self", null=True, blank=True,on_delete=models.CASCADE)
content = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True)
objects = CommentManager()
def __str__(self):
return str(self.user.username)
I use this serializer:
class CommentDetailSerializer(ModelSerializer):
user = SerializerMethodField()
replies = SerializerMethodField()
class Meta:
model = Comment
fields = [
'user',
'content',
'replies',
'timestamp'
]
def get_user(self,obj):
return str(obj.user.username)
def get_replies(self,obj):
if obj.is_parent:
return CommentChildSerializer(obj.children(),many=True).data
for this view:
class CommentDetailApiView(RetrieveAPIView):
queryset = Comment.objects.all()
serializer_class = CommentDetailSerializer
lookup_field = 'slug'
here is the PostSerializer I use,
class PostSerializer(ModelSerializer):
user = SerializerMethodField()
comments = SerializerMethodField()
class Meta:
model = Post
fields = [
'user',
'title',
'content',
'comments'
]
def get_user(self,obj):
return str(obj.user.username)
def get_comments(self,obj):
comments_qs = Comment.objects.filter_by_instance(obj)
comments = CommentSerializer(comments_qs, many=True).data
return comments
this is what I get in the PostDetailAPIView:
{
"user": "abc",
"title": "blabla",
"content": "bla",
"comments": [
{
"user": 1,
"content": "hey",
"timestamp": "2020-02-18T00:07:29.932850Z"
}
]
}
How do I get the comment user username instead of its id?
I get the username of the comment user only in the CommentDetailApiView.
Thank you
I added the get_user method to the CommentSerializer and now it works:
class CommentSerializer(serializers.ModelSerializer):
user = SerializerMethodField()
class Meta:
model = Comment
fields = [
'user',
'content',
'timestamp'
]
def get_user(self,obj):
return str(obj.user.username)
You can add an UserSerializer to indicate what you want to show of the user:
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username',)
class CommentDetailSerializer(ModelSerializer):
user = UserSerializer()
..... # the rest of your code
You need to override to_representation() function just like below:
def to_representation(self, instance):
"""Convert `username` to lowercase."""
ret = super().to_representation(instance)
ret['username'] = ret['username'].lower()
return ret
The above code is just an example. It will return username in lowercase letters. In your case just change the line
ret['username'] = ret['username'].lower()
to
ret['username'] = self.validated_data['username']
StringRelatedField may be used to represent the target of the relationship using its __str__ method.
For example, the following serializer.
class CommentDetailSerializer(serializers.ModelSerializer):
user = serializers.StringRelatedField()
...
class Meta:
model = Comment
fields = [
'user',
'content',
'replies',
'timestamp'
]
...
So I have a model called 'Task' and the task has 'UserProfile's working on them. These 'UserProfile' models are just normal Users but inside the App of the 'Task'.
This is the API I have right now:
"tasks": [
{
"id": 1,
"name": "Läs På Internet",
"description": "asdasdasdasa",
"created": "2019-06-08",
"deadline": "2019-06-19",
"state": "new",
"stickers": [
{
"id": 1,
"name": "Detta är en sticker",
"content": "Sillicon Valley",
"created": "2019-06-08",
"creator": {
"id": 1,
"user": 1
}
}
],
"checkmarks": [
{
"id": 1,
"name": "Googla",
"checked": false
}
],
"workers": [
{
"id": 1,
"user": 1
}
]
},
{
"id": 2,
"name": "Läs i böcker",
"description": "aaa",
"created": "2019-06-10",
"deadline": "2019-06-25",
"state": "done",
"stickers": [],
"checkmarks": [],
"workers": [
{
"id": 1,
"user": 1
}
]
}
],
As you can see every user now just has 'id' and 'user', which are both the ID. How do I get the username for every user and display them in the rest api?
# Users
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
class Meta:
verbose_name_plural = 'All Users'
def __str__(self):
return self.user.username
#receiver(post_save, sender=User)
def create_user_data(sender, update_fields, created, instance, **kwargs):
if created:
user = instance
profile = UserProfile.objects.create(user=user)
#Tasks
class Task(models.Model):
name = models.CharField(max_length=200)
description = models.CharField(max_length=2000)
created = models.DateField(default=date.today)
deadline = models.DateField(default=date.today)
state = models.CharField(max_length=20, default='new')
stickers = models.ManyToManyField(Sticker, blank=True)
checkmarks = models.ManyToManyField(Checkmark, blank=True)
workers = models.ManyToManyField(UserProfile, blank=True, related_name='TaskWorkers')
class Meta:
verbose_name_plural = 'Tasks'
def __str__(self):
return "{name}".format(name=self.name)
serializers:
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ('id', 'user')
class TaskSerializer(serializers.ModelSerializer):
stickers = StickerSerializer(many=True, read_only=True)
checkmarks = CheckmarkSerializer(many=True, read_only=True)
workers = UserProfileSerializer(many=True, read_only=True)
class Meta:
model = Task
fields = ('id', 'name', 'description', 'created', 'deadline', 'state', 'stickers', 'checkmarks', 'workers')
views:
class TaskView(viewsets.ModelViewSet):
http_method_names = ['get', 'post', 'put', 'delete', 'patch']
queryset = Task.objects.all()
serializer_class = TaskSerializer
class UserTaskView(TaskView):
def get_queryset(self):
return Task.objects.filter(workers__user=self.request.user)
The User Views are for displaying only for the users that are assigned!
You can either update the user serializer inside your UserProfileSerializer with something like this (creating a serializer with the fields you want for the User):
class UserProfileSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = UserProfile
fields = ('id', 'user')
or this (accessing user fields from within the UserProfileSerializer):
class UserProfileSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
class Meta:
model = UserProfile
fields = ('id', 'user', 'username')
As documented below in
https://www.django-rest-framework.org/api-guide/serializers/#writable-nested-representations
You can get the user profile details inside the tasks as:
class TaskSerializer(serializers.ModelSerializer):
......
user = UserSerializer()
class Meta:
model = Task
fields = (...., 'user',....., 'workers')
This will return all the fields in the the user model as part of the task nested json
I am trying to run custom validation on a serializer in Django. The serializer is as simple as:
class PostsSerializer(serializers.ModelSerializer):
user = UserSerializer(many=False, required=False, allow_null=True)
def validate(self, data):
print('Validating')
print(data)
return data
class Meta:
model = Post
fields = ["id", "user", "type", "title", "content", "created_ts"]
read_only_fields = ["id", "user", "created_ts"]
And the serializer is called as such:
def create_post(self, request):
serializer = PostsSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
#echo something
else:
fail
And the model is this:
from django.apps import apps
from django.db import models
from ..enums import PostTypes
class Post(models.Model):
user = models.ForeignKey("auth.User", on_delete=models.DO_NOTHING)
type = models.IntegerField(choices=[(tag.name, tag.value) for tag in PostTypes])
title = models.TextField()
content = models.TextField()
class Meta:
db_table = "post"
ordering = ["-created_ts"]
verbose_name = "Post"
verbose_name_plural = "posts"
Any idea what would be causing the validate function not to execute?
I'm using Django 2.x and Django REST Framework
My models.py file contents
class ModeOfPayment(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField()
class AmountGiven(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
contact = models.ForeignKey(Contact, on_delete=models.PROTECT)
amount = models.FloatField()
mode_of_payment = models.ForeignKey(
ModeOfPayment,
on_delete=models.PROTECT,
blank=True,
default=None,
null=True
)
and serializers.py
class ModeOfPaymentSerializer(serializers.ModelSerializer):
class Meta:
model = ModeOfPayment
fields = ('id', 'title')
class AmountGivenSerializer(serializers.ModelSerializer):
mode_of_payment = ModeOfPaymentSerializer()
class Meta:
model = AmountGiven
depth = 1
fields = (
'id', 'contact', 'amount', 'mode_of_payment',
)
def update(self, instance, validated_data):
mode_of_payment = validated_data.pop('mode_of_payment')
instance.mode_of_payment_id = mode_of_payment.id
return instance
and views.py
class AmountGivenViewSet(viewsets.ModelViewSet):
serializer_class = AmountGivenSerializer
permission_classes = (IsAuthenticated, AdminAuthenticationPermission,)
filter_fields = ('contact__id',)
def get_queryset(self):
queryset = AmountGiven.objects.filter(
contact__user=self.request.user
)
return queryset
But when I post data using postman with PUT method to update the existing record
It still says
{
"mode_of_payment": [
"This field is required."
]
}
Edit 2: Response after Daniel answer
{
"id": "326218dc-66ab-4c01-95dc-ce85f226012d",
"contact": {
"id": "b1b87766-86c5-4029-aa7f-887f436d6a6e",
"first_name": "Prince",
"last_name": "Raj",
"user": 3
},
"amount": 3000,
"mode_of_payment": "0cd51796-a423-4b75-a0b5-80c03f7b1e65",
}
You've told AmountSerializer to accept a nested dict representing a ModeOfPayment instance, by setting the mode_of_payment field to ModeOfPaymentSerializer. But that's not what you're sending; you're sending the ID of the ModeOfPayment.
You should remove that line in AmountGivenSerializer.
Edit
I was wrong, you need to declare the field explicitly as a PrimaryKeyRelatedField:
class AmountGivenSerializer(serializers.ModelSerializer):
mode_of_payment = serializers.PrimaryKeyRelatedField(queryset=ModeOfPayment.objects.all())
class Meta:
...
Now it will accept a UUID in the data.