New to Django and DRF, I have a method in the model properties which accept arguments. I have managed to call it successful though a serializer class with default paramenters and getting a JSON response. My problem is I can't pass argument to that function named balance. I have successful pass my argument from view to serializer class but from serializer to model that where I have failed. I thought will be appreciated.
model.py
class Item(models.Model):
entered_by = models.ForeignKey(User, on_delete=models.PROTECT)
name = models.CharField(max_length=50, blank=True)
#property
def balance(self, stock_type='Retail'):
stock = Stock.objects.filter(item=self, type=stock_type, status='InStock').aggregate(models.Sum('quantity')).get('quantity__sum')
return stock or 0
views.py
def getItemInfo(request):
if request.is_ajax and request.method == "GET":
id = request.GET.get("item_id", None)
sell_type = request.GET.get("sell_type", None)
item = Item.objects.get(id=id)
if item:
serializer = ItemSerializer(item, context={'sell_type':sell_type})
return JsonResponse(serializer.data, status = 200, safe=False)
else:
return JsonResponse({"data":False}, status = 400)
serializer.py
from rest_framework import serializers
from .models import Item
class ItemSerializer(serializers.ModelSerializer):
balance = serializers.SerializerMethodField()
class Meta:
model = Item
fields = ('entered_by', 'name', 'balance', 'sell_mode')
def get_balance(self, object):
sell_type = self.context.get("sell_type")
if sell_type:
return object.balance(sell_type)
return object.balance
The error I'm getting
'int' object is not callable
#property couldn't be called. So I made member variable and setter (with calculation) methods in Item model, then make sure setter method will be called in get_balance serializer method, just before returning balance.
Django ORM model itself is just a class; you can do anything class could, not just only linking with ORM.
My Code:
models.py
from django.db import models
from django.contrib.auth.models import User
class Item(models.Model):
entered_by = models.ForeignKey(User, on_delete=models.PROTECT)
name = models.CharField(max_length=50, blank=True)
_balance = 0
def calculate_balance(self, stock_type='Retail'):
stock = Stock.objects.filter(item=self, type=stock_type, status='InStock').aggregate(
models.Sum('quantity')).get('quantity__sum')
self._balance = stock or 0
#property
def balance(self):
return self._balance
serializer.py
from django.contrib.auth.models import User
from rest_framework import serializers
from .models import Item
class ItemSerializer(serializers.ModelSerializer):
balance = serializers.SerializerMethodField()
class Meta:
model = Item
fields = ('entered_by', 'name', 'balance')
def get_balance(self, object):
sell_type = self.context.get("sell_type")
if sell_type:
object.calculate_balance(sell_type)
return object.balance
views.py
from .models import Item
from .serializer import ItemSerializer
from django.http.response import JsonResponse
def getItemInfo(request):
if request.is_ajax and request.method == "GET":
id = request.GET.get("item_id", None)
if id is None:
return JsonResponse({"data": False}, status=400)
sell_type = request.GET.get("sell_type", None)
try:
item = Item.objects.get(id=id)
serializer = ItemSerializer(item, context={'sell_type': sell_type})
return JsonResponse(serializer.data, status=200, safe=False)
except Item.DoesNotExist:
return JsonResponse({"data": False}, status=400)
Related
Views.py -
#api_view(['GET'])
def view_items(request):
if request.query_params:
items = Item.objects.filter(**request.query_param.dict()) #error line
else:
items=Item.objects.all()
serializer=ItemSerializer(items,many=True)
if items:
return Response(serializer.data)
else:
return Response(status=status.HTTP_404_NOT_FOUND)
The **request.query_param should be changed into **request.query_params.
Let's say you want to filter by name.
#api_view(['GET'])
def view_items(request):
name = request.query_params.get("name", None)
if name:
items = Item.objects.filter(name=name)
I would suggest you to use django-filters, that's a better way to structure your code.
Create Item app
urls.py
app_name = "app-name"
router = SimpleRouter()
router.register("items", ItemViewSet, basename="item")
views.py
class ItemViewSet(views.ModelViewSet):
queryset = Item.objects.all()
serializer_class = ItemSerializer
filterset_class = ItemFilterSet
serializers.py
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ("id", "name", "label")
filters.py
from django_filters import rest_framework as filters
class ItemFilterSet(filters.FilterSet):
class Meta:
model = Item
fields = ("name", "label")
I'm new to Django/Django REST FW (and new to this community). I've spent a lot of time reading the documentation but I'm spinning my wheels at this point. I apologize in advance for being so long-winded here.
My back end DB is Postgres. I've got 3 Models, User, Item and ShoppingList. I need Item to contain a description (the item_name field) and its location. The user will select an item and add it to the ShoppingList for that day. The idea is that the user will then "check off" the item once acquired and it'll be "removed" from the view of the ShoppingList.
Here is where I'm having trouble: I don't want to duplicate the item_name and item_location fields in the shopping_list table, but I need to display those fields in the view of the shopping list (shopping_lists.py).
There is a one-to-many relationship between Item and ShoppingList respectively. The Item object is considered a "master items table" that stores descriptions and locations for each item. The ShoppingList object holds a temporary list of these "master items". I need a queryset that contains all fields from ShoppingList and 2 or more fields from Item.
I think this would be what Django REST FW considers a Reverse Relationship. I've tried a variety of changes to my Serialzer(s) and Models (including adding the Item Serializer to the ShoppingList Serializer) and gotten a variety of errors.
models/item.py:
from django.db import models
from django.contrib.auth import get_user_model
class Item(models.Model):
item_name = models.CharField(max_length=50, db_index=True)
item_location = models.CharField(max_length=10, blank=True, db_index=True)
item_class = models.CharField(max_length=20, blank=True)
# This is a relationship with User model
shopper_id = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE
)
def __str__(self):
return f"item_name: {self.item_name}, item_location: {self.item_location}, shopper_id: {self.shopper_id}"
models/shopping_list.py:
from django.db import models
from django.contrib.auth import get_user_model
from .item import Item
class ShoppingList(models.Model):
item_num = models.ForeignKey(
'Item',
on_delete=models.DO_NOTHING # we don't want to delete the item from the "master" item list, just from this shopping list
)
# This is a relationship with user model.
shopper_id = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE # ...but we do want to delete the item if the user goes away as items are user-specific
)
item_qty = models.PositiveIntegerField()
item_complete = models.BooleanField(default=False)
added_on = models.DateField(auto_now=True)
# setting list_num to blank=True for this version
list_num = models.PositiveIntegerField(blank=True)
def __str__(self):
return f"item_num: {self.item_num}, shopper_id: {self.shopper_id}, item_qty: {self.item_qty}, item_complete: {self.item_complete}"
serializers/item.py:
from rest_framework import serializers
from ..models.item import Item
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('id', 'item_name', 'item_location', 'item_class', 'shopper_id')
serializers/shopping_list.py:
from rest_framework import serializers
from ..models.shopping_list import ShoppingList
class ShoppingListSerializer(serializers.ModelSerializer):
class Meta:
model = ShoppingList
fields = ('id', 'item_num', 'shopper_id', 'item_qty', 'item_complete', 'added_on', 'list_num')
Getting ERROR AttributeError: Manager isn't accessible via ShoppingList instances when I execute the GET method in class ShoppingListItemView in views/shopping_lists.py below:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.shortcuts import get_object_or_404
from rest_framework.exceptions import PermissionDenied
from ..models.shopping_list import ShoppingList
from ..serializers.shopping_list import ShoppingListSerializer
from ..models.item import Item
from ..serializers.item import ItemSerializer
class ShoppingListsView(APIView):
def get(self, request, list_num):
shopping_items = ShoppingList.objects.filter(shopper_id=request.user.id)
shopping_list_items = shopping_items.filter(list_num=list_num)
data = ShoppingListSerializer(shopping_list_items, many=True).data
return Response(data)
def post(self, request):
request.data['shopper_id'] = request.user.id
list_item = ShoppingListSerializer(data=request.data, partial=True)
if list_item.is_valid():
list_item.save()
return Response(list_item.data, status=status.HTTP_201_CREATED)
else:
return Response(list_item.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, list_num):
shopping_items = ShoppingList.objects.filter(shopper_id=request.user.id)
shopping_list_items = shopping_items.filter(list_num=list_num)
response_data = shopping_list_items.delete()
return Response(response_data, status=status.HTTP_204_NO_CONTENT)
class ShoppingListsAllView(APIView):
def get(self, request):
shopping_items = ShoppingList.objects.filter(shopper_id=request.user.id)
data = ShoppingListSerializer(shopping_items, many=True).data
return Response(data)
class ShoppingListItemView(APIView):
def get(self, request, pk):
list_item = get_object_or_404(ShoppingList, pk=pk)
if request.user != list_item.shopper_id:
raise PermissionDenied('Unauthorized, this item belongs to another shopper')
else:
list_entry = list_item.objects.select_related('Item').get(id=pk)
print(list_entry)
data = ShoppingListSerializer(list_item).data
return Response(data)
def delete(self, request, pk):
list_item = get_object_or_404(ShoppingList, pk=pk)
if request.user != list_item.shopper_id:
raise PermissionDenied('Unauthorized, this item belongs to another shopper')
else:
list_item.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
def patch(self, request, pk):
list_item = get_object_or_404(ShoppingList, pk=pk)
if request.user != list_item.shopper_id:
raise PermissionDenied('Unauthorized, this item belongs to another shopper')
else:
request.data['shopper_id'] = request.user.id
updated_list_item = ShoppingListSerializer(list_item, data=request.data, partial=True)
if updated_list_item.is_valid():
updated_list_item.save()
return Response(updated_list_item.data)
else:
return Response(updated_item.errors, status=status.HTTP_400_BAD_REQUEST)
if you want to display only a few properties of an item in your ShoppingList you can use the SerializerMethodField method in your serializer
this would work as -
class ShoppingListSerializer(serializers.ModelSerializer):
itemProperty1 = serializers.SerializerMethodField()
itemProperty2 = serializers.SerializerMethodField()
class Meta:
model = ShoppingList
fields = ('id', "itemProperty1", "itemProperty2", 'more_fields')
def get_itemProperty1(self, instance):
return instance.item.anyPropertyOfItem if instance.item else ''
def get_itemProperty2(self, instance):
return instance.item.anyPropertyOfItem if instance.item.else ''
anyPropertyOfItem can be anything from item models.
Setting your serializer this way, your ShoppingList view will automatically show 2 new fields.
or you can also define read only fields with the help of #property in models to get the required field.
If you want to display all the properties of the item in the ShoppingList view, you can write here, will edit my answer. There you need to use related_name and get the item serializer in Shoppinglist serializer as the extra field.
For reverse relationship you should use related_name when defining the model or using the suffix _set.
The related_name attribute specifies the name of the reverse relation
from the User model back to your model. If you don't specify a
related_name, Django automatically creates one using the name of your
model with the suffix _set
Copied from What is related_name used for? by Wogan
My models are:
class User(models.Model):
id = models.AutoField(primary_key=True)
email = models.EmailField()
class Lawyer(models.Model):
user = models.OnetoOneField(User)
class Session(models.Model):
name = models.CharField()
lawyer = models.ForeignKey(Lawyer)
I am trying to create multiple objects with a list serializer for Session.
From the app side they don't have the id of lawyer, but have the email of each lawyer. How can I write a list serializer where I can take the following json input and use email to fetch lawyer to store multiple session objects?
The json input sent will be like:
[
{
"name": "sess1",
"email": "lawyer1#gmail.com",
},
{
"name": "sess1",
"email": "lawyer1#gmail.com",
}
]
You can do it in this way but I think email should be unique=True.
Then use a serializer like this:
from django.utils.translation import ugettext_lazy as _
class SessionCreateManySerializer(serializers.ModelSerializer):
email = serializers.SlugRelatedField(
source='lawyer',
slug_field='user__email',
queryset=Lawyer.objects.all(),
write_only=True,
error_messages={"does_not_exist": _('lawyer with email={value} does not exist.')}
)
class Meta:
model = Session
fields = ('name', 'email')
and a generic create view (just override get_serializer and place many=True in kwargs ):
from rest_framework.response import Response
from rest_framework import generics
class SessionCreateManyView(generics.CreateAPIView):
serializer_class = SessionCreateManySerializer
def get_serializer(self, *args, **kwargs):
kwargs['many'] = True
return super(SessionCreateManyView, self).get_serializer(*args, **kwargs)
You can use bulk creation as well:
# serializers.py
from myapp.models import Session
from rest_framework import serializers
class SessionSerializer(serializers.Serializer):
email = serializers.EmailField(required=True)
name = serializers.CharField(required=True)
def validate_email(self, email):
lawyer = Lawyer.objects.filter(user__email=email).first()
if not lawyer:
raise ValidationError(detail="user dose not exist.")
return lawyer
def create(self, validated_data):
return Session.objects.create(name=validated_data.get('name'), lawyer=validated_data.get('email'))
and in your api.py file allow bulk creation:
# api.py
from rest_framework import generics
class SessionCreateAPIView(generics.CreateAPIView):
"""Allows bulk creation of a resource."""
def get_serializer(self, *args, **kwargs):
if isinstance(kwargs.get('data', {}), list):
kwargs['many'] = True
return super().get_serializer(*args, **kwargs)
When i am trying to POST using the browsable API of DRF, i get the following error:
Got a TypeError when calling Note.objects.create(). This may be
because you have a writable field on the serializer class that is not
a valid argument to Note.objects.create(). You may need to make the
field read-only, or override the NoteSerializer.create() method to
handle this correctly.
I don't know what is generating this error or how to overcome it. Literally spent hours google searching or changing the code. Can someone explain how to overcome this error? (Please note i am new to Django and DRF!)
Here is the models.py:
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
import uuid
class Stock(models.Model):
'''
Model representing the stock info.
'''
user = models.ForeignKey(User)
book_code = models.CharField(max_length=14, null=True, blank=True)
def __str__(self):
return self.book_code
class Note(models.Model):
'''
Model representing the stock note.
'''
user = models.ForeignKey(User)
note = models.TextField(max_length=560)
stock = models.ForeignKey(Stock, related_name='notes')
date_note_created = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.note
This is the views.py:
from rest_framework import generics
from stocknoter.models import Stock, Note
from api.serializers import StockSerializer, NoteSerializer
# Create your views here.
class StockList(generics.ListCreateAPIView):
serializer_class = StockSerializer
def get_queryset(self):
user = self.request.user
return Stock.objects.filter(user=user)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
def perform_update(self, serializer):
serializer.save(user=self.request.user)
class NoteList(generics.ListCreateAPIView):
serializer_class = NoteSerializer
def get_queryset(self):
user = self.request.user
return Note.objects.filter(user=user)
def perform_create(self, serializer):
serializer.save(data=self.request.data)
def perform_update(self, serializer):
serializer.save(data=self.request.data)
This is the serializers.py:
from stocknoter.models import Stock, Note
from rest_framework import serializers
class StockSerializer(serializers.ModelSerializer):
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
notes = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Stock
fields = ('id', 'user', 'book_code', 'notes')
class NoteSerializer(serializers.ModelSerializer):
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = Note
fields = ('user', 'note', 'stock')
I think this is because you are providing keyword argument data to save method. This is unnecessary. save() dont have such argument. Try just this:
def perform_create(self, serializer):
serializer.save()
take a print() of serializer_data in the view.py
to see what you actually sending to model and create.
then in your serializer.py override the create method..
for example:
def post(self, request):
print(serializer_data.validated_data)
def create(self, validated_data):
del validated_data['c']
return X.objects.create(a=validated_data['a'],
b=validated_data['b'],
)
models.py:
from django.db import models
from django.contrib.auth.models import User as BaseUser
CHOICE_GENDER = ((1, 'Male'), (2, 'Female'))
class Location(models.Model):
city = models.CharField(max_length=75)
country = models.CharField(max_length=25)
def __str__(self):
return ', '.join([self.city, self.state])
class Users(BaseUser):
user = models.OneToOneField(BaseUser, on_delete=models.CASCADE)
gender = models.IntegerField(choices=CHOICE_GENDER)
birth = models.DateField()
location = models.ForeignKey(Location)
class Meta:
ordering = ('user',)
forms.py:
from django.contrib.auth.forms import UserCreationForm
from django import forms
from .models import Users, Location
class LocationForm(forms.ModelForm):
class Meta:
model = Location
fields = '__all__'
class RegistrationForm(UserCreationForm):
class Meta:
model = Users
fields = ('username', 'email', 'first_name', 'last_name', 'gender', 'birth', 'location')
views.py:
def signup(request):
if request.method == "POST":
reg_form = forms.RegistrationForm(request.POST)
loc_form = forms.LocationForm(request.POST)
if loc_form.is_valid():
reg_form.location = loc_form.save()
if reg_form.is_valid():
reg_form.save()
return redirect('./')
reg_form = forms.RegistrationForm()
loc_form = forms.LocationForm()
return render(request, 'signup.html', {'loc_form': loc_form, 'reg_form': reg_form})
I can't manage to make this work, it gives location - This field is required error. I've tried every combination in views.py, and it never passed the reg_form.is_valid() command due to that reason. Can anybody help me in this? Thanks in advance!
SOLVED: views.py new, working code:
def signup(request):
if request.method == "POST":
reg_form = forms.RegistrationForm(request.POST)
loc_form = forms.LocationForm(request.POST)
if reg_form.is_valid():
reg = reg_form.save(commit=False)
if loc_form.is_valid():
reg.location = loc_form.save()
reg.save()
return redirect('./')
reg_form = forms.RegistrationForm()
loc_form = forms.LocationForm()
return render(request, 'signup.html', {'loc_form': loc_form, 'reg_form': reg_form})
Removing location from RegistrationForm fields tuple should stop the behavior.
Since you are using a separate form for Location, you shouldn't populate location field using RegistrationForm.