Tastypie Attributes & Related Names, Empty Attribute Error - python

I'm getting this error:
The object '' has an empty attribute 'posts' and doesn't allow a default or null value.
I'm trying to get the number of 'votes' on a post and return it in my models.py:
class UserPost(models.Model):
user = models.OneToOneField(User, related_name='posts')
date_created = models.DateTimeField(auto_now_add=True, blank=False)
text = models.CharField(max_length=255, blank=True)
def get_votes(self):
return Vote.objects.filter(object_id = self.id)
Here's my resource:
class ViewPostResource(ModelResource):
user = fields.ForeignKey(UserResource,'user',full=True)
votes= fields.CharField(attribute='posts__get_votes')
class Meta:
queryset = UserPost.objects.all()
resource_name = 'posts'
authorization = Authorization()
filtering = {
'id' : ALL,
}
What am I doing wrong?

The attribute value that you have defined isn't proper.
You can achieve what you want in a few ways.
Define a dehydrate method:
def dehydrate(self, bundle):
bundle.data['custom_field'] = bundle.obj.get_votes()
return bundle
Or set the get_votes as property and define the field in resource like so (I recommend this one as it is the most clear):
votes = fields.CharField(attribute='get_votes', readonly=True, null=True)
Or define it this way:
votes = fields.CharField(readonly=True, null=True)
And in the resources define the dehydrate_votes method like so:
def dehydrate_votes(self, bundle):
return bundle.obj.get_votes()

Related

How can I set foreign key from post request in Django

I have this models
class Driver(models.Model):
first_name = models.CharField(max_length=250)
last_name = models.CharField(max_length=250)
created_at = models.DateTimeField(default=NOW)
updated_at = models.DateTimeField(default=NOW)
def __str__(self):
return self.first_name
class Vehicle(models.Model):
driver_id = models.ForeignKey(Driver,on_delete=SET_NULL,unique=True,null=True, blank=True)
make = models.CharField(max_length=150)
model = models.CharField(max_length=150)
plate_number = models.CharField(max_length=10,validators = [validate_plate_numberLATIN,validate_plate_numberCYRYLLIC], unique=True)
created_at = models.DateTimeField(default=NOW)
updated_at = models.DateTimeField(default=NOW)
def __str__(self):
return self.make
I try to set foreign key in my post request into Vehicle model
#method_decorator(csrf_exempt, name='dispatch')
def post(self,request,*args, **kwargs):
body = json.loads(request.body.decode("utf-8"))
newCar = Vehicle.objects.create(driver_id=body['driver_id'],make=body['make'],model=body['model'],plate_number=body['plate_number'])
data = json.loads(serializers.serialize('json',[newCar]))
return JsonResponse({'success':data})
And get this error
ValueError: Cannot assign "1": "Vehicle.driver_id" must be a "Driver" instance.
How to get rid off this error? How I can create an instance of Driver and 'post' an id?
You can do it in 2 ways
If you need the driver instance in somewhere in the code you can use this
driver_instance = Driver.objects.get(pk=body['driver_id'])
Vehicle.objects.create(driver_id=driver_instance,..)
Vehicle.objects.create(driver_id_id=body['driver_id'], ...)
The raw value of a ForeignKey can be accessed by appending "_id" to the field name, this can also be used to create an instance using the raw value
Vehicle.objects.create(driver_id_id=body['driver_id'], ...)

Sorting (order_by) in list

I am building a Blog App and I am trying to sort or order_by in list which contains multiple queries.
models.py
class BlogPost(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=30)
date = models.DateTimeField(auto_now_add=True)
class Comments(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
blog_of = models.ForeignKey(BlogPost, on_delete=models.CASCADE)
body = models.CharField(max_length=30, default='')
date_added = models.DateTimeField(auto_now_add=True)
views.py
def mypage(request):
query_1 = list(BlogPost.objects.filter(user=request.user).order_by('-date'))
query_2 = list(Comment.objects.filter(user=request.user).order_by('date_added'))
results = sorted(chain(query_1, query_2),key=attrgetter('date') , reverse=True)
context = {'results':results}
return render(reques, 'mypage.html', context)
But is showing
'Comment' object has no attribute 'date'
And I think this is because date field name is different in both model and i am sorting with only one, But i have no idea how can I sort with different field name.
Any help would be much Appreciated. Thank You
Or just add it as a property:
class Comments(models.Model): # do NOT give a model a plural name!
# ....
#property
def date(self):
return self.date_added
# or if it is a datetimefield
# return self.date_added.date()
ALso you can just write a more customized sorting key (e.g. in case the involved models are from third-party apps).
def order(obj):
try:
return obj.date
except AttributeError:
return obj.date_added
# ...
results = sorted(chain(query_1, query_2), key=order, reverse=True)

Type error to create and update my list in django rest framework

I'm trying to use my api to create and update products in a bundle. I did so:
model.py
class Business(models.Model):
name = models.CharField(max_length=155)
class Product(models.Model):
business = models.ForeignKey(
Business,
on_delete=models.CASCADE,
blank=True,
null=True,
)
name = models.CharField(max_length=200)
description = models.TextField(null=True, blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
def __str__(self):
return self.name
class Meta:
verbose_name = "Product"
class Bundle(models.Model):
business = models.ForeignKey(
Business,
on_delete=models.CASCADE,
blank=True,
null=True,
)
name = models.CharField(max_length=100)
description = models.TextField(null=True, blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
products = models.ManyToManyField(Product, related_name="bundles",blank=True, null=True, through="BundleProduct")
class Meta:
verbose_name = "Bundle"
def __str__(self):
return self.name
class BundleProduct(models.Model):
bundle = models.ForeignKey(Bundle, on_delete=models.CASCADE, related_name="bundleproducts")
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="bundleproducts")
number = models.IntegerField(default=1)
class Meta:
verbose_name = "Bundle of Product"
def __str__(self):
return str(self.product.name) + " do " + self.bundle.name
def get_absolute_url(self):
return reverse("BundleProduct_detail", kwargs={"pk": self.pk})
And here is my serializers.py:
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = "__all__"
class BundleProductSerializer(serializers.ModelSerializer):
class Meta:
model = BundleProduct
fields = "__all__"
class BundleSerializer(serializers.ModelSerializer):
class Meta:
model = Bundle
fields = "__all__"
My viewset.py
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
model = Product
class BundleProductViewSet(viewsets.ModelViewSet):
queryset = BundleProduct.objects.all()
serializer_class = BundleProductSerializer
model = BundleProduct
class BundleViewSet(viewsets.ModelViewSet):
queryset = Bundle.objects.all()
serializer_class = BundleSerializer
model = Bundle
When I try to post some products in bundleproducts I receive "Incorrect type. Expected pk value, received list."
Reading about this error, I found some issues relating to PrimaryKeyRelatedField and SlugRelatedField. I know I need to override but I have no idea how to do it.
It's an example of how to post would works:
{
"number": 1,
"bundle": 2,
"product":
[
1,
2
]
}
After watching the video commented by Neil, I created the following method:
class BundleSerializer(
serializers.ModelSerializer
):
products = ProductSerializer(many=True)
def create(self, validated_data):
products = validated_data.pop('products')
bundle = BundleProduct.objects.create(**validated_data)
for product in products:
BundleProduct.objects.create(**product, bundle=bundle)
return Bundle
class Meta:
model = Bundle
fields = "__all__"
But doesn't work. I receive this error: "TypeError at /api/v1/bundle/
'name' is an invalid keyword argument for this function"
If you are making post via BundleSerializer you need to pass products with list of ProductSerializer data not just id since products in BundleSerializer is accepting productsSerializer data. You are getting type error 'name' is an invalid keyword argument for this function" because your validated_data contain name and BundleProduct object Does not have name field.And you are creating BundleProduct objects with validated_data.
Create bundle object and pass id of bundle object to BundleProduct object.
If you do not want to create product and just pass existing product id you need to make ListField
You need to Override get_fields and check the requests
override to_representation to return always List of ProdutSerializer Data
Override create for POST request
Override update for PUT and PATCH Request
Below is solution for POST Request
For PATCH AND PUT Request you need to override update method of ModelSerializer and handle the products accordingly.
class BundleSerializer(serializers.ModelSerializer):
def create(self, validated_data):
products = validated_data.pop('products')
bundle = Bundle.objects.create(**validated_data)
for product_id in products:
product = get_object_or_404(Product, pk=product_id)
BundleProduct.objects.create(product=product, bundle=bundle)
return bundle
class Meta:
model = Bundle
fields = "__all__"
def to_representation(self, instance):
repr = super().to_representation(instance)
repr['products'] = ProductSerializer(instance.products.all(), many=True).data
return repr
def get_fields(self):
fields = super().get_fields()
if self.context['request'].method in ['POST', "PATCH","PUT"]:
fields['products'] = serializers.ListField(
write_only=True,
child=serializers.IntegerField()
)
return fields
sample POST data to BundleSerializer
{
"products":[1,2],
"name":"Offer One",
"description":"description",
"price":1212,
"business":1
}
In my experience, if you want to update a model and a related model in one request, with DRF, the easiest way to do this is to override the "create" method of a serializer. There's a good video on this here which I used as my reference: https://www.youtube.com/watch?v=EyMFf9O6E60
The issue here is that you are posting a list to BundleProduct's product field yet it is an ForeignKey. To join Bundle to a Product, simply POST:
{
"bundle": 2,
"product" 1,
"number": 1
}
You can repeat this:
{
"bundle": 2,
"product" 4,
"number": 1
}
to add yet another product 4 to the same bundle and so on. Just make sure you do them one by one and not in a list as you had done earlier.

Trying to filter django view serializer based on nested model foreignkey

I have a django app. Within it, I have 2 models. One of them is a group and the other is a member model. Then member model has a foriegnkey which is the group model. I serialized the models and now I am trying to get the api to to work as I want. I want to be able to call an api that has a group name at the end that is passed as a filter for the members to only return the members of the group name. I have 2 urls. the first one returns all the members of every group while I want the second one to return just the members of a certain group. I have tried a few different things from suggestions but none of them are working. This is the last thing I tried below. I will add my code below.
Models:
class Group(models.Model):
name = models.CharField(max_length=42)
description = models.CharField(max_length=220)
user_count = models.IntegerField()
status = models.CharField(max_length=12)
image = models.ImageField(upload_to='group_images/')
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name + ' - ' + self.created_by.username
class Member(models.Model):
group = models.ForeignKey(Group, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
host = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.group.name + ' - ' + self.user.username
Urls:
path('members/', MemberListView.as_view()),
path('members/<name>', MemberGroupListView.as_view()),
views:
class MemberListView(ListAPIView):
queryset = Member.objects.all()
serializer_class = MemberSerializer
class MemberGroupListView(ListAPIView):
queryset = Member.objects.all()
serializer_class = MemberSerializer
filter_backends = (filters.DjangoFilterBackend,)
filterset_fields = ('user', 'host', 'group')
def get_queryset(self):
return self.queryset.filter(group__name=self.request.query_params.get('name'))
the MemberListView is working properly but the MemberGroupListView is not working.
Update:
added the serialzers:
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ('name', 'description', 'user_count', 'status', 'image', 'created_by')
class MemberSerializer(serializers.ModelSerializer):
class Meta:
model = Member
fields = ('group', 'user', 'host')
Url parameters, such as name in your case (path('members/<name>', MemberGroupListView.as_view()),) are stored in self.kwargs in class-based views, so your get_queryset should be:
def get_queryset(self):
return self.queryset.filter(group__name=self.kwargs.get('name'))

How to get last updated product in Django restframe work?

Here i have mentioned my model,serializer and view. Actually im new to this concepts. I don't how to get the last added product.
Models.py
class Products(models.Model):
name = models.CharField(max_length=100)
image = models.CharField(max_length=100, null=True)
categories = models.ArrayModelField(
model_container=Category,
model_form_class=CategoryForm
)
specifications = models.ArrayModelField(
model_container=Specifications,
model_form_class=SpecificationsForm
)
description = models.CharField(max_length=1000)
reviews = models.ArrayModelField(
model_container=Reviews,
model_form_class=ReviewsForm
)
drizzly = models.BooleanField(default=False)
complete = models.BooleanField(default=False)
comment = models.CharField(max_length=500)
click_count = models.IntegerField()
serializer.py
class ProductsSerializer(serializers.ModelSerializer):
class Meta:
model = Products
fields = ('id',
'name',
'image',
'categories',
'specifications',
'description',
'reviews',
'drizzly',
'complete',
'comment',
'click_count')
views.py
class ProductsViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Products.objects.all()
serializer_class = ProductsSerializer
Please tell me how to do that?.
Since the last product have the highest id then you can try like this
last_product = Products.objects.order_by('-id')[0]
What I'm doing here is ordered all the project in reverse order by id then get first element from that.
The easiest way to get last product would be:
Products.objects.last()
With reverse(), you can do : Products.objects.all().reverse()[0]
For first one: Products.objects.first()
Is that what you want?
You need some "last_modified" timestamp field on your model obviously - else you have no way to know when a record has been updated:
import time
class Products(models.Model):
# your existing fields here
last_updated = models.FloatField(
"last_updated",
editable=False,
default=time.time
)
def save(self, *args, **kw):
self.last_updated = time.time()
if "update_fields" in kw:
kw["update_fields"].append("last_updated")
super(Product, self).save(*args, **kw)
and of course generate and pass the migration.
Then you can get the last updated record using QuerySet.latest(*fields):
last_updated_product = Product.objects.latest("last_modified")
I'm not using DRF so I can't tell how to plug this into your ProductsViewSet but there's certainly some documentation available...
Try
from {app_name} import Products
Products.objects.reverse()[0]
Since there is a primary key pk as default in each model created, you can filter specifically using
Products.objects.filter(order_by='pk')
Working with filtering objects by when they are added to the database using a data time field to track the updated time will provide a less error prune way to get the latest objects.
See Also:
Retrieving the latest object, Ordering model objects
models.py
class Products(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
name = models.CharField(max_length=100)
image = models.CharField(max_length=100, null=True)
categories = models.ArrayModelField(
model_container=Category,
model_form_class=CategoryForm
)
specifications = models.ArrayModelField(
model_container=Specifications,
model_form_class=SpecificationsForm
)
description = models.CharField(max_length=1000)
reviews = models.ArrayModelField(
model_container=Reviews,
model_form_class=ReviewsForm
)
drizzly = models.BooleanField(default=False)
complete = models.BooleanField(default=False)
comment = models.CharField(max_length=500)
click_count = models.IntegerField()
class Meta:
# Latest by ascending updated_at.
get_latest_by = "updated_at"
# This will ensure the objects returned are ordered latest to earliest.
ordering = ['-updated_at']
serializer.py
class ProductsSerializer(serializers.ModelSerializer):
class Meta:
model = Products
exclude = ['created_at', 'updated_at']
views.py
class ProductsViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
# Using Products.objects.latest() should return the latest using
# the updated_at DateTimeField.
# Note this will raise `Models.DoesNotExist` if there is no object.
# Using the Model._meta.ordering the queryset should be ordered by
# the `latest` first or you can also use
# `Model.objects.order_by('-updated_at')`
queryset = Products.objects.all()
serializer_class = ProductsSerializer

Categories

Resources