How to use PUT method for creating an object on particular id if no object is available
on that id in Django Rest Framework?
You can try update_or_create()
e.g:
class YourAPIView(APIView):
def put(self, request, **kwargs):
serializer = YourSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
obj, created = YourModel.objects.update_or_create(
id=kwargs['id'],
defaults=serializer.validated_data)
return Response()
A RESTFUL API should error out for a PUT request on an object that doesn't exist. The idea being that if it had existed at one point to create the id, it has since been deleted. It makes more sense to keep it deleted than to re-create it.
This is especially true if the id is auto-generated. And even more so if it's an auto-incrementing integer id like the default id of Django models. If you were to support this functionality in that case, a user would create an instance of data with an id that the table hasn't incremented over yet potentially leading to errors like this.
Related
So I have a function in the Django admin that allows me to create a duplicate MyModel in the database:
def save_model(self, request, obj, form, change):
if '_saveasnew' in request.POST:
old_obj_id = resolve(request.path).args[0]
old_obj = MyModel.objects.get(id=old_obj_id)
obj.other_id = old_obj.other_id
obj.status = old_obj.status
obj.project_id = old_obj.project_id
obj.test_url = old_obj.test_url
obj.save()
super(MyModelAdmin, self).save_model(request, obj, form, change)
This creation works fine, but I have another system interacting with this database that is seeing insert failures every time this function has been called. For example, if I create 2 duplicate entries in the Django admin this way, then the other system will see two errors like
IntegrityError duplicate key value violates unique constraint "my_model_pkey" DETAIL: Key (id)=(1234) already exists.
I'm using Django 1.11.15 & PostgreSQL 9.5.15.
My best guess is that somewhere your code is telling your database to create a new object row and explicitly set the ID of that row to be X. When really the code should be telling your database to create a new object row and implicitly set the ID of that row to be whatever the next available integer is.
Your code is confusing because you're doing it in a very complicated way. Why does this function take in both an object and a request? And then it finds the old object from the request? Where was the new object created?
A simpler way would be to first check if you want to save as new. If not, have a function that updates an object and give it the existing object. If it is a Save As New Request, create a new object row that has similar the values as the existing object (except ID). And then update that new object with the changes. Or however your logic should work. In any case, there is a much more straightforward way of accomplishing the steps that you want to happen if you think about how the steps are ordered.
I am trying to implement a simple flow where the a user POSTs a secret code to an api endpoint. By doing so the user creates a foreign key relationship with another model.
I achieved the desired behaviour on the serializer by overwriting the create method like so:
class RegisterUserToCustomerSerializer(serializers.Serializer):
company_code = serializers.CharField(allow_blank=False)
def create(self, validated_data):
user = validated_data['user']
try:
customer = Customer.objects.get(company_code=validated_data['company_code'])
except Customer.DoesNotExist:
return HttpResponse(status=404)
user.related_customer = customer
user.save()
return customer
In normal Django I would have implemented the behaviour on the forms save(commit=False) method. Since DRF does not have this function I feel stuck with the create() and update().
Two things really bug me about my solution:
I have to return the customer from create method eventhough the user was edited. But since the 'company_code' variable does not exist on the user DRF will through an error if I return the user
I am overwriting the create() method but I am not really creating anything. Sure, I could use update but in terms of design, this makes it even worse I fear. Everything about using the create() method feels weird about this. From accessing the user to the return statement.
Do you guys see ways to avoid this?
This doesn't feel like a job for a serializer at all. Note that you are using a plain serializer, which doesn't usually have create or update methods at all; there is no reason to create them here.
You should do this in the view.
I'm using Django Rest Framework to handle token authentication for a mobile application for a school. In particular, when a student logs in, the mobile application sends a token to my Django backend, which then combines data from its database and some external data from another source. I found it easiest to use a generic RetrieveAPIView to accomplish what I needed.
My code is working, and my main question is around the url. For most retrievals, we usually have the primary key as well (e.g. /students/SOME-ID), but in this case, I'm using the token to retrieve the user rather than the primary key. In fact, if SOME-ID passed in was different from the Token, the user associated with the Token would be returned anyway (which seems kinda strange).
I'm wondering whether it is better to have my url route be just (/students) instead though this seems to be a list rather than a retrieve operation.
WHAT I HAVE NOW
http://localhost:8000/api/v1/revision/students/1
IS THIS BETTER
http://localhost:8000/api/v1/revision/students/
CODE
class StudentView(generics.RetrieveAPIView):
model = Student
serializer_class = StudentSerializer
# combines data from both current and legacy database
def retrieve(self, request, pk=None):
obj = get_object_or_404(Student, user=request.user)
# KIV -> unsure if this is the best way to combine data from legacy and current database
# or should it be done in the serializer
data = StudentSerializer(obj).data
# combines existing data stored in database with legacy data from database
legacy_data = SOME_EXTERNAL_API_SERVICE.get_student_info(obj)
data['avatar'] = legacy_data['avatar']
data['coins'] = legacy_data['coins']
return Response(data)
I would definitely not use /students/id/ with the behaviour you're describing: This URL should always return the student with the given id of error (depending on whether the user fetching this resource is allowed to do so). You might want to use this URL for admins to view students in the future.
And for the same reason, I wouldn't use /students/ because I'd expect it to return a list of all students, or at least the list of all students the particular logged in user is allowed to see. This might fit your purpose now (where the logged in user can only see himself), but maybe not in the future if you create new roles that can view more students.
There are two approaches here:
Either you treat this as a filter on all the students: /students/?current=true which I personally find ugly because you're not actually filtering on the total set of students.
Or you treat this as a special case: /students/current using a special keyword for fetching this one specific student.
I would choose the latter one because it is more descriptive and easier to understand when looking at the API. Note of course that id can never be 'current' in this case, which is why some people discourage this kind of special resource queries and opt for the first option.
Definitely, the url http://localhost:8000/api/v1/revision/students/ looks better.
But you don't need to write this in a RetrieveAPIView, you could always do this in base APIView,
class StudentView(APIView):
def get(self, request, *args, **kwargs):
obj = get_object_or_404(Student, user=request.user)
data = StudentSerializer(obj).data
legacy_data = SOME_EXTERNAL_API_SERVICE.get_student_info(obj)
data['avatar'] = legacy_data['avatar']
data['coins'] = legacy_data['coins']
return Response(data)
By using like this, you can avoid the extra pk keyword argument from your url.
I am using Django 1.9, and now trying to override save method behaviour. The problem is that when I do instance.some_field = some_value, the self object is already modified, whereas I need to know that was the original value of some_field. To do this, the only way out seems to be fetching object from database by self's pk.
So I wonder what is GENERIC syntax - if there's any - to fetch instance from the database? By saying GENERIC, I imply that I don't want to explicitly type the model name (like MYMODEL.objects.get(...)), but rather make Django figure out the right model based on target instance's model.
To make the question clearer, let me illustrate my goal in a pseudo-code:
def save_extented(self, *args, **kwargs):
original_object = (self model).objects.get(pk = self.pk)
Is it possible ? Or maybe I don't need this, and there's some smart Django hack to fetch the instance with rolled back field values ?
You can use django-dirtyfields and pre_save signal:
#receiver(pre_save, sender=MyModel)
def pre_save_logic(sender, instance, **kwargs):
if 'my_field' in instance.get_dirty_fields():
do_some_logic()
I have a model with a custom json serializer that performs some processing prior to dumping to json.
Now, when fetching a single obj i want to use the custom serializer from the model to fetch the entire object (with the processing mentioned above). When fetching a list i want to use the default serializer to fetch only the headers (render only the model fields).
I looked into three options
overriding obj_get
def obj_get(self, bundle, **kwargs):
obj = ComplexModel.objects.get(pk=kwargs['pk'])
return obj.to_serializable()
i got thrown with
{"error": "The object LONG JSON DUMP has an empty attribute 'description' and doesn't allow a default or null value."}
not sure why this is happening - the field description is nullable, Plus - why tastypie is checking validation for objects already in the database, and... while fetching ??
using dehydrate
def dehydrate(self, bundle):
return bundle.obj.to_serializable()
This is great but the cycle is executed before each object - so i cann't tell if I'm fetching a list or a single object. The result here is the full serizliazed objects whether it's a list or a single entry.
creating a custom serializer
class CustomComplexSerializer(Serializer):
def to_json(self, data, options=None):
if isinstance(data,ComplexModel):
data = data.to_serializable()
return super(CustomComplexSerializer,self).to_json(data)
Same problem here, when fetching one entry the serializer accepts the obj in data.obj, when it's fetching a list it accepts a dict (odd...). I can check if bundle is an instance of dict as well - but testing for the type of ComplexModel felt awkward enough.
So what is the best way to implement a custom serialization for fetching only a single entry ?
Just for future reference, I think i found the right way to do this and it's by using full_dehydrate.
def full_dehydrate(self, bundle, for_list=False):
if not for_list:
return bundle.obj.to_serializable()
return super(ReportResource,self).full_dehydrate(bundle,for_list)