How to POST Model with Many to Many through in Django REST - django

I have a model with a many to many connection. I would like to make this model available in Django REST. By default such a model is read only, but I would also like to write. Furthermore, it would be great to get the information of the through connection integrated into the GET as a nested model.
...
class KeyDateCase(models.Model):
...
diagnoses_all_icd_10 = models.ManyToManyField(
'ICD10', through='CaseICD10Connection')
...
class CaseICD10Connection(models.Model):
case = models.ForeignKey('KeyDateCase', on_delete=models.CASCADE)
icd_10 = models.ForeignKey('ICD10', on_delete=models.CASCADE)
is_primary = models.BooleanField(default = False)
certainty = models.CharField(
max_length=1,
choices=CERTAINTY_CHOICES,
default='G',
)
class ICD10(models.Model):
primary_key_number = models.CharField(max_length=10, primary_key=True)
star_key_number = models.CharField(max_length=10, blank=True, null=True)
additional_key_number = models.CharField(
max_length=10, blank=True, null=True)
preferred_short_description = models.CharField(max_length=128, )
...
class KeyDateCaseViewSet(viewsets.ModelViewSet):
???
class KeyDateCaseSerializer(serializers.ModelSerializer):
???
How can I achieve this? What should my view and serializer look like?

Normally I workaround by indirect way by POST to through table and implement nested-create(). Please provide me more information if my answer is inaccurate.
models.py
from django.db import models
class ICD10(models.Model):
primary_key_number = models.CharField(max_length=10, primary_key=True)
star_key_number = models.CharField(max_length=10, blank=True, null=True)
additional_key_number = models.CharField(max_length=10, blank=True, null=True)
preferred_short_description = models.CharField(max_length=128, )
def __str__(self):
return f'{self.primary_key_number} {self.star_key_number}'
class CaseICD10Connection(models.Model):
case = models.ForeignKey('KeyDateCase', related_name='connections', related_query_name='key_date_cases', on_delete=models.CASCADE)
icd_10 = models.ForeignKey('ICD10', related_name='connections', related_query_name='icd_10s', on_delete=models.CASCADE)
is_primary = models.BooleanField(default=False)
certainty = models.CharField(max_length=1, default='G', )
class KeyDateCase(models.Model):
name = models.CharField(max_length=20)
diagnose_all_icd_10 = models.ManyToManyField(ICD10, related_name='icd10s', related_query_name='icd10s',
through=CaseICD10Connection)
serializers.py
from rest_framework import serializers
from keydatecases.models import KeyDateCase, ICD10, CaseICD10Connection
class KeyDateCaseSerializer(serializers.ModelSerializer):
class Meta:
model = KeyDateCase
fields = [
'id',
'name',
'diagnose_all_icd_10',
]
read_only_fields = ['id', 'diagnose_all_icd_10']
class ICD10Serializer(serializers.ModelSerializer):
class Meta:
model = ICD10
fields = [
'primary_key_number',
'star_key_number',
'additional_key_number',
'preferred_short_description',
]
class CaseICD10ConnectionSerializer(serializers.ModelSerializer):
case = KeyDateCaseSerializer()
icd_10 = ICD10Serializer()
class Meta:
model = CaseICD10Connection
fields = [
'case',
'icd_10',
'is_primary',
'certainty',
]
def create(self, validated_data) -> CaseICD10Connection:
# import ipdb;
# ipdb.set_trace()
# create key_date_case
key_date_case = KeyDateCase.objects.create(**validated_data.get('case'))
# create icd10
icd10 = ICD10.objects.create(**validated_data.get('icd_10'))
# create connection
conn = CaseICD10Connection.objects.create(
case=key_date_case, icd_10=icd10, is_primary=validated_data.get('is_primary'),
certainty=validated_data.get('certainty')
)
return conn
viewsets.py
from rest_framework import viewsets
from keydatecases.api.serializers import CaseICD10ConnectionSerializer
from keydatecases.models import CaseICD10Connection
class CaseICD10ConnectionViewSet(viewsets.ModelViewSet):
permission_classes = ()
queryset = CaseICD10Connection.objects.all()
serializer_class = CaseICD10ConnectionSerializer
My Repository:
I share my repository with many questions. Please do not mind it.
https://github.com/elcolie/tryDj2

In regards to creating or updating nested objects, the documentation actually has a great example. I would provide you a better one if I could. If there is anything confusing in the example, happy to explain it here.
If you follow this approach, your GET requests will expand the nested objects automatically for you.

Related

django serializer error: images_data = self.context['request'].FILES KeyError: 'request'

models.py
#
from django.db import models
from user.models import User
from chat.models import TradeChatRoom, AuctionChatRoom
class Goods(models.Model):
class Meta:
db_table = 'Goods'
ordering = ['-created_at'] # 일단 추가해뒀습니다
seller = models.ForeignKey(User, on_delete=models.CASCADE, related_name='sell_goods')
buyer = models.ForeignKey(User, on_delete=models.CASCADE, related_name='buy_goods', null=True)
trade_room = models.ForeignKey(TradeChatRoom, on_delete=models.CASCADE)
auction_room = models.ForeignKey(AuctionChatRoom, on_delete=models.CASCADE)
title = models.CharField(max_length=256)
content = models.TextField()
category = models.CharField(max_length=32)
status = models.BooleanField(null=True)
predict_price = models.IntegerField()
start_price = models.IntegerField()
high_price = models.IntegerField(null=True)
start_date = models.DateField(null = True)
start_time = models.DateTimeField(null=True)
created_at = models.DateTimeField(auto_now_add=True)
like = models.ManyToManyField(User, related_name='like_goods', null=True)
class GoodsImage(models.Model):
class Meta:
db_table = "GoodsImage"
goods = models.ForeignKey(Goods, on_delete=models.CASCADE)
image = models.ImageField(upload_to='goods/')
serializer.py
from rest_framework import serializers
from .models import Goods,GoodsImage
class GoodImageSerializer(serializers.ModelSerializer):
image = serializers.ImageField(use_url=True)
def get_image(self, obj):
image = obj.goods_set.all()
return GoodsPostSerializer(instance=image, many = True, context = self.context)
class Meta:
model = GoodsImage
field =('image',)
class GoodsPostSerializer(serializers.ModelSerializer):
image = GoodImageSerializer(many=True, read_only = True)
class Meta:
model = Goods
fields = (
'seller', 'buyer','auction_room','title','content',
'category','status','predict_price','start_price','high_price',
'trade_room','start_date','start_time','created_at','like','image',
)
read_only_fields = ("seller",)
def create(self, validated_data):
goods = Goods.objects.create(**validated_data)
images_data = self.context['request'].FILES
for image_date in images_data.getlist('image'):
GoodsImage.objects.create(goods = goods, image = image_date)
return goods
error
images_data = self.context['request'].FILES
KeyError: 'request'
I want to save multiple images, but I keep getting an error. I don't know what to do anymore.
I searched for a method and followed it, but it seems that I am the only one who gets an error.
Please help if you know how to solve this problem.
And I want to know if it is correct to put it in a list like "image":["12.jpeg,"13.jpeg] when inserting multiple images through postman.
It's hard not being able to solve this problem. please help me if you know the answer
Change GoodImageSerializer calling this:
GoodImageSerializer(instance=images, many = True, context={'request': request})
Then change your GoodsPostSerializer's create method like this:
def get_image(self, obj):
image = obj.goods_set.all()
request = self.context['request']
return GoodsPostSerializer(instance=image, many = True, context={'request': request})

Getting an Aggregate Value from a Many-to-Many Field inside a Serializer

We have an API endpoint that generates a response. However, we would like to aggregate some of the data and add an additional field to the serializer.
{
"id": 61,
"not_owned_value": # This is what we're trying to get (Aggregate best_buy_price from not_owned_inventory PlayerProfiles)
"not_owned_inventory": [ # PlayerProfile objects
"29666196ed6900f07fc4f4af4738bffe",
"0ff73ca20cd787c5b817aff62e7890da",
"99d4eaef9991695d7ad94b83ad5c5223",
"6fcabe9f9c8a95980923530e7d7353a7",
"80b34c84a6e5ed25df112c11de676adc",
"0a4c5b96474f0584519d1abc4364d5a2",
"9ed1f55ac4f3b402b1d08b26870c34a6",
]
}
Here is the models.py
class PlayerProfile(models.Model):
card_id = models.CharField(max_length=120, unique=True, primary_key=True)
name = models.CharField(max_length=120, null=True)
class PlayerListing(models.Model):
player_profile = models.OneToOneField(
PlayerProfile,
on_delete=models.CASCADE,
null=True)
best_buy_price = models.IntegerField(null=True)
class InventoryLiveCollection(models.Model):
not_owned_inventory = models.ManyToManyField(PlayerProfile, related_name="not_owned_inventory")
Here is the serializer.py
class InventoryLiveCollectionSerializer(serializers.ModelSerializer):
not_owned_inventory = PlayerProfileAndListingForNesting(read_only=True, many=True)
class Meta:
model = InventoryLiveCollection
fields = (
'id',
'date',
'not_owned_value', # Trying to get this
'not_owned_inventory',
)
How can I aggregate the best_buy_price of the player_profile objects that belong to a specific InventoryLiveCollection__not_owned_inventory?
You can try this.
from django.db.models import Sum
not_owned_value = serializers.SerializerMethodField()
def get_not_owned_value(self, obj):
value = obj.not_owned_inventory.all().aggregate(total=Sum('playerlisting__best_buy_price'))
return value['total']

Import column from another table

I made API with Django Restframework.
[models.py]
from django.db import models
class Article(models.Model):
article_no = models.AutoField(primary_key=True)
content = models.CharField(max_length=500, null=False)
password = models.CharField(max_length=20, null=False, default='1234')
date = models.DateTimeField(auto_now_add=True)
class Comment(models.Model):
article_no = models.ForeignKey('Article', on_delete=models.CASCADE)
content = models.CharField(max_length=50, null=False, default='')
password = models.CharField(max_length=20, null=False, default='1234')
date = models.DateTimeField(auto_now_add=True)
[views.py]
class ArticleDetail(APIView):
def get(self, request, article_no, format=None):
try:
article = models.Article.objects.get(article_no=article_no)
serializer = serializers.ArticleDetailSerializer(article)
return Response(status=status.HTTP_200_OK, data=serializer.data)
except models.Article.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
[urls.py]
urlpatterns = [
path('article/<int:article_no>', views.ArticleDetail.as_view(), name='article_detail'),
]
[serializers.py]
class ArticleDetailSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
comment = CommentSerializer(many=True, read_only=True)
class Meta:
model = models.Article
fields = (
'article_no',
'content',
'password',
'date',
'comment',
)
In serializers.py, I defined comment = CommentSerializer(many=True, read_only=True) and add it to fields.
And to test it, I add comment for article_no=1
But When I connect to /article/1, comment doesn't show anything.
I want to show all comments related it's article_no.
How can I fixed it?
Thanks.
Fixed source is here.
[serializers.py]
class ArticleDetailSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
class Meta:
model = models.Article
fields = (
'article_no',
'content',
'password',
'date',
'comments',
)
[models.py]
class Comment(models.Model):
article_no = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comments')
content = models.CharField(max_length=50, null=False, default='')
password = models.CharField(max_length=20, null=False, default='1234')
date = models.DateTimeField(auto_now_add=True)
When I connect to my server,
It only shows related comment's article_no.
But I want to show content and date also.
According to the Django documentation at https://docs.djangoproject.com/en/2.0/topics/db/queries/#backwards-related-objects you can access the list of objects by calling article_instance.comment_set.all() or you could set the related_name argument on the model on initialization
article_no = models.ForeignKey('Article', on_delete=models.CASCADE, related_name="comments")
and access is like article_instance.comments.all() or filter() or exclude()
There are quite a few options actually, and it depends on the use case, but for simplicity, in this case you may be able to just change the comment variable to comment_set, or you could change the related_name to comments and refer to it as such in your serializer.
required changes to ArticleDetailSerializer...
comment = CommentSerializer(many=True, read_only=True)
to
comments = CommentSerializer(many=True, read_only=True)
You also haven't created a CommentSerializer class, or you haven't posted it to the question.
example CommentSerializer....
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model=Comment
exclude=('article_no',)
I exclude the article_no and the remaining fields should be handled due to the assigned defaults on the django models.
The related object manager in Django returns a queryset that can be acted on like any other queryset. So you will want to consider whether an article might have an absurd amount of comments and limit the returned amount.
you can also use a SerializerMethodField and have more control over the returned queryset
comments = serializers.SerializerMethodField()
def get_comments(self, obj):
comments = obj.comments/comment_set.all()[:20] #return the first 20 comments
return CommentSerializer(comments/comment_set, many=True, read_only=True).data
now add comments/comment_set to the class Meta/fields tuple

How do create an object that has multiple objects in django rest framework via a POST method using class based views

I have created an Employee class below in my models. The Employee class has multiple Foreign Keys such as User,Contact,Skill etc. I would like to make it possible that when I create an Employee all the other objects will be created plus including the User object. I have implemented a POST method in my view that does this but I feel like my code is too long. How do I make a single POST to create all these multiple objects? An illustration using Managers will be also nice.
class Employee(models.Model):
"""
Model, which holds general information of an employee.
"""
user = models.OneToOneField(settings.AUTH_USER_MODEL,
related_name='employees', null=True)
company = models.ForeignKey(
'hr.Company',
verbose_name='Company',
related_name='companies',
null=True, blank=True,
)
hr_number = models.CharField(
verbose_name='HR number',
blank=True, null=True,
max_length=20, unique=True
)
identification_number = models.CharField(
verbose_name='ID Number',
blank=True, null=True,
max_length=20, unique=True
)
contract_type = models.ForeignKey(Contract)
tax_id_number = models.CharField(
max_length=20, null=True, verbose_name='Tax ID', blank=True, unique=True)
skill = models.ForeignKey(Skill)
# joining can be added in user profile
joining_date = models.DateField(null=True, verbose_name="Joining Date")
job_title = models.ForeignKey(
Job, related_name='job_titles', null=True, blank=True, help_text='Default Permission for different modules in Portal depends upon employee\'s Designation.')
department = models.ForeignKey(
Department, related_name='department', null=True, blank=True, on_delete=models.SET_NULL)
is_manager = models.BooleanField(default=False)
# leave_count = models.IntegerField(default=0)
active = models.BooleanField(default=True)
In my views I have have implemented the POST method below:
class AddEmployee(APIView):
# permission_classes = (permissions.DjangoObjectPermissions,)
# serializer_class = EmployeeSerializer
"""
{
"user":null,
"new_user":{
"first_name":"John",
"last_name":"Wane",
"username":"Wai",
"email":"jwane#gmail.com",
"password":"123"
},
"company":1,
"department":1,
"identification_number":"234567",
"hr_number":"GH/099/2017",
"tax_id_number":"AEEEEEE",
"joining_date":"2018-04-02",
"job_title":null,
"new_job":{
"name":"Doctor",
"min_salary":50000,
"max_salary":50000
}
}
"""
def post(self, request, format=None):
try:
company = Company.objects.get(id=request.data['company'])
department = Department.objects.get(id=request.data['department'])
try:
c_user = User.objects.get(id=request.data['user'])
except:
new_user = request.data['new_user']
c_user = User.objects.create(first_name=new_user['first_name'],
last_name=new_user['last_name'],
username=new_user['username'],
email=new_user['email'],
password=new_user['password'])
try:
job_title = Job.objects.get(id=request.data['job_title'])
except:
new_job = request.data['new_job']
if new_job:
job_title = Job.objects.create(
name=new_job['name'],
min_salary=new_job['min_salary'],
max_salary=new_job['max_salary']
)
employee = Employee.objects.create(
user=c_user,
company=company,
department=department,
job_title=job_title,
hr_number=request.data['hr_number'],
identification_number=request.data['identification_number'],
tax_id_number=request.data['tax_id_number'],
joining_date=request.data['joining_date']
)
except Exception as e:
print(e)
return Response(status=status.HTTP_201_CREATED)
DRF do not manage nested serialiser or this kind of things. that said, you can simplify your code using Model.objects.get_or_create
example :
try:
job_title = Job.objects.get(id=request.data['job_title'])
except:
new_job = request.data['new_job']
if new_job:
job_title = Job.objects.create(
name=new_job['name'],
min_salary=new_job['min_salary'],
max_salary=new_job['max_salary']
)
# can be write with get_or_create:
job_defaults = {
'name': new_job['name'],
'min_salary': new_job['min_salary'],
'max_salary': new_job['max_salary']
}
Job.objects.get_or_create(name=new_job['name'],defaults=job_defaults)
You can also use Model Serializer to manage filtering + validating + save sub object
examples :
# serializers.py
class JobSerializer(serializers.ModelSerializer):
class Meta:
model = Job
fields = ('id', 'name', 'min_salary', 'max_salary')
# inside views.py's post method
try:
job_title = Job.objects.get(id=request.data['job_title'])
except:
JobSerializer(data=new_job).save()
see also:
DRF documentation about writable nested serializers
External extension to help with nested serializers

Django contenttypes how to get foreign key values

I'm working on project managment tool for improve my django-skills.
I have a problem with contenttypes.
I have next models:
Project
Ticket - has ForeignKey to Project
Discussion - has ForeignKey to Project
Comment - has ForeignKey to Discussion
UserProfile - extension of django User
ProfileWatch - Projects or Discussions which users watch, contenttype use here
ProfileActions - contains users actions (add comment to discussion, start discussion, add ticket) contenttype also use here. Records in this table creating by signals from Ticket, Discussion and Comment
User recive notification about things that he watch(somebody leave new comment or start discussion or add ticket).
In view I get all notification for current user.
I know wich objects(Project, Discussion) have user actions but I don't know which object trigger this UserAction(Ticket, Comment, Discussion).
Ideally in template I need something like this (in brackets fields of ProfileAction model):
13:55 19.11.2012(action_date) admin(profile) add ticket(action_type) deploy this(?) to JustTestProject(content_object)
But now I have this:
13:55 19.11.2012(action_date) admin(profile) add ticket(action_type) to JustTestProject(content_object)
Any ideas about how organize models for store trigger objects?
Thanks for any help
View:
from django.views.generic import ListView
from app.models import ProfileAction
class ActionsList(ListView):
context_object_name = "actions"
template_name = 'index.html'
def get_queryset(self):
profile = self.request.user.get_profile()
where = ['(content_type_id={0} AND object_id={1})'.format(\
x.content_type_id,\
x.object_id\
) for x in profile.profilewatch_set.all()\
]
recent_actions = ProfileAction.objects.extra(
where=[
' OR '.join(where),
'profile_id={0}'.format(profile.pk)
],
order_by=['-action_date']
)
return recent_actions
Models:
#models
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _
class UserProfile(models.Model):
user = models.OneToOneField(User, verbose_name=_("Django user"))
first_name = models.CharField(_("First name"), blank=True, max_length=64, null=True)
last_name = models.CharField(_("Last name"), blank=True, max_length=64, null=True)
info = models.TextField(_("Additional information"), null=True)
phone = models.CharField(verbose_name=_("Phone"), max_length=15, blank=True, null=True)
class ProfileWatch(models.Model):
profile = models.ForeignKey(to=UserProfile, verbose_name=_(u"User profile"))
start_date = models.DateTimeField(verbose_name=_(u"Start date"), auto_now_add=True)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
class ProfileAction(models.Model):
ACTION_TYPE_CHOICES = (
(0, _(u"Add comment")),
(1, _(u"Add discussion")),
(2, _(u"Add ticket")),
)
profile = models.ForeignKey(to=UserProfile, verbose_name=_(u"User profile"))
action_date = models.DateTimeField(verbose_name=_(u"Start date"), auto_now_add=True)
action_type = models.PositiveSmallIntegerField(
verbose_name=_("Status"),
choices=ACTION_TYPE_CHOICES
)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
class Project(models.Model):
title = models.CharField(_("Project title"), max_length=128)
description = models.TextField(_("Project description"), blank=True, null=True)
members = models.ManyToManyField(UserProfile, through='Participation', verbose_name=_("Members"), blank=True, null=True)
actions = generic.GenericRelation(ProfileAction)
class Ticket(models.Model):
title = models.CharField(_("Title"), max_length=256)
project = models.ForeignKey('Project', verbose_name=_("Project"))
description = models.TextField(_("Ticket description"), blank=True, null=True)
creator = models.ForeignKey(UserProfile, verbose_name=_("Creator"), related_name='created_tickets')
#receiver(post_save, sender=Ticket)
def add_action_for_ticket(sender, instance, created, **kwargs):
if created:
ProfileAction.objects.create(
profile=instance.creator,
action_type=2,
content_object=instance.project
)
class Discussion(models.Model):
project = models.ForeignKey(
to=Project,
verbose_name=_(u"Project"),
)
creator = models.ForeignKey(
to=UserProfile,
verbose_name=_(u"Creator"),
)
updated = models.DateTimeField(
verbose_name=_("Last update"),
auto_now=True
)
title = models.CharField(
verbose_name=_(u"title"),
max_length=120
)
actions = generic.GenericRelation(ProfileAction)
#receiver(post_save, sender=Discussion)
def add_action_for_discussion(sender, instance, created, **kwargs):
if created:
ProfileAction.objects.create(
profile=instance.creator,
action_type=1,
content_object=instance.project
)
class Comment(models.Model):
discussion = models.ForeignKey(
to=Discussion,
verbose_name=_(u"Discussion")
)
creator = models.ForeignKey(
to=UserProfile,
verbose_name=_(u"Creator"),
)
pub_date = models.DateTimeField(
verbose_name=_("Publication date"),
auto_now_add=True
)
text = models.TextField(
verbose_name=_("Comment text")
)
#receiver(post_save, sender=Comment)
def add_action_for_comment(sender, instance, created, **kwargs):
if created:
ProfileAction.objects.create(
profile=instance.creator,
action_type=0,
content_object=instance.discussion
)
I solved the problem. Just add next adtitional fileds to ProfileAction model and change signals handlers.
trigger_content_type = models.ForeignKey(ContentType, related_name='triggers')
trigger_id = models.PositiveIntegerField()
trigger_object = generic.GenericForeignKey('trigger_content_type', 'trigger_id')