I have defined a Model like this:
class Doctor(models.Model):
name = models.CharField(max_length=100)
is_active = models.BooleanField(default=True)
My Serializer:
class DoctorSerializer(serializers.ModelSerializer):
class Meta:
model = Doctor
fields = ('id', 'name', )
In View:
class DoctorViewSet(viewsets.ModelViewSet):
queryset = Doctor.objects.all()
serializer_class = DoctorSerializer
Now, I can delete a doctor by calling the url: 'servername/doctors/id/', with the http method DELETE. However, I want to override the delete behavior for this model. I want that, when the user deletes a record, it's is_active field is set to false, without actually deleting the record from the database. I also want to keep the other behaviors of Viewset like the list, put, create as they are.
How do I do that? Where do I write the code for overriding this delete behavior?
class DoctorViewSet(viewsets.ModelViewSet):
queryset = Doctor.objects.all()
serializer_class = DoctorSerializer
def destroy(self, request, *args, **kwargs):
doctor = self.get_object()
doctor.is_active = False
doctor.save()
return Response(data='delete success')
Related
I am building a blog app with React and Django and I am serializing model's instances saved by particular user, First I am just trying to test with .all() then I am planning to filter by specific user But when I serialize queryset with Serializer like:
class BlogSerializerApiView(viewsets.ModelViewSet):
serializer_class = BlogSerializer
def get_queryset(self, *args, **kwargs):
queryset = Blog.objects.all()
output_serializer = BlogSerializer(queryset, many=True)
print(output_serializer.data)
return "Testing"
It is showing in console:
[OrderedDict(), OrderedDict()]
and when I access it like
print(output_serializer)
Then it is showing:
BlogSerializer(<QuerySet [<Blog: user_1 - Blog_title>, <Blog: user_2 - second_blog_title>]>, many=True):
serializer.py:
class BlogSerializer(serializers.Serializer):
class Meta:
model = Blog
fields = ['title']
models.py:
class Blog(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=30, default='')
def __str__(self):
return f"{self.user} - {self.title}"
What I am trying to do:
I am trying to serialize queryset to show on page in react frontend, I will relate with specific user later.
I have tried many times by changing CBV serialization method by generics.ListAPIView instead of viewsets.ModelViewSet but still same thing.
There is a concept error here. The get_queryset function is not supposed to return serialized data. It must return a QuerySet of model objects.
To achieve what you want you can just do:
class BlogSerializerApiView(viewsets.ModelViewSet):
serializer_class = BlogSerializer
def get_queryset(self, *args, **kwargs):
return Blog.objects.all()
The Django Rest Framework will take care of serializing data.
In fact, you can even do it way more simple. Defining the view's queryset field like this:
class BlogSerializerApiView(viewsets.ModelViewSet):
queryset = Blog.objects.all()
serializer_class = BlogSerializer
Additional:
You said you will relate to current user later. You could achieve that in fact in the get_queryset method filtering aginst the user
class BlogSerializerApiView(viewsets.ModelViewSet):
serializer_class = BlogSerializer
def get_queryset(self, *args, **kwargs):
return Blog.objects.filter(user_id=USER_ID)
Hope this helps!
I was using
class BlogSerializer(serializers.Serializer):
.......
so it was showing empty results (no idea why, I think its deprecated)
After replaceing it with
class BlogSerializer(serializers.HyperlinkedModelSerializer):
It worked
The current problem is that my form shows the logged in user all Portfolios ever created. The form should only show portfolios that the logged-in user created.
Something like this:
associated_portfolios manytomany field = ...objects.filter(user=user_id)
I'm not sure if this should be implemented in the forms.py or views.py and if so how. I've been going through the django documentation and found 'formfield_for_manytomany' but not sure if this is only meant for admin.
Models.py
class Portfolio(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=20)
description = models.CharField(max_length=250, blank=True, null=True)
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=50)
body = RichTextUploadingField(blank=True, null=True)
associated_portfolios = models.ManyToManyField(Portfolio, blank=True)
created_on = models.DateField(auto_now_add=True, editable=False)
Views.py
class PostCreate(CreateView):
model = Post
form_class = PostCreateForm
def formfield_for_manytomany(self, db_field, request, **kwargs):
self.fields['associated_portfolios'] = Portfolio.objects.filter(user=self.request.user)
return super().formfield_for_manytomany(db_field, request, using=self.using, **kwargs)
forms.py
class PortfolioCreateForm(ModelForm):
class Meta:
model = Portfolio
fields = ['user', 'name', 'description']
class PostCreateForm(ModelForm):
class Meta:
model = Post
fields = ['user', 'title', 'body', 'category', 'associated_portfolios']
Since you're using a ModelForm, the associated_protfolios field will be a ModelMultipleChoiceField [docs]. This field has a queryset attribute [docs]. We want to modify that attribute.
Django's CreateView has a method get_form, which in this case will grab your PostCreateForm. This is a good spot to filter the field's queryset, since we have access to the user:
class PostCreate(CreateView):
model = Post
form_class = PostCreateForm
def get_form(self, *args, **kwargs):
form = super().get_form(*args, **kwargs) # Get the form as usual
user = self.request.user
form.fileds['associated_portfolios'].queryset = Portfolio.objects.filter(user=user)
return form
Did you try this
self.fields['associated_portfolios'] = Post.objects.filter(associated_portfolios__portfolio__user=request.user)
OR
user_posts = Post.objects.filter(user=request.user)
self.fields['associated_portfolios'] = user_posts.associated_portfolios.all()
read more about M2M relationships querying here, because I think your problem may be with it.
Also, I'm not sure about your actual data maybe it's right and it gives a correct result as filtering Portfolio model against current user to get its objects looks right for me, but anyway double check everything again.
And as a final note, add related_name to your model fields so you can use it easily for reverse relations rather than going with Django's default naming, it will be clearer and give a better understanding.
So I’m having a bit of trouble with something I was trying to do.
Basically, I have these models:
class Package(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=255, null=False, blank=False)
contact = models.BooleanField(default=False)
downloaded = models.BooleanField(default=False)
created = models.DateTimeField(auto_now_add=True)
class Item(models.Model):
[…. all the atributes of the item model….]
class PackageItems(models.Model):
package = models.ForeignKey(Package, on_delete=models.CASCADE)
item = models.ForeignKey(Item, on_delete=models.CASCADE)
and now I am trying to make an endpoint that allows my users to add “package” and add a pre-existing item in an item model to that newly created package. One package can of course have many items.
SO I wrote a Package serializer then added a SerializerMethodField that allows one to do a get operation on any item that a given package contains. The method makes a call to different serializer.
Here’s the code for both the serializers:
class PackageItemsSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = PackageItems
fields = ('package', 'item')
class PackagesSerializer(serializers.ModelSerializer):
"""
Creation only requires a title
"""
package_items = serializers.SerializerMethodField(read_only=True)
#swagger_serializer_method(serializer_or_field=packageItemsSerializer)
def get_package_items(self, obj):
packageItems = PackageItems.objects.all().filter(package=obj.id)
return PackageItemsSerializer(packageItems, many=True, context={'request': self.context['request']}).data
def create(self, validated_data):
package = super(packagesSerializer, self).create(validated_data)
return package
class Meta:
model = packages
fields = ('id', 'user', 'title', 'contact', 'downloaded', 'created', 'package_items')
read_only_fields = [ 'user', 'contact', 'downloaded', 'package_items’]
Now for my views, I have decided to do this:
class PackagesViewSet(viewsets.ModelViewSet):
queryset = Packages.objects.all()
serializer_class = PackagesSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
if getattr(self, 'swagger_fake_view', False):
return Packages.objects.none()
return Packages.objects.filter(user=self.request.user).order_by('id')
def perform_create(self, serializer):
# sending default values for user, contact and downloaded
serializer.save(user=self.request.user, contact=False, downloaded=False)
def partial_update(self, request, *args, **kwargs):
response_with_updated_instance = super(PackagesViewSet, self).partial_update(request, *args, **kwargs)
return response_with_updated_instance
#action(methods=['post', 'delete'], detail=True, serializer_class=PackageItemsSerializer)
def item(self, request, pk=None):
package = self.get_object()
serializer = self.get_serializer_class()(data=request.data)
if serializer.is_valid():
serializer.save(package=package)
return Response(serializer.data)
else:
return Response(status=status.HTTP_400_BAD_REQUEST)
if request.method == 'delete':
serializer.delete(package=package)
This is what works with this:
I get these routes:
[get/post] for /api/packages/
[all methods] for /api/packages/{id}
[post/delete] for /api/packages/{id}/item
I get the right routes, but for one, Swagger gives me the wrong models for post:
{
"title": "string",
"package_items": {
"items": 0
}
}
Not sure why it's expecting this read_only field to be entered (which is a serializermethodfield as well) when I do a post to /api/packages.
Similarly, these fields show up for PUT under /api/packages/{id}.
For the nested "item" route, the POST works well, but the DELETE doesn't. The POST route allows me to enter an item ID and adds it to the given package ID under /api/packages/{id}/item route.
But delete doesn't allow me to enter an item ID to delete.
So I'm thinking my approach to the delete method for the nested ITEM is completely wrong.
I’m new to DRF/django, trying to validate if I’m going in the right direction with this.
How do I get drf-yasg to get me the right model for the HTTP verbs like POST for example?
What's the right way to delete the nested Item?
Am I approaching this problem right? Is there another way to do this more efficiently?
If someone can help me out to answer those questions, it would be really appreciated :)
Thanks in advance folks.
Instead of nesting serializer in serializer.SerializerMethodField directly, nest it like:
package_item = PackageItemsSerializer(source="package_set", many=True, read_only=True)
try this
i am using django rest framework for my project.
i have a gallery model which has a user field. this field is a Foreign key to the user that created the gallery. and a name field which is gallery's name.
class Gallery(models.Model):
user = models.ForeignKey(User,related_name='galleries')
name = models.CharField(max_length=64)
here is my serializer:
class GallerySerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
def validate_name(self, name):
if len(name) < 3:
raise serializers.ValidationError("name must at least 3 letters")
return name
class Meta:
model = Gallery
fields = ('id', 'user', 'name',)
def create(self, validated_data):
"""
Create and return a new `Gallery` instance, given the validated data.
"""
return Galleries.objects.create(**validated_data)
and here is my views:
class GalleryList(generics.ListCreateAPIView):
queryset = Gallery.objects.all()
serializer_class = GallerySerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
def perform_create(self, serializer):
serializer.save(user=self.request.user, )
class GalleryDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Gallery.objects.all()
serializer_class = GallerySerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
i want to validate post (and put) data, and prevent user from creating two gallery with the same name.(if "user A" has a "gallery A", he can't make a gallery with the same name or rename another gallery to "gallery A". but "user B" is allowed to make a "gallery A")
to do so, i need to check if user.galleries.filter(name=name) exist or not.
but i don't know how to get user in serializer.
You get it from the context that was passed to the serializer. This is done automatically for you, so you can access it like that:
user = self.context['request'].user
If you want to have the ability to specify another user, you can add it to the context yourself:
# This method goes in your view/viewset
def get_serializer_context(self):
context = super().get_serializer_context()
context['user'] = #whatever you want here
return context
That would make the user available as self.context['user']. This is a bit more verbose, but it is more versalite as it allows the serializer to be passed a user different from the one who did the request.
I have a model with a ForeignKey
models.py
class B(models.Model):
user = models.ForeignKey(contrib.auth.User)
class A(models.Model):
b = models.ForeignKey(B)
serializers.py
class ASerializer(serializers.ModelSerializer):
class Meta:
model = A
fields = ['b']
views.py
class AViewSet(iewsets.ModelViewSet):
queryset = A.objects.all()
serializer_class = ASerializer
Now what I want is to restrict the A.b values to the B instances owned by the currently logged-in user.
I know how to enforce that at saving-time, but I would like to only present the relevant queryset in the dropdown choice in the browsable API interface.
If one can define a queryset argument on the RelatedField, it's static and can't depend on the current request.
Any ideas ?
Well you could try overriding queryset in init of serializer.
something like
def __init__(self, *args, **kwargs):
super(MySerializerClass, self).__init__(*args, **kwargs)
if self.context.get('request', None):
field = self.fields.get('b')
field.queryset = field.queryset.filter(user=request.user)
Current user shall be accessible through self.context.
You can modify the get_queryset:
class AViewSet(iewsets.ModelViewSet):
serializer_class = ASerializer
def get_queryset(self):
user = self.request.user
return A.objects.filter(b__user = user)
ref: http://www.django-rest-framework.org/api-guide/filtering/