rest_framework access objects of ManyToManyField in intermediate model - django-rest-framework

I have a patient model. Each patient can have several "Medical Records" where choices come from models like "BodyPart" or "Medicin":
class Patient(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class Examination(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class BodyPart(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class Medicin(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class MedicalRecord(models.Model):
patient = models.ForeignKey('Patient', on_delete=models.CASCADE,)
examination = models.ForeignKey('Examination', on_delete=models.CASCADE)
part_of_body = models.ForeignKey('BodyPart', on_delete=models.CASCADE)
medicin = models.ManyToManyField('Medicin')
def __str__(self):
return self.patient.name
Using Inlines we can nicely add Records to each Patient:
class MedicalRecordAdmin(admin.TabularInline):
model = MedicalRecord
extra = 1
class PatientAdmin(admin.ModelAdmin):
model = Patient
inlines = [MedicalRecordAdmin,]
...
admin.site.register(Patient, PatientAdmin)
...
Where I'm failing is (already tried trough, serializers.StringRelatedField(many=True), etc.), how can I serialize these relations from the side of the Patient Model?
# view
class PatientViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = PatientSerializer
queryset = Patient.objects.all()
# serializer
class PatientSerializer(serializers.ModelSerializer):
class Meta:
model = Patient
fields = '__all__'
depth = 3
ends in:
[
{
"id": 1,
"name": "Bert"
}
]
## But I would expect something like:
[
{
"id": 1,
"name": "Bert",
"medical_records":[
[
{
"medicine": ["a","b","c"],
"body_part": "leg",
"examination": "x-ray"
}
],
[
{
"medicine": ["e","f","g"],
"body_part": "head",
"examination": "surgery"
}
],
]
}
]

You have to use a nested serializer and use source fields to achieve the desired result.
Nested relationships- DRF documentation
Somewhat like this :
class Patient(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class Examination(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class BodyPart(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class Medicin(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class MedicalRecord(models.Model):
patient = models.ForeignKey('Patient', on_delete=models.CASCADE, related_name='patient_med')
examination = models.ForeignKey('Examination', on_delete=models.CASCADE, related_name='examination_med')
part_of_body = models.ForeignKey('BodyPart', on_delete=models.CASCADE, relayted_name='part_of_body_med')
medicin = models.ManyToManyField('Medicin', related_name='medicin_med')
class MedicalRecordSerializer(serializers.ModelSerializer):
body_part = serializers.CharField(source='part_of_body.name', read_only=True)
examination = serializers.CharField(source='examination.name', read_only=True)
medicine = serializers.ListField(source='medicine.name', read_only=True)
class Meta:
model = MedicalRecord
fields = ('body_part', 'examination', 'medicine')
class PatientSerializer(serializers.ModelSerializer):
patient_med = MedicalRecordSerializer(many=True, read_only=True)
class Meta:
model = Patient
fields = '__all__'
depth = 3

Related

Django Form Form to fill multiple models at once

I have a few models that are all coming together in the Thema model. But I need a form to fill them all at once from the thema form. My problem is that when I create a form from the model it just creates a selection of for example tags that already exist in the database. But I need to create new ones in the form as well at the same time.
I want the user to fill in stuff in the Thema, upload a file to the file Model, create or select tags in the tags model and select or create into the Categories model.
The Problem is how can I bring all of them together in one Form? Also the Thema Model uses a related field to the Bilder model but I can´t upload new pics in the Thema form which are then stored in the Bilder Model.
This is the model.py
from django.db import models
from django.urls import reverse
from datetime import datetime
class Category(models.Model):
name = models.CharField(max_length=250, help_text='Kategorie', blank=True)
class Meta:
verbose_name_plural = "Categories"
def __str__(self):
return self.name
class Link(models.Model):
alias = models.CharField(max_length=250, help_text='Linkalias', blank=True)
link = models.URLField(("Links zum Thema"), max_length=128, db_index=True, unique=True, blank=True)
def __str__(self):
return self.alias
class File(models.Model):
name = models.CharField(max_length=250, help_text='Dateiname', blank=True)
upload = models.FileField(upload_to ='uploads/', null=True, blank=False)
def __str__(self):
return self.name
class Tag(models.Model):
name = models.CharField(max_length=250, help_text='tagname', blank=True)
def __str__(self):
return self.name
class Thema(models.Model):
publication_date = models.DateField(verbose_name="Date the Post was published.", default=datetime.now)
title = models.CharField(max_length=70, help_text="The title of the Post")
text = models.TextField(max_length=5000,default='text 1')
text2 = models.TextField(max_length=5000,default='text 2')
text3 = models.TextField(max_length=5000,default='text 3')
# Relation Fields
category = models.ForeignKey(Category, on_delete=models.CASCADE)
links = models.ManyToManyField(Link)
files = models.ManyToManyField(File)
tags = models.ManyToManyField(Tag)
def get_absolute_url(self):
return reverse('thema-detail', kwargs={'pk' : self.pk})
def __str__(self):
return self.title
class Bilder(models.Model):
name = models.CharField(max_length=250, help_text='Bildname', blank=True)
beschreibung = models.CharField(max_length=400, help_text='Kurzbschreibung', blank=True)
thema = models.ForeignKey(Thema, on_delete=models.CASCADE, related_name='bilder', null=True)
image = models.ImageField(null=True, blank=True, upload_to='images/')
class Meta:
verbose_name_plural = "Bilder"
def __str__(self):
return self.name
and this is my attempt to create the form:
from django import forms
from django.forms import formset_factory
from .models import Thema, Bilder, Category, Link, File, Tag
from django.forms import formset_factory
class BilderForm(forms.ModelForm):
class Meta:
model = Bilder
fields = ['name', 'beschreibung', 'image']
BilderFormSet = formset_factory(BilderForm, extra=1)
class ThemaForm(forms.ModelForm):
bilder_formset = BilderFormSet()
class Meta:
model = Thema
fields = ['publication_date', 'title', 'text', 'text2', 'text3', 'category', 'links', 'files', 'tags']
def __init__(self, *args, **kwargs):
self.bilder_formset = kwargs.pop('bilder_formset', None)
super().__init__(*args, **kwargs)

How to retrieve multiple slugs as url parameters with Django REST Framework DRF RetrieveAPIView?

I am new to DRF. I am very interested in application of generic views. I want to rewrite the following code with generic DRF view RetrieveAPIView:
views.py
class ProductDetail(APIView):
def get_object(self, category_slug, product_slug):
try:
return Product.objects.filter(category__slug=category_slug).get(slug=product_slug)
except Product.DoesNotExist:
raise Http404
def get(self, request, category_slug, product_slug, format=None):
product = self.get_object(category_slug, product_slug)
serializer = ProductSerializer(product)
return Response(serializer.data)
urls.py
urlpatterns = [
path('products/<slug:category_slug>/<slug:product_slug>/',
ProductDetail.as_view())
]
models.py
class Category(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField()
class Meta:
ordering = ('name',)
def __str__(self):
return self.name
def get_absolute_url(self):
return f'/{self.slug}/'
class Product(models.Model):
category = models.ForeignKey(
Category, related_name='products', on_delete=models.CASCADE)
name = models.CharField(max_length=255)
slug = models.SlugField()
description = models.TextField(blank=True, null=True)
price = models.DecimalField(max_digits=6, decimal_places=2)
image = models.ImageField(upload_to='uploads/', blank=True, null=True)
thumbnail = models.ImageField(upload_to='uploads/', blank=True, null=True)
date_added = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('-date_added',)
def __str__(self):
return self.name
def get_absolute_url(self):
return f'{self.category.slug}/{self.slug}/'
How can I use RetrieveAPIView instead of above APIView?
I tried to add a custom mixin, did not help:
class MultipleFieldLookupMixin:
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset) # Apply any filter backends
multi_filter = {field: self.kwargs[field] for field in self.lookup_fields}
obj = get_object_or_404(queryset, **multi_filter) # Lookup the object
self.check_object_permissions(self.request, obj)
return obj
class ProductDetail(MultipleFieldLookupMixin, RetrieveAPIView):
queryset = Product.objects.filter(category__slug=category_slug).get(slug=product_slug)
serializer = ProductSerializer
lookup_fields = ['category_slug', 'product_slug']
Does not work.

DRF nested serializer - object has no attribute

I am a newbie with django rest framework and python. and it is first time I asking a question here. thanks for helps from now..
--------------my models
class defined_views(models.Model):
name = models.CharField(max_length=150)
class defined_permissions(models.Model):
name = models.CharField(max_length=150)
class menuler(models.Model):
adi= models.CharField(max_length=150, null=True)
sira= models.PositiveIntegerField(null=True, blank=True)
parentId= models.ForeignKey('self', null=True, on_delete=models.CASCADE)
viewId= models.ForeignKey(defined_views, null=True, on_delete=models.CASCADE)
menu_type= models.CharField(max_length=150, null=True)
class user_menu_permission(models.Model):
userId = models.ForeignKey(User, null=False, on_delete=models.CASCADE)
menuID = models.ForeignKey(menuler, null=False, on_delete=models.CASCADE)
permissionID = models.ForeignKey(defined_permissions, null=True, on_delete=models.CASCADE)
-------------------serializer
class menulerSerializer(serializers.ModelSerializer):
class Meta:
model = menuler
fields = ['id', 'adi', 'sira', 'parentId', 'viewId', 'menu_type']
class user_menu_permissionSerializer(serializers.ModelSerializer):
menu = menulerSerializer()
class Meta:
model = user_menu_permission
fields = ['menuID', 'permissionID', 'menu']
class UserSerializer(serializers.ModelSerializer):
menuler = user_menu_permissionSerializer(
source='user_menu_permission_set', many=True)
class Meta:
model = User
fields = ['id', 'username', 'menuler']
------------------and view
class userView(APIView):
def get(self, request):
queryset = User.objects.all()
serializer = UserSerializer(queryset, many=True)
return Response(serializer.data)
I get this errror
"Got AttributeError when attempting to get a value for field menu on serializer user_menu_permissionSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the user_menu_permission instance.
Original exception text was: 'user_menu_permission' object has no attribute 'menu'."
if I change user_menu_permissionSerializer like that
class user_menu_permissionSerializer(serializers.ModelSerializer):
class Meta:
model = user_menu_permission
fields = ['menuID', 'permissionID']
I get result like that bu I want get menu as tree also.. but unfortunetly...
[
{
"id": 1,
"username": "admin",
"menuler": []
},
{
"id": 2,
"username": "taner.kader",
"menuler": [
{
"menuID": 2,
"permissionID": 1
},
{
"menuID": 3,
"permissionID": 1
},
{
"menuID": 5,
"permissionID": 1
}
]
}
]
I have solved like below
models
class defined_views(models.Model):
name = models.CharField(max_length=150)
class defined_permissions(models.Model):
name = models.CharField(max_length=150)
class menuler(models.Model):
adi= models.CharField(max_length=150, null=True)
sira= models.PositiveIntegerField(null=True, blank=True)
parentId= models.ForeignKey('self', null=True, on_delete=models.CASCADE)
viewId= models.ForeignKey(defined_views, null=True, on_delete=models.CASCADE)
menu_type= models.CharField(max_length=150, null=True)
permissionID = models.ManyToManyField(defined_permissions, through='user_menu_permission')
userId = models.ManyToManyField(User, through='user_menu_permission')
class user_menu_permission(models.Model):
# userId = models.ForeignKey(User, related_name='kullanici_menuler', null=False, on_delete=models.CASCADE)
userId = models.ForeignKey(User, null=False, on_delete=models.CASCADE)
menuID = models.ForeignKey(menuler,null=False, on_delete=models.CASCADE)
permissionID = models.ForeignKey(defined_permissions, null=True, on_delete=models.CASCADE)
serializers
class defined_permissionsSerializer(serializers.ModelSerializer):
class Meta:
model = defined_permissions
fields = ['id', 'name']
class defined_viewsSerializer(serializers.ModelSerializer):
class Meta:
model = defined_views
fields = ['id', 'name']
class menulerSerializer(serializers.ModelSerializer):
class Meta:
model = menuler
fields = ['id', 'adi', 'sira', 'parentId', 'viewId', 'menu_type']
# fields = ['adi']
class user_menu_permissionSerializer(serializers.ModelSerializer):
# menu = menulerSerializer(many=True)
class Meta:
model = user_menu_permission
fields = ['menuID', 'permissionID']
class UserSerializer(serializers.ModelSerializer):
# menuler = user_menu_permissionSerializer(
# source='user_menu_permission_set', many=True)
# kullanici_menuler = user_menu_permissionSerializer(many=True)
kullanici_menuler= menulerSerializer(source='menuler_set', many=True)
class Meta:
model = User
fields = ['id', 'username', 'kullanici_menuler']
and result
[
{
"id": 1,
"username": "admin",
"kullanici_menuler": []
},
{
"id": 2,
"username": "tayfun.uzun",
"kullanici_menuler": [
{
"id": 2,
"adi": "Yapılacaklar Listesi",
"sira": 1,
"parentId": 1,
"viewId": 1,
"menu_type": "view"
},
{
"id": 3,
"adi": "Raporlamalar",
"sira": 2,
"parentId": 1,
"viewId": 2,
"menu_type": "view"
},
{
"id": 5,
"adi": "Süreç İşleyişleri",
"sira": 1,
"parentId": 4,
"viewId": 3,
"menu_type": "view"
}
]
}
]
Try below something like this:
serializer.py :
class user_menu_permissionSerializer(serializers.ModelSerializer):
menu = menulerSerializer(many=True)
class Meta:
model = user_menu_permission
fields = ['menuID', 'permissionID', 'menu']
views.py :
class userView(APIView):
def get(self, request):
data = User.objects.all()
serializer = UserSerializer(data, many=True)
return Response(serializer.data)

Django Rest Framework Relationships Serialization

I am trying to get serialized data for the below model with foreign keys but in the output i am only getting the ids instead of the fields of the model associated with hyperlink.
I have tried getting all the fields of the Skills model using the commented lines in the profile serializer but no luck
models.py
class Profile(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
first_name = models.CharField(max_length=20, null=True)
last_name = models.CharField(max_length=20, null=True)
def __str__(self):
return 'Profile for user {}'.format(self.user.username)
class Skill(models.Model):
skill = models.CharField(max_length=20, null=True)
level = models.CharField(max_length=20, null=True)
user = models.ForeignKey(Profile, null=True,
on_delete=models.PROTECT, related_name='skills')
def __str__(self):
return '%s: %s: %s' % (self.user, self.skill, self.level)
serializer.py
class SkillSerializer(serializers.ModelSerializer):
class Meta:
model = Skill
fields = ('user', 'skill', 'level')
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
# skills = SkillSerializer(many=True, read_only=True) # serializes child model's data inside this model's data
# skills = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='skill-detail')
# skills = serializers.StringRelatedField(many=True)
# skills = serializers.HyperlinkedIdentityField( view_name = "skill-list", lookup_field = "user")
url = HyperlinkedIdentityField( view_name="profile-detail", lookup_field = "id")
# user = serializers.ReadOnlyField(source='user.username')
# pdb.set_trace()
model = Profile
fields = ['id', 'user', 'url', 'skills']
views.py
class ProfileList(generics.ListCreateAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
class ProfileDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
class SkillList(generics.ListCreateAPIView):
queryset = Skill.objects.all()
serializer_class = SkillSerializer
class SkillDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Skill.objects.all()
serializer_class = SkillSerializer
urls.py
path('profiles/', views.ProfileList.as_view(), name='profile-list'),
path('profiles/<int:pk>/', views.ProfileDetail.as_view(), name='profile-detail'),
path('skills/', views.SkillList.as_view(), name='profile-list'),
path('skills/<int:pk>/', views.SkillDetail.as_view(), name='skill-list'),
Output: http://127.0.0.1:8000/authenticator/profiles/
[ "id": 6,
"user": 4,
"url": "http://127.0.0.1:8000/authenticator/profiles/6/",
"skills": [
57,
55
],
]
Expected output:
[ "id": 6,
"user": 4,
"url": "http://127.0.0.1:8000/authenticator/profiles/6/",
"skills": [
{
"user": 6,
"skill": "ABC",
"level": "Beginner"
},
{
"user": 6,
"skill": "DEF",
"level": "Intermediate"
},
]
]
This should normally work with the following in serializer.py:
class ProfileSerializer(serializers.ModelSerializer):
skills = SkillSerializer(many=True, read_only=True) # serializes child model's data inside this model's data
url = HyperlinkedIdentityField( view_name="profile-detail", lookup_field = "id")
class Meta:
model = Profile
fields = ['id', 'user', 'url', 'skills']
So basically uncomment the line in which the ProfileSerializer is told to serialize the skills with your SkillsSerializers, such that the entire skill objects are included in the result instead of its IDs.
You can use the concept of nested serializer for the solution.
Create two serializer ProfileReadSerializer and ProfileWriteSerializer
serializers.py
class SkillSerializer(serializers.ModelSerializer):
class Meta:
model = Skill
fields = ('user', 'skill', 'level')
class ProfileReadSerializer(serializers.ModelSerializer):
skills = SkillSerializer(many=True, read_only=True)
url = HyperlinkedIdentityField( view_name="profile-detail", lookup_field = "id")
class Meta:
model = Profile
fields = ('id', 'user', 'url', 'skills')
class ProfileWriteSerializer(serializers.ModelSerializer):
skills = SkillSerializer(many=True)
class Meta:
model = Profile
fields = ('id', 'user', 'url', 'skills')
In views you simply can use ModelViewSet to make things easy and use get_serializer_class
views.py
from rest_framework.viewsets import ModelViewSet
class ProfileVewSet(ModelViewSet):
queryset = Profile.objects.all()
def get_serializer_class(self):
if self.request.method == 'POST' or self.request.method == 'PUT' or self.request.method == 'PATCH':
return ProfileWriteSerializer
else:
return ProfileReadSerializer
And at last if you are using ModelViewSet you need to change the urls.py
urls.py
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('profile', views.ProfileViewSet, base_name='profile')
urlpatterns = [
path('', include(router.urls))
]
I think this will solve your problem. Please have a look.

TypeError: 'FieldFile' object is not callable

I'm getting the TypeError: 'FieldFile' object is not callable when trying to get URL of uploaded file:
class FetchedDataSerializer(serializers.ModelSerializer):
file_fields = serializers.SerializerMethodField()
class Meta:
model = FetchedData
fields = ('model_id', 'config_id', 'config_name', 'file_fields')
def get_file_fields(self, obj):
queryset = obj.config_id.config_file() ###
return ShoppingListSerializer(queryset, many=True).data
The model consists of the following fields:
class ShoppingList(models.Model):
id = models.CharField(max_length=40, primary_key=True)
name = models.CharField(max_length=40)
session_id = models.CharField(max_length=40)
config_file = models.FileField(upload_to=upload_config_file)
def __str__(self):
return self.id
What should be done with the queryset to eventually display uploaded file URL?
Can you edit your get_file_fields like this ?
class FetchedDataSerializer(serializers.ModelSerializer):
file_fields = serializers.SerializerMethodField()
class Meta:
model = FetchedData
fields = ('model_id', 'config_id', 'config_name', 'file_fields')
def get_file_fields(self, obj):
queryset = ShoppingList.objects.filter(config_file = obj)
return [ShoppingListSerializer(cf).data for cf in queryset]
As written, you are trying to execute a field in this line obj.config_id.config_file().
If you want to serialize the object you can simply serialize the one model, you don't need a queryset.
class FetchedDataSerializer(serializers.ModelSerializer):
file_fields = serializers.SerializerMethodField()
class Meta:
model = FetchedData
fields = ('model_id', 'config_id', 'config_name', 'file_fields')
def get_file_fields(self, obj):
return ShoppingListSerializer(obj).data

Resources