I am creating an api using tastypie. Code is as follows. I have inspected code using ipdb.
class UserProfileResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'userprofiles'
excludes = ['password', 'is_active', 'is_staff', 'is_superuser']
filtering = {
'username': ALL,
}
class UserResource(ModelResource):
user = fields.ForeignKey(UserProfileResource, 'user', full=True)
class Meta:
queryset = User.objects.all()
resource_name = 'users'
list_allowed_methods = ['get','post']
allowed_methods = ['get', 'put', 'patch', 'delete']
authentication = ApiKeyAuthentication()
serializer = Serializer(formats=['json', 'jsonp', 'xml', 'yaml', 'html', 'plist'])
def post_list(self, request, **kwargs):
print 'post list method'
mandatory_fields = ['username', 'email', 'first_name', 'last_name', 'address', 'city', 'pin']
fields_apicall = request.GET.keys()
#import ipdb;ipdb.set_trace();
print request.POST
if set(mandatory_fields).issubset(fields_apicall):
print request.GET.keys()
create_object = True
else:
print 'NO'
err_dict = { 'error':'Mandatory Fields Missing.' }
return HttpResponse(json.dumps(err_dict))
def obj_create(self, bundle, **kwargs):
print 'obj_create() '
mandatory_fields = ['username', 'email', 'first_name', 'last_name', 'address', 'city', 'pin']
fields_apicall = bundle.data.keys()
# import ipdb;ipdb.set_trace();
if set(mandatory_fields).issubset(fields_apicall):
bundle.obj = self._meta.object_class()
else:
err_dict = { 'error':'Mandatory Fields Missing.' }
return HttpResponse(json.dumps(err_dict))
I want to create nested user object, ie user object together with userprofile. For that I have to make sure all fields exists in the json. I tried overriding hydrate(),dehydrate(),and post_list() methods. Among these only post_list() get invoked on a post request.
I can check for fields in request json by overriding post_list() method but is this a good way to check for missing fields? I have googled and have gone through many SO posts but I didn't find any posts mentioning to override post_list() to check for missing fields some of them said to override hydrate(). Also is it necessary to override obj_create() method to create objects using tastypie?
obj_create() seems like the best place to do it. Something like this should work:
def obj_create(self, bundle, **kwargs):
mandatory_fields = ['username', 'email', 'first_name', 'last_name', 'address', 'city', 'pin']
for field_name, field_object in self.fields.items():
if field_name not in mandatory_fields:
err_dict = { 'error':'Mandatory Fields Missing.' }
return HttpResponse(json.dumps(err_dict))
super(SomeResource, self).obj_create(self, bundle, **kwargs)
Related
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):
...
When creating an object in my api view it seems fine but whenever I update the object even I didn't change my email field it says user with this Email Address already exists. I am using generics.RetrieveUpdateView for my view.
I thought generics.RetrieveUpdateView automatically handles this. What am I missing?
my model looks like this:
class User(AbstractUser):
email= models.EmailField(unique=True)
...
my serializer looks like this:
class UserListForProfileSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username','email','last_login', 'date_joined','is_active', 'is_student', 'is_staff', 'is_teacher', 'is_registrar', 'is_superuser']
read_only_fields = ('username','date_joined', 'last_login','is_active', 'is_student', 'is_staff', 'is_teacher', 'is_registrar', 'is_superuser')
class StaffProfileDetailSerializer(CreateProfileSerializer):
user = UserListForProfileSerializer(many= False)
class Meta:
model = StaffProfile
fields = ['user','first_name', 'middle_name', 'last_name', 'gender', 'employee_number', 'date_of_birth', 'mobile_number','dob_month','dob_day','dob_year','address',]
read_only_fields = ('date_joined', 'last_login',)
my view looks like this:
class UserProfileDetail(generics.RetrieveUpdateAPIView):
authentication_classes = [SessionAuthentication, BasicAuthentication]
permission_classes = [IsAuthenticated]
serializer_class = StaffProfileDetailSerializer
def get_queryset(self, *args, **kwargs):
userid = self.request.user.id
return StaffProfile.objects.filter(pk= userid)
urls.py:
urlpatterns = [
path('api/', apiOverview, name='apioverview'),
path('api/user/profile/detail/<int:pk>/', UserProfileDetail.as_view(),
name='userprofile-detail'),
...
]
So far I have ->
serializer:
class UserSerializer(serializers.ModelSerializer):
"""Serializer to map the model instance into json format."""
class Meta:
"""Map this serializer to a model and their fields."""
model = User
fields = ('id','username', 'mobile', 'password',
'first_name','last_name','middle_name',
'profile_pic','short_bio','friends_privacy',
'address_1','address_2','city',
'state','country','pin','verification_code',
'is_active','is_blocked','is_reported',
'date_created','date_modified')
extra_kwargs = {'password': {'write_only': True}}
read_only_fields = (
'date_created', 'date_modified',
'is_staff', 'is_superuser', 'is_active',
'date_joined',)
def create(self, validated_data):
mobile_ = validated_data['mobile']
password_ = validated_data['password']
username_ = validated_data['username']
motp = self.context['request'].GET['motp']
eotp = self.context['request'].GET['eotp']
email_ = self.context['request'].GET['email']
mflag = api.views.checkOTP_(mobile_,motp)
eflag = api.views.checkOTP_(email_,eotp)
if (mflag and eflag):
user = User(
username=username_,
email =email_,
password = make_password(password_),
mobile = mobile_,
)
user.set_password(validated_data['password'])
user.save()
return user
view:
class UserView2(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
model = User
def get_permissions(self):
# allow non-authenticated user to create via POST
return (AllowAny() if self.request.method == 'POST'
else IsStaffOrTargetUser()),
I need to check for OTP of mobile and email and also if a user with same mobile or email already exists.
If user already exists return a json response with error: already exists!.
If user is new and OTP is wrong again raise an error.
If user is new and OTP is correct, create an account.
Problem here is I tried to add the function to check for otp verification inside the def create of UserSerializer. But a serializer is supposed to return the model instance. But if you see the code, I am able to create a user only if OTP is right and user instance is returned. And there is no else.
So is there a better way to check for OTP in the view itself?
I don't agree with #Anjaneyulu there..
Serializer handles creation of objects as well.. hence the reason you have serializer.save().
But for the purpose of raising an exception for existing user with same OTP email/phone, you should write your own def validate_mobile(self, data) and def validate_email(self, data). DRF serializer will look for these methods in the class first and will run them if they exist. So your custom logic for checking those fields could be:
class UserSerializer(serializers.ModelSerializer):
def validate_mobile(self, value):
ModelClass = self.Meta.model
if ModelClass.objects.filter(mobile=value).exists():
raise serializers.ValidationError('already exists')
return value
def validate_email_(self, value):
ModelClass = self.Meta.model
if ModelClass.objects.filter(email_=value).exists():
raise serializers.ValidationError('already exists')
return value
class Meta:
model = User
fields = (
...,
)
That is not the correct way of implementing it. Serializers are meant only for validation purposes. you should not implement the create method in serializer instead write it in ViewSet. Creating object is a business logic. It should always go in a ViewSet. Write a validation method to the serializer. I'm writing an example code below
serializers.py
class UserSerializer(serializers.ModelSerializer):
def validate_mobile(self, mobile_num):
is_already_exists = Model.objects.filter(mobile=mobile_num).exists()
if is_already_exists:
raise serializers.ValidationError('already exists')
return mobile_num
class Meta:
model = User
fields = (
'id','username', 'mobile', 'password',
'first_name','last_name','middle_name','profile_pic',
'short_bio','friends_privacy','address_1',
'address_2','city','state','country',
'pin','verification_code','is_active',
'is_blocked','is_reported',
'date_created','date_modified'
)
extra_kwargs = {'password': {'write_only': True}}
read_only_fields = (
'date_created', 'date_modified','is_staff',
'is_superuser', 'is_active', 'date_joined',
)
Viewsets.py(Business Logic)
class UserView2(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
def get_permissions(self):
# allow non-authenticated user to create via POST
return (AllowAny() if self.request.method == 'POST'
else IsStaffOrTargetUser()),
def create(self, serializer):
# your logic goes here.
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...
I have 2 resources that are linked by a foreignKey
I would like to make the AUser resource read-only when creating/modifying AJob
class AUser(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'user'
authentication = SessionAuthentication()
authorization = Authorization()
excludes = ['email', 'password', 'is_superuser', 'is_staff', 'is_active', 'date_joined', 'last_login']
def can_update(self):
return False
def can_create(self):
return False
def can_delete(self):
return False
def apply_authorization_limits(self, request, object_list):
return object_list.filter(pk=request.user.pk)
class AJob(ModelResource):
user = fields.ForeignKey( AUser, 'user', full=True)
paused = fields.BooleanField(attribute='isPaused', readonly=True)
hasRules = fields.BooleanField(attribute='hasRules', readonly=True)
class Meta:
queryset = Job.objects.all()
resource_name = 'job'
authentication = SessionAuthentication()
api_name = 'v1'
authorization = Authorization()
allowed_methods = ['get', 'post', 'delete']
def obj_create(self, bundle, request=None, **kwargs):
return super(AJob, self).obj_create(bundle, request, user=request.user)
def apply_authorization_limits(self, request, object_list):
return object_list.filter(user=request.user)
I tried adding readonly=True to the foreignKey directly but then it's ignored when hydrating
and get a constraint violation because user is null
if in my POST AJob request I append
"user":{"id":"5","is_staff":false}
5 being the current user
the User model get updated, removing the admin role
it seems that tastypie when doing save_related does not check any authorization
How can I make this User resource read-only ?
I am using tastypie v0.9.12-alpha
You can modify the save_related method inside the AJob resource and define it not to modify the AUser. You can define the ForeignKey do be readonly as you wanted to but then you have to supply the dehydrate_user method and inside get the value that you want to return. It will be something like return bundle['data'].user.