How can I customize drf-yasg endpoints's example response? - django

on my Django project, I'm using DRF and drf-yasg. At some endpoint, the example response body shows the incorrect example. like the following example:
But some of them don't show the correct example response body.
This endpoint actually returns access_token and refresh_token, doesn't return the email and password. It's wrong information for the front-end devs. Is there any way to change this?

Below is an example using #swagger_auto_schema annotation with 3 serializers. 1 request serializer and 2 response serializers (success and error serializers).
class RequestSerializer(serializers.Serializer):
param = serializers.CharField()
class SucessSerializer(serializers.Serializer):
success = serializers.BooleanField()
message = serializers.CharField()
class ErrorSerializer(serializers.Serializer):
success = serializers.BooleanField()
errors = serializers.IntegerField()
class ExampleViewView(APIView):
#swagger_auto_schema(
request_body = RequestSerializer,
responses={
'200': SucessSerializer,
'500': ErrorSerializer,
},
operation_description = 'Doc description'
)
def post(self, request):
return successResponse({
'sucess': True
})

Related

Django rest framework return validation error messages from endpoints

Using
Django==3.0.8
djangorestframework==3.11.0
I am trying to validate a GET endpoint which looks like this.
/users?role=Prospect&limit=10&offset=0
How can we validate this request in DRF using serializers.Serializer and get all the validation error messages when invalid and return in api response?
Serializer using for this request:
class UserIndexSerializer(serializers.Serializer):
offset = serializers.IntegerField(required=True)
limit = serializers.IntegerField(required=True)
role = serializers.CharField(allow_null=True, default=None, max_length=255)
View function looks like:
#api_view(["GET"])
def user_list(request):
serializer = UserIndexSerializer(data=request.data) // trying to validate using this serializer
print("query params", request.GET)
print("request valid", serializer.is_valid())
users = User.objects.all()
serializer = UserGetSerializer(users, many=True)
return AppResponse.success("User list found.", serializer.data)
You need to make use of serializers.ValidationError here.
To validate a field of name xyz you need to make a function named validate_xyz and write your validation code inside it.
For example, to validate the limit field of your serializer, we need to do:
class UserIndexSerializer(serializers.Serializer):
offset = serializers.IntegerField(required=True)
limit = serializers.IntegerField(required=True)
role = serializers.CharField(allow_null=True, default=None, max_length=255)
def validate_limit(self, value):
if limit > 5:
raise serializers.ValidationError('Limit cannot exceed 5.')
return value
After defining these validator functions, you can check if the serializer is valid by doing
serializer.is_valid()
django-rest-framework takes in the validation errors raised if any, and acts accordingly.
you need to return a value from the validator function.

Django REST testing - how to specify pk argument referring to the model instance created in setUp function

I have a model "Article" and I want to test if authorized user can GET an individual article.
The testing class is:
class TestPost(APITestCase):
def setUp(self):
self.factory = APIRequestFactory()
self.user = User.objects.create_user(
username='Name', email='test#company.com', password='secret')
self.article = Article.objects.create(
author = 'Author', title = 'Article title', body = 'Body content ...')
def test_detail_user(self):
request = self.factory.get(reverse('article_id', kwargs={'pk': 1}))
request.user = self.user
response = ArticleDetail.as_view()(request, pk=1)
self.assertEqual(response.status_code, 200,
f'Expected Response Code 200 - OK, received {response.status_code} instead.')
The URL pattern is:
path('<int:pk>/', ArticleDetail.as_view(), name = 'article_id'),
And when running tests I get the following error:
f'Expected Response Code 200 - OK, received {response.status_code} instead.')
AssertionError: 404 != 200 : Expected Response Code 200 - OK, received 404 instead.
I suppose the problem is in the specified 'pk', but I cannot figure out how to specify pk without stating an exact figure of 1. How can I refer to the article created in setUp function instead?
I may be misunderstanding, but you should be able to reference it by simply doing something like:
def test_detail_user(self):
article_id = self.article.pk
...
# the rest of your code here using article_id as the id of
# the article you are retrieving

Django REST Framework - unittest client failing to resolve hyperlinks relation for POST

I have this test:
class AttributeTest(APITestCase):
def setUp(self):
user1 = User.objects.create(pk=1, username='pepa', email='ads#asasd.cz', is_active=True, is_staff=True)
user1.set_password('mypass')
user1.save()
self.c1 = Campaign.objects.create(pk=1, owner=user1, project_name='c1')
def test(self):
campaign_url = 'http://testserver/api/campaigns/{}/'.format(self.c1.pk)
self.client.login(username='pepa', password='mypass')
data = {
"label": "something_here",
"parent_campaign": campaign_url,
}
# campaign clearly exists (created in setUp) and GET retrieve it:
assert self.client.get(campaign_url).json()['project_name'] == 'c1'
# I can even try it myself using pdb
# but this doesn't work - response return 400 Bad Request
# complaining about the very same hyperlink I can GET above
response = self.client.post('/api/keys', data, format="json")
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
but when run, it fails with {'parent_campaign': ['Invalid hyperlink - No URL match.']}.
When I try using curl or browsable API (outside the test environment), everything works as expected.
My serializer corresponding to the /api/keys:
class AttributeSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='api:key-detail')
parent_campaign = serializers.HyperlinkedRelatedField(
view_name='api:campaign-detail',
lookup_field='cid',
queryset=Campaign.objects.all())
def _get_user_campaigns(self):
user = self.context['view'].request.user
return Campaign.objects.filter(owner=user)
def get_fields(self, *args, **kwargs):
fields = super(AttributeSerializer, self).get_fields(*args, **kwargs)
fields['parent_campaign'].queryset = self._get_user_campaigns()
return fields
class Meta:
model = Key
fields = ("id", 'url', "label", 'parent_campaign')
Using serializer directly:
(Pdb) from api.attribute.serializers import AttributeSerializer
(Pdb) ser = AttributeSerializer(data=data)
(Pdb) ser.is_valid()
True
(Pdb) ser.save()
<Key: Something1 | MAROO | CID: lrvyw93>
Try reversing your url name and passing c1.pk as a url parameter, not just formatting it into your url:
from rest_framework.reverse import reverse
campaign_url_name = 'api:campaign-detail' # Use URL name instead of raw URL path
response = self.client.get(reverse(campaign_url_name, kwargs={'pk': self.c1.pk}))
I don't know why, but the results of tests had to be somehow cached. I restarted the PC and it worked with exactly the same commit. Solved.

Make BooleanField required in Django Rest Framework

I've got a model with a boolean field that I'd like to deserialize with the Django rest framework and I want the serializer to complain when a field is missing in the post request. Yet, it doesn't. It silently interprets a missing boolean as False.
class UserProfile(models.Model):
"""
Message between two users
"""
user = models.OneToOneField(User, verbose_name="django authentication user", related_name='user_profile')
newsletter = models.BooleanField(null=False)
research = models.BooleanField(null=False)
The model is created with a Serialiser like this:
class UserProfileSerializer(serializers.ModelSerializer):
research = BooleanField(source='research', required=True)
newsletter = BooleanField(source='newsletter', required=True)
class Meta:
model = UserProfile
fields = ('research', 'newsletter')
In my view I'm also creating a user, so I have some manual steps:
def post(self, request, format=None):
userprofile_serializer = UserProfileSerializer(data=request.DATA)
reg_serializer = RegistrationSerializer(data=request.DATA)
phone_serializer = PhoneSerializer(data=request.DATA)
errors = {}
if userprofile_serializer.is_valid() and reg_serializer.is_valid() and phone_serializer.is_valid():
user = reg_serializer.save()
data = reg_serializer.data
user_profile = userprofile_serializer.object
user_profile.user = user
userprofile_serializer.save()
return Response(data, status=status.HTTP_201_CREATED)
errors.update(reg_serializer.errors)
# ...
return Response(errors, status=status.HTTP_400_BAD_REQUEST)
However, the following test case fails, because the rest framework doesn't complain about the missing param but instead inserts a False in from_native
def test_error_missing_flag(self):
data = {'username': "test", 'password': "123test", 'email': 'test#me.com',
'newsletter': 'true', 'uuid': self.uuid}
response = self.client.post(reverse('app_register'), data)
# should complain that 'research' is not found
self.assertTrue('research' in response.data)
If I replace my 'research' field with an Integer field that the serializer fails as expected. Any ideas?
There was an issue with Boolean fields and the required argument. Should now be fixed in master.
See this issue: https://github.com/tomchristie/django-rest-framework/issues/1004
Add
your_field = serializers.NullBooleanField(required=False)
in serializer.
That's it. It'll work :)
For anyone who has read #Tom's accepted answer from 2013 and finds that this still doesn't work, it's because this behavior is intended for HTML form inputs. Here's the original issue.
To use serializers.BooleanField with a JSON payload, convert your request.POST to a Python dict by doing request.POST.dict() and pass it to your serializer while initializing.
Create a new custom class:
from rest_framework import serializers
class RequirableBooleanField(serializers.BooleanField):
default_empty_html = serializers.empty
Now, you can use:
research = RequirableBooleanField(required=True)
or
research = RequirableBooleanField(required=False)

How to write the django-piston handler to create and return ApiKey?

I'm currently working on the project that use Android for client and Django for web server. I decided to use piston-django to create REST API authentication and I've follow this instruction:
What is the right way to write a django-piston client?
and write my own handler (api/handlers.py) to create and return ApiKey like this:
class ApiKeyhandler(Basehandler):
model = ApiKey
allowed_methods = ('GET', 'POST', 'PUT', 'DELETE')
fields = ('user', 'keys')
def create(self, request):
attrs = self.flatten_dict(request.POST)
if self.exists(**attrs):
return rc.DUPLICATE_ENTRY
else:
apikey = ApiKey(user=request.user)
apikey.save()
return apikey
and in urls.py I use HttpBasicAuthentication for this handler:
auth = HttpBasicAuthentication(realm="Authentication API")
apikey = Resource(handler=ApiKeyHandler, authentication=auth)
but when I test it with http://hurl.it
This is the response from GET
This is the response from POST method
Can anyone tell me how to write the complete code for this question or any suggestion on this problem?
Since nobody answer this question, I figured it out myself and some help from my friend. I have to edit ApiKeyHandler to
class ApiKeyHandler(BaseHandler):
model = ApiKey
allowed_methods = ('GET', 'POST')
fileds = ('user', 'key')
def read(self, request):
# Return the API key for request.user
values_query_set = request.user.keys.values('key')
api_key = list(values_query_set)[0]['key']
return HttpResponse(api_key)
def create(self, request):
#Create a new API Key.
# Check if API key already exists
if request.user.keys.count() > 0:
values_query_set = request.user.keys.values('key')
api_key = list(values_query_set)[0]['key']
return HttpResponse(api_key)
else:
# Create API key
api_key = ApiKey(user=request.user)
api_key.save()
return HttpResponse(api_key)
According to django-piston doc methodreadis called on GET and methodcreateis called on POST. Thus, when client want to create new API key; client need to request HTTP POST to create API key forrequest.userif the API key is not already exists.
Finally in models.py I need to edit the ApiKey model to
class ApiKey(models.Model):
user = models.ForeignKey(User, related_name='keys', unique=True)
key = models.CharField(max_length=KEY_SIZE, null=True, blank=True)
def save(self, *args, **kwargs):
self.key = User.objects.make_random_password(length=KEY_SIZE)
while ApiKey.objects.filter(key__exact=self.key).count():
self.key = User.objects.make_random_password(length=KEY_SIZE)
super(ApiKey, self).save(*args, **kwargs)
def __unicode__(self):
return self.key
We need to call the "real" save() method
super(ApiKey, self).save(*args, **kwargs)
and APIKeyAuthenticatin is work now.
Last but not least, when authenticating user, client need to request the HTTP request with HEADER ('Authorization', api_key).api_key must match with therequest.user.