django test - how to get response data for future use - django

I'm running a login test like so:
def test_login_user(self):
client = APIClient()
url = reverse('rest_login')
data = {
'username': 'test',
'password': 'Welcome2'
}
response = self.client.post(url, data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
client.logout()
If I login to the app normally I see a json return like this:
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6ImV2YW4iLCJleHAiOjE1MTQ2NzYzNTYsImVtYWlsIjoiZXZhbkAyOGJlYXR0eS5jb20iLCJvcmlnX2lhdCI6MTUxNDY3Mjc1Nn0.8CfhfgtMLkNjEaWBfNXbUWXQMZG4_LIru_y4pdLlmeI",
"user": {
"pk": 2,
"username": "test",
"email": "test#test.com",
"first_name": "",
"last_name": ""
}
}
I want to be able to grab that token value for future use however the response does not seem to have a data value to grab.

What I'm looking for is response.content per the official documentation
https://docs.djangoproject.com/en/2.0/topics/testing/tools/#testing-responses

You can use response.json()['token'] to get data from the token field.
Usage as below:
token = response.json()['token'];

show error response:
response.context["form"].errors

Related

Fastapi Testclient not able to send POST request using form-data

Currently I am doing Unit Testing in Fastapi using from fastapi.testclient import TestClient
def test_login_api_returns_token(session,client):
form_data = {
"username": "mike#gmail.com",
"password": "mike"
}
response = client.post(
"/api/login",
data=form_data,
headers={"content-type": "multipart/form-data"}
# headers={"content-type": "application/x-www-form-urlencoded"}
)
result = response.json()
assert response.status_code == 200
I am supposed to get token as response which I am getting when I run the fastapi application but not able to proceed with Unit Testing with the same.
Example of postman request for the same
How do I make sure form-data is being sent from TestClient?
api/login.py
#router.post("/login")
async def user_login(form_data: OAuth2PasswordRequestForm = Depends(), session: Session = Depends(get_session)):
user = authenticated_user(form_data.username, form_data.password, session)
user = user[0]
if not user:
raise token_exception()
token_expires = timedelta(minutes=120)
token = create_access_token(username=user.username, user_id=user.id, expires_delta=token_expires)
token_exp = jwt.decode(token, SECRET, algorithms=[ALGORITHM])
return {
"status_code": status.HTTP_200_OK,
"data":{
"username": user.username,
"token": token,
"expiry": token_exp['exp'],
"user_id": user.id
}
}
Try set the header to Content-Type form-data like
def test_login_api_returns_token(session,client):
form_data = {
"username": "mike#gmail.com",
"password": "mike"
}
response = client.post(
"/api/login",
json=form_data,
headers={ 'Content-Type': 'application/x-www-form-urlencoded'}
)
result = response.json()
assert response.status_code == 200

FastAPI - return Single element in from Database

I have 2 api endpoints. This one returns a list of all my users.
#app.get("/entity/", response_model=List[schemas.Userentity])
def read_entities(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
users = crud.get_entities(db, skip=skip, limit=limit)
return users
def get_entities(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Userentity).offset(skip).limit(limit).all()
This works fine. See the data:
[
{
"id": "89f8a844-a30e-41b2-947d-6eb0fcadb1bf",
"email": "xxx#hotmail.com",
"first_name": "xxx",
"last_name": "xxx",
"username": "schoedeld",
"created_timestamp": "1633711745164"
},
{
"id": "abd6bb6b-ad80-431c-9f64-d9e651638f4c",
"email": "xxxsd#hotmail.com",
"first_name": "xxx",
"last_name": "xxx",
"username": "xxx",
"created_timestamp": "1633711733338"
},
{
"id": "ad0bd5ca-5aed-4d7f-a6ac-2620f2133685",
"email": "xxxx#hotmail.com",
"first_name": "fsdfs",
"last_name": "fsdfs",
"username": "testadmin",
"created_timestamp": "1633710812666"
}
]
Here I have an endpoint to return a single user:
I could return a List with a single element, but I would like to return a single value, so I updated my endpoint to this:
#app.get("/entity/{id}", response_model=schemas.Userentity)
def read_entity(id: str, db: Session = Depends(get_db)):
user = crud.get_entity(db, id=id)
return user
def get_entity(db: Session, id: str):
return db.query(models.Userentity).filter_by(id=id)
This results in the following error:
pydantic.error_wrappers.ValidationError: 6 validation errors for Userentity
response -> id
field required (type=value_error.missing)
response -> email
field required (type=value_error.missing)
response -> first_name
field required (type=value_error.missing)
response -> last_name
field required (type=value_error.missing)
response -> username
field required (type=value_error.missing)
response -> created_timestamp
field required (type=value_error.missing)
I am pretty new to FastAPI and do not understand why this error happens, can anyone help me and explain what I am doing wrong here?
You're never actually running your SQLAlchemy query, so you're returning the query itself:
return db.query(models.Userentity).filter_by(id=id)
Instead you should make SQLAlchemy run your query by either:
# raise an exception if not exactly one row is returned (so more than one or zero)
return db.query(models.Userentity).filter_by(id=id).one()
# Either one or zero rows - return `None` if user wasn't found
return db.query(models.Userentity).filter_by(id=id).one_or_none()
# Return None if user isn't found, otherwise return the first row
# (multiple rows isn't an error)
return db.query(models.Userentity).filter_by(id=id).first()
Next time attach set debugger breakpoint at the return call from your view function (or at least print the value), and you can see exactly what you're trying to return to FastAPI for serialization.

Return custom payload from default response

In Django 2.0 I am using the rest_auth and currently it returns a response like
{
"token": "foo_token",
"user":{
"pk": 1,
"username": "admin",
"email": "test#test.com",
"first_name": "John",
"last_name": "Doe"
}
}
I would like to change this to return something besides the default response django provides. Something like...
{
"token": "foo_token",
"pk":1,
"username": "admin",
"somefield": "Foo Funk"
}
My urls.py look like this currently
url(r'^rest-auth/registration/', include('rest_auth.registration.urls')),
url(r'^refresh-token/', refresh_jwt_token),
url(r'^api/userlist', users.user_list),
The only place I can find to possibly change the response is in library files which I am sure is not wise to change. Any help would be great.
rest_auth allows you to change the responses by specifying your own serializer implementation in your settings.py.
For example if you wanted to customize the response for JWT authentication, then you might create:
# myapp/serializers.py
class MyCustomJWTSerializer(serializers.Serializer):
token = serializers.CharField()
pk = serializers.IntegerField()
username = serializers.CharField()
...
which you can then configure in your settings.py as:
REST_AUTH_SERIALIZERS = {
'JWT_SERIALIZER': 'myapp.serializers.MyCustomJWTSerializer'
}
More info here: https://django-rest-auth.readthedocs.io/en/latest/configuration.html

How do i flatten error messages to display a single consistent error message in Django?

I am making a mobile app that uses Django Rest Framework. When one of my models fails validation, it uses one of the model.attributes as a key inside the error message, for example:
{'status_code': 400, 'name': [u'Ensure this field has no more than 32 characters.']}
{'status_code': 400, 'password': [u'Ensure this field has no more than 32 characters.']}
{'status_code': 400, 'arbitrary_field': [u'Ensure this field has no more than 32 characters.']}
This is very difficult to scale in a mobile, so I want to take the error messages and deliver a consistent 'error' key to the mobile device. For example,
{'status_code': 400, 'error': [u' Name: Ensure this field has no more than 32 characters.']}
{'status_code': 400, 'error': [u'Password: Ensure this field has no more than 32 characters.']}
{'status_code': 400, 'error': [u'Arbitrary Field: Ensure this field has no more than 32 characters.']}
In Rails, I could do this by saying:
model.errors.full_messages
But I'm not sure what the equivalent in Django is?
Thanks
You can define a property custom_full_errors in your serializer which will return the errors formatted according to your requirement. Doing serializer.custom_full_errors will give you the desired response.
We first call the serializer.errors to get the default errors dictionary. Then we iterate on this dictionary and create our desired response.
class MySerializer(serializers.ModelSerializer):
#property
def custom_full_errors(self):
"""
Returns full errors formatted as per requirements
"""
default_errors = self.errors # default errors dict
errors_messages = []
for field_name, field_errors in default_errors.items():
for field_error in field_errors:
error_message = '%s: %s'%(field_name, field_error)
errors_messages.append(error_message) # append error message to 'errors_messages'
return {'error': errors_messages}
...
For example:
# serializer.errors
{'name': [u'Ensure this field has no more than 32 characters.']}
will translate to
# serializer.custom_full_errors
{'error': [u'Name: Ensure this field has no more than 32 characters.']}
In case of multiple errors for a single field, we will get the following output on doing serializer.custom_full_errors:
# serializer.custom_full_errors
{'error': [u'Name: Error1', u'Name: Error2', u'Password: Error1', u'Password: Error2' ]}
I read this blog article, am yet to test it out
https://masnun.com/2015/11/06/django-rest-framework-custom-exception-handler.html
You create a custom error handler that concatenates the key-value pairs, after passing the exception through the default error handler method
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
if response is not None:
data = response.data
response.data = {}
errors = []
for field, value in data.items():
errors.append("{} : {}".format(field, " ".join(value)))
response.data['errors'] = errors
response.data['status'] = False
response.data['exception'] = str(exc)
return response
Then you point DRF to your custom error handler
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
),
'EXCEPTION_HANDLER': 'api.utils.custom_exception_handler'
}
In case somebody is still looking for a solution, here is a great library that helps a lot in dealing with custom error validation: https://pypi.org/project/drf-standardized-errors/, here is the GitHub repo: https://github.com/ghazi-git/drf-standardized-errors
The response will be flat and instead of this:
{
"shipping_address":
{
"non_field_errors": [ErrorDetail("We do not support shipping to the provided address.", code="unsupported")]
}
}
You'll get this:
{
"code": "unsupported",
"detail": "We do not support shipping to the provided address.",
"attr": "shipping_address.non_field_errors"
}
It deals pretty good with List Serializers, changing such response:
{
"recipients": [
{"name": [ErrorDetail("This field is required.", code="required")]},
{"email": [ErrorDetail("Enter a valid email address.", code="invalid")]},
]
}
To this:
{
"type": "validation_error",
"errors": [
{
"code": "required",
"detail": "This field is required.",
"attr": "recipients.0.name"
},
{
"code": "invalid",
"detail": "Enter a valid email address.",
"attr": "recipients.1.email"
}
]
}
Based on this flat response it's much easier to form one string message to represent all the errors if you need to.
Basically, it handles the hardest part - getting a flat response out of a complex Django structure (all the nested fields, dicts, lists, etc.)
I know it is a bit late but I made this one using backtracking in case you have a lot of embedded nested serialisers.
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
stack = [response.data]
response.data = {}
errors = {}
if response is not None:
while len(stack) > 0:
data = stack.pop()
for field, value in data.items():
if type(value) is dict:
stack.append(value)
else:
if value is not list:
errors[field] = value
continue
for val in value:
code = val.code
message = str(val)
errors[field] = {
'message': message,
'code': code
}
response.data['errors'] = errors
return response

django test for upload image

i want to test user registration but i can't test image here is my test:
test.py
response = self.client.post('/api/v1/signup/',
content_type='application/json',
data=json.dumps({"username": "casino", "email": "casinoluxwin#gmail.com",
"password1": "android12", "password2": "android12", "photo": {
'real_filename': "u'banner3.jpg'",
'path': "u'C:/Users/Dima/Desktop'"}
}))
self.assertEqual(response.status_code, 200)
i get code 400(bad request), but without photo my test pass
service/users.py
#validate_input({
'username': {'min_length': 3, 'max_length': 50},
'email': {'validation_type': "email", 'max_length': 50},
'password1': {'min_length': 8, 'max_length': 50},
'password2': {'min_length': 8, 'equal_to': 'password1',
'messages': {'equal_to': _(u"Пароли не совпадают")}},
'photo': {'required': True}
})
#transaction.atomic
def signup(self, data):
user_data = {
'username': data['username'],
'email': data['email'],
'password': data['password1'],
'coins_amount': 0
}
user = self._create_user(user_data)
if data.get("photo"):
self._attach_photo(user, data["photo"])
obj, created = VerificationCode.objects.get_or_create(user=user, code_type="registration")
obj.create_expiration_date()
obj.create_code()
obj.save()
return user.id
So i want to test user photo anything else works fine. Thanks for any help
Problem probably resides in either Users._attach_photo() or your user model. Not enough information here to decipher it entirely. There are a couple of things to try.
I'd write a normal unittest that does not use the client. It'll give you a more helpful traceback than just the HTTP status code from the running server. Something like:
def test_user_add_method(self):
x = Users.signup(json.dumps({"username": "casino", "email": "casinoluxwin#gmail.com",
"password1": "android12", "password2": "android12", "photo": {
'real_filename': "u'banner3.jpg'",
'path': "u'C:/Users/Dima/Desktop'"})
Users.get(pk=x) #will fail if user was not created.
Second, try commenting out your validator. 400 bad request could easily be kicked off by that. It is possible that your validator isn't playing nice with the image and you'll have to mess around with that.