Inheritance of permission between the related models in DRF - django

I have 3 models like bellow:
class CustomUser(AbstractUser):
pass
class PropertyPost(models.Model):
owner = models.ForeignKey(
get_user_model(),
related_name='posts4thisowner',
on_delete=models.CASCADE)
class Image(models.Model):
prop_post = models.ForeignKey(
PropertyPost,
related_name='images4thisproperty',
on_delete=models.CASCADE)
The idea here is that the user can post many PropertyPost and each PropertyPost can have many images.
Things are working fine. My problem is in the permission. I have set a set of permissions as follows for my PropertyPostDetail views, and no permission for the ImageDetail view:
class PropertyPostDetail(generics.RetrieveUpdateDestroyAPIView):
...
permission_classes = (
permissions.IsAuthenticatedOrReadOnly,
custompermission.IsCurrentUserOwnerOrReadOnly,
)
class ImageList(generics.ListCreateAPIView):
queryset = Image.objects.all()
serializer_class = ImageSerializer
name = 'image-list'
class ImageDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Image.objects.all()
serializer_class = ImageSerializer
name = 'image-detail'
which I meant that only the post owner can mess around with its posts.
Now, because the image model is for the PropertyPost I expected that this permission carry over to the images as well. However apparantly any user can edit any image, which is obviously a flaw.
My question is how could I set my image view to inherit permissions from its parent which is PropertyPost.

ok, so hopfully there would be easier solution but here is how I resolved it. I tried here not to create an owner for image model, because image model has PropertyPost as its parent, which has an owner. So Image should inherit that owner:
class Image(models.Model):
prop_post = models.ForeignKey(
PropertyPost,
related_name='images4thisproperty',
on_delete=models.CASCADE)
prop_post_owner=models.CharField(max_length=150,null=True,blank=True)
and this would be the serializer:
class ImageSerializer(serializers.HyperlinkedModelSerializer):
prop_post = serializers.SlugRelatedField(queryset=PropertyPost.objects.all(),
slug_field='pk')
prop_post_owner = serializers.ReadOnlyField(source='prop_post.owner.username')
class Meta:
model = Image
fields = (
'pk',
'url',
'prop_post',
'prop_post_owner'
)
this way my image model has an owner that is comming from the PropertyPost.
now at list level user can create an object and since it is not clear which object the user is going to select, so custompermisions will fail. Finally this is how I prevent non-owners from creating an image field for a property:
class ImageList(generics.ListCreateAPIView):
queryset = Image.objects.all()
serializer_class = ImageSerializer
name = 'image-list'
....
def perform_create(self, serializer):
obj=PropertyPost.objects.get(pk=int(self.request.data['prop_post']))
if self.request.user!=obj.owner:
raise ValidationError('you are not the owner')
serializer.save(prop_post_owner=self.request.user)

Related

display only some fields in get api response django serializer

I have an example model which has a fk relation with user model and Blog model. Now I have a get api which only requires certain fields of user to be displayed.
My model:
class Example(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
null=True,
related_name="user_examples",
)
blog = models.ForeignKey(
Blog,
on_delete=models.CASCADE,
null=True,
related_name="blog_examples",
)
/................./
Now my view:
class ExampleView(viewsets.ModelViewSet):
queryset = Example.objects.all()
serializer_class = ExampleSerializer
def list(self, request, *args, **kwargs):
id = self.kwargs.get('pk')
queryset = Example.objects.filter(blog=id)
serializer = self.serializer_class(queryset,many=True)
return Response(serializer.data,status=200)
My serializer:
class ExampleSerializer(serializers.ModelSerializer):
class Meta:
model = Example
fields = ['user','blog','status']
depth = 1
Now when I call with this get api, I get all example objects that is required but all the unnecessary fields of user like password, group etc . What I want is only user's email and full name. Same goes with blog, I only want certain fields not all of them. Now how to achieve this in a best way??
You will have to specify the required fields in nested serializers. e.g.
class BlogSerializer(serializers.ModelSerializer):
class Meta:
model = Blog
fields = ['title', 'author']
class ExampleSerializer(serializers.ModelSerializer):
blog = BlogSerializer()
class Meta:
model = Example
fields = ['user','blog','status']
are you setting depth in serializer's init method or anywhere else? beacause ideally it should only display id's and not anything else. if yes then set depth to zero and use serializer's method field to return data that you need on frontend. I can provide you with example code samples

DRF: Is two sided object creation and relation assignment possible?

For example, I have two models,
Questionnaire Model
Profile Model
These two models are linked by a one-to-one relation, where the one-to-one-field is declared on the questionnaire model.
I want to be able to:
Create a new questionnaire instance, and assign an existing profile instance to it
Create a new profile instance, and assign an existing questionnaire instance to it
Currently, because my one-to-one relation is defined on the questionnaire model, I can do 1)
However, I am not able to achieve 2)
How do i get both 1) and 2) working?
Serializer
class ProfileCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = "__all__"
Models
class Questionnaire(models.Model):
distance = models.FloatField(null=True)
profile = models.OneToOneField("Profile", on_delete=models.SET_NULL, null=True, related_name="questionnaire", blank=True)
class Profile(models.Model):
bio = models.CharField(max_length=300, blank=True)
Views
class ProfileCreate(generics.CreateAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileCreateSerializer
permission_classes = [IsAuthenticated]
POST Request
{
"bio": "I like to eat pizza",
"questionnaire": 2
}
SOLVED with kind help from awesome people on Django Discord!
class ProfileCreateSerializer(serializers.ModelSerializer):
questionnaire_id = serializers.IntegerField(write_only=True)
class Meta:
model = Profile
fields = "__all__"
def create(self, validated_data):
questionnaire_id = validated_data.pop("questionnaire_id")
print(questionnaire_id)
profile = super().create(validated_data)
questionnaire = Questionnaire.objects.get(pk=questionnaire_id)
questionnaire.profile = profile
questionnaire.save()
return profile

Polymorphic models serializer

I'm using a Polymorphic model for setting up notifications:
My models:
class Notification(PolymorphicModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
created_by = models.ForeignKey(ElsUser, on_delete=models.CASCADE, default=None, related_name="creatednotifications")
created_on = models.DateTimeField(default=timezone.now)
created_for = models.ForeignKey(ElsUser, on_delete=models.CASCADE, default=None, related_name="receivednotifications")
read = models.DateTimeField(default=None, null=True, blank=True)
message = models.CharField(default=None, blank=True, null=True, max_length=800)
#property
def total(self):
return self.objects.filter(created_for=self.request.user).count()
#property
def unread(self):
return self.objects.filter(created_for=self.request.user,read=None).count()
#property
def read(self):
return self.objects.filter(created_for=self.request.user).exclude(read=None).count()
class WorkflowNotification(Notification):
# permission_transition = models.ForeignKey(WorkflowStatePermissionTransition, on_delete=models.CASCADE)
action = models.ForeignKey(UserAction, on_delete=models.CASCADE)
Currently i have just one model WorkFlowNotification inheriting from the Polymorphic model,but many would be there in the future.
Im trying to get the count(total) of notifications for the logged in user in the API ..total is given as property field to help in the same
my serializer:
class NotificationSerializer(serializers.ModelSerializer):
total = serializers.ReadOnlyField()
read = serializers.ReadOnlyField()
unread = serializers.ReadOnlyField()
class Meta:
model = Notification
fields = ['id', 'total','read', 'unread']
In the view:
class NotificationsMeta(generics.ListAPIView):
serializer_class = NotificationSerializer
queryset = Notification.objects.all()
When i try to run the server it shows:
Got AttributeError when attempting to get a value for field `total` on serializer `NotificationSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `WorkflowNotification` instance.
Original exception text was: Manager isn't accessible via WorkflowNotification instances.
Since you need the 'meta data' only, what is the use of making a model serializer? Or any serializer, for that matter? Serializers will give you serialized instances of the objects of your model. So if you have multiple objects, you will get multiple serialized objects in response.
Just make your view a normal APIView. Since there is no necessity of serializing anything.
class NotificationsMeta(APIView):
def get(self, request, format=None):
qs = Notification.objects.filter(created_for=self.request.user)
response = {
'total': qs.count(),
'read': qs.filter(read=None).count(),
'unread': qs.exclude(read=None).count()
}
return Response(response)
Now remove those property functions from your model.
I didn't test your queries, just copied them from your model. You will need to check if they are working properly. Hope this helps.
I am not sure about how calling a model property who is responsible for querying in model can give appropriate data from serializer. Unfortunately i do have knowledge gap about that. I am thinking about an alternative solution. I hope following should work.
class NotificationSerializer(serializers.ModelSerializer):
total = serializers.serializers.SerializerMethodField()
read = serializers.ReadOnlyField()
unread = serializers.ReadOnlyField()
class Meta:
model = Notification
fields = ['read', 'unread']
def get_total(self, obj):
user = self.context['request'].user
return Notification.objects.filter(created_for=user).count()
If this work then you can able to do similar kind of thing for read and unread too.
In order to get notification for current_user we need to overwrite get_queryset from view.
class NotificationsMeta(generics.ListAPIView):
serializer_class = NotificationSerializer
def get_queryset(self):
return Notification.objects.filter(created_for=self.request.user)

How to define object-level permissions for foreign-key relationships

I'm having trouble defining object-level permissions for foreign-key relationships in my ModelViewSet. I'm not sure if it's entirely possible what I'm trying to do or if there's a better solution, but any hint in the right direction would be much appreciated. I've shortened the models and serializers for the sake of brevity.
I have the following models:
class Team(models.Model):
name = models.CharField(max_length=50)
class CustomUser(AbstractUser):
teams = models.ManyToManyField(Team)
class Client(models.Model):
name = models.CharField(max_length=50)
owner = models.ForeignKey(Team, on_delete=models.CASCADE)
class FinancialAccount(models.Model):
account_name = models.CharField(max_length=50)
client = models.ForeignKey(Client, on_delete=models.CASCADE)
Then I have the following serializers:
class ClientSerializer(serializers.ModelSerializer):
class Meta:
model = Client
fields = ('name', 'owner')
class FinancialAccountSerializer(serializers.ModelSerializer):
owner = serializers.SerializerMethodField()
class Meta:
model = FinancialAccount
fields = ('name', 'client', 'owner')
def get_owner(self, obj):
return client.owner.name
Then I'm trying to define a permission that I can use in all of my ModelViewSets. I'd like it to be somewhat dynamic as I have many more models than the ones above that are related to Client or even below FinancialAccount. The permission and viewset are as follows:
class IsOwnerTeam(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
teams = request.user.teams.values_list('name', flat=True)
return obj.owner in teams
class FinancialAccountViewSet(viewsets.ModelViewSet):
serializer_class = FinancialAccountSerializer
permission_classes = (IsOwnerTeam, )
def get_queryset(self):
teams = self.request.user.teams.all()
clients = Client.objects.filter(owner__in=teams)
return FinancialAccount.objects.filter(account__in=accounts)
So, right now I'm receiving this error: 'FinancialAccount' object has no attribute 'owner', which makes sense because I don't have an owner field on the FinancialAccount object. But, I thought if I had an owner field in the serializer (and put an owner field in each of the serializers) I could retrieve it that way. Any help would be appreciated!
You can do something like this:
class IsOwnerTeam(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if hasattr(obj, 'client'):
owner = obj.client.owner
else:
owner = obj.owner
teams = request.user.teams.values_list('name', flat=True)
return owner in teams

Why can't I get the followed users of the current user in my Django application?

I am using the standard auth.User model for the User objects, and my Follow object model is defined as follows:
class Follow(models.Model):
owner = models.ForeignKey(
'auth.User',
related_name='followers',
on_delete=models.CASCADE,
null=False
)
following = models.ForeignKey(
'auth.User',
related_name='following',
on_delete=models.CASCADE,
null=False
)
The serializer I am using is as follows:
class PublicUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username')
read_only_fields = ('id', 'username')
And my view is as follows:
class FollowingView(generics.ListAPIView):
serializer_class = PublicUserSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_queryset(self):
return self.request.user.following.all()
This is returning an empty result set, for some reason that I cannot understand. However, if I use the following code for the view, it DOES return the correct queryset:
class FollowingView(generics.ListAPIView):
serializer_class = PublicUserSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_queryset(self):
follows = Follow.objects.filter(owner=self.request.user).values_list('following_id', flat=True)
return User.objects.filter(id__in=follows)
So why can't I get the correct queryset by using self.request.user.following.all()?
The difference here is that, this:
User.objects.filter(id__in=follows)
Is using a Django Model Manager in order to obtain the list of available users from the database and then filtering it down to a subset of the users.
Whereas this:
self.request.user.following.all()
Is getting it's information from the request. So, unless you were passing the complete dataset in with your request you would never get what you were expecting here.