DRF : how to add user permissions to user detail api? - django

So I am writing a UserDetails view as follows.
class UserDetailsView(RetrieveUpdateAPIView):
serializer_class = AuthUserSerializer
def get_object(self):
return self.request.user
My Serializers are as follows.
class PermissionSerializer(serializers.ModelSerializer):
class Meta:
model = Permission
fields = ('id', 'name', 'codename')
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ('id', 'name')
class AuthUserSerializer(serializers.ModelSerializer):
groups = GroupSerializer(many=True)
# permissions = PermissionSerializer(many=True)
# permissions = serializers.PrimaryKeyRelatedField(many=True, queryset=Permission.objects.all())
class Meta:
model = User
fields = ('id', 'username', 'first_name', 'last_name', 'email',
'is_staff', 'is_active', 'is_superuser', 'last_login',
'date_joined', 'groups', 'user_permissions')
groups = GroupSerializer(many=True) gives me following.
"groups": [
{
"id": 2,
"name": "A"
},
{
"id": 1,
"name": "B"
},
{
"id": 3,
"name": "C"
}
],
I expect the similar from permissions = PermissionSerializer(many=True) but I get the following error.
Got AttributeError when attempting to get a value for field `permissions` on serializer `AuthUserSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.
Original exception text was: 'User' object has no attribute 'permissions'.
but instead, if add user_permissions to the fields directly without adding related reference it gives me all ids of permissions. I want to have id, name, codename also. And, Of course, UserPermissions model is not found. ;-(
How do I fix this?

You can use source parameter on the Serializer.
class AuthUserSerializer(serializers.ModelSerializer):
groups = GroupSerializer(many=True)
permissions = PermissionSerializer(many=True, source='user_permissions')
class Meta:
model = User
fields = ('id', 'username', 'first_name', 'last_name', 'email',
'is_staff', 'is_active', 'is_superuser', 'last_login',
'date_joined', 'groups', 'user_permissions', 'permissions')

Related

Add additional field to response in pytest-django

I'm new to testing and I spent a day finding a solution for my problem but I couldn't find any.
this is my serializer
serilaizer.py
class LeadSerializer(serializers.ModelSerializer):
def create(self, validated_data):
user = self.context['user']
return Lead.objects.create(organizer=user.organizeruser, **validated_data)
class Meta:
model = Lead
fields = ['id', 'first_name', 'last_name', 'age', 'agent', 'category', 'description', 'date_added',
'phone_number', 'email', 'converted_date'
]
I have two types of users, organizer, and agent. organizer can create a lead but agent can't. and as you see I don't have organizer field. authenticated user will be added to the organizer field when a Lead is created.
test.py
def test_if_lead_exist_return_200(self, api_client, leads_factory, user_factory):
user = user_factory.create(is_organizer=True)
api_client.force_authenticate(user=User(is_staff=True))
lead = leads_factory.create()
serializer = LeadSerializer(context={'request': user})
print(serializer)
# here I can see the user
response = api_client.get(f'/api/leads/{lead.id}/', )
assert response.status_code == status.HTTP_200_OK
assert response.data == {
'id': lead.id,
'first_name': lead.first_name,
'last_name': lead.last_name,
'age': lead.age,
'organizer': lead.organizer.id,
'agent': lead.agent.id,
'category': lead.category.id,
'description': lead.description,
'date_added': lead.date_added,
'phone_number': lead.phone_number,
'email': lead.email,
'converted_date': lead.converted_date,
}
because there is no organizer field in the serialzier test won't pass and this is the result of the test
what can I do here? can I pass the organizer user to the response?
You should add the organizer into the fields.
class LeadSerializer(serializers.ModelSerializer):
class Meta:
model = Lead
# here I added the `organizer` field
fields = ['id', 'first_name', 'last_name', 'age', 'agent', 'category', 'description', 'date_added', 'phone_number', 'email', 'converted_date', 'organizer']
def create(self, validated_data):
...

Add group to user in Django

I have created 3 groups 1. Staff, 2. Admin, 3. Operational Manager and assigned permission. Now whenever I wanted to add new user I wanted to show the choice field of the group and able to select the group.
As per the framework the User and Groups have many-to-many relations and I am trying to implement nested serializer. And as per documentation to add nested data I need to implement create or update method in the serializer but here I am not getting the choice field in group and the data is null.
GroupSerializer:
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ('name',)
UserSerializers:
class UserSerializers(serializers.ModelSerializer):
groups = GroupSerializer(many=True)
class Meta:
model = User
fields = ('first_name', 'last_name','address','contact', 'email', 'date_of_birth', 'branch', 'groups')
def create(self, validated_data):
groups_data = validated_data.pop('groups')
user = User.objects.create(**validated_data)
for group_data in groups_data:
Group.objects.create(user=user, **group_data)
return user
when I wanted to make a post request I am getting :
{
"first_name": "",
"last_name": "",
"address": "",
"contact": null,
"email": "",
"date_of_birth": null,
"branch": null,
"groups": []
}
here groups fields are empty.
If I try to make a post request it says
{
"groups": [
"This field is required."
]
}
Edit : Solved
I removed GroupSerializer because User model has many-to-many relation with Group so we don't need to explicitly specify Group object
# class GroupSerializer(serializers.ModelSerializer):
# class Meta:
# model = Group
# fields = ('name',)
class UserSerializers(serializers.ModelSerializer):
# groups = GroupSerializer(many=True)
class Meta:
model = User
fields = ('username','first_name', 'last_name','address','contact', 'email', 'date_of_birth', 'branch', 'groups')
def create(self, validated_data):
groups_data = validated_data.pop('groups')
user = User.objects.create(**validated_data)
for group_data in groups_data:
# Group.objects.create(user=user, **group_data)
user.groups.add(group_data)
return user
You can use PrimarykeyRelatedField and provide id of groups that you want to add in a list. like below:
class UserSerializers(serializers.ModelSerializer):
groups = serializers.PrimaryKeyRelatedField(queryset=Group.objects.all())
class Meta:
model = User
fields = ('first_name', 'last_name', 'address', 'contact', 'email', 'date_of_birth', 'branch', 'groups')
def create(self, validated_data):
groups_data = validated_data.pop('groups')
user = User.objects.create(**validated_data)
for group_data in groups_data:
Group.objects.create(user=user, **group_data)
return user
def to_representation(self, instance):
r = super(UserSerializers, self).to_representation(instance)
r.update({
'groups': GroupSerializer(many=True).to_representation(instance.groups)
})
return r
So just provide list of groups like this: "groups": [1,2,3]
and you should also override to_representation method if you want to have serialized json result of each group in response.

Namespacing Hyperlinked Serializers in Django REST Framework

I'm currently doing the tutorial on Relationships and hyperlinked API's. However I've come across a strange problem that I can't seem to fix. My serializers.HyperlinkedIdentityField and serializers.HyperlinkedRelatedField doesnt seem to detect the namespace I'm using.
My serializers look like this
class SnippetSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
highlight = serializers.HyperlinkedIdentityField(view_name='snippets:snippet-highlight', format='html')
class Meta:
model = Snippet
fields = ('url', 'id', 'highlight', 'owner', 'title', 'code', 'linenos', 'language', 'style')
class UserSerializer(serializers.HyperlinkedModelSerializer):
snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippets:snippet-detail', read_only=True)
class Meta:
model = User
fields = ('url', 'id', 'username', 'snippets')
Which is pretty much the same as the tutorial except I am adding view_name='snippets:snippet-detail' in the serializer field.
I am creating my namespace as suggested by the Django documentation, by adding app_name = 'snippets' above my urlpatterns.
This is the error I'm getting
ImproperlyConfigured at /snippets/
Could not resolve URL for hyperlinked relationship using view name
"snippet-detail". You may have failed to include the related model in
your API, or incorrectly configured the lookup_field attribute on
this field.
As you see, I have approached the problem the same way other people have but without resolving the issue. Anyone have an idea about what I could try next?
Solved the problem after reading some more about Hyperlinks and noticing that I should add extra_kwargs for the url field SnippetSerializer
class Meta:
model = Snippet
fields = ('url', 'id', 'highlight', 'owner', 'title', 'code', 'linenos', 'language', 'style')
extra_kwargs = {
'url': {'view_name': 'snippets:snippet-detail'},
}
And UserSerializer
class Meta:
model = User
fields = ('url', 'id', 'username', 'snippets')
extra_kwargs = {
'url': {'view_name': 'snippets:user-detail'},
}
fix this issue by modify serializer.py
we can define url, such as:
url = serializers.HyperlinkedIdentityField(view_name='snippets:user-detail', lookup_field='pk')
or define in Meta, such as
extra_kwargs = {
'url': {'view_name': 'snippet:user-detail', 'lookup_field': 'pk'},
}
all serializer.py code:
from rest_framework import serializers
from snippets.models import Snippet
from django.contrib.auth.models import User
class SnippetSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
highlight = serializers.HyperlinkedIdentityField(lookup_field="pk", view_name='snippets:snippet-highlight', format='html')
class Meta:
model = Snippet
fields = ('url', 'id', 'highlight', 'owner', 'title', 'code', 'linenos', 'language', 'style')
extra_kwargs = {
'url': {'view_name': 'snippets:snippet-detail', 'lookup_field': 'pk'},
}
class UserSerializer(serializers.HyperlinkedModelSerializer):
snippets = serializers.HyperlinkedRelatedField(lookup_field="pk", many=True, view_name='snippets:snippet-detail', read_only=True)
url = serializers.HyperlinkedIdentityField(view_name='snippets:user-detail', lookup_field='pk')
class Meta:
model = User
fields = ('url', 'id', 'username', 'snippets')
# extra_kwargs = {
# 'url': {'view_name': 'snippet:user-detail', 'lookup_field': 'pk'},
# }
Try to pass lookup_field and lookup_url_kwarg arguments.
snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippets:snippet-detail',
lookup_field="",
lookup_url_kwarg="", read_only=True)
Refer HyperlinkedRelatedFieldDoc

Django query taking too much time

I'm using django and django rest framework to make a query from all users in data that have a permission sent as a url parameter but this query is taking too long.
I'm user pycharm debugger how can I try to check why is it taking to long, this is the function:
#list_route(url_path='permission/(?P<permission>.+)')
def read_permission(self, request, *args, **kwargs):
serializer = self.get_serializer_class()
qs = get_user_model().objects.filter_by_permission(self.kwargs.get('permission'))
qs = qs.order_by(Lower('username'))
return Response(serializer(qs, many=True).data)
Update
Adding the serializer
class UserSerializer(UserLabelMixin):
user_permissions = serializers.SlugRelatedField(many=True, read_only=True, slug_field='codename')
class Meta:
model = get_user_model()
fields = ['id', 'email', 'is_superuser', 'is_staff', 'label',
'full_name', 'first_name', 'last_name', 'username',
'teams', 'date_joined', 'last_login',
'user_permissions', 'groups', 'ui_preferences', 'internal_project',
'staff_id', 'oem_id', 'oem_email', 'oem_department', 'comment']
read_only_fields = fields
This may help you
get_user_model().objects.prefetch_related("user_permissions", "groups").filter_by_permission...

Django Rest Framework make OnetoOne relation ship feel like it is one model

I have my User saved in two different models, UserProfile and User. Now from API perspective, nobody really cares that these two are different.
So here I have:
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'first_name', 'last_name', 'email')
and
class UserPSerializer(serializers.HyperlinkedModelSerializer):
full_name = Field(source='full_name')
class Meta:
model = UserProfile
fields = ('url', 'mobile', 'user','favourite_locations')
So in UserPSerializer the field user is just a link to that resource. But form a User perspective there is really no reason for him to know about User at all.
Is there some tricks with which I can just mash them together and present them to the user as one model or do I have to do this manually somehow.
You can POST and PUT with #kahlo's approach if you also override the create and update methods on your serializer.
Given a profile model like this:
class Profile(models.Model):
user = models.OneToOneField(User)
avatar_url = models.URLField(default='', blank=True) # e.g.
Here's a user serializer that both reads and writes the additional profile field(s):
class UserSerializer(serializers.HyperlinkedModelSerializer):
# A field from the user's profile:
avatar_url = serializers.URLField(source='profile.avatar_url', allow_blank=True)
class Meta:
model = User
fields = ('url', 'username', 'avatar_url')
def create(self, validated_data):
profile_data = validated_data.pop('profile', None)
user = super(UserSerializer, self).create(validated_data)
self.update_or_create_profile(user, profile_data)
return user
def update(self, instance, validated_data):
profile_data = validated_data.pop('profile', None)
self.update_or_create_profile(instance, profile_data)
return super(UserSerializer, self).update(instance, validated_data)
def update_or_create_profile(self, user, profile_data):
# This always creates a Profile if the User is missing one;
# change the logic here if that's not right for your app
Profile.objects.update_or_create(user=user, defaults=profile_data)
The resulting API presents a flat user resource, as desired:
GET /users/5/
{
"url": "http://localhost:9090/users/5/",
"username": "test",
"avatar_url": "http://example.com/avatar.jpg"
}
and you can include the profile's avatar_url field in both POST and PUT requests. (And DELETE on the user resource will also delete its Profile model, though that's just Django's normal delete cascade.)
The logic here will always create a Profile model for the User if it's missing (on any update). With users and profiles, that's probably what you want. For other relationships it may not be, and you'll need to change the update-or-create logic. (Which is why DRF doesn't automatically write through a nested relationship for you.)
I just came across this; I have yet to find a good solution particularly for writing back to my User and UserProfile models. I am currently flattening my serializers manually using the SerializerMethodField, which is hugely irritating, but it works:
class UserSerializer(serializers.HyperlinkedModelSerializer):
mobile = serializers.SerializerMethodField('get_mobile')
favourite_locations = serializers.SerializerMethodField('get_favourite_locations')
class Meta:
model = User
fields = ('url', 'username', 'first_name', 'last_name', 'email', 'mobile', 'favourite_locations')
def get_mobile(self, obj):
return obj.get_profile().mobile
def get_favourite_locations(self, obj):
return obj.get_profile().favourite_locations
This is horribly manual, but you do end up with:
{
"url": "http://example.com/api/users/1",
"username": "jondoe",
"first_name": "Jon",
"last_name": "Doe",
"email": "jdoe#example.com",
"mobile": "701-680-3095",
"favourite_locations": [
"Paris",
"London",
"Tokyo"
]
}
Which, I guess is what you're looking for.
I would implement the modifications on the UserPSerializer as the fields are not going to grow:
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'first_name', 'last_name', 'email')
class UserPSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.CharField(source='user.url')
username = serializers.CharField(source='user.username')
first_name = serializers.CharField(source='user.first_name')
last_name = serializers.CharField(source='user.last_name')
email = serializers.CharField(source='user.email')
class Meta:
model = UserProfile
fields = ('mobile', 'favourite_locations',
'url', 'username', 'first_name', 'last_name', 'email')