DRF Updating model's CharFiled with choices doesn't do anything - django

I have a Task app in my django project.
So this is the base class for the task model:
TASK_STATUSES = [
("DN", "Done"),
("IP", "In progress"),
("IR", "In review"),
("NW", "New"),
("RJ", "Rejected"),
("TD", "To do"),
]
class TaskBase(models.Model):
STATUS_CHOICES = TASK_STATUSES
status = models.CharField("State", max_length=2, default="NW", choices=STATUS_CHOICES)
[...]
class Meta:
abstract = True
This is the actual model:
class Task(TaskBase):
TYPE_CHOICES = TASK_TYPES
type = models.CharField("Type", max_length=1, default="T", choices=TYPE_CHOICES)
project = models.ForeignKey(Project, on_delete=models.CASCADE)
Serializer:
class TaskSerializer(serializers.ModelSerializer):
status = serializers.CharField(source="get_status_display")
class Meta:
model = Task
fields = "__all__"
And the viewset:
class TaskViewSet(viewsets.ModelViewSet):
queryset = Task.objects.all()
serializer_class = TaskSerializer
lookup_field = "name"
permission_classes = []
authentication_classes = []
Now I update the task's status via PATCH request to /api/task/<task_name>/ with {"status": "TD"}.
So the response is "PATCH /api/task/XYZ/ HTTP/1.1" 200 233 - everything is fine, I receive "TD" in the response data - the status changed. But when I get the task again, it still has his previous status ("New").
So I see two problems:
I change the status, 200 HTTP response, the response from api contains changed status - but it does not change in my DB
The status that I receive in response is not in "To do" format, it's TD
No matter what I put, "TD" or "To do", the response is 200 and the status is not actually being changed. The thing is, when I remove the status field from the serializer - the changes are actually being done! But then, I lose the display feature (getting e.g. "New" instead of "NW" from the API) even in GET requests. Even so, I am pretty sure that i should get the display name in both get/update requests.
Anyway, I even tried doing status = serializers.ChoiceField(choices=Task.STATUS_CHOICES) and it works the same way. When I add source="get_status_display" here, it starts to behave the same way as with CharField - doesn't actually update.
Is there a way to get the behavior I want here - both get and update type response return status as its display name + the changes made via PATCH requests are actually being done?
#Edit
I guess it's the same as in here: Django Rest Framework Serializer charfield not updating when source is given - unanswered

it's because you're overriding the name of real database field with a property of the model.
class TaskSerializer(serializers.ModelSerializer):
long_status_name = serializers.CharField(source="get_status_display")
class Meta:
model = Task
fields = "__all__"
This will allow you to update "status" now with no problems, and return an additional field called "long_status_name" with the full text of the status.

Related

Related model ID gets lost in validated_data even though it is present in request

I am working on my create() method for my webapp which uses Django REST framework for the backend API.
In this case, I'm trying to create a new RECIPE, which has a foreignkey field to a related model STYLE...
I am running into an issue when trying to associate my new recipe record with an existing related object via the ID.
My serializers looks like this:
class StyleSerializer(serializers.ModelSerializer):
http_method_names = ['get']
class Meta:
model = Style
exclude = ()
class RecipeSerializer(serializers.ModelSerializer):
hops = HopAdditionSerializer(many=True, read_only=True)
fermentables = FermentableAdditionSerializer(many=True, read_only=True)
style = StyleSerializer()
yeast = YeastSerializer(read_only=True)
class Meta:
model = Recipe
exclude = ()
def create(self, validated_data):
style_data = validated_data.pop('style')
style = Style.objects.get(pk=style_data.get('id'))
reipce = Recipe.objects.create(**validated_data)
recipe.style = style
recipe.save();
return recipe
You can see I'm trying to assign the new recipe object with a related Style object.
On my POST request for a new recipe, I include the style, which is all of the related attributes including the field ID. I have verified this info is getting POSTED both in the request via dev console AND in the django viewset via terminal log.
However, in my serializer create() method, the ID value is always missing from the dictionary object returned by validated_data.pop('style'). Every other field is present, just the ID that is missing.
I can get the style object using the name attribute, but I don't understand why the ID of the related Style object gets lost in the validated_data, even though it is present in the POST.
Actually, I found the answer...
I needed to include this line in the StyleSerializer to explicity include the ID in the validated_data:
id = serializers.IntegerField(required=False)
Full Serializer
class StyleSerializer(serializers.ModelSerializer):
http_method_names = ['get']
id = serializers.IntegerField(required=False)
class Meta:
model = Style
exclude = ()
Related StackOverflow Question
def create(self, instance, validated_data):
style_data = validated_data.pop('style')
style = (instance.style).all()
style = list(style)

Django Rest Framework Serializer charfield not updating when source is given

I have a model field with choices charfield
class Vehicle(models.Model):
name = models.CharField(max_length=100)
STATUS_CHOICES = (
("N", "New"),
("U", "Used"),
("P", "Just Purchased")
)
status = models.CharField(max_length=3, choices=STATUS_CHOICES)
The serializer class also has charfield for status but with source argument to display the readable value
class VehicleSerializer(ModelSerializer):
status = serializers.CharField(source='get_status_display')
class Meta:
model = Vehicle
When I try to update vehicles through patch request with data {'status': "U"}, there is no update performed.
However the update occurs when I remove the source from serializer status field.
Providing source is necessary to display proper value in web view.
I know the option of changing the name of status in serializer to something other and using that in the template. Also there is the option to override update method in serializer, however my question is what is source doing to prevent the update?
I think you need to add status to the fields list in meta.
class VehicleSerializer(ModelSerializer):
status = serializers.CharField(source='get_status_display')
class Meta:
model = Vehicle
fields = ('status',)

django rest framework dynamic related object

Problem - I have a REST server using django-rest-framework (django v1.7.7, django-rest-framework v3.1.1). In the notifications, I let a user know if they've received a new friend request, or have earned a new badge. There are other notification types as well, but this simple example can explain my problem.
In my GET response, I want to get the notification with a dynamic related object, which is determined by type. if the type is friendreq then I want the relatedObject to be a User instance, with a UserSerializer. If the type is badge, I want to have the relatedObject be a Badge instance, with a BadgeSerializer.
Note: I already have these other serializers (UserSerializer, BadgeSerializer).
Below is what I am wanting to achieve in a response:
{
"id": 1,
"title": "Some Title",
"type": "friendreq"
"relatedObject": {
// this is the User instance. For badge it would be a Badge instance
"id": 1,
"username": "foo",
"email": "foo#bar.com",
}
}
And here are what I have for models and serializer:
# models.py
class Notification(models.Model):
"""
Notifications are sent to users to let them know about something. The
notifications will be about earning a badge, receiving friend request,
or a special message from the site admins.
"""
TYPE_CHOICES = (
('badge', 'badge'),
('friendreq', 'friend request'),
('system', 'system'),
)
title = models.CharField(max_length=30)
type = models.CharField(max_length=10, choices=TYPE_CHOICES)
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="user")
related_id = models.PositiveIntegerField(null=True, blank=True)
# serializers.py
class NotificationSerializer(serializers.ModelSerializer):
if self.type == "badge":
related_object = BadgeSerializer(
read_only=True,
queryset=Badge.objects.get(id=self.related_id)
)
elif self.type == "friendreq":
related_object = FriendRequestSerializer(
read_only=True,
queryset=FriendRequest.objects.get(id=self.related_id)
)
class Meta:
model = Notification
This code does not work but hopefully it explains what I'm trying to accomplish and the direction I'm trying to go. Maybe that direction is completely wrong and I should be trying to accomplish this by using some other method.
Another option I tried would be to use a SerializerMethodField and perform this in a method, but that seemed not as clean for this case of trying to return a Serialized object based upon another field.
I believe what you're going to want to use is the .to_representation() approach mentioned here in the DRF documentation: http://www.django-rest-framework.org/api-guide/relations/#generic-relationships

Django REST framework and creating two table entries from a POST form

I want to create entries in two tables (Log and Post) using the DRF Browseable API POST form.
The example below is contrived, but it outlines what I am trying to do.
class Post(models.Model):
info = models.CharField()
class Log(TimeStampedModel):
ip = models.GenericIPAddressField(('IP Address'))
name = models.CharField()
data = models.ForeignKey(Post)
I want to use the browseable API to submit a form to create a Log entry. Here are the serializers:
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('info',)
class LogSerializer(serializers.ModelSerializer):
data = serializers.Field()
class Meta:
model = Log
fields = ('ip', 'name', 'data')
The problem with the above is that serializer.Field is read only so does not show up on the POST form. If I change it to CharField it shows up, but then I get an error because an instance of a Post is expected not just a field of the Post object.
Here are my views:
class LogMixin(object):
queryset = Log.objects.all()
serializer_class = LogSerializer
class LogList(LogMixin, ListCreateAPIView):
pass
class LogDetail(LogMixin, RetrieveUpdateDestroyAPIView):
pass
What's the correct way of doing this?
From what I can tell you want to create a nested Log object. There are 2 ways of doing this:
Send 2 POST Requests, One to create the Post, and the other to create the Log contained the received HTTP 200 data from the API.
(Django and best way) Send the data all in one POST and parse it server side. Django Rest Framework takes care of this for you.
I have changed your code so that it should work.
Source
class LogSerializer(serializers.ModelSerializer):
class Meta:
model = Log
fields = ('ip', 'name')
class PostSerializer(serializers.ModelSerializer):
log = LogSerializer()
class Meta:
model = Post
fields = ('info', 'log')
views.py
import generics
class PostCreateAPIView(generics.CreateAPIView):
model = Post
serializer_class = PostSerializer
Then you can send a POST Request containing 'info', 'ip', and 'name'.
This is a hacky way and the best way to use the nested serializer as stated above. But just to show another way I am posting it here.
# Add New Item
#api_view(('POST',))
def add_new_item(request):
request.data['areaname_name'] = request.data['item_areaname']
request.data['buildingname_name'] = request.data['item_buildingname']
item_serializer = TblItemSerializer(data=request.data)
area_name_serializer = TblAreanameSerializer(data=request.data)
building_name_serializer = TblBuildingnameSerializer(data=request.data)
response = []
if item_serializer.is_valid() & area_name_serializer.is_valid() & building_name_serializer.is_valid():
# Save To Item Table
item_serializer.save()
# Save Unique Values Into Areaname and Buildingname Tables
area_name_serializer.save()
building_name_serializer.save()
return Response(item_serializer.data, status=status.HTTP_201_CREATED)
else:
response.append(item_serializer.errors)
response.append(area_name_serializer.errors)
response.append(building_name_serializer.errors)
return Response(response, status=status.HTTP_400_BAD_REQUEST)
In the error response you could also use (depending on how you want to handle on client side)
merge = {**item_serializer.errors, **area_name_serializer.errors, **building_name_serializer.errors}
return Response(merge, status=status.HTTP_400_BAD_REQUEST)

Can a Tastypie filter be applied to a detail resource?

I have a Django model with a sequence_number field that is incremented each time the model instance is updated:
class Foo(models.Model):
bar = models.TextField()
sequence_number = models.IntegerField(default=0)
I have implemented a Tastypie resource for this model, like so:
class FooResource(ModelResource):
class Meta:
queryset = Foo.objects.all()
resource_name = 'foo'
list_allowed_methods = ['get']
detail_allowed_methods = ['put', 'get']
So a client user can request a particular instance of Foo using an URL like this:
http://.../api/foo/123/
Clients applications retain the sequence_number so they know which version of the object they are holding.
I want to implement a filter on the detail resource so that a client can pass the sequence_number they are holding to the server:
If someone else has updated the object since the client's last request (so the server sequence_number will be greater than client sequence_number) then the resource should return the updated object.
If the server sequence_number is unchanged, then the resource should return nothing (or empty fields etc.).
I tried to implement this by adding a filter to the resource meta:
filter = {
'sequence_number': ('gt',),
}
And using the following url:
http://.../api/foo/123/?sequence_number__gt=123
But it had no effect. it did work when I used the list resource i.e.
http://.../api/foo/?sequence_number__gt=123
So - is it possible to configure Tastypie to accept filters on detail resources? If not, any suggestions as to how I should roll my own solution (while still using Tastypie)?