3

I'm using Django 2.x and Django REST framework.

I'm using django-oauth-toolkit to enable OAuth2 authentication and django-rest-auth for login and django-allauth for user registration.

I want to generate access token in the response when a user is successfully registered. For that, I'm using a custom registration view.

For that, I have created a function utils like

def generate_token(request, user):
    # Get OAuth application to use
    application_: Application = Application.objects.filter(
        client_type=Application.CLIENT_CONFIDENTIAL,
        authorization_grant_type=Application.GRANT_PASSWORD
    ).first()

    if not application_:
        raise Exception('No OAuth2 Application is setup')

    auth_data = {
        'username': user.username,
        'password': password,
        'grant_type': 'password',
        'client_id': application_.client_id,
        'client_secret': application_.client_secret
    }

    if request.data:

        mutable = request.data._mutable
        request.data._mutable = True
        request.data.update(auth_data)
        request.data._mutable = mutable

    if request.POST:
        mutable = request.POST._mutable
        request.POST._mutable = True
        request.POST.update(auth_data)
        request.POST._mutable = mutable

    return TokenView().create_token_response(request=request)

When the endpoint is hit by Postman it works fine and request.data has _mutable attribute.

But when it is hit by Angular application, it gives error

'dict' object has no attribute '_mutable'

and the error points to mutable = request.data._mutable

Why _mutable is missing for some requests?

Edit 2: Request headers

The request header sent by Postman is

Content-Type:"application/json"
Accept:"application/json, text/plain, /"
User-Agent:"PostmanRuntime/7.15.2"
Cache-Control:"no-cache"
Postman-Token:"b4461728-a6a9-48da-a4fa-3894920b5484"
Host:"api.staging.scanova.io"
Cookie:"messages="660481c3ce8c48b1884641ffdec0a3f701cdc9cf$..."; csrftoken=tNa6o6RDkEUTBti1cDJCvgV5oLG84qczgADeDfSY7hROsLP6cmhIgQKaamPQU7Xy; sessionid=r0l8kh58n8i14quyemj60ur6i59uhy1i"
Accept-Encoding:"gzip, deflate"
Content-Length:635
Connection:"keep-alive"

The request header from Angular is

Accept: application/json, text/plain, */*
Authorization: false false
Content-Type: application/json
Origin: https://localhost:4200
Referer: https://localhost:4200/auth/register
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36

Edit 3: Request type

The main view which is called from the endpoint is CreateAPIView. generate_token is called from this view which uses TokenView to generate an access token. The TokenView use Django's generic View.

Anuj TBE
  • 9,198
  • 27
  • 136
  • 285
  • What HTTP type request do you execute in angular? GET, POST etc.? – cagrias Jul 31 '19 at 07:08
  • its `POST`. Updated question with the request headers in both scenario. – Anuj TBE Jul 31 '19 at 07:12
  • What is the type of your ```request``` object? Django's ```HttpRequest``` or DRF's ```Request```? – cagrias Jul 31 '19 at 07:16
  • Since the request is going through REST framework, its DRF's `Request`. If it would be `HttpRequest` then `request.POST` will work instead of `request.data` – Anuj TBE Jul 31 '19 at 07:22
  • I mean, what type is your ```TokenView```? I'm guessing that you need to switch your if statements and edit ```if request.data``` as ```elif request.data``` because the request you are getting might be type of django's ```HttpRequest```. – cagrias Jul 31 '19 at 07:24
  • But same code is working from **Postman** then why not from **Angular**? Is there something missing in the request? – Anuj TBE Jul 31 '19 at 07:35
  • The only difference I can see is your ```content-type```. Can you modify your angular request so that it has ```content-type``` header exactly as ```application/json``` only? – cagrias Jul 31 '19 at 07:38
  • No, even `content-type` is same in both set to `application/json`. – Anuj TBE Jul 31 '19 at 07:40
  • @AnujTBE Would you print `request.META.get('CONTENT_TYPE')` on the django function and test from both postman and Angular? – Abdul Niyas P M Jul 31 '19 at 07:41
  • The Postman content type is `multipart/form-data` while the Angular's is `application/json`. It seems this is the main issue. But Postman's console is saying the Content-Type header is `application/json` – Anuj TBE Jul 31 '19 at 07:58

2 Answers2

6

For posterity (even though the question is rather old):

It seems like django/DRF treats multipart/form-data and application/json content type in a different enough way to cause issues.

So, even when using the same endpoint(viewset) and depending on whether the form is sent as multipart or app/json - request.data will be a different object.

In one case it would be a 'normal' dict while in other it would be of QueryDict type. So, in order to use the hacky _mutable on/off an additional check similar to this is needed:

...
# one can also use:
# if 'multipart/form-data' in request.META.get('CONTENT_TYPE'):
# instead of the condition below
if isinstance(request.data, QueryDict):
  auth_data = json.dumps(auth_data) # <----- QueryDict expects string values

request.POST._mutable = True # <----- mutable needs to be modified on POST and not on data
request.data.update(auth_data)
request.POST._mutable = False
...
draganstankovic
  • 5,382
  • 1
  • 27
  • 33
1

I used (request.POST._mutable = True) and this way does not work for me. then I copied request.POST in a new variable and I use the new variable in whole my code and this way work fine