I have test defined looking like below
#patch('user.serializers.validate_sns_token', return_value=True)
def test_can_create_user(self, validate):
"""
Test that user can be created without logging in
:return:
"""
user_payload = self.generate_user_payload()
resp = self.client.post(
CREATE_USER_URL,
user_payload,
)
self.assertTrue(validate.called)
self.assertEqual(
resp.status_code,
status.HTTP_200_OK,
)
Inside the user.serializers.py file I have below function that I would like to mock
def validate_sns_token(**kwargs):
try:
...
return True
except ValueError as e:
...
return False
It's an external call to google api so I would like to always return True from it.
Inside the serializer I am calling the create function like below
def create(self, validated_data):
...
is_token_valid = validate_sns_token(
sns_id=sns_id,
sns_type=sns_type,
token=token,
)
if is_token_valid is False:
raise serializers.ValidationError('토큰이 유효하지 않습니다. 다시 로그인해주세요.', code='AUTH')
...
return user
Unfortunately, the validate.called is always False and the original validate_sns_token is still getting called. How do I write a test so that the original validate_sns_token is not called at all but its return value is set to True?
Related
How can I prevent Django rest throttling count the request when the user request is invalid or the server failed to complete the process?
For example, I need params from the user, but when the user does not give the params, Django rest throttling still counts it.
Is there any solution to skipping the throttling counter when the request is not successful?
Example
class OncePerHourAnonThrottle(AnonRateThrottle):
rate = "1/hour"
class Autoliker(APIView):
throttle_classes = [OncePerHourAnonThrottle]
def get(self, request):
content = {"status": "get"}
return Response(content)
def post(self, request):
post_url = request.POST.get("url", None)
print(post_url)
content = {"status": "post"}
return Response(content)
def throttled(self, request, wait):
raise Throttled(
detail={
"message": "request limit exceeded",
"availableIn": f"{wait} seconds",
"throttleType": "type",
}
)
You can create a decorator to do so.
class OncePerHourAnonThrottle(AnonRateThrottle):
rate = "1/hour"
def allow_request(self, request, view):
"""
This function is copy of SimpleRateThrottle.allow_request
The only difference is, instead of executing self.throttle_success
it directly returns True and doesn't mark this request as success yet.
"""
if self.rate is None:
return True
self.key = self.get_cache_key(request, view)
if self.key is None:
return True
self.history = self.cache.get(self.key, [])
self.now = self.timer()
# Drop any requests from the history which have now passed the
# throttle duration
while self.history and self.history[-1] <= self.now - self.duration:
self.history.pop()
if len(self.history) >= self.num_requests:
return False
return True
def rate_limiter(view_function):
#wraps(view_function)
def inner(view_obj, request, *args, **kwargs):
throttle = OncePerHourAnonThrottle()
allowed = throttle.allow_request(request, None)
if not allowed:
raise exceptions.Throttled(throttle.wait())
try:
response = view_function(view_obj, request, *args, **kwargs)
except Exception as exc:
response = view_obj.handle_exception(exc)
if response.status_code == 200:
# now if everything goes OK, count this request as success
throttle.throttle_success()
return response
return inner
class Autoliker(APIView):
#rate_limiter
def post(requests):
# view logic
pass
This is the basic idea how you can do it, now you can make it a generic decorator or even class based decorator.
Hello I want to add method to list display in django admin but am getting error say SalryAdmin has no attribute request
here my admin
#admin.register(Salary)
class SalaryAdmin(admin.ModelAdmin):
list_display = ['user_name', 'action']
def action(self, obj):
if not self.request.user.is_superuser:
if obj.hr_state == 'request-change-approved' and self.request.user.user_role.position.code == 'HRM':
return True
else:
return False
else:
return True
any help appreciated
You are getting error for obvious reason - ModelAdmin class does not have request member. Instead of this, you should mark method as action and request would be passed as an argument to the method - like so:
#admin.register(Salary)
class SalaryAdmin(admin.ModelAdmin):
list_display = ['user_name', 'action']
actions = ['action']
#admin.action(description='')
def action(self, request, queryset):
obj = queryset.first() # TODO - implement object retrieval logic here
if not request.user.is_superuser:
if obj.hr_state == 'request-change-approved' and request.user.user_role.position.code == 'HRM':
return True
else:
return False
else:
return True
Here is the source to the official docs.
I have following APIView:
class SubmitFormAPIView(APIView):
def put(self, request, pk):
# some other codes
form = Form.objects.get(id=pk)
tu_filter, target_user = self._validate_target_user(request, form)
user_status, created = UserFormStatus.objects.get_or_create(
tu_filter,
form_id=pk,
user_id=request.user.pk
)
# Some other codes.
def _validate_target_user(request, form):
if some_conditions:
return Q(), None
else:
try:
target_user_id = int(request.GET.get('target_user_id))
except ValueError:
raise ValidationError()
target_user = get_user_model().objects.get(id=target_user_id)
return Q(target_user_id=target_user_id), target_user
but when django wants to execude get_or_create method, raises following error:
TypeError: 'Q' object is not iterable
Note: If _validate_target_user() returns Q(), None, no errors raised and view works fine. The error will be raised when return Q(target_user_id=target_user_id), target_user is returned.
I know, question information is not completed, just I want to know, what may cause this error?
From the source of get_or_create(...), def get_or_create(self, defaults=None, **kwargs):
which indicating that the get_or_create(...) doesn't not accept any args unlike the get() or filter(...) methods.
Since your are executing the function as below, Python thinks that the tu_filter is the value for default parameter, which is expected to be an iterable.
get_or_create(
tu_filter,
form_id=pk,
user_id=request.user.pk
)
Instead of returning a Q object, you can also just pass a dictionary of filters instead, like
{ 'target_user_id': target_user_id }
The you can run the get_or_create with **tu_filter as arguments, bypassing the need for Q.
class SubmitFormAPIView(APIView):
def put(self, request, pk):
# some other codes
form = Form.objects.get(id=pk)
tu_filter, target_user = self._validate_target_user(request, form)
user_status, created = UserFormStatus.objects.get_or_create(
**tu_filter,
form_id=pk,
user_id=request.user.pk
)
# Some other codes.
def _validate_target_user(request, form):
if some_conditions:
return {}, None
else:
try:
target_user_id = int(request.GET.get('target_user_id))
except ValueError:
raise ValidationError()
target_user = get_user_model().objects.get(id=target_user_id)
return { 'target_user_id': target_user_id }, target_user
Edit: As to what causes the error, my guess would be that using Q as part of your get_or_create statement is unclear to Django, because it doesn't know what to do with it in case the object needs to be created. A better approach would therefor be:
UserFormStats.objects.filter(tu_filter).get_or_create(form_id=pk, user_id=request.user.pk)
The scenario is quite straight-forward:
Say i have a user model where email should be unique. I did a custom validation for this like.
def validate_email(self, value):
if value is not None:
exist_email = User.objects.filter(email=value).first()
if exist_email:
raise serializers.ValidationError("This Email is already taken")
return value
from rest_framework response when input validation occur we should return status_code_400 for BAD_REQUEST but in this scenario we should or we need to return status_code_409 for conflicting entry. What is the best way to customize status_code response from serializer_errors validation.
I think is better to define custom exception_handler like:
settings.py
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'myproject.common.custom_classes.handler.exception_handler',
}
handler.py
def exception_handler(exc, context):
# Custom exception hanfling
if isinstance(exc, UniqueEmailException):
set_rollback()
data = {'detail': exc.detail}
return Response(data, status=exc.status_code)
elif isinstance(exc, (exceptions.APIException, ValidationError)):
headers = {}
if getattr(exc, 'auth_header', None):
headers['WWW-Authenticate'] = exc.auth_header
if getattr(exc, 'wait', None):
headers['Retry-After'] = '%d' % exc.wait
if hasattr(exc, 'error_dict') and isinstance(exc, ValidationError):
exc.status_code = HTTP_400_BAD_REQUEST
data = exc.message_dict
elif isinstance(exc.detail, (list, dict)):
data = exc.detail
else:
data = {'detail': exc.detail}
set_rollback()
return Response(data, status=exc.status_code, headers=headers)
elif isinstance(exc, Http404):
msg = _('Not found.')
data = {'detail': six.text_type(msg)}
set_rollback()
return Response(data, status=status.HTTP_404_NOT_FOUND)
return None
exceptions.py
class UniqueEmailException(APIException):
status_code = status.HTTP_409_CONFLICT
default_detail = 'Error Message'
And finally the validator:
def validate_email(self, value):
if value is not None:
exist_email = User.objects.filter(email=value).first()
if exist_email:
raise UniqueEmailException()
return value
I would go for intercepting ValidationError exception and return the Response object with 409 status code:
try:
serializer.is_valid(raise_exception=True)
except ValidationError, msg:
if str(msg) == "This Email is already taken":
return Response(
{'ValidationError': str(msg)},
status=status.HTTP_409_CONFLICT
)
return Response(
{'ValidationError': str(msg)},
status=status.HTTP_400_BAD_REQUEST
)
Short answer:
You can't return custom response codes from a serializer.
This is because the serializer is just that: A Serializer. It doesn't, or shouldn't, deal with HTTP at all. It's just for formatting data, usually as JSON, but it'll usually do HTML for showing your API, and one or two other formats.
Long answer:
One way to accomplish this is to raise something (doesn't matter what, but make it descriptive) in your serializer, and add code to your view to catch the error. Your view can return a custom response code with a custom response body as you like it.
Like this:
add something like this to your view class:
def create(self, request, *args, **kwargs):
try:
return super().create(request, *args, **kwargs)
except ValidationError as x:
return Response(x.args, status=status.HTTP_409_CONFLICT)
How can I access the detail endpoint object being accessed in the request during a tastypie authorization?
I noticed that one of the overridden methods in the docs has an object parameter -- how can I set this?
In branch perms,
https://github.com/toastdriven/django-tastypie/blob/perms/tastypie/authorization.py
Class Authorization has a set of methods for example:
def read_detail(self, object_list, bundle):
"""
Returns either ``True`` if the user is allowed to read the object in
question or throw ``Unauthorized`` if they are not.
Returns ``True`` by default.
"""
return True
Here You can try to access the obj through bundle.obj
If You can't use the perms branch, I suggest you this way:
class MyBaseAuth(Authorization):
def get_object(self, request):
try:
pk = resolve(request.path)[2]['pk']
except IndexError, KeyError:
object = None # or raise Exception('Wrong URI')
else:
try:
object = self.resource_meta.object_class.objects.get(pk=pk)
except self.resource_meta.DoesNotExist:
object = None
return object
class FooResourceAuthorization(MyBaseAuth):
def is_authorized(self, request, object=None):
if request.method in ('GET', 'POST'):
return True
elif request.method == 'DELETE':
object = self.get_object(request)
if object.profile = request.user.profile:
return True
return False
Hackish, but with a simple way of getting to the object from the request URL (inspired by the code inside DjangoAuthorization).
def is_authorized(self, request, object=None):
meta = self.resource_meta
re_id = re.compile(meta.api_name + "/" + meta.resource_name + "/(\d+)/")
id = re_id.findall(request.path)
if id:
object = meta.object_class.objects.get(id=id[0])
# do whatever you want with the object
else:
# It's not an "object call"