I have a Django REST project. I have a models User, Store and Warehouse.
And I have a module with marketplace parser, that gets data from marketplace API. In this module there is a class Market and a method "get_warehouses_list". This method returns a JSON with a STORE's warehouse list.
Examle answer:
{
"result": [
{
"warehouse_id": 1,
"name": "name1",
"is_rfbs": false
},
{
"warehouse_id": 2,
"name": "name2",
"is_rfbs": true
}
]
}
What I have to do is to make creating and updating methods to set and update this warehouse list into my MySQL DB (with creating an endpoint for setting and updating this data).
I don't know what is incorrect in my code, but when I send POST request to my endpoint in urls.py
router.register("", WarehouseApi, basename="warehouse")
I get 400 error instead of setting warehouse list into my DB.
My code:
user/models.py
class User(AbstractUser):
username = models.CharField(
max_length=150,
unique=True,
null=True)
id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
unique=True,
editable=False)
store/models.py
`
class Store(models.Model):
user = models.ForeignKey(
User,
on_delete=models.PROTECT)
name = models.CharField(max_length=128,
blank=True)
type = models.PositiveSmallIntegerField(
choices=MARKET,
default=1,
verbose_name="Type API")
api_key = models.CharField(max_length=128)
client_id = models.CharField(max_length=128)
warehouses/models.py
class Warehouse(models.Model):
store = models.ForeignKey(
Store,
on_delete=models.СASCAD, null=True)
warehouse_id = models.BigIntegerField(
unique = True,
null = True)
name = models.CharField(
max_length=150)
is_rfbs = models.BooleanField(default=False)
`
serializers.py
`
class WarehouseSerializer(serializers.ModelSerializer):
class Meta:
model = Warehouse
fields = '__all__'
store = serializers.CharField(max_length=50)
warehouse_id = serializers.IntegerField()
name = serializers.CharField(max_length=100)
is_rfbs = serializers.BooleanField()
is_default_warehouse = serializers.BooleanField()
class WarehouseUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = Warehouse
fields = ('name', 'is_rfbs',)
def save(self, **kwargs):
self.instance.name = self.validated_data["name"]
self.instance.is_rfbs = self.validated_data["is_rfbs"]
self.instance.save
returself.instance
views.py
class WarehouseApi(ModelViewSet):
def get_queryset(self):
return Warehouse.objects.filter(
store__user_id=self.request.user.pk)\
.order_by('-warehouse_id')
def get_serializer_class(self):
if self.request.method in ("PUT", "PATCH"):
return WarehouseUpdateSerializer
return WarehouseSerializer
def create(self, request, *args, **kwargs):
st = Store.objects.filter(user=self.request.user, # getting all stores with marketplace type = 1
type=1)
for e in st:
api = Market(api_key=e.api_key, # call parser class Market
client_id=e.client_id)
data = api.get_warehouses_list() # call Market's method 'get_warehouses_list'
if len(data['result']) > 0:
for wh in data['result']:
alreadyExists = Warehouse.objects.filter( # check if warehouses with such ID already exists
warehouse_id=wh.get(
'warehouse_id')).exists()
if alreadyExists:
return Response({'message':'Warehouse ID already exists'})
else:
wh_data = {
'warehouse_id': wh.get('warehouse_id'),
'name': wh.get('name'),
'is_rfbs': wh.get('is_rfbs')
}
Warehouse.objects.create(**wh_data)
warehouses = Warehouse.objects.filter(
marketplace=1, store__in=st).order_by(
'-warehouse_id')
s = WarehouseSerializer(warehouses, many=True)
return Response(status=200, data=s.data)
else:
return Response(status=400,
data={"Error": "Store has no warehouses"})
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
api = Market(api_key=instance.store.api_key,
client_id=instance.store.client_id)
warehouses = Warehouse.objects.filter(store__user_id=self.request.user,
store__type=1) # get all store's warehouses
if warehouses:
for warehouse in warehouses:
r = api.get_warehouses_list() # call Market's method 'get_warehouses_list'
if len(r['result']) > 0:
for wh in r['result']:
if serializer.validated_data["name"] != wh['name']:
instance.name = wh['name']
instance.save(['name'])
if serializer.validated_data["is_rfbs"] != wh['is_rfbs']:
instance.name = wh['is_rfbs']
instance.save(['is_rfbs'])
serializer = WarehouseUpdateSerializer(instance)
return Response(serializer.data, {
'status': status.HTTP_200_OK,
'message': 'Warehouse updated successfully'
})
else:
return Response({
'message': 'Store has no warehouses'
})
else:
return Response({
'message': 'There are no saved warehouses in DB'
})
`
The 400 Error might be caused by a number of reasons. Do you have any logging information you can provide? Either from the Python logs or using the browser developer tools?
In your own code you are returning a 400 code if the variable data is empty. Are you sure you are not hitting this validation?
If nothing turns up, I advise you to add some exception dealing and logging capabilities to your own code as it seems to be very unprotected and post that information here if needed.
Related
I'm try to write test for creating sales data.
But keep getting KeyError: 'content' when running python manage.py test.
The test is to ensure user can add/create sales data with its details (nested)
with this as reference to create writeable nested serializers
models.py
# abstract base table for transactions
class Base_transaction(models.Model):
is_paid_off = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
pass
class Meta:
abstract = True
# sales table to store surface level sales information
# consist of sales_id as pk, customer_name, sub_total, discount, user_id,
# total, is_paid_off, created_at, updated_at
class Sales(Base_transaction):
sales_id = models.BigAutoField(
primary_key=True,
unique=True
)
customer_name = models.CharField(max_length=25, blank=True)
customer_contact = models.CharField(max_length=13, blank=True)
user_id = models.ForeignKey(
User,
on_delete=models.PROTECT,
null=True,
db_column='user_id'
)
def __str__(self) -> str:
return f'{self.sales_id} at {self.created_at} | Lunas={self.is_paid_off}'
class Meta:
db_table = 'sales'
# sales_detail table store the detail of sales per sparepart
# consist of sales_detail_id as pk, quantity, individual_price, total_price
# sales_id
class Sales_detail(models.Model):
sales_detail_id = models.BigAutoField(
primary_key=True,
unique=True
)
quantity = models.PositiveSmallIntegerField()
is_grosir = models.BooleanField(default=False)
sales_id = models.ForeignKey(
Sales,
on_delete=models.CASCADE,
db_column='sales_id'
)
sparepart_id = models.ForeignKey(
'Sparepart',
on_delete=models.SET_NULL,
null=True,
db_column='supplier_id'
)
def __str__(self) -> str:
return f'{self.sales_id} - {self.sparepart_id}'
serializers.py
class SalesDetailSerializers(serializers.ModelSerializer):
sparepart = serializers.ReadOnlyField(source='sparepart_id.name')
class Meta:
model = Sales_detail
fields = ['sales_detail_id', 'sparepart', 'quantity', 'is_grosir']
class SalesSerializers(serializers.ModelSerializer):
content = SalesDetailSerializers(many=True, source='sales_detail_set')
class Meta:
model = Sales
fields = ['sales_id', 'customer_name', 'customer_contact', 'is_paid_off', 'content']
def create(self, validated_data):
details = validated_data.pop('content')
sales = Sales.objects.create(**validated_data)
for detail in details:
Sales_detail.objects.create(sales_id=sales, **detail)
return sales
test.py
class SalesAddTestCase(APITestCase):
sales_url = reverse('sales_add')
def setUp(self) -> None:
# Setting up sparepart data
for i in range(3):
Sparepart.objects.create(
name=f'random name{i}',
partnumber=f'0Y3AD-FY{i}',
quantity=50,
motor_type='random m',
sparepart_type='random s',
price=5400000,
grosir_price=5300000,
brand_id=None
)
self.spareparts = Sparepart.objects.all()
# Creating data that gonna be use as input
self.data = {
'customer_name': 'someone',
'customer_contact': '085634405602',
'is_paid_off': False,
'content': [
{
'sparepart': self.spareparts[1].sparepart_id,
'quantity': 1,
'is_grosir': False,
},
{
'sparepart': self.spareparts[0].sparepart_id,
'quantity': 30,
'is_grosir': True,
}
]
}
def test_user_successfully_add_sales(self) -> None:
"""
Ensure user can add new sales data with it's content
"""
response = self.client.post(self.sales_url, self.data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data['customer_name'], 'someone')
self.assertEqual(len(response.data['content']), 2)
The view i'm using generics.CreateAPIView
the expected response would be the same as self.data with sparepart field became the name of that sparepart.
The example below using random data to give some context
{
'customer_name': 'someone',
'customer_contact': '085456105311',
'is_paid_off': False,
'content': [
{
'sparepart': 'something1', # the name of sparepart 1
'quantity': 5,
'is_grosir': False,
},
{
'sparepart': 'something2', # the name of sparepart 2
'quantity': 3,
'is_grosir': False,
}]
}
i tried to change sparepart in content to = self.spareparts[1] but now give type error Object of type Sparepart is not JSON serializable, change it to self.spareparts[1].name gives same KeyError as before (content).
i suspect the error because sparepart field is readonlyfield in SalesDetailSerializer then i change it to charfield, but still got same KeyError
I'm really confused what make this error occur in the first place.
I found the problem:
sparepart field in SalesDetailSerializers is ReadOnly and cannot be used for my purpose.
content field on SalesSerializers using argument source sales_detail_set. I don't know why but the content key field in validated_data became sales_detail_set not content.
My Solutions:
Because i already write a test to ensure user can get list of sales with it's detail which required sparepart field to show sparepart name, i can't change it. Instead i create another serializer named SalesDetailPostSerializers and SalesPostSerializers Like this.
class SalesDetailPostSerializers(serializers.ModelSerializer):
class Meta:
model = Sales_detail
fields = ['sales_detail_id', 'sparepart_id', 'quantity', 'is_grosir']
class SalesPostSerializers(serializers.ModelSerializer):
content = SalesDetailPostSerializers(many=True, source='sales_detail_set')
class Meta:
model = Sales
fields = ['sales_id', 'customer_name', 'customer_contact', 'is_paid_off', 'content']
def create(self, validated_data):
details = validated_data.pop('sales_detail_set')
sales = Sales.objects.create(**validated_data)
for details in details:
Sales_detail.objects.create(sales_id=sales, **details)
return sales
in SalesPostSerializer change
details = validated_data.pop('content')
To
details = validated_data.pop('sales_detail_set')
Then change sparepart key name inside self.data in test.py to sparepart_id
'sparepart_id': self.spareparts[1].sparepart_id,
I'm working on an app that lets you track your expenses and I'm trying to create an 'balnace' object when the user registers, but when I try so I get an Error: Cannot assign "15": "Balance.user_id" must be a "Users" instance.
Model
class Balance(models.Model):
balance = models.FloatField(
verbose_name="Balance", blank=False, null=False)
user_id = models.ForeignKey(
'expenseApi.Users', verbose_name="Usuario", blank=False, null=False, on_delete=models.PROTECT)
def __str__(self):
return '{}'.format(self.pk)
serializer
class RegisterSerializer(serializers.ModelSerializer):
tokens = serializers.SerializerMethodField()
email = serializers.EmailField(max_length=50)
class Meta:
model = Users
fields= ['id', 'email', 'password', 'name', 'lastname', 'birthday', 'tokens']
extra_kwargs = {
'password': {
'write_only': True,
},
}
def get_tokens(self,user):
refresh = RefreshToken.for_user(user)
data = {
'refresh': str(refresh),
'access': str(refresh.access_token),
}
return data
def create(self, request):
email= Users.objects.filter(email=request['email'])
if email:
raise serializers.ValidationError({'detail': 'El email ya esta en uso'})
user = Users.objects.create_user(email=request['email'],
password=request['password'],
name=request['name'],
lastname=request['lastname'],
birthday=request['birthday'])
user['balance'] = Balance.objects.create(balance=0,user_id=user.id)
return user
I didn't go in detail about logic on your code but I guess error based on your code at
user['balance'] = Balance.objects.create(balance=0,user_id=user.id)
should be
user['balance'] = Balance.objects.create(balance=0, user_id=user)
Since you named your ForeignKey user_id, this means that you can assign a User object to .user_id, or the primary key to .user_id_id:
user['balance'] = Balance.objects.create(
balance=0,
user_id_id = user.id
)
I would however advise to rename user_id to user, since now the field "hints" that it is an id, but it is not.
class Balance(models.Model):
balance = models.FloatField(verbose_name='Balance')
user = models.ForeignKey(
'expenseApi.Users',
verbose_name='Usuario',
on_delete=models.PROTECT
)
# …
Then we thus can set this with:
user['balance'] = Balance.objects.create(
balance=0,
user_id = user.id
)
I am learning DRF and I've now run into a problem that has stalled me for days. The app is a zumba class application and I'm using DRF to create an API on top of it. The part I am trying to build right now is the part where a user can add himself to a zumba class(so, he has to be able to update a manytomany field) What I'd like the API to do when the user register himself to the class(PUT, or PATCH), is to take his username, that we get from the authentication, and add him to "myusers" field. But since the PUT is empty, the API keeps complaining that "myusers" is required.
Is there a way to tell the API that "myusers" is not required in the PUT request since it is extracted from the authentication token? (If I manually create a PUT request with "myusers": [{"id": 9}] in the body, it works but I'd like to avoid adding that client side since the data passed is not even used.)
The serializer(all the reado-only fields are to make sure the user cannot update them):
class UserActivityClassesSerializer(serializers.ModelSerializer):
activitytypename = serializers.SlugRelatedField(source='activitytype', slug_field='name', read_only=True)
activitytype = ActivityTypeSerializer(read_only=True)
myusers = MyUsersSerializer(many=True) # serializers.HyperlinkedRelatedField(many=True, view_name='myusers-detail', read_only=True)
uri = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Activity
fields = [
'uri',
'id',
'name',
'date',
'activitytype',
'activitytypename',
'status',
'myusers',
]
read_only_fields = [
'uri',
'id',
'name',
'date',
'activitytype',
'activitytypename',
'status',
]
def get_uri(self, obj):
request = self.context.get('request')
return api_reverse("api-activities:classes-detail", kwargs={"id": obj.id}, request=request)
The view
class ActivityViewSet(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated]
queryset = Activity.objects.all()
lookup_field = 'id'
def get_serializer_class(self):
if self.request.user.is_staff or self.action == 'create':
return AdminActivityClassesSerializer
else:
return UserActivityClassesSerializer
def perform_update(self, serializer):
instance = self.get_object()
request = serializer.context['request']
auth_user = request.user
qs = instance.myusers.filter(id=auth_user.id)#we verify is the user is already registered, and if yes, we remove him
if qs:
print('delete')
instance.myusers.remove(auth_user)
return instance
else:
result = verify_valid_season_pass(auth_user, instance)
if result == 1:
print('add')
instance.myusers.add(auth_user.id)
else:
raise serializers.ValidationError("No valid seasonpass found.")
The model that gets updated:
class Activity(models.Model):
CLOSE = 0
OPEN = 1
ACTIVITY_STATUS_CHOICES = [
(CLOSE, 'Closed'),
(OPEN, 'Open'),
]
name = models.CharField('Activity Name', max_length=64, blank=False, null=False)
date = models.DateTimeField('Start Date & Time', blank=False, null=False)
activitytype = models.ForeignKey(ActivityType, blank=False, null=False, default=1, on_delete=models.DO_NOTHING)
status = models.IntegerField('Status', choices=ACTIVITY_STATUS_CHOICES, default=OPEN,) # 1 = open, 0 = closed
myusers = models.ManyToManyField("users.User")
def __str__(self):
return self.name
Any clues?
Have you tried
myusers = MyUsersSerializer(many=True, required=False)
I'm new to DRF (and Django) and trying to create a nested serializer which is able to validate the following request data:
{
"code": "12345",
"city": {
"name": "atlanta",
"state": "Georgia"
},
"subregion": {
"name": "otp north"
}
}
To simplify things for the client, I'd like this single request to create multiple records in the database:
A City (if a matching one doesn't already exist)
A Subregion (if a matching one doesn't already exist)
A CodeLog which references a city and (optionally) a subregion
Models:
class City(models.Model):
name = models.CharField(max_length=75, unique=True)
state = models.CharField(max_length=50, blank=True)
class Subregion(models.Model):
city = models.ForeignKey(City)
name = models.CharField(max_length=75)
class CodeLog(models.Model):
code = models.CharField(max_length=10)
city = models.ForeignKey(City)
subregion = models.ForeignKey(Subregion, blank=True, null=True)
Serializers:
class CitySerializer(serializers.ModelSerializer):
class Meta:
model = City
fields = ('name', 'state')
class SubregionSerializer(serializers.ModelSerializer):
class Meta:
model = Subregion
fields = ('name',)
class CodeLogSerializer(serializers.ModelSerializer):
city = CitySerializer()
subregion = SubregionSerializer(required=False)
class Meta:
model = CodeLog
fields = ('code', 'city', 'subregion')
# This is where I'm having troubles
def create(self, validated_data):
city_data = validated_data.pop('city', None)
subregion_data = validated_data.pop('subregion', None)
if city_data:
city = City.objects.get_or_create(**city_data)[0]
subregion = None
if subregion_data:
subregion = Subregion.objects.get_or_create(**subregion_data)[0]
code_log = CodeLog(
code=validated_data.get('code'),
city=city,
subregion=subregion
)
code_log.save()
return code_log
View:
class CodeLogList(APIView):
serializer_class = CodeLogSerializer
def post(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I was able to get it working with a flat request structure, nested serializers are proving difficult to grasp.
Am I on the right track, or is there a more ideal structure that would work better? Any help is greatly appreciated!
Your Subregion model has a city field which is foreign key and cannot be null. Your create method should be like this.
def create(self, validated_data):
city_data = validated_data.pop('city', None)
subregion_data = validated_data.pop('subregion', None)
if city_data:
city = City.objects.get_or_create(**city_data)[0]
subregion = None
if subregion_data:
# Add city in Subregion data
subregion_data.update({'city': city})
subregion = Subregion.objects.get_or_create(**subregion_data)[0]
code_log = CodeLog(
code=validated_data.get('code'),
city=city,
subregion=subregion
)
code_log.save()
return code_log
The design of my api is
{
"id": "667c476ca953483493afa265e5d500b0",
"name": "Home"
}
This is the result of GET API. In posting, i want the list of devices to be posted. For example, if user want to post devices to Home group, then the url i have designed is /group/group_token/add(/group/667c476ca953483493afa265e5d500b0/add). The data sending format is
{
"devices":[<device_id1>, <device_id2>]
}
i.e
{
"devices":"[5ac41ba7e6ae4628982b2c81c99343a8], [7nu21ba7e6ae4628982b2c81c99343a8]"
}
Here is my model, serializer and APIView i have done so far
class BaseDevice(PolymorphicModel):
name = models.CharField(max_length=250, blank=False, null=False)
owner = models.ForeignKey(User, blank=False, null=False)
token = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
group = models.ForeignKey('DeviceGroup', related_name="groups", null=True, blank=True)
class Device(BaseDevice):
description = models.TextField(blank=True, null=True)
class DeviceGroup(models.Model):
token = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
name = models.CharField(max_length=250, blank=False, null=False)
owner = models.ForeignKey(User, blank=False, null=False)
class DeviceGroupSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex', read_only=True)
class Meta:
model = DeviceGroup
fields = ['id','name']
class DevicesGroupsAPIView(APIView):
permission_classes = (permissions.IsAuthenticated,)
def get_serializer_class(self):
if self.request.user.is_staff:
return DeviceGroupSerializer
def get_object(self, user, token):
try:
return BaseDevice.objects.filter(owner=user).get(token=token)
except ObjectDoesNotExist:
return error.RequestedResourceNotFound().as_response()
def get(self, request, format=None):
reply = {}
try:
groups = DeviceGroup.objects.filter(owner=request.user)
reply['data'] = DeviceGroupSerializer(groups, many=True).data
except:
reply['data'] = []
return Response(reply, status.HTTP_200_OK)
def post(self, request, token=None, format=None):
device_group_instance = DeviceGroup.objects.get(token=token)
for device_token in request.data['devices']:
device = Device.objects.get(token=device_token, owner=request.user)
device.group = device_group_instance
device.save()
How can i do the posting? I get an error on token part. It says
DeviceGroup matching query does not exist.
How should my posting be?
UPDATE
Frist screenshot
The url is device_group/ . When i try to post from there. i get above error not matching query one
Second Screenshot
The url is in the screenshot browser. I dont get device list to post rather i see name field.
I'm not sure how this is supposed to work, but for whatever reason your post() function is not obtaining a valid token that matches an existing DeviceGroup.
Make sure that DeviceGroup is the model you are intending to call.
If you are manually instantiating post() somewhere else, check to see that a valid token is being passed to it.
Otherwise you will need to retrieve it in the function itself. For example, if the token is stored in a session variable, you can do:
def post(self, request, token=None, format=None):
token = request.session.token
device_group_instance = DeviceGroup.objects.get(token=token)
for device_token in request.data['devices']:
device = Device.objects.get(token=device_token, owner=request.user)
device.group = device_group_instance
device.save()