Django Rest Framework Serializer charfield not updating when source is given - django

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',)

Related

django swagger api returned object url instead of readable name

I have an model which is for mapping book(item) to categories(tag),
it shows like this in the django admin page.
id item_uid tag_uid
407 Food Recipe
but in django swagger page, when I try to GET this mapping api with ID 407, it returned like this:
"id": 407,
"item_uid": "http://127.0.0.1:8000/items/237/";
"tag_uid": "http://127.0.0.1:8000/tags/361/"
as you can see, it mapped together correctly, but the response body showed the object url and it's object id, which is not readable for human users. I wonder that if there is anyway to make them like this:
"id": 407,
"item_uid": "Food";
"tag_uid": "Recipe"
edit: codes,
#models.py
class Map_item_tag(models.Model):
item_uid = models.ForeignKey(items, on_delete=models.CASCADE, verbose_name='Item UID')
tag_uid = models.ForeignKey(tags, on_delete=models.CASCADE, verbose_name='Tag UID')
#admin.py
#admin.register(Map_item_tag)
class map_item_tag_admin(ImportExportModelAdmin):
resource_class = map_item_tag_Resource
readonly_fields = ('id',)
list_display = ['id','item_uid','tag_uid']
#serializers.py
class Map_item_tag_Serializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Map_item_tag
fields = ['id','item_uid','tag_uid']
#views.py
class Map_item_tag_ViewSet(viewsets.ModelViewSet):
queryset = Map_item_tag.objects.all().order_by('item_uid')
serializer_class = Map_item_tag_Serializer
parser_classes = (FormParser, MultiPartParser)
permission_classes = [permissions.IsAuthenticated]
thank you for answering!
It seems you are using a HyperlinkedModelSerializer instead of a regular ModelSerializer
Try changing the serializer class to a ModelSerializer:
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = [] # list of fields you want to include in your Item serializer
class Map_item_tag_Serializer(serializers.ModelSerializer):
item_uid = ItemSerializer()
class Meta:
model = Map_item_tag
fields = ['id','item_uid','tag_uid']
In addition, I would advise you to use CamelCase notation for all your classes. For example: instead of using Map_item_tag_Serializer, change the name to MapItemTagSerializer. The same goes for all your other classes.
I would also avoid using using the _uuid suffix when using ForeignKey relationships. In the MapItemTag model, the ForeignKey relationship inherently means that the field will point to an object Item of Tag object. Hence, no need to specify the _uuid part again.
For example, the following changes would make the model a lot more readable:
class MapItemTag(models.Model):
item = models.ForeignKey(Item, on_delete=models.CASCADE, verbose_name='map_item')
tag = models.ForeignKey(Tag, on_delete=models.CASCADE, verbose_name='map_tag')

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

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.

Update three-level nested django model using serializer

I am trying to update one of my models (which is a nested model - three level actually as you can see below) and I am getting the following error:
AssertionError: The .update() method does not support writable nestedfields by default. Write an explicit .update() method for serializer SystemSettingsSerializer, or set read_only=True on nested serializer fields.
All day I have been reading about nested models and nested serializers, trying to add update and create methods setting fields as read_only=True but no matter what I did, it just didn't work :( :(
These are my models:
class SystemSettings(models.Model):
# ... some fields
class Components(models.Model):
settings = models.ForeignKey(SystemSettings, related_name="Components")
class SysComponent(models.Model):
class Meta:
abstarct = True
index = models.PositiveIntegerField(primery_key=True)
is_active = models.BooleanField(default=False)
component = NotImplemented
class Foo(SysComponent):
component = models.ForeignKey(Components, related_name="Foo")
class Bar(SysComponent):
component = models.ForeignKey(Components, related_name="Bar")
task_id = models.PositiveIntegerField(default=0)
and serializers:
class SystemSettingsSerializer(ModelSerializer):
Components = ComponentsSerializer(many=True)
class Meta:
model = SystemSettings
fields = [# some fields,
Components]
class ComponentsSerializer(ModelSerializer):
Foo = FooSerializer(many=True)
Bar = BarSerializer(many=True)
class Meta:
model = Components
fields = ['Foo',
'Bar']
class FooSerializer(ModelSerializer):
class Meta:
model = Foo
class BarSerializer(ModelSerializer):
class Meta:
model = Bar
My logic is the following:
I am fetching the SystemSettings via GET and display it in a form.
The user changes it as much as he want and by clicking submit I send it back via PUT.
As I said I am getting the error above after clicking submit.
Any help would be appreciated.
EDIT: I am using django 1.7.8 by the way

Why did my Serializer stop recognizing a given required value?

I have the following Model and Serializer:
Model
class Location(models.Model):
company = models.ForeignKey(Company)
title = models.CharField(max_length=100)
class Meta:
ordering = ['company']
unique_together = ['company', 'title']
Serializer
class LocationSerializer(serializers.ModelSerializer):
company = serializers.StringRelatedField()
class Meta:
model = Location
When I try to create a new Location using the Serializer:
lo = LocationSerializer(data={'title': 'test', 'company': 2})
I get back the following error:
{'company': ['This field is required.']}
What gives? The only thing I'd changed recently in either the Model or the Serializer was adding the unique_together constraint to the Model. Why is the Serializer now unable to recognize the company value?
It turns out the error message is a complete red herring. What's really happening is that there's an incompatibility between the unique_together constraint in the Model and read-only fields like StringRelatedField defined in the Serializer.
Removing unique_together from the Model restored the Serializer's functionality. Alternatively, you could remove the read-only field declaration from the Serializer. Neither really seem like an appropriate solution as they require sacrificing functionality in pretty major ways.
Right now there appears to be an issue open on DRF's GitHub related to this problem but it remains to be seen if this is Working As Intendedtm or is in fact a bug.

Django Form with no required fields

I want to make a form used to filter searches without any field being required. For example given this code:
models.py:
class Message(models.Model):
happened = models.DateTimeField()
filename = models.CharField(max_length=512, blank=True, null=True)
message = models.TextField(blank=True, null=True)
dest = models.CharField(max_length=512, blank=True, null=True)
fromhost = models.ForeignKey(Hosts, related_name='to hosts', blank=True, null=True)
TYPE_CHOICES = ( (u'Info', u'Info'), (u'Error', u'Error'), (u'File', u'File'), (u'BPS', u'BPS'),)
type = models.CharField(max_length=7, choices=TYPE_CHOICES)
job = models.ForeignKey(Jobs)
views.py:
WHEN_CHOICES = ( (u'', ''), (1, u'Today'), (2, u'Two days'), (3, u'Three Days'), (7, u'Week'),(31, u'Month'),)
class MessageSearch(ModelForm): #Class that makes a form from a model that can be customized by placing info above the class Meta
message = forms.CharField(max_length=25, required=False)
job = forms.CharField(max_length=25, required=False)
happened = forms.CharField(max_length=14, widget=forms.Select(choices=WHEN_CHOICES), required=False)
class Meta:
model = Message
That's the code I have now. As you can see it makes a form based on a model. I redefined message in the form because I'm using an icontains filter so I didn't need a giant text box. I redefined the date mostly because I didn't want to have to mess around with dates (I hate working with dates! Who doesnt?) And I changed the jobs field because otherwise I was getting a drop down list of existing jobs and I really wanted to be able to search by common words. So I was able to mark all of those as not required
The problem is it's marking all my other fields as required because in the model they're not allowed to be blank.
Now in the model they can't be blank. If they're blank then the data is bad and I don't want it in the DB. However the form is only a filter form on a page to display the data. I'm never going to save from that form so I don't care if fields are blank or not. So is there an easy way to make all fields as required=false while still using the class Meta: model = Message format in the form? It's really handy that I can make a form directly from a model.
Also this is my first serious attempt at a django app so if something is absurdly wrong please be kind :)
You can create a custom ModelForm that suit your needs. This custom ModelForm will override the save method and set all fields to be non-required:
from django.forms import ModelForm
class SearchForm(ModelForm):
def __init__(self, *args, **kwargs):
super(SearchForm, self).__init__(*args, **kwargs)
for key, field in self.fields.iteritems():
self.fields[key].required = False
So you could declare your forms by simply calling instead of the ModelForm, e.g.:
class MessageForm(SearchForm):
class Meta:
model = Message
You could also pass empty_permitted=True when you instantiate the form, e.g.,
form = MessageSearch(empty_permitted=True)
that way you can still have normal validation rules for when someone does enter data into the form.
I would give a try to the django-filter module :
http://django-filter.readthedocs.io/en/develop/
fields are not required. these are filters actually. It would look like this :
import django_filters
class MessageSearch(django_filters.FilterSet):
class Meta:
model = Message
fields = ['happened', 'filename', 'message', '...', ]
# django-filter has its own default widgets corresponding to the field
# type of the model, but you can tweak and subclass in a django way :
happened = django_filters.DateFromToRangeFilter()
mandatory, hidden filters can be defined if you want to narrow a list of model depending on something like user rights etc.
also : setup a filter on a 'reverse' relationship (the foreignkey is not in the filtered model : the model is referenced elsewhere in another table), is easy, just name the table where the foreign key of the filtered model field is :
# the 'tags' model has a fk like message = models.ForeignKey(Message...)
tags= django_filters.<some filter>(name='tags')
quick extendable and clean to setup.
please note I didn't wrote this module, I'm just very happy with it :)