Django rest framework test does not create instances in test database - django

I'm having an issue in DRF tests when creating instance of a model, the status code in response is 'HTTP_201_CREATED' but the instance it self does not exist in the testing db.
here is my model :
class Item(SafeDeleteModel):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name=_("Owner"))
name = models.CharField(_("Name"), max_length=150)
category = TreeForeignKey('merssis.ItemCategory', on_delete=models.SET_NULL, null=True, verbose_name=_("Category"))
fixed_price = models.FloatField(_("Fixed price"), default=0)
main_pic = ProcessedImageField(verbose_name=_("Main picture"), upload_to='item_pics', processors=[ItemWatermarker()],format='JPEG')
main_pic_thumbnail = ImageSpecField(source='main_pic',
processors=[ResizeToFill(384, 256)],
format='JPEG',
options={'quality': 100})
geo_location = models.PointField(srid=4326, null=True, blank=True, verbose_name=_("Geolocation"))
_safedelete_policy = SOFT_DELETE_CASCADE
def __str__(self):
return self.name
Serializer :
class ItemCreateSerializer(GeoFeatureModelSerializer):
PRICE_TYPE_CHOICES = (
('fixed', _('Fixed') ),
('open', _('Open') ),
)
owner = serializers.HiddenField(default=serializers.CurrentUserDefault())
price_type = serializers.ChoiceField(choices=PRICE_TYPE_CHOICES)
category = serializers.PrimaryKeyRelatedField(queryset=ItemCategory.objects.all(), many=False)#ItemCategorySerializer(many=False)
main_pic = serializers.ImageField(use_url='item_pics')
def validate(self, data):
user = self.context['request'].user
geo_data = data.get('geo_location')
#Validate fixed price value
if data['price_type'] == 'fixed':
if data.get('fixed_price') == None or int(data.get('fixed_price')) <= 0:
raise serializers.ValidationError({"fixed_price" :INVALIDE_PRICE_ERROR})
#Price type is open should explicitly set fixed price to 0
if data['price_type'] == 'open':
data['fixed_price'] = 0
#Validate geo_location
#geo_location post data form ====> {"type":"Point", "coordinates":[37.0625,-95.677068]}
if geo_data:
if not validate_in_country_location(user, geo_data):
raise serializers.ValidationError({"geo_location":OUTSIDE_COUNTRY_MSG})
return data
def create(self, validated_data):
#Remove price_type value since it is not a field in the model
#We used to determine th price type on the serializer only
validated_data.pop('price_type')
return Item(**validated_data)
class Meta:
model = Item
geo_field = 'geo_location'
fields = ( 'owner',
'name',
'price_type',
'category',
'fixed_price',
'main_pic',
'geo_location',
)
The view :
class ItemCreateAPIView(CreateAPIView):
queryset = Item.objects.all()
serializer_class = ItemCreateSerializer
permission_classes = [permissions.IsAuthenticated]
def perform_create(self, serializer, *args, **kwargs):
self.check_object_permissions(self.request, self.request.user)
serializer.save()
The test case :
class ItemTestCase(APITestCase):
def test_create_new_item(self):
"""
Testing Add new item functionality
"""
self.client_1 = APIClient()
self.user_1 = create_new_user(email='tester1#gmail.com', username='tester_1', password='qsdf654654', gender='male')
self.client_1.login(username='tester_1',password='qsdf654654')
image_file = create_test_image()
category = ItemCategory.objects.create(name='SomeCat')
new_item_data = {
'name': 'New Item',
'price_type' : 'open',
'category': str(category.pk),
'main_pic': image_file,
}
response = self.client_1.post(url, new_item_data, format='multipart')
items = Item.objects.filter(name='New Item')
print(response.status_code)
self.assertEqual( response.status_code, status.HTTP_201_CREATED)
self.assertEqual( items.count(), 1)
and when i run the test i get '201' printed in console AND AssertionError: 0 != 1
i'm fvkin confused

In the serializer create() the object was never saved so change:
return Item(**validated_data)
to:
return Item.objects.create(**validated_data) # create the object

Related

How can we pass another field to Create Model Mixin?

Model:
class ShopItem(models.Model):
id = models.AutoField(db_column='ID', primary_key=True)
name = models.CharField(db_column='Name', max_length=255)
price = models.IntegerField(db_column='Price', default=0)
description = models.CharField(db_column='Description', max_length=63)
seller_id = models.ForeignKey(Seller, models.DO_NOTHING, db_column='SellerID')
View:
class SellerItemAPIView(GenericAPIView, ListModelMixin, CreateModelMixin, UpdateModelMixin):
serializer_class = ShopItemSerializer
permission_classes = [AllowAny]
def get_seller(self, *args, **kwargs):
phone_number = self.kwargs.get('phone_number')
seller = Seller.objects.filter(Q(user_id__phone_number=phone_number))[0]
return seller
def post(self, request):
seller = self.get_seller()
return self.create(request, seller_id=seller.id)
Is there any way to use this Creat method with another field?
It now give me this error:
{
"seller_id": [
"This field is required."
]
}
you can add the seller_id to your request.data before calling self.create like this:
seller = Seller.objects.get(user_id=self.request.user.id)
if request.data = {}:
request.data.update(seller_id=seller)
else:
try:
if not request.data._mutable:
request.data._mutable = True
request.data.update(seller_id=seller)
except:
request.data.update(seller_id=seller)

pass username from token to save username in model

This is my model file which has the owner field name
class PotholeImages(models.Model):
"""Upload images with details """
image = models.ImageField(
upload_to='photos/%Y/%m/%d',
)
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
default = 'unknown',
)
state_name = models.CharField(
blank = False,
max_length = 30,
default = 'unknown',
)
country_name = models.CharField(
blank = False,
max_length = 30,
default = 'unknown',
)
name = models.CharField(default=uuid.uuid4, max_length=40)
cordinate_X = models.DecimalField(max_digits=22, decimal_places=16)
cordinate_Y = models.DecimalField(max_digits=22, decimal_places=16)
created_date = models.DateTimeField(auto_now_add=True)
road_name = models.CharField(max_length = 100, default = 'unknown')
def __str__(self):
return self.name
Serializer file
class ImageSerializer(serializers.ModelSerializer):
"""Uploading Pothoele Images"""
class Meta:
model = PotholeImages
fields = ('image','state_name',
'country_name','cordinate_X',
'cordinate_Y','road_name',)
def to_representation(self, instance):
rep = super(ImageSerializer, self).to_representation(instance)
rep['owner'] = instance.owner.username
# print(rep)
return rep
views file
class ImageViewSet(viewsets.ModelViewSet):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
queryset = PotholeImages.objects.all()
serializer_class = serializers.ImageSerializer
def perform_create(self, serializer):
print(self.request.user)
serializer.save(user=self.request.user)
I want whenever a person sends a post request on
http://127.0.0.1:8000/images_api/imageView/
Then from the token, username gets detected and it should be passed in the owner field name.
Could anyone please help
Thanks
Finally, I have found the answer I have made through APIView
class ImageAPIView(APIView):
authentication_classes = (TokenAuthentication,)
def post(self, request):
## username
user = request.user
is_tokened = Token.objects.filter(user = user)
print("user name is ", user)
data = request.data
data['owner'] = user.id
serializer = serializers.ImageSerializer(data=data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=200)
ResponseData = "{} image sent by user".format(user.id)
return Response(ResponseData, status=status.HTTP_200_OK)

Django REST api and django_filter problem

I have problem with rest_framework.viewsets.ReadOnlyModelViewSet.
class ProductFilter(filters.FilterSet):
meat_type = filters.CharFilter(lookup_expr='slug__iexact')
category = filters.CharFilter(lookup_expr='slug__iexact')
class Meta:
model = Product
fields = {
'price': ['gte', 'lte'],
}
ordering_fields = ['price', ]
class ProductViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filterset_class = ProductFilter
#action(methods=['get'], detail=False)
def get_products(self, request):
products = self.get_queryset().order_by('-created')
serializer = self.get_serializer_class()(products, many=True)
print('SHOW IT')
if len(products) == 0:
return Response(status=status.HTTP_204_NO_CONTENT)
return Response(serializer.data, status=status.HTTP_200_OK)
My problem is that print in get_products not work, but code give good result with filters objects. My urls:
router = routers.DefaultRouter()
router.register('', views.ProductViewSet)
urlpatterns = [
path('shop/', include(router.urls))
]
Tests:
class TestViews(TestCase):
def setUp(self):
self.client = Client()
self.url = "/api/shop/"
self.search_url = "/api/shop/?price__lte={}&price__gte={}&meat_type={}&category={}"
self.category1 = Category.objects.create(name='cattest1',
slug='cattest1')
self.category2 = Category.objects.create(name='cattest2',
slug='cattest2')
self.meat_type1 = MeatType.objects.create(name='meattest1',
slug='meattest1')
self.meat_type2 = MeatType.objects.create(name='meattest2',
slug='meattest2')
self.product1 = Product.objects.create(category=self.category1,
meat_type=self.meat_type2,
name='prodtest1',
slug='prodtest1',
price=50)
self.product2 = Product.objects.create(category=self.category1,
meat_type=self.meat_type1,
name='prodtest2',
slug='prodtest2',
price=75)
self.product3 = Product.objects.create(category=self.category2,
meat_type=self.meat_type2,
name='prodtest3',
slug='prodtest3',
price=20)
self.product4 = Product.objects.create(category=self.category2,
meat_type=self.meat_type1,
name='prodtest4',
slug='prodtest4',
price=150)
def test_get_products_all(self):
response = self.client.get(self.url)
self.assertEqual(200, response.status_code)
self.assertEqual(4, len(response.data))
def test_get_products_no_content(self):
Product.objects.all().delete()
response = self.client.get(self.url)
self.assertEqual(204, response.status_code)
def test_product_greater_than(self):
response = self.client.get(self.search_url.format(
"", "55", "", ""
))
self.assertEqual(200, response.status_code)
self.assertEqual(2, len(response.data))
Test test_get_products_no_content fail with error:
assertionError: 204 != 200.
Somebody have any idea?
Thanks for any answer
Magnus
EDIT
Created this function, is pass good data to filter.
DICT {'price__lte': '50', 'price__gte': '100', 'meat_type': 'wieprzowina'}
But I have problem when I put it as filter argument. Got error:
invalid literal for int() with base 10: 'wieprzowina'. It try to change string to number, but I dont know why.
def get_queryset(self):
filter_params = self.request.query_params
filter_params_dict = {k: str(v) for (k, v) in filter_params.dict().items()
if v is not None and str(v) != ""}
print('DICT', filter_params_dict)
queryset = Product.objects.filter(**filter_params_dict)
return queryset
EDIT 2:
class Product(models.Model):
category = models.ForeignKey(Category,
related_name='products',
on_delete=models.CASCADE)
meat_type = models.ForeignKey(MeatType,
on_delete=models.CASCADE)
name = models.CharField(max_length=150,
db_index=True)
slug = models.SlugField(max_length=150,
db_index=True)
image = models.ImageField(upload_to='products/%Y/%m/%d',
default='no-image.png')
description = models.TextField(blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
available = models.BooleanField(default=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
ordering = ('price',)
index_together = (('id', 'slug'),)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('shop:detail',
args=[self.category.slug, self.id, self.slug])
You are using the incorrect url ,you should use this url for your custom action:
/api/shop/get_products/
The url you are using i.e /api/shop/ will call the default list action of the viewset not your custom action and thats why you are getting 200 status code always
You can read more about viewset here:
ViewSets

Django Rest Framework multiple image Upload: Expected a dictionary, but got InMemoryUploadedFile

I'm trying to create an API to upload multiple images per session, I'm using the Django REST Framework. This is the error that I get in Postman:
{
"images": {
"0": {
"non_field_errors": [
"Invalid data. Expected a dictionary, but got InMemoryUploadedFile."
]
}
}
}
models.py with 2 tables
class PostSession(models.Model):
class Meta:
verbose_name = 'session'
verbose_name_plural = 'sessions'
name = models.CharField(
max_length=255,
)
class PostImage(models.Model):
class Meta:
verbose_name = 'photo'
verbose_name_plural = 'photos'
name = models.ForeignKey(
to=PostSession,
on_delete=models.CASCADE,
null=True,
blank=True,
)
file = models.ImageField(
upload_to='photos',
)
serializers.py
class PostImageRetrieveSerializer(serializers.ModelSerializer):
class Meta:
model = PostImage
fields = '__all__'
class PostImageUpdateSerializer(serializers.Serializer):
"""
This class for validate and save child items purpose
"""
name = serializers.CharField(required=False, allow_null=True, allow_blank=True, )
file = serializers.ImageField(required=True, allow_null=False, allow_empty_file=False, )
def create(self, validated_data):
session_name = validated_data.get('name')
image_file = validated_data.get('file')
if session_name and isinstance(session_name, str):
session, _ = PostSession.objects.get_or_create(name=session_name, )
else:
session = None
instance = PostImage.objects.create(
name=session,
file=image_file,
)
return self.update(
instance=instance,
validated_data=validated_data,
)
def update(self, instance, validated_data):
instance.save()
return instance
class PostUploadSerializer(serializers.Serializer):
images = serializers.ListField(
child=PostImageUpdateSerializer(required=True, allow_null=False, many=False, ),
required=True,
allow_null=False,
allow_empty=False,
)
def validate(self, attrs):
images_list = attrs.get('images')
if not isinstance(images_list, list):
raise exceptions.ValidationError(detail={
'images': ['`images` field must be a list of dict object!', ],
})
return attrs
def save_many(self):
images_list = self.validated_data.get('images')
post_image_instances = []
for image_obj in images_list:
try:
post_image_serializer = PostImageSerializer(
context=self.context,
data=image_obj,
many=False,
)
post_image_serializer.is_valid(raise_exception=True, )
post_image = post_image_serializer.save()
post_image_instances.append(post_image)
except:
# TODO: Remove previous saved instances if needed (inside `post_image_instances`)?
pass
return post_image_instances
def create(self, validated_data):
pass
def update(self, instance, validated_data):
pass
viewsets.py
class PostViewSet(viewsets.GenericViewSet):
parser_classes = [parsers.MultiPartParser, parsers.JSONParser, ]
serializer_class = PostImageRetrieveSerializer
queryset = PostImage.objects.all()
#action(methods=['POST', ], detail=False, serializer_class=PostUploadSerializer, )
def upload_images(self, request, *args, **kwargs):
upload_serializer = PostUploadSerializer(
context={'request': request, },
data=request.data,
many=False,
)
upload_serializer.is_valid(raise_exception=True, )
post_image_instances = upload_serializer.save_many()
serializer = self.get_serializer(
post_image_instances,
many=True,
)
return response.Response(
data=serializer.data,
status=status.HTTP_200_OK,
)
The Idea is that the API can create a session with multiple images.
https://gist.github.com/cokelopez/a98ee5569991b6555ecd216764c193ec

Django rest PUT method to update foreign key data

DRF doc's say's i need to override serializers create and update methods to update and/or create nested data. But when i am trying to edit existing descriptions my code creates a new one. Is there a simple way to detect which descriptions was edit and update only it?
class TaskSerializer(ModelSerializer):
url = HyperlinkedIdentityField(
view_name='tasks:tasks-detail',
lookup_field='pk',
)
author = SlugField(source='author.username')
executor = SlugField(source='executor.username')
descriptions = DescriptionSerializer(many=True)
class Meta:
model = Task
fields = (
'pk',
'url',
'title',
'project',
'status',
'author',
'executor',
'descriptions'
)
def create(self, validated_data):
descriptions_data = validated_data.pop('descriptions', None)
author = validated_data.pop('author', None)
executor = validated_data.pop('executor', None)
try:
task_author = User.objects.get(username=author['username'])
task_executor = User.objects.get(username=executor['username'])
except User.DoesNotExist:
raise ValidationError(
_("Такого пользователя не сущетсвует!"))
task = Task.objects.create(author=task_author, executor=task_executor, **validated_data)
if descriptions_data:
for description in descriptions_data:
description, created = Description.objects.get_or_create(
text=description['text'],
task=task
)
task.descriptions.add(description)
return task
def update(self, instance, validated_data):
descriptions_data = validated_data.pop('descriptions', None)
instance.title = validated_data.get('title', instance.title)
instance.project = validated_data.get('project', instance.project)
instance.status = validated_data.get('status', instance.status)
author = validated_data.pop('author', None)
executor = validated_data.pop('executor', None)
try:
if author:
task_author = User.objects.get(username=author['username'])
instance.author = task_author
if executor:
task_executor = User.objects.get(username=executor['username'])
instance.executor = task_executor
except User.DoesNotExist:
raise ValidationError(
_("Такого пользователя не сущетсвует!"))
descriptions_list = []
if descriptions_data:
for description in descriptions_data:
description, created = Description.objects.get_or_create(
text=description["text"],
task=instance
)
descriptions_list.append(description)
instance.descriptions.set(descriptions_list)
instance.save()
return instance
class DescriptionSerializer(ModelSerializer):
class Meta:
model = Description
fields = (
'pk',
'text',
)
class Task(models.Model):
STATUS_CHOICES = (
(1, _("В процессе разработки")),
(2, _("В процессе тестирования")),
(3, _("На ревью")),
(4, _("Открыта")),
(5, _("Закрыта"))
)
title = models.CharField(_('Название'), max_length=60)
project = models.CharField(_('Название проекта'), max_length=60)
status = models.IntegerField(_('Статус'), choices=STATUS_CHOICES,
default=4)
author = models.ForeignKey(settings.AUTH_USER_MODEL,
max_length=60, on_delete=models.CASCADE,
related_name='author',
verbose_name=_('Автор'),
validators=[validate_user])
executor = models.ForeignKey(settings.AUTH_USER_MODEL,
max_length=60, on_delete=models.CASCADE,
related_name='executor',
verbose_name=_('Исполнитель'),
validators=[validate_user])
def __str__(self):
return self.title
def __unicode__(self):
return u'{}'.format(self.title)
class Meta:
verbose_name = _('Задача')
verbose_name_plural = _('Задачи')
class Description(models.Model):
task = models.ForeignKey(Task, on_delete=models.CASCADE,
related_name='descriptions')
text = models.TextField(_('Описание'))
def __str__(self):
return '#{}'.format(self.pk)
def __unicode__(self):
return u'#{}'.format(self.pk)
class Meta:
verbose_name = _('Описание')
verbose_name_plural = _('Описания')