Django Rest Framework - HyperlinkedRelatedField for abstract base class - django-rest-framework

My question is if is possible to add an HyperlinkedRelatedField in a serializer to get only the attributes of the base class. for example:
I want a json like that:
{
"modules": [
{
"moduleName": "M1: Fundamentos Técnicos",
"moduleDetails": "Bla bla bla.",
"moduleID": 0,
"userScore": 3,
"slides": [
{
"slideType": "Content",
"slideID": 0
},
{
"slideType": "Minigame1",
"slideID": 1
},
{
"slideType": "Video",
"slideID": 6
}
]
}
]
}
Here, Slide is the base class and Video and Minigame are subclasses.
Is possible this?, is possible add a HyperlinkedRelatedField for the url of each slide?.
Thanks in advance!
UPDATE!
this are my models:
class Module(TimeStampedModel):
moduleID = models.AutoField(primary_key=True)
moduleName = models.CharField(
max_length=100,
verbose_name='Nombre del modulo')
moduleDetails = models.TextField(verbose_name='Detalle')
moduleBullet1 = models.CharField(max_length=100, verbose_name='Punto 1')
moduleBullet2 = models.CharField(max_length=100, verbose_name='Punto 2')
moduleBullet3 = models.CharField(max_length=100, verbose_name='Punto 3')
moduleImageURL = models.ImageField(
upload_to="modulos", verbose_name='Imagen')
userScore = models.PositiveSmallIntegerField(
default=0, verbose_name='Score de usuario')
class Slide(TimeStampedModel):
CONTENT = 'Content'
MINIGAME = 'Minigame'
VIDEO = 'Video'
SLIDE_TYPE_CHOICES = (
(CONTENT, 'Contenido'),
(MINIGAME, 'Minigame'),
(VIDEO, 'Video'),
)
slideType = models.CharField(
max_length=20,
choices=SLIDE_TYPE_CHOICES,
default=CONTENT,
)
slideID = models.AutoField(primary_key=True)
slideOrder = models.PositiveSmallIntegerField(
verbose_name='Orden de visualizacion')
module = models.ForeignKey(Module, on_delete=models.CASCADE,
related_name="%(app_label)s_%(class)s_related",
related_query_name="%(app_label)s_%(class)ss",)
class Meta:
abstract = True
class VideoContent(Slide):
videoURL = models.URLField(verbose_name='URL de video')
class Minigame(Slide):
timeToFail=models.PositiveSmallIntegerField()
And in my serializers.py i want something like this:
class SlideSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Slide
fields = ('url', 'slideType', 'slideID')
class ModuleSerializer(serializers.ModelSerializer):
slides = SlideSerializer(many=True, read_only=True)
class Meta:
model = Module
fields = ('moduleID', 'moduleName', 'moduleDetails', 'moduleImageURL', 'userScore', 'slides')
My view.py
class ModuleViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
"""
queryset = Module.objects.all()
serializer_class = ModuleSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class SlideViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
"""
queryset = Slide.objects.all()
serializer_class = SlideSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
Slide.Objects raise an error, AttributeError: type object 'Slide' has no attribute 'objects'

You can't query abstract base classes. Slice is an abstract class, you cannot use queryset = Slide.objects.all() in SlideViewSet because an abstract model isn't an actual database object, and therefore cannot be queried.
You have to choose between abstract inheritance, in which case there is no database relationship between the two classes, or multi-table inheritance, which keeps the database relationship at a cost of efficiency (an extra database join) for each query.
Using multi-table inheritance
class Slide(TimeStampedModel):
CONTENT = 'Content'
MINIGAME = 'Minigame'
VIDEO = 'Video'
SLIDE_TYPE_CHOICES = (
(CONTENT, 'Contenido'),
(MINIGAME, 'Minigame'),
(VIDEO, 'Video'),
)
slideType = models.CharField(
max_length=20,
choices=SLIDE_TYPE_CHOICES,
default=CONTENT,
)
slideID = models.AutoField(primary_key=True)
slideOrder = models.PositiveSmallIntegerField(
verbose_name='Orden de visualizacion')
module = models.ForeignKey(Module, on_delete=models.CASCADE,
related_name="%(app_label)s_%(class)s_related",
related_query_name="%(app_label)s_%(class)ss",)
class VideoContent(Slide):
videoURL = models.URLField(verbose_name='URL de video')
class Minigame(Slide):
timeToFail=models.PositiveSmallIntegerField()
Now, your viewset:
class SlideViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
"""
queryset = Slide.objects.all()
serializer_class = SlideSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
Documentation on multi-table inheritance:
multi-table inheritance uses an implicit OneToOneField to link the
child and the parent, it’s possible to move from the parent down to
the child
Hope this clears the things up.

Related

How to get item name instead of item id in django restframework (foreignkey case)

I am trying to get itemwise inventory desired result is like this
[
{
'item' : shoes,
'total_unit' : 134
},
{
'item': sneaker,
'unit': 100
}
]
but I am getting result like
[
{
"item": 5,
"unit": 134
},
{
"item": 4,
"unit": 100
}
]
I want to get item name instead of id
Models.py
class Item(models.Model):
item_name = models.CharField(max_length=50)
srn_code = models.CharField(max_length=20,unique=True)
category = models.ForeignKey(Category,related_name='categories',on_delete=models.CASCADE)
def __str__(self):
return self.item_name
class Transaction(models.Model):
category = models.ForeignKey(Category,on_delete=models.CASCADE)
item = models.ForeignKey(Item,on_delete=models.CASCADE)
size = models.CharField(blank=True,max_length=20)
unit = models.IntegerField()
unit_price = models.DecimalField(decimal_places=2,max_digits=20)
supplier = models.ForeignKey(Supplier,on_delete=models.CASCADE)
tran_date = models.DateField(auto_now=False,auto_created=False,blank=False)
created_date = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.item.item_name} {self.unit}"
serializer.py
class InventorySerializer(serializers.ModelSerializer):
item = serializers.CharField(read_only=True)
total_unit = serializers.IntegerField(read_only=True)
class Meta():
model = Transaction
fields = ['item','total_unit']
views.py
class InventoryModelViewSet(ModelViewSet):
queryset = Transaction.objects.all()
serializer_class = InventorySerializer
def get_queryset(self):
return Transaction.objects.values('item').annotate(
total_unit = Sum('unit')
).order_by('item')
Thanks for posting my question, I have sorted out my question, my solution is below, if anybody needs or gets query like this, then its might be helpful.
I have declared a method in serializers class for get item_name from another table
class InventorySerializer(serializers.ModelSerializer):
item = serializers.CharField(read_only=True)
total_unit = serializers.IntegerField(read_only=True)
item_name = serializers.SerializerMethodField()
class Meta():
model = Transaction
fields = ['item','item_name','total_unit']
def get_item_name(self, obj):
item_obj = Item.objects.filter(id=obj['item']).first()
item_name = model_to_dict(item_obj)
return item_name['item_name']
`

How do I upload multiple images using django rest framework?

I am learning how to use djangorestframework by building a microblog and I want users to be able to upload multiple (kind of like how twitter works). I got a particular error(check below) after using a particular approach(check code).
I have attached my models.py, serializers.py and views.py file:
MODELS.PY FILE:
class TweetFile(models.Model):
tweep = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
file = models.FileField(upload_to='images')
class Tweets(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
texts = models.TextField()
file_content = models.ManyToManyField(TweetFile, related_name='file_content')
date_posted = models.DateTimeField(auto_now_add=True)
tweep = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
likes = models.PositiveIntegerField(default=0)
liker = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='user_like')
# link = models.URLField()
class Meta:
# verbose_name = _('my thing')
verbose_name_plural = _('Tweets')
def __str__(self):
return f"{self.texts}"
SERIALIZERS.PY FILE:
class TweetSerializer(serializers.ModelSerializer):
tweep = serializers.SerializerMethodField('get_tweep_username')
likes = serializers.SerializerMethodField('get_tweet_likes')
liker = serializers.StringRelatedField(many=True)
class Meta:
model = Tweets
fields = ['id','texts', 'file_content', 'date_posted', 'tweep', 'likes', 'liker']
extra_kwargs = {
"file_content": {
"required": False,
}
}
VIEWS.PY FILE:
#api_view(['POST'])
#permission_classes([IsAuthenticated])
def create_tweet(request):
user = request.user
if request.method == 'POST':
serializer = TweetSerializer(data=request.data)
if serializer.is_valid():
serializer.save(tweep=user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
return Response(serializer.errors, status=status.HTTP_405_METHOD_NOT_ALLOWED)
This particular approach gave me this error in my postman:
{
"file_content": [
"Incorrect type. Expected pk value, received InMemoryUploadedFile."
]
}.
Could anybody tell me what I am doing wrong? or what I need to do? any help will be appreciated, thanks.
Okay, so I found a solution to this issue....and this is the code, hopefully someone finds it helpful:
models.py file
class TweetFile(models.Model):
tweep = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
media = models.FileField(upload_to='images')
def __str__(self):
return f"{self.tweep.username}'s media images"
class Tweets(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
texts = models.TextField()
file_content = models.ManyToManyField(TweetFile, related_name='file_content', blank=True, null=True)
date_posted = models.DateTimeField(auto_now_add=True)
tweep = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
class Meta:
verbose_name_plural = _('Tweets')
def __str__(self):
return f"{self.texts}"
Serializer.py file
from rest_framework import serializers
from tweets.models import Tweets, TweetFile, Comments
class TweetSerializer(serializers.ModelSerializer):
tweep = serializers.SerializerMethodField('get_tweep_username')
class Meta:
model = Tweets
fields = ['id','texts', 'file_content', 'date_posted', 'tweep']
extra_kwargs = {
"file_content": {
"required": False,
}
}
# function that returns the owner of a tweet
def get_tweep_username(self, tweets):
tweep = tweets.tweep.username
return tweep
Views.py file
#api_view(['POST'])
#permission_classes([IsAuthenticated])
#parser_classes([MultiPartParser, FormParser])
def create_tweet(request):
user = request.user
if request.method == 'POST':
files = request.FILES.getlist('file_content')
if files:
request.data.pop('file_content')
serializer = TweetSerializer(data=request.data)
if serializer.is_valid():
serializer.save(tweep=user)
tweet_qs = Tweets.objects.get(id=serializer.data['id'])
uploaded_files = []
for file in files:
content = TweetFile.objects.create(tweep=user, media=file)
uploaded_files.append(content)
tweet_qs.file_content.add(*uploaded_files)
context = serializer.data
context["file_content"] = [file.id for file in uploaded_files]
return Response(context, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
serializer = TweetSerializer(data=request.data)
if serializer.is_valid():
serializer.save(tweep=user)
context = serializer.data
return Response(context, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
return Response(serializer.errors, status=status.HTTP_405_METHOD_NOT_ALLOWED)
configure your urls.py files appropriately and test the endpoint on POSTMAN, everything should work fine.

one to one field update serialization issue

###################### Requirement #############################
/add_value/
payload:
{
"profile_id": "34AB",
"amt": 100.00
}
###################### Viewset ####################################
class cash_viewset(ModelViewSet):
queryset = Cashbalance.objects.all()
serializer_class = SampleSerializer
####################### Models ####################################
MOdel -1 ---
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
profile_id = models.CharField(max_length=50,unique=True)
MOdel -2 ---
class Cashbalance(models.Model):
profile = models.OneToOneField(Profile, on_delete=models.CASCADE,db_column="profile_id")
remianingAmount = models.FloatField()
amt = models.FloatField()
############################### Serializer ##############################
class SampleSerializer(serializers.ModelSerializer):
profile_id = serializers.CharField(source="profile.profile_id")
remianingAmount = serializers.FloatField(read_only=True)
class Meta:
model = CashBalance
fields = ["profile_id", "amt", "remianingAmount"]
def create(self, validated_data):
profile_dict = validated_data['profile']
userprofile = Profile.objects.get(profile_id=profile_dict['profile_id'])
bank_obj = CashBalance.objects.filter(profile_id=userprofile)
bank_obj.update(amt=validated_data['amt'], remianingAmount=validated_data['amt'])
return bank_obj[0]
def to_representation(self, instance):
data = super(serializers.ModelSerializer, self).to_representation(instance)
print(instance.profile.user.username)
result = {"profile_id": data['profile_id'], "name": instance.profile.user.username,
"remianingAmount": data['remianingAmount']}
return result
################################# Question is ######################
Assume that, there is already some data in user,profile and Cashbalance tables
As per the payload given above,Based on the profile_id ,I have to update amt value to 100, for this I have implemented the solution in create method(create method given above), please share your inputs ,I am hoping that update cannot be done in create method, how can i improve this in more good way according to my model designs by using modelviewset
To perform the update, you have to override the update method in the serializer.
def update(self, instance, validated_data):
instance.profile.profile_id
= validated_data["profile"].get('profile_id',instance.profile.profile_id)
instance.amt = validated_data.get('amt', instance.amt)
return instance
in your viewset
def update(self,request,pk=None):
profile_id = request.data.get("profile_id")
profile = Profile.objects.get(profile_id = profile_id)
ser = self.get_serializer_class()(instance=profile.cash_balance , data = request.data)
ser.is_valid(raise_exception=True)
ser.save()
in your CashBalanceModel, pass a related_name
profile = models.OneToOneField(Profile, on_delete=models.CASCADE,db_column="profile_id" , related_name="cash_balance")
check docs: https://www.django-rest-framework.org/api-guide/serializers/

django rest framework writable nested serializer returns empty nested data

I'm following this link (http://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers) to write nested serializer. But when I pop the 'vars' from validated_data in the create method of HostSerializer, I found it's empty.
I'm using django 1.9.2 and django restframework 3.3.2.
My model:
class Host(models.Model):
name = CharField(max_length=20, primary_key=True)
vm_cpu = IntegerField(default=2)
vm_mem = IntegerField(default=2048)
create_vm = BooleanField(default=True)
def __unicode__(self):
return('%s' % (self.name))
class Variable(models.Model):
name = CharField(max_length=10)
value = CharField(max_length=20)
host = models.ForeignKey(Host, related_name='vars')
def __unicode__(self):
return('%s=%s' % (self.name, self.value))
Serializer
class VariableSerializer(ModelSerializer):
class Meta:
model = Variable
class HostSerializer(ModelSerializer):
vars = VariableSerializer(many=True)
class Meta:
model = Host
def create(self, validated_data):
# i set a break point here and found vars_data is empty
vars_data = validated_data.pop('vars')
host = Host.objects.create(**validated_data)
for v in vars_data:
Variable.objects.create(host = host, **v)
return host
This is the problem I found vars_data is an empty list:
def create(self, validated_data):
# i set a break point here and found vars_data is empty
vars_data = validated_data.pop('vars')
Here's the rest of the code
admin.py
class VariableAdmin(admin.ModelAdmin):
list_display = ['name', 'value']
class HostAdmin(admin.ModelAdmin):
list_display = ['name']
admin.site.register(Variable, VariableAdmin)
admin.site.register(Host, HostAdmin)
urls.py
router = DefaultRouter()
router.register(r'variables', VariableViewSet, base_name='variables')
router.register(r'hosts', HostViewSet, base_name='hosts')
urlpatterns = [
url(r'^', include(router.urls)),
]
views.py
class VariableViewSet(ModelViewSet):
queryset = Variable.objects.all()
serializer_class = VariableSerializer
class HostViewSet(ModelViewSet):
queryset = Host.objects.all()
serializer_class = HostSerializer
My test program
post.py
import json
import requests
file = 'host.json'
url = 'http://localhost:8001/test_nest/hosts/'
with open(file, 'r') as f:
j = f.read()
data = json.loads(j)
r = requests.post(url, data = data)
print r.text
And here's the test data
host.json
{
"name": "host4",
"vars": [
{
"name": "var2-a",
"value": "a1"
},
{
"name": "var2-b",
"value": "a2"
}
],
"vm_cpu": 2,
"vm_mem": 2048,
"create_vm": true
}
I'm new to django. So I'm wondering if it's something simple and obvious. Did I use the wrong viewset? Did I post to the wrong URL? Or I setup the URL structure wrong?
I your serializers try using...
def update(self, instance,validated_data):
instance.vars_data = validated_data.get('vars',instance.vars)
instance.host = Host.objects.create(**validated_data)
for v in vars_data:
v,created=Variable.objects.create(host = host, **v)
instance.v.add(v)
return host
The following code works for me. Maybe you can try it:
models.py
class UserProfile(AbstractUser):
pass
class TobaccoCert(models.Model):
license = models.CharField(primary_key=True, max_length=15)
license_image = models.ImageField(upload_to="certs", blank=True, null=True, verbose_name='cert image')
views.py
class UserViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
serializer_class = UserRegSerializer
...
def create(self, request, *args, **kwargs):
data = request.data
# here i make my nested data `certs` and update it to data
image_data = request.FILES.get('images')
_license = data.get('username', None)
certs = {'license': _license, 'license_image': image_data}
data['certs'] = certs
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
user = self.perform_create(serializer) # save it
ret_dict = serializer.data
payload = jwt_payload_handler(user)
ret_dict["token"] = jwt_encode_handler(payload) # 获取token
ret_dict["name"] = user.name if user.name else user.username
headers = self.get_success_headers(serializer.data)
return Response(ret_dict, status=status.HTTP_201_CREATED, headers=headers)
serializer.py
class CertSerializer(serializers.ModelSerializer):
pass
class UserRegSerializer(serializers.ModelSerializer):
# [Serializer relations - Django REST framework](https://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers)
# this line is vars of your code
certs = CertSerializer(many=True,write_only=True)
...
password = serializers.CharField(
style={'input_type': 'password'}, help_text="passowrd", label="passowrd", write_only=True,
)
class Meta:
model = User
fields = ("username", "mobile", "password", 'province', 'city', 'dist', 'code', 'certs')
def create(self, validated_data):
"""
"""
# here we get certs
certs_data = self.initial_data.get('certs')
# just pop certs because it is useless for user create
certs_empty = validated_data.pop('certs')
user = super().create(validated_data=validated_data)
user.set_password(validated_data["password"])
user.save()
# use certs data to create cert
image_url = self.save_image(certs_data)
_license = validated_data.get('username', None)
TobaccoCert.objects.create(license=_license, license_image=image_url)
return user
In short, you need to add your nested parameters directly in the request parameters. In my example code, it is certs which is vars in your example and then use certs_data = self.initial_data.get('certs') get the parameters you passed in create method.
Another possible way is post your data before your requests:
In postman:
enter image description here
in request:
python - Django Rest Framework writable nested serializer with multiple nested objects - Stack Overflow
In addition, you can try to modify queryDict directly. You can refer to this link and here
Some useful links
django - Need help understanding many and source fields in a serializer - Stack Overflow
python - Django rest framework writeable nested serializer data missing from validated_data - Stack Overflow

Django get_initial not working

Django get_initial is not populating product field in the form. I am expecting a drop down, with the queryset results as defined in the get_initial overridden function.
class PurchaseRequestDetailForm(forms.ModelForm):
class Meta:
model = PurchaseRequestDetail
fields = ["product", "variations", "quantity", "fulfilled", "vat", "discount", "surcharges", "active"]
exclude = ("purchase_request", )
class PurchaseRequestDetailCreateView(CreateView):
model = PurchaseRequestDetail
form_class = PurchaseRequestDetailForm
template_name = "inventory/purchaserequestdetail_form.html"
def get_pr_obj(self):
pr_id = self.request.session["pr_id"]
return PurchaseRequest.objects.get(id=pr_id)
def get_initial(self):
initial = super(PurchaseRequestDetailCreateView, self).get_initial()
try:
pr_obj = self.get_pr_obj()
initial["product"] = pr_obj.vendor.vendors_products.all()
except KeyError:
pass
self.form_class(initial)
return initial
template:
<td>{{ form.product|css_class:"form-control" }}</td>
An easy way to set a ModelChoiceField queryset is to set the field attribute in the form init();
class PurchaseRequestDetailForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
# Get initial data passed from the view
self.product = None
if 'product' in kwargs['initial']:
self.product = kwargs['initial'].pop('product')
super(PurchaseRequestDetailForm, self).__init__(*args, **kwargs)
self.fields['product'].queryset = self.product
class Meta:
model = PurchaseRequestDetail
fields = ["product", "variations", "quantity", "fulfilled", "vat", "discount", "surcharges", "active"]
exclude = ("purchase_request", )
You should hook in to get_form_kwargs from ModelFormMixin to pass your data to the form.
class PurchaseRequestDetailCreateView(CreateView):
model = PurchaseRequestDetail
form_class = PurchaseRequestDetailForm
template_name = "inventory/purchaserequestdetail_form.html"
def get_pr_obj(self):
pr_id = self.request.session["pr_id"]
return PurchaseRequest.objects.get(id=pr_id)
def get_form_kwargs(self):
"""
Returns the keyword arguments for instantiating the form.
"""
kwargs = super(PurchaseRequestDetailCreateView, self).get_form_kwargs()
kwargs.update(
{'initial':
{'product': pr_obj.vendor.vendors_products.all()}
}
)
return kwargs

Resources