How can we pass another field to Create Model Mixin? - django

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)

Related

Django rest framework test does not create instances in test database

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

How can I show the StringRelatedField instead of the Primary Key while still being able to write-to that field using Django Rest Framework?

Models:
class CrewMember(models.Model):
DEPARTMENT_CHOICES = [
("deck", "Deck"),
("engineering", "Engineering"),
("interior", "Interior")
]
first_name = models.CharField(max_length=25)
last_name = models.CharField(max_length=25)
email = models.EmailField()
department = models.CharField(max_length=12, choices=DEPARTMENT_CHOICES)
date_of_birth = models.DateField()
join_date = models.DateField()
return_date = models.DateField(null=True, blank=True)
leave_date = models.DateField(null=True, blank=True)
avatar = models.ImageField(null=True, blank=True)
active = models.BooleanField(default=True)
def __str__(self):
return f"{self.first_name} {self.last_name}"
class RosterInstance(models.Model):
date = models.DateField(default=timezone.now)
deckhand_watchkeeper = models.ForeignKey(CrewMember, on_delete=models.PROTECT, null=True, related_name="deckhand_watches")
night_watchkeeper = models.ForeignKey(CrewMember, on_delete=models.PROTECT, null=True, related_name="night_watches")
def __str__(self):
return self.date.strftime("%d %b, %Y")
Views:
class CrewMemberViewSet(viewsets.ModelViewSet):
queryset = CrewMember.objects.all()
serializer_class = CrewMemberSerializer
filter_backends = [SearchFilter]
search_fields = ["department"]
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
instance.active = False
instance.save()
return Response(status=status.HTTP_204_NO_CONTENT)
class RosterInstanceViewSet(viewsets.ModelViewSet):
queryset = RosterInstance.objects.all()
serializer_class = RosterInstanceSerializer
Serializers:
class CrewMemberSerializer(serializers.ModelSerializer):
class Meta:
model = CrewMember
fields = "__all__"
class RosterInstanceSerializer(serializers.ModelSerializer):
class Meta:
model = RosterInstance
fields = "__all__"
The resulting data looks like this:
{
"id": 2,
"date": "2020-12-09",
"deckhand_watchkeeper": 1,
"night_watchkeeper": 3
}
But I want it to look like this:
{
"id": 2,
"date": "2020-12-09",
"deckhand_watchkeeper": "Joe Soap",
"night_watchkeeper": "John Smith"
}
I can achieve the above output by using StringRelatedField in the RosterInstanceSerializer but then I can no longer add more instances to the RosterInstance model (I believe that is because StringRelatedField is read-only).
Because StringRelaredField is always read_only, you can use SlugRelatedField instead:
class RosterInstanceSerializer(serializers.ModelSerializer):
deckhand_watchkeeper = serializers.SlugRelatedField(
slug_field='deckhand_watchkeeper'
)
night_watchkeeper = serializers.SlugRelatedField(
slug_field='night_watchkeeper'
)
class Meta:
model = RosterInstance
fields = ['id', 'date', 'deckhand_watchkeeper', 'night_watchkeeper']
I was created a WritableStringRelatedField to do that.
class WritableStringRelatedField(serializers.SlugRelatedField):
def __init__(self, display_field=None, *args, **kwargs):
self.display_field = display_field
# Set what attribute to be represented.
# If `None`, use `Model.__str__()` .
super().__init__(*args, **kwargs)
def to_representation(self, obj):
# This function controls how to representation field.
if self.display_field:
return getattr(obj, self.display_field)
return str(obj)
def slug_representation(self, obj):
# It will be called by `get_choices()`.
return getattr(obj, self.slug_field)
def get_choices(self, cutoff=None):
queryset = self.get_queryset()
if queryset is None:
# Ensure that field.choices returns something sensible
# even when accessed with a read-only field.
return {}
if cutoff is not None:
queryset = queryset[:cutoff]
return OrderedDict([
(
self.slug_representation(item),
# Only this line has been overridden,
# the others are the same as `super().get_choices()`.
self.display_value(item)
)
for item in queryset
])
Serializers:
class RosterInstanceSerializer(serializers.ModelSerializer):
deckhand_watchkeeper = WritableStringRelatedField(
queryset=CrewMember.objects.all(),
slug_field='id',
label='Deckhand Watchkeeper',
)
night_watchkeeper = WritableStringRelatedField(
queryset=CrewMember.objects.all(),
slug_field='id',
label='Night Watchkeeper',
)
class Meta:
model = RosterInstance
fields = "__all__"

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

Nested JSON and HyperlinkedModelSerializer problem

I'm working on a tweet App. I have 2 main models : Tweets and Comments. I'm using HyperlinkedModelSerializer to get absolute url for my comments instances with the "url" added in the field. But when It comes to display the comments inside my tweet JSON format, i have this error :
`HyperlinkedIdentityField` requires the request in the serializer context. Add `context={'request': request}` when instantiating the serializer.
This error is gone when I remove the url from the field.
Here is my Comment Model :
class CommentManager(models.Manager):
def filter_by_instance(self, instance):
content_type = ContentType.objects.get_for_model(instance.__class__)
obj_id = instance.id
qs = super(CommentManager, self).filter(content_type=content_type, object_id=obj_id)
return qs
class Comment(models.Model):
content = models.TextField(max_length=150)
author = models.ForeignKey(
User,
on_delete = models.CASCADE
)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField(blank=True)
content_object = GenericForeignKey('content_type', 'object_id')
parent = models.ForeignKey(
"self",
on_delete = models.CASCADE,
blank=True,
null=True
)
datestamp = models.DateTimeField(auto_now_add=True)
objects = CommentManager()
def __str__(self):
return str(self.content[:30])
def save(self):
self.object_id = self.parent.id
super(Comment, self).save()
def children(self):
return Comment.objects.filter(parent=self)
#property
def is_parent(self):
if self.parent is None:
return False
return True
Here is my comment serializer :
class CommentSerializer(serializers.HyperlinkedModelSerializer):
children = serializers.SerializerMethodField()
datestamp = serializers.SerializerMethodField()
def get_datestamp(self, obj):
return obj.datestamp.date()
def get_children(self, obj):
qs = obj.children()
return ChildrenCommentSerializer(qs, many=True).data
class Meta:
model = Comment
fields = [
"url",
"datestamp",
"content",
"is_parent",
"object_id",
"children"
]
class ChildrenCommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = [
"content"
]
And finally my tweet serializer :
class TweetSerializer(serializers.ModelSerializer):
author = serializers.SlugRelatedField(slug_field="username", queryset=User.objects.all())
comments = serializers.SerializerMethodField()
datestamp = serializers.SerializerMethodField()
def get_datestamp(self, obj):
return obj.datestamp.date()
def get_comments(self, obj):
qs = Comment.objects.filter_by_instance(obj)
print()
print()
print(CommentSerializer(qs, many=True))
print()
print()
return CommentSerializer(qs, many=True).data
class Meta:
model = Tweet
fields = ["datestamp", "id", "content", "author", "nb_likes", "nb_comments", "slug", "comments" ]
def to_representation(self, obj):
representation = super().to_representation(obj)
if obj.nb_likes > 50:
representation['super_popular'] = True
return representation
I do not understand how and where in the code i should add the "context={'request': request}"
place it when instantiating the serializer
class ViewExample(APIView):
def get(self, request, pk, format=None):
listexample = Example.objects.all()
serializer = ExampleSerializer(listexample, many=True, context={'request':request})
return Response(serializer.data, status=status.HTTP_200_OK)