The problem in my code is when I make any updates for my objects especially the Geometry Model. My code will update every object in this Model with the same values. While I am tying is to update each row with its values instead of updating the model with the same values.
I have tried several ways but the problem still occurs.
def update(self, instance, validated_data):
geo = address_data.get('geo')
lat = geo.pop('lat')
lng = geo.pop('lng')
...
gathers = geometry.objects.update(lat=lat, lng=lng)
address = Address.objects.update(address=address, country=country, description=description, geo=gathers )
...
user_data = validated_data.pop('user')
username = user_data.pop('username')
user = User.objects.update(username=username)
gather = userProfile.objects.update(address=address, user=user)
return instance.
class geometry(models.Model):
lat = models.IntegerField(default='')
lng = models.IntegerField(default='')
class Address(models.Model):
...
geo = models.OneToOneField(geometry, on_delete=models.CASCADE, default='')
class userProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, name='user', primary_key=True)
address = models.OneToOneField(Address, on_delete=models.CASCADE, name='address')
The problem is my code updating the whole rows in the model with the same values.
{
"address": {
"id": 1,
"address": "2,lot chragua",
"country": "Morocco",
"description": "dfvfdv",
"geo": {
"id": 1,
"lat": 471,
"lng": 39
}
},
"user": {
"id": 1,
"username": "sdfvedfbf",
"first_name": "",
"last_name": "",
"email": "",
"date_joined": "2019-01-19T11:31:00.415990Z",
"last_login": null
}
},
{
"address": {
"id": 2,
"address": "2.Lot Chraga",
"country": "Morocco",
"description": "sfvsfv",
"geo": {
"id": 2,
"lat": 471,
"lng": 39
}
},
"user": {
"id": 2,
"username": "svscwdc",
"first_name": "",
"last_name": "",
"email": "",
"date_joined": "2019-01-19T11:36:50.266225Z",
"last_login": null
}
}
The .update() method is used to update multiple objects. As you found out, it will update all objects in a queryset.
If I understand you correctly you want to update a single instance, but from your code, it is not clear to me which instance that is.
You can filter the queryset, as #ozcanyarimdunya suggested:
geometry.objects.filter(pk=geo_pk).update(...)
It should be noted that .update() doesn't call .save() on your model or emit any signals. To update a single object, it is usually preferable to assign the new values to the instance and call save, like this:
geo = geometry.objects.get(pk=geo_pk)
geo.lat = lat
geo.lon = lon
geo.save()
On a side note, the common convention is to capitalize class names (Geometry instead of geometry).
Related
I have two models, User and Post, where each user can have many posts.
class User(models.Model):
first_name = models.CharField("First name", max_length=150)
last_name = models.CharField("Last name", max_length=150)
class Post(models.Model):
created = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="posts")
content = models.CharField(max_length=300)
Now for a sample data like this:
User
id first_name last_name
1 Jon Skeet
2 Gordon Linoff
3 Another Soul
Post
user content
1 Jon #1
1 Jon #2
1 Jon #3
2 Gordon #1
2 Gordon #2
I'm trying to create an API that returns a response like this:
[
{
"first_name": "Jon",
"last_name": "Skeet",
"posts_count": "3",
"recent_posts": [
{
"content": "Jon #1",
"created": "2022-07-22T07:48:12.299032Z"
},
{
"content": "Jon #2",
"created": "2022-07-22T07:47:26.830772Z"
},
{
"content": "Jon #3",
"created": "2022-07-22T07:02:31.654366Z"
}
]
},
{
"first_name": "Gordon",
"last_name": "Linoff",
"posts_count": "2",
"recent_posts": [
{
"content": "Gordon #1",
"created": "2022-07-22T09:59:36.965825Z"
},
{
"content": "Gordon #2",
"created": "2022-07-22T09:59:18.544077Z"
},
]
},
{
"first_name": "Another",
"last_name": "Soul",
"posts_count": "0",
"recent_posts": []
}
]
So for each user, I want to include these info in the result:
The count of their posts
Their top three most recent posts
Here's what I've done so far. I created two serializers, one for each model:
class UserSerializer(serializers.ModelSerializer):
posts_count = serializers.IntegerField(required=False)
recent_posts = serializers.ListField(required=False)
class Meta:
model = User
fields = ("first_name", "last_name", "posts_count", "recent_posts")
class PostsSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ("content", "created")
And then I count the posts, and for each user I fetch the top three most recent posts and return the serialized data:
#api_view(["GET"])
def get_user(request):
users = (
User.objects
.filter(posts__isnull=False)
.values("id", "first_name", "last_name")
.annotate(posts_count=Count("id"))
)
for user in users:
recent_addresses = (
Post.objects
.filter(user=user["id"])
.order_by("-created")
)[:3]
user["recent_posts"] = PostsSerializer(recent_addresses, many=True).data
serializer = UserSerializer(users, many=True)
return Response(serializer.data)
But the result is this:
[
{
"first_name": "Jon",
"last_name": "Skeet",
"posts_count": 3,
"recent_posts": [
{
"content": "Jon #3",
"created": "2022-07-22T14:02:00.928810Z"
},
{
"content": "Jon #2",
"created": "2022-07-22T14:01:51.328254Z"
},
{
"content": "Jon #1",
"created": "2022-07-22T14:01:41.935594Z"
}
]
},
{
"first_name": "Gordon",
"last_name": "Linoff",
"posts_count": 2,
"recent_posts": [
{
"content": "Gordon #2",
"created": "2022-07-22T14:02:16.865880Z"
},
{
"content": "Gordon #1",
"created": "2022-07-22T14:02:08.371927Z"
}
]
}
]
As you can see, it didn't include users that have no posts. I can understand why, because I've explicitly said .filter(posts__isnull=False). But I don't know how to solve this. Now I can't remove that filter, or it will return 1 for every posts_count. I believe I've complicated things more than necessary with the for loop, but don't know any other way. How can I solve this?
Try this
users = (
User.objects
.values("id", "first_name", "last_name")
.annotate(posts_count=Count("posts"))
)
I'm learning how to use Django Rest Framework and I came out with a question that I can't get to answer.
I understood that the concept of nested serializer but I think there should be a way to get a specific field in an "upper level" instead of "digging" trough levels. Let me better explain what I mean.
I have Users which then are classified into 2 categories Customers (which are the default User category) and Drivers. All the Users have a Profile with their picture so I would like to obtain the name and the image in an API. The only solution I've been managing to find is the following
class OrderProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ("id", "image")
class OrderUserSerializer(serializers.ModelSerializer):
profile = OrderProfileSerializer()
class Meta:
model = User
fields = ("id", "profile")
class OrderCustomerSerializer(serializers.ModelSerializer):
name = serializers.ReadOnlyField(source="get_full_name")
profile = OrderProfileSerializer()
class Meta:
model = User
fields = ("id", "name", "profile")
class OrderDriverSerializer(serializers.ModelSerializer):
name = serializers.ReadOnlyField(source="user.get_full_name")
user = OrderUserSerializer()
class Meta:
model = Driver
fields = ("id", "name", "user")
This is the JSON response
{
"shipping": {
"id": 25,
"customer": {
"id": 14,
"name": "Test Customer",
"profile": {
"id": 10,
"image": "/media/profile_pics/default.jpg"
}
},
"driver": {
"id": 1,
"name": "Test Driver",
"user": {
"id": 28,
"profile": {
"id": 11,
"image": "/media/profile_pics/default.jpg"
}
}
}
}
}
I can make it to work anyway, but I believe that to learn I should always push for a more "elegant" solution rather than "keep the easiest". So I would like to achieve the following response:
{
"shipping": {
"id": 25,
"customer": {
"id": 14,
"name": "Test Customer",
"image": "/media/profile_pics/default.jpg"
},
"driver": {
"id": 1,
"name": "Test Driver",
"image": "/media/profile_pics/default.jpg"
}
}
}
Thank you for any tips/help/suggest that you can provide
Instead of using:
profile = OrderProfileSerializer()
re-write to:
image = serializers.SerializerMethodField()
def get_image(self, obj):
# obj is the customer object
# return what u need (like this example) or write a model inner method
return obj.profile.image
This would align nicely to the json you want
Do the same thing for Driver and User
I have a ModelSerializer:
class WorkOrderRetrieveSerializer(ModelSerializer):
workordercomments = WorkOrderCommentForWorkOrderSerializer(many=True, read_only=True)
class Meta:
model = WorkOrder
fields = "__all__"
The JSON data is bellow:
{
"id": 1,
"workordercomments": [
.....
{
"id": 21,
"content": "test files",
"files": "[71]",
"ctime": "2018-01-11T11:03:17.874268+08:00",
"uptime": "2018-01-11T11:03:17.874362+08:00",
"workorder": 1,
"comment_user": {
"id": 5,
"username": "test03",
"is_admin": true
}
}
],
"workorder_num": "WON15118747168252",
"name": "order01",
"content": "first conntetn",
"workordertype": "teck",
"workorder_status": "created",
"user_most_use_email": "lxas#128.com",
"server_id": null,
"public_ip": null,
"belong_area": null,
"files": null,
"ctime": "2017-11-28T21:11:56.826971+08:00",
"uptime": "2017-11-28T21:11:56.827064+08:00",
"to_group": 3,
"user": 2
}
The "files": "[71]", in my JSON is a string of a group contains file ids.
workordercomments is the related-name of the workorder.
I want in the JSON workordercomments shows the files like this:
{
"id": 21,
"content": "test files",
"files": "['/media/images/xxxxx.png']",
"ctime": "2018-01-11T11:03:17.874268+08:00",
"uptime": "2018-01-11T11:03:17.874362+08:00",
"workorder": 1,
"comment_user": {
"id": 5,
"username": "test03",
"is_admin": true
}
}
The "files" value I want to is the link rather than its id.
"files": "['/media/images/xxxxx.png']",
or
"files": ['/media/images/xxxxx.png'],
Is it possible to customize the format? should I come true what function in serializer ?
You need to implement a custom serialzier, as per the docs, and override the default values generated by ModelSerializer.
For example:
from rest_framework.fields import Field
class FileRelatedField(RelatedField):
def to_representation(self, instance):
return instance.file_path # or whereever that path comes from
class WorkOrderRetrieveSerializer(ModelSerializer):
class Meta:
model = WorkOrder
fields = '__all__'
files = FileRelatedField(
many=True,
source='file_set.all'
)
Depending on how your __str__ value is on your File model, you can maybe do this:
from rest_framework.serializers import StringRelatedField
class WorkOrderRetrieveSerializer(ModelSerializer):
class Meta:
model = WorkOrder
fields = '__all__'
files = StringRelatedField(
many=True,
source='file_set.all'
)
I have a few related models and I wanted to have related object information nested into the read so I followed the example outlined here:
http://www.django-rest-framework.org/api-guide/relations/#nested-relationships
A couple of my serializers look like this:
class ShowSerializer(serializers.ModelSerializer):
artists = ArtistSerializer(many=True)
venue = VenueSerializer()
class Meta:
model = Show
class GuestSerializer(serializers.ModelSerializer):
show = serializers.RelatedField()
class Meta:
model = Guest
And when I read them (GET), they're perfect, the output is something like:
"results": [
{
"id": 1,
"show": {
"id": 2,
"artists": [
{
"id": 13,
"name": "Some Artist"
}
],
"venue": {
"id": 3,
"name": "Some Venue",
},
"date": "2015-11-20"
},
"first_name": "Amanda",
"last_name": "Hugankiss",
}
]
The problem I'm having is when I POST, this configuration seems to expect an entire nested structure just as above, where I'd prefer to specify an id for a relationship on POST, like so:
{
"show": 16,
"first_name": "Maya",
"last_name": "Normusbutt",
}
Is there a way to configure my serializers/view (by the way, I'm using ModelViewSet) to do this?
Lets say I have a model:
class MyModel(models.Model):
name = models.CharField(max_length=100)
description= models.TextField()
...
Then I created ModelViewSet with HyperLinkedSerializer, so when I call my /api/mymodels endpint I get responses like this:
{
"count": 2,
"next": null,
"previous": null,
"results": [
{ "name": "somename", "description": "desc"},
{ "name": "someothername", "description": "asdasd"},
]
}
and when I call /api/mymodels/1 I get:
{ "name": "somename", "description": "asdasd"}
but what I would like to get is:
{
"metadata":{ ...},
"results": { "name": "somename", "description": "desc"}
}
And I would like to use this format for all models at my website, so I dont want to change every viewset, I want to implement it in (most likely) one class and then use it for all my viewsets.
So my question is: which renderer or serializer or other class (Im really not sure) should I alter or create to get this behavior of json response?
The first response appears to be a paginated response, which is determined by the pagination serializer. You can create a custom pagination serializer that will use a custom format. You are looking for something similar to the following:
class MetadataSerialier(pagination.BasePaginationSerializer):
count = serializers.Field(source='paginator.count')
next = NextPageField(source='*')
previous = PreviousPageField(source='*')
class CustomPaginationSerializer(pagination.BasePaginationSerializer):
metadata = MetadataSerializer(source='*')
This should give you an output similar to the following:
{
"metadata": {
"count": 2,
"next": null,
"previous": null
},
"results": [
{ "name": "somename", "description": "desc"},
{ "name": "someothername", "description": "asdasd"}
]
}
The pagination serializer can be set globally through your settings, as described in the documentation.
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_SERIALIZER_CLASS': {
'full.path.to.CustomPaginationSerializer',
}
}