Django Rest Framework Update or Create if not exists - django

I want to use the perform an update or create in django-rest-framework, by passing or not the id field. I've got this model
class Etiqueta(models.Model):
name_tag = models.CharField(max_length=200, blank=False, null=False)
description_tag = models.TextField(max_length=500, blank=False, null=False)
def __unicode__(self):
return self.name_tag
And in django-rest-framework I've got this serializer
from myapp.modulos.estado_1.models import Etiqueta
from rest_framework import serializers, viewsets
# Serializers define the API representation.
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Etiqueta
fields = (
'id',
'name_tag',
'description_tag'
)
# ViewSets define the view behavior.
class TagViewSet(viewsets.ModelViewSet):
queryset = Etiqueta.objects.all()
serializer_class = TagSerializer
Normally when I create an object, I perform a POST to the URL without the /:id, but if I've got an object with a local id, I want him to be created in the REST with the same id (remote id), django overwrite my local id and creates a new one. Does anybody know how achieve this? Also it is important to mention that I'm working with google-app-engine, google-cloud-datastore and django-dbindexer.

This code should work for your case -
class TagViewSet(viewsets.ModelViewSet):
queryset = Etiqueta.objects.all()
serializer_class = TagSerializer
def get_object(self):
if self.request.method == 'PUT':
obj, created = Etiquetta.objects.get_or_create(pk=self.kwargs.get('pk'))
return obj
else:
return super(TagViewSet, self).get_object()

You should have a look at how Django REST framework does currently and adapts your create method to update whenever you have an id field.
The original ViewSet.create is here and the ViewSet.update is here.
Please note that you will probably end up with two different serializers for /tag/ and /tag/:id since the later should not allow the id field to be writable while the former should.

I've write a drf views mixin for updating an object by id, if no corresponding object, just create it then update.

Related

What is the best method for retrieving all objects associated with a foreign key using Django rest_framework?

I have a React application where I'm using Django+REST for the API and backend. On the frontend, I send a POST request to create a Job model and run the job. In the response, I retrieve the ID of the new Job object.
axios.post('http://127.0.0.1:8000/api/jobs/', query)
.then((resp) => {
setActiveJobs([...activeJobs, resp.data.id])
}, (error) => {
console.log(error)
})
When the job finishes running, it creates multiple Result objects associated to it by a ForeignKey. These are my two models:
class Job(models.Model):
query = models.CharField(max_length=255, blank=True, null=True)
status = models.CharField(max_length=10, blank=True, null=True)
class Result(models.Model):
job = models.ForeignKey(Job, blank=False, null=False, on_delete=models.CASCADE)
result_no = models.IntegerField(blank=False, null=False)
... more fields
I'm having trouble using rest_framework in Django to send a GET request using the Job's ID to get a list of all the Result associated with it. I've tried to give custom implementations for retrieve(self, request, pk=None) and get_queryset(self) but so far my approaches have not worked. Ideally, I'd like to call a url such as localhost:8000/api/results/15 where 15 would be the object id of the Job that was created. If there are 5 Result objects that have the job ForeignKey field set as the Job object with an ID of 15, a list of those 5 Results would be returned in the response.
My parsed-down views.py and urls.py are below:
views.py
class ResultsView(viewsets.ModelViewSet):
serializer_class = ResultSerializer
queryset = Result.objects.all()
# one potential solution could be along the lines of this? Does not work tho
def get_queryset(self):
job_id = self.kwargs['pk']
queryset = self.queryset.filter(job = job_id)
return queryset
urls.py
router = routers.DefaultRouter()
router.register(r'results', views.ResultsView, 'my_project')
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include(router.urls)),
# re_path('^api/results/(?P<job>.+)/$', views.ResultsView),
]
I think there may be an issue with attempting to match an integer ID to a ForeignKey field that is an object, but when I view localhost:8000/api/results/ I am able to see an object where the ForeignKey field job is listed as a plain integer.
Is there an appropriate pattern for a task like this?
I believe you need to either use queryparam or kwarg other than pk in get_queryset because base retrieve method filters based on pk
def get_queryset(self):
queryset = Result.objects.all()
job_id = self.request.query_params.get('job_id')
if job_id is not None:
queryset = queryset.filter(job=job_id)
return queryset
#OR
def get_queryset(self):
job_id = self.kwargs['job_id']
queryset = self.queryset.filter(job = job_id)
return queryset
If you are not using query param, router in urls.py needs to be changed to
router.register('results/(?P<job_id>.+)', ResultsView)
However, a better way would be using related name to retrieve all results with certain job id. You could do something like this in JobView
#action(detail="False")
def result_list(self,request, pk):
job = Job.objects.get(id=pk)
result_qs = job.result_set.all()
serializer = ResultSerializer(result_qs, many=True)
return Response(serializer.data)
The url for this would be /api/jobs/1/result_list/. Here related name is not defined so default is result_set. But we can define it in Result model.
class ResultsView(viewsets.ModelViewSet):
serializer_class = ResultSerializer
queryset = Result.objects.all()
# one potential solution could be along the lines of this? Does not work tho
def get_queryset(self):
job_id = self.kwargs['pk']
queryset = self.queryset.filter(job = job_id)
return queryset
The problem here is that self.kwargs['pk'] is an id for a Result, not a Job.
There are two possible solutions here:
Accept queryparams with your view so that the request can specify a job id with something like http://localhost:8000/api/results/?job_id=1. I suggest using django-filter to do this with minimal amounts of code and high amounts of flexibility.
Add an #action to your JobsView that allows you to make a request like http://localhost:8000/api/jobs/1/results/ to get all the requests for a given job.

How to implement a simple "like" feature in Django REST Framework?

I'm a beginner building the backend API for a social media clone using DRF. The frontend will be built later and not in Django. I'm currently using Postman to interact with the API.
I'm trying to implement a "like" feature as you would have on Facebook or Instagram. I cannot send the correct data with Postman to update the fields which bear the many-to-many relationship.
Here is some of my code:
models.py
class User(AbstractUser):
liked_haikus = models.ManyToManyField('Haiku', through='Likes')
pass
class Haiku(models.Model):
user = models.ForeignKey(User, related_name='haikus', on_delete=models.CASCADE)
body = models.CharField(max_length=255)
liked_by = models.ManyToManyField('User', through='Likes')
created_at = models.DateTimeField(auto_now_add=True)
class Likes(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
haiku = models.ForeignKey(Haiku, on_delete=models.CASCADE)
serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username', 'password', 'url', 'liked_haikus']
extra_kwargs = { 'password' : {'write_only': True}}
def create(self, validated_data):
password = validated_data.pop('password')
user = User(**validated_data)
user.set_password(password)
user.save()
token = Token.objects.create(user=user)
return user
class HaikuSerializer(serializers.ModelSerializer):
class Meta:
model = Haiku
fields = ['user', 'body', 'liked_by', 'created_at']
class LikesSerializer(serializers.ModelSerializer):
model = Likes
fields = ['haiku_id', 'user_id']
views.py
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [permissions.IsAuthenticated]
#action(detail=True, methods=['get'])
def haikus(self, request, pk=None):
user = self.get_object()
serializer = serializers.HaikuSerializer(user.haikus.all(), many=True)
return Response(serializer.data)
class UserCreateViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [permissions.AllowAny]
class HaikuViewSet(viewsets.ModelViewSet):
queryset = Haiku.objects.all()
serializer_class = HaikuSerializer
permission_classes = [permissions.IsAuthenticated]
class LikesViewSet(viewsets.ModelViewSet):
queryset = Likes.objects.all()
serializer_class = LikesSerializer
permission_classes = [permissions.IsAuthenticated]
urls.py
router = routers.DefaultRouter(trailing_slash=False)
router.register('users', views.UserViewSet)
router.register('haikus', views.HaikuViewSet)
router.register('register', views.UserCreateViewSet)
router.register('likes', views.LikesViewSet)
urlpatterns = [
path('admin/', admin.site.urls),
path('', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
path('api-auth-token', obtain_auth_token, name='api_token_auth')
]
Using the Django Admin I can manually set users to like posts and the fields in the db will update and reflect in API requests.
With Postman, I've tried sending both PUT and PATCH to, for example:
http://127.0.0.1:8000/haikus/2
with "form data" where key ="liked_by" and value="3" (Where 3 is a user_id). I got a 200 response and JSON data for the endpoint back, but there was no change in the data.
I've tried GET and POST to http://127.0.0.1:8000/likes and I receive the following error message:
AttributeError: 'list' object has no attribute 'values'
I've looked at nested-serializers in the DRF docs, but they don't seem to be quite the same use-case.
How can I correct my code and use Postman to properly update the many-to-many fields?
I think I need to probably write an update function to one or several of the ViewSets or Serializers, but I don't know which one and don't quite know how to go about it.
All guidance, corrections and resources appreciated.
To update the liked_by Many2Many field, the serializer expect you to provide primary key(s).
Just edit your HaikuSerializer like the following. It will work.
class HaikuSerializer(serializers.ModelSerializer):
liked_by = serializers.PrimaryKeyRelatedField(
many=True,
queryset=User.objects.all())
class Meta:
model = models.Haiku
fields = ['created_by', 'body', 'liked_by', 'created_at']
def update(self, instance, validated_data):
liked_by = validated_data.pop('liked_by')
for i in liked_by:
instance.liked_by.add(i)
instance.save()
return instance
adnan kaya has provided the correct code and I have upvoted him and checked him off as the correct answer. I want go through his solution to explain it for future readers of this question.
liked_by = serializers.PrimaryKeyRelatedField(
many=True,
queryset=User.objects.all())
You can read about PrimaryKeyRelatedField here: https://www.django-rest-framework.org/api-guide/relations/
Since liked_by is a ManyToManyField it has special properties in that ManyToMany relations create a new table in the DB that relates pks to each other. This line tells Django that this field is going to refer to one of these tables via its primary key. It tells it that liked by is going to have multiple objects in it and it tells it that these objects are going to come from a particular queryset.
def update(self, instance, validated_data):
liked_by = validated_data.pop('liked_by')
for i in liked_by:
instance.liked_by.add(i)
instance.save()
return instance
ModelSerializers is a class that provides its own built in create and update functions that are fairly basic and operate in a straightforward manner. Update, for example, will just update the field. It will take the incoming data and use it to replace the existing data in the field it is directed at.
You can read more about ModelSerializers here: https://www.django-rest-framework.org/api-guide/serializers/#modelserializer
You can overwrite these functions and specify custom functions by declaring them. I have declared update here. Update is a function that takes 3 arguments. The first is self. You can call this whatever you want, but there is a strong convention to call it self for readability. Essentially this is importing the class the function belongs, into the function so you can utilize all that classes functions and variables. Next is instance. Instance is the data that is currently in the entry you are trying to update. It is a dictionary like object. Finally, there is validated_data. This is the data you are trying to send to the entry to update it. When using form data, for example, to update a database, this will be a dictionary.
liked_by = validated_data.pop('liked_by')
Because validated_data is a dictionary you can use the .pop() method on it. Pop can take the key of the dictionary and "pop it off" leaving you with the value (more formally, .pop('key') will return its 'value'). This is nice because, at least in my case, it is the value that you want added to the entry.
for i in liked_by:
instance.liked_by.add(i)
this is a simple python for-loop. A for loop is here because in my use-case the value of the validated_data dictionary is potentially a list.
The .add() method is a special method that can be used with ManytoMany relationships. You can read about the special methods for ManytoMany relations here: https://docs.djangoproject.com/en/3.1/ref/models/relations/
It does what it advertises. It will add the value you send send to it to data you call it for, instead of replacing that data. In this case it is instance.liked_by (the current contents of the entry).
instance.save()
This saves the new state of the instance.
return instance
returns the new instance, now with the validated data appended to it.
I'm not sure if this is the most ideal, pythonic, or efficient way implementing a like feature to a social media web app, but it is a straightforward way of doing it. This code can be repurposed to add all sorts of many-to-many relationships into your models (friends lists/followers and tags for example).
This is my understanding of what is going on here and I hope it can help make sense of the confusing topic of ManytoMany relationships for clearer.

Edit update all schedules except primary key in Django Rest Framework

I wanna change all fields of a json object except 'pk' in DRF. I just need to keep one json data. When adding a new data ,this one should override existing data. Is there a way to do it with django ?
my models.py
class ClientUser2(models.Model):
phone_number = models.CharField(max_length=20,unique=True)
name = models.CharField(max_length=100,blank=True)
status = models.IntegerField(default=1)
class ClientNameSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ClientUser2
fields = ('url','phone_number','name','status','pk')
my views.py
class ClientViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows messages to be viewed or edited.
"""
queryset = ClientUser2.objects.all()
serializer_class = ClientNameSerializer
and it's my api root
api_root
If you want to be able to only retrieve and update models you can use RetrieveUpdateApiView
Reference : https://www.django-rest-framework.org/api-guide/generic-views/#retrieveupdateapiview

Filtering Django queryset with custom function

I'm developing a REST API for an existing system that uses custom permission handling. I'm attempting to use the built-in generics from the Django REST Framework, but I'm running into trouble filtering the list views using my custom permissions. An example of my current view is:
class WidgetList(generics.ListCreateAPIView):
permission_classes = (permissions.IsAuthenticated,)
model = Widget
serializer_class = WidgetSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ('widget_type', 'widget_owner')
def get_queryset(self):
"""
Overwrite the query set to check permissions
"""
qs_list = [w.id for w in self.model.objects.all() if
canReadWidget(self.request.user, w)]
return self.model.objects.filter(id__in=qs_list)
This works, however I feel like the get_queryset function could be improved. Because my canReadWidget is custom, I have to evaluate self.model.objects.all() and check which widgets the user can read, but the function must return a query set so I use the id__in=qs_list part. The result being that I make two database calls for what is really just one list fetch.
Is there a standard way to handle this kind of per-object filtering for a generic list view?
At some point, it's better to drop the default generic views or function and roll your own.
You should have a look at the ListModelMixin and override the list to deal with the list instead of turning it into a queryset.
You should adapt the filtering and pagination but you won't hit the DB twice as you currently do.
first install django-filter package and register in settings.py
Write this code on filter.py file
import django_filters
from .models import CustomUser
class UserFilter(django_filters.FilterSet):
first_name = django_filters.CharFilter(label="First Name", lookup_expr='icontains')
last_name = django_filters.CharFilter(label="Last Name", lookup_expr='icontains')
email = django_filters.CharFilter(label="Email", lookup_expr='icontains')
mobile_number = django_filters.CharFilter(label="Mobile No.", lookup_expr='icontains')
##Change Your Fields What You Want To Filtering
class Meta:
model = Widget
fields = {'is_verify'}
On Your Views File write this code:
class WidgetViewSet(MyModelViewSet):
queryset = Widget.objects
serializer_class = "pass your serializer"
def get_filter_data(self):
_data = self.queryset.all()
data = UserFilter(self.request.GET, queryset=_data)
return data.qs.order_by('-id')
def get_queryset(self):
return self.get_filter_data()

Modify Django Rest Framework ModelViewSet behavior

I basically have the following model in my project:
class ShellMessage(TimeStampedModel):
# There is a hidden created and modified field in this model.
ACTION_TYPE = (
('1' , 'Action 1'),
('2' , 'Action 2')
)
type = models.CharField(max_length=2,choices=ACTION_TYPE,default='1')
action = models.CharField(max_length=100)
result = models.CharField(max_length=300, blank=True)
creator = models.ForeignKey(User)
I created a serializer:
class ShellMessageSerializer(serializers.ModelSerializer):
class Meta:
model = ShellMessage
fields = ('action', 'type', 'result', 'creator')
And a ModelViewSet:
class ShellListViewSet(viewsets.ModelViewSet):
serializer_class = ShellMessageSerializer
queryset = ShellMessage.objects.all()
My issue is the following:
When I create a new ShellMessage with a POST to my API, I don't want to provide the foreignKey of 'creator' but instead just the username of the guy and then process it in my ViewSet to find the user associated with this username and save it in my ShellMessage object.
How can I achieve this using Django rest Framework? I wanted to supercharge create() or pre_save() methods but I'm stuck as all my changes overwrite 'normal' framework behavior and cause unexpected errors.
Thank you.
I finally find my solution just after posting my question :)
So I did the following:
class ShellListViewSet(viewsets.ModelViewSet):
serializer_class = ShellMessageSerializer
queryset = ShellMessage.objects.all()
def pre_save(self, obj):
obj.creator = self.request.user
return super(ShellListViewSet, self).pre_save(obj)
This is working as expected. I hope I did well.
UPDATE: This topic seems to be a duplicate to Editing django-rest-framework serializer object before save
If you intend to intercept and perform some processing before the object gets saved in the model database, then what you're looking for is overriding the method "perform_create" (for POST) or "perform_update" (for PUT/PATCH) which is present within the viewsets.ModelViewSet class.
This reference http://www.cdrf.co/3.1/rest_framework.viewsets/ModelViewSet.html lists all available methods within viewsets.ModelViewSet where you can see that the "create" method calls "perform_create" which in turn performs the actual saving through the serializer object (the object that has access to the model):
def perform_create(self, serializer):
serializer.save()
We can override this functionality that is present in the base class (viewsets.ModelViewSet) through the derived class (the ShellListViewSet in this example) and modify the model attribute(s) that you want to be changed upon saving:
class ShellListViewSet(viewsets.ModelViewSet):
serializer_class = ShellMessageSerializer
queryset = ShellMessage.objects.all()
def findCreator(self):
# You can perform additional processing here to find proper creator
return self.request.user
def perform_create(self, serializer):
# Save with the new value for the target model fields
serializer.save(creator = self.findCreator())
You can also opt to modify the model fields separately and then save (probably not advisable but is possible):
serializer.validated_data['creator'] = self.findCreator()
serializer.save()
Later if the object is already created and you also want to apply the same logic during an update (PUT, PATCH), then within "perform_update" you can either do the same as above through the "serializer.validated_data['creator']" or you could also change it directly through the instance:
serializer.instance.creator = self.findCreator()
serializer.save()
But beware with such updating directly through the instance as from https://www.django-rest-framework.org/api-guide/serializers/ :
class MyModelSerializer(serializers.Serializer):
field_name = serializers.CharField(max_length=200)
def create(self, validated_data):
return MyModel(**validated_data)
def update(self, instance, validated_data):
instance.field_name = validated_data.get('field_name', instance.field_name)
return instance
This means that whatever you assign to the "instance.field_name" object could be overriden if there is a "field_name" data set within the "validated_data" (so in other terms, if the HTTP Body of the PUT/PATCH Request contains that particular "field_name" resulting to it being present in the "validated_data" and thus overriding whatever value you set to the "instance.field_name").