Django Rest Framework - create object with field value from URL - django

Is it possible to use a ModelViewSet to create an instance from a POST request where one of the field values is specified in the URL?
EG: POST /teams/3/members with data name = Bob should create Member(team_id=3, name="Bob")
Conceptually similar to filtering against the url

Yes, you can do it and quite simple, look here, that would be your url:
re_path('teams/(?P<team_id>\d+)/members/', views.MemberViewSet.as_view({'post': 'create_member'}), name='members'),
and in views just use team_id as an argument:
#class MemberViewSet
def create_member(request, team_id):
...

Related

Django rest framework hyperlinkrelatedfield for one table using primary key

I have a table called 'users' and 'location'. Users table has a foreign key that relates to location table. I have a users serializer to get the JSON. What would I do to get the hyperlinks for the users table using its primary key?
In django rest framework documentation, I couldn't find a solution. I tried using hyperlinkrelatedfield. But still I couldn't achieve this. Can someone help me in finding the solution?
Using rest-framework HyperlinkedRelatedField does not work because it was never built to expose the URL of the object being requested. Mainly because since the client already has the url of the user, why send it back again? Nevertheless you can achieve this by doing something like this.
class UserSerializer(serializers.ModelSerializer):
user_url = serializers.SerializerMethodField()
class Meta:
model = User
def get_label_location(self, obj):
return HyperlinkedRelatedField(view_name='user-detail',
read_only=True) \
.get_url(obj, view_name='label-detail',
request=self.context['request'], format=None)
Take note on a few things,
view-name param to the HyperlinkedRelatedField should be based on your url configuration
read-only has to be true since otherwise you'll have to specify the queryset. But since we have the object needed to generate the url we can ignore that.
I've set format param to None but you might want to set it based on your settings.
You can read up about SerializerMethodField here.

How to modify Django Rest Framework incoming request?

I am building a web app using Django that is pretty much only serving as the API server. I have a single-page application that connects to it as well as an Android client. I have a need to modify some of the incoming POST requests that are coming through.
My two use cases:
If during the registration process the user does not select an avatar image to upload (which is a simple TextField that is the URL to the image), I should be able to insert the default avatar URL. So something like if request.data["avatar"] is None: <use default>
The incoming "timestamp" requests from the Android client are all unix timestamps. I would like to convert this to Django's datetime on the fly - so, current request comes in with date_time = 1473387225, I'd like to convert that to a DateTime object.
Now, I'm already doing something similar for certain POST parameters. The way I do it right now is in the post() function of my generic ListCreateApiView I would directly modify the request object and then call the self.create() with that new request object. Is this the right way, or is there a much better way to do it?
Thanks!
If you are using django-rest-framework these things can be done by serializers.
For avatar use an URLField with default value.
For the timestamp you should probably create a custom field.
Check out this site: http://www.cdrf.co It is an easily navigable display of all the methods available on a given class. You can simply use this to overwrite the View you are using. If a model ViewSet, you likely want perform_create and perform_update.
I often do something like this:
class SomeViewSet(viewsets.ModelViewSet):
queryset = SomeModel.objects.all()
serializer_class = SomeModelSerializer
def perform_create(self, serializer):
data = self.request.data
# make some changes to self.request here
serializer.save(
#change some things here
field='some new value'
)
You can do this in a number of ways. As a part of your validation or in the to_internal_value of the request serializer or in a custom field serializer.
Heres an example of doing this as a part of a custom field serializer.
class AccountCreationSerializer(serializers.Serializer):
avatar = AvatarField(
required=False
allow_files=True
)
# Custom Field Serializer
class AvatarField(serializers.FilePathField):
def to_internal_value(self, value):
user_defined_path = super(AvatarField, self).to_internal_value(value)
if user_defined_path:
return user_defined_path
return default_path

django update forms, who pass object id?

I've tried to create a simple ModelForm, and I notice that even if I pass an instance for update like that
mymodel = MyModel.objects.get(pk=1)
MyModelForm(instance=mymodel)
django does not create an hidden field or include in some way the pk of the object in the template. So I need to pass this by myself?
I prefer not passing the my id's like 1,2,3.. to the templates, so I would prefer passing something like uuid, or using signing.dumps(object_id), and then signing.loads(object_id), from django signing library.
So if I want to include this id in my template with the form POST data,
I didn't understand who is exactly responsible for the retrieve of that id - Is that the view or the form itself?
By view I mean to the built-ins FormView, or UpdateView, how these views find the object id? Assume to store the output of signing.dumps(object_id) in a hidden field
By the time you are in the template the form construction has completed. You can try accessing form.instance.id if its modelForm.
However, most likely you do not need the pk in the template, do you ? You can also inject a hidden form field with the instance pk value if you like. Why do you need the pk in the template ?
If you want to redirect to another page from the POST data you will have access to the object pk in the view itself.
According to official documentation the Built-in Views inherit from django.views.generic.detail.SingleObjectTemplateResponseMixin class which requires the views it is mixed with to provide a self.object attribute.

django - "Incorrect type. Expected pk value, received str" error

I my django-rest-framework I have the following models:
Basically every ride has one final destination and can have multiple middle destinations.
models.py:
class Destination(models.Model):
name=models.CharField(max_length=30)
class Ride(models.Model):
driver = models.ForeignKey('auth.User', related_name='rides_as_driver')
destination=models.ForeignKey(Destination, related_name='rides_as_final_destination')
leaving_time=models.TimeField()
num_of_spots=models.IntegerField()
passengers=models.ManyToManyField('auth.User', related_name="rides_as_passenger")
mid_destinations=models.ManyToManyField(Destination, related_name='rides_as_middle_destination')
serializers.py - RideSerializer
class RideSerializer(serializers.ModelSerializer):
driver = serializers.ReadOnlyField(source='driver.user.username')
class Meta:
model = Ride
fields = ('driver', 'destination', 'leaving_time',
'num_of_spots', 'passengers', 'mid_destinations')
read_only_fields = ('driver', 'passengers', 'mid_destinations')
Problem is - When I am trying to POST to /rides/ in order to add a ride - for example {destination=LA, leaving_time=19:45, num_of_spots=4}
I get error "destination":["Incorrect type. Expected pk value, received str."]}
couple of questions:
What is this error? if I have a destination as a foreign key in the Ride model, does it mean that the destination that I am adding has to be already in the Destinations table?
How to fix this error?
The issue is that you are passing the name of the related Destination object into the serializer, instead of passing the pk/id of the Destination object. So Django REST framework is seeing this and complaining, because it can't resolve LA into an object.
It sounds like you may actually be looking for a SlugRelatedField, which allows you to identify objects by a slug (LA in this case) instead of their primary keys.
Angular/Django Rest Framework
I had similar problem when trying to post a formdata that has a field with some array values. It turned out that I was using a wrong content-type in the post headers of angular frontend. All I needed was comment out Content-Type.
Post(endpoint, postData) {
let auth = `Bearer ${this.token}`;
let headers = new HttpHeaders({
// "Content-Type": "application/x-www-form-urlencoded",
Authorization: auth
});
let fullurl = `${this.baseUrl}${endpoint}`;
.....
....
}
There is a minor difference between Django and Django Rest Framework whenever we try to add the Foreign key references to an instance
Let us assume we want to add the user references to another DB Table
Django
In Django, we reference the whole instance of the user as mentioned below
user = request.user
Django Rest Framework
However in DRF if we follow the same procedure we get the errors like
Incorrect type. Expected pk value, received str/User type
In order to overcome this error, we need to reference the Primary Key of the table instead (Here id is the PK)
user = request.user.id

Creating manytomany objects on a fresh django db object

I am using Django-rest-framework and am trying to add tags to my models.
Every thing is ready on the database-side, but how do I do it on the django-rest side?
Simplified, my model looks like:
name = models.CharField()
tags = models.ManyToManyField(Tags)
I am presenting the tags as a comma-list in django-rest to make it easy for people using the API to add and change tags. However, how can I add tags to a object that doesn’t exists yet?
Using django-rest restore_object in my serializer, I am able to create the list of manytomany objects, but how do I add them to django-rest attrs so it will add them to my object?
In short, how can I add a list of items to .tags in django-rest restore_object function?
Or is this impossible and I need to do the tags handeling -after- the object is created and therefor hide the "tags" field when creating the object in django-rest and display it at the detailed serializer page instead?
I'm not sure if this is the best method, but this seems to work with overriding the save_object method in the serializer.
def save_object(self, obj, **kwargs):
super(MySerializer, self).save_object(obj, **kwargs)
tags = self.init_data.get('tags', None)
if tags:
obj.tags.clear()
tags = tags.split(',')
for t in tags:
tag_obj, created = Tag.objects.get_or_create(name=t, owner=self.context['request'].user)
obj.tags.add(tag_obj)
Is it a bad idea to grab this data from init_data? Seems a little dirty...