DRF: Djoser override custom user serializer - python

Previously I had been working with the back-end, but after few months of not coding, I'm now not really comfortable with the code I have now. So I use Djoser and I have this in my project settings.py file
...
'SERIALIZERS': {
'user_create': 'backend.accounts.serializers.UserCreateSerializer',
'user': 'backend.accounts.serializers.UserCreateSerializer',
'user_delete': 'djoser.serializers.UserDeleteSerializer',
}
...
This should override the default Djoser serializer, but as I can see it does nothing.
/auth/users/me/ - Returns this
{
"email": "test#gmail.com",
"id": 1,
"username": "test"
}
but I have this as my serializer inside backend/accounts/serializers
class UserCreateSerializer(UserCreateSerializer):
delete_request = DeleteUserSerializer(read_only=True)
class Meta(UserCreateSerializer.Meta):
model = User
fields = (
'id',
'email',
'username',
'password',
'delete_request',
)
Why does it not override?

/users/me/ endpoint is handled by current_user key in djoser serializers dict, so you should add it:
'SERIALIZERS': {
# [...]
'current_user': 'backend.accounts.serializers.UserSerializer',
# [...]
}
You can have more information about djoser serializers on the documentation.
They say:
Key 'user' is used for general users whereas 'current_user' lets you set serializer for special /users/me endpoint. They both default to the same serializer though.
And by the way I think you should create a serializer inheriting from djoser.serializers.UserSerializer and not from djoser.serializers.UserCreateSerializer for user and cuttent_user keys.

Related

How to include Oauth2 auth urls in Swagger?

I use Django drf-spectacular OAuth Toolkit for an Oauth2 password flow. Unfortunately, Swagger doesn't recognize the auth URLs.
This is my urls.py
urlpatterns = [
# schema
path("api/schema/", SpectacularAPIView.as_view(api_version='v1'), name="schema"),
path(
"api/schema/swagger/",
SpectacularSwaggerView.as_view(url_name="schema"),
name="swagger-ui",
),
path(
"api/schema/redoc/",
SpectacularRedocView.as_view(url_name="schema"),
name="redoc",
),
path("api/oauth/", include("apps.main.oauth.urls", namespace="oauth2_provider")),
]
How can I fix thit?
To make it available to swagger you have to override the Oauth API, for example, override the token API and and write an inline serializer in the #extend_schema and pass the post method.
from drf_spectacular.utils import extend_schema, inline_serializer
from oauth2_provider.views.application import TokenView
class TokenApiView(TokenView, APIView):
#extend_schema(
request=inline_serializer(
name="InlineTokenSerializer",
fields={
"username": serializers.CharField(),
"password": serializers.CharField(),
"grant_type": serializers.CharField(required=False),
"Scope": serializers.CharField(required=False),
"client_id": serializers.CharField(),
},
)
)
def post(self, request, *args, **kwargs):
return super().post(request, *args, **kwargs)
The oauth toolkit does provides regular html views, which are not DRF views. Therefore they do not appear in the schema because spectacular can only parse any of the DRF-type views.
What you need to do is add some settings that direct SwaggerUI to those auth views:
SPECTACULAR_SETTINGS = {
# Oauth2 related settings. used for example by django-oauth2-toolkit.
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#oauth-flows-object
'OAUTH2_FLOWS': [],
'OAUTH2_AUTHORIZATION_URL': None,
'OAUTH2_TOKEN_URL': None,
'OAUTH2_REFRESH_URL': None,
'OAUTH2_SCOPES': None,
# other spectcular settings
}
So 3 steps are basically required to make this fully functional:
Add the views to urlpatterns for oauth2_provider (as you did)
Make sure the views have OAuth2Authentication and the corresponding permission_classes (directly or via DRF default setting)
Add the settings as outlined above. You may not need all of them depending on the supported flows you have to set at least some URLs there.

DRF: request.user returns nothing for token based authentication

I am basically new to DRF & trying to make one API endpoint to update user via mobile app,here is my code for that,
class MobileAppUserUpdateView(APIView):
def post(self, request, format=None):
user = request.user
print('USER',user) #<- prints nothing
print('TOKEN',request.auth) #<- prints the token
return ResponseBuilder.success([constants.RECORD_UPDATED])
there I want to access the user who has made that request using the auth token but getting nothing there.Here is my settings for DRF,
NON_FIELD_ERRORS_KEY = 'message'
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
# 'rest_framework.authentication.TokenAuthentication',
],
'DEFAULT_AUTHENTICATION_CLASSES': (
# 'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.TokenAuthentication',
),
'NON_FIELD_ERRORS_KEY': NON_FIELD_ERRORS_KEY,
}
I have also added the "rest_framework.authtoken" in the INSTALLED_APPS list as per the documentation.Any help would be appreciated...
Just few things more..
You need to add authentication_classes in MobileAppUserUpdateView which includes TokenAuthentication, like this:
class MobileAppUserUpdateView(APIView):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request, format=None):
...
Then make sure you have added rest_framework.authtoken in your INSTALLED_APPS settings:
INSTALLED_APPS = [
...
'rest_framework.authtoken'
]
Then run manage.py migrate after changing your settings.
Use a debugger to debug the code and check what your request object is receiving.
You can follow this doc for more.
The problem was in the users table, User model returns the email for str method & the email field had null value that's why I was getting nothing when I try to print it.

Django REST Framework Web Login not working

I can't seem to figure out why my rest framework login in my django project won't work... I have got the log in link there and all and it does redirect me to the rest login page but after filling in my credentials and clicking login it just takes me back to the page I came from but it does not seem to authenticate me as it still says "Login" in the top right corner...
I would expect it to change to "admin" in this case as I am trying to login using my superuser named "admin"...
I am surely missing something..
Here are my REST Urls
path("rest/auctions", auction_list, name="auction-list"),
path('rest/auctions/<int:pk>', auction_detail, name="auction-detail"),
path('rest-auth/', include('rest_framework.urls', namespace='rest_framework')),
The rest-auth/ one I know is the one that somehow adds the login button and all (Magic?) But maybe I have to do something else too to make it authenticate? I don't know... I am using the built in django authentication system and User model.
views.py
#api_view(['GET'])
def auction_list(request, format=None):
if request.method == 'GET':
query = request.GET.get('q')
if query is not None:
auctions = Auction.objects.filter(Q(title__icontains=query), deadline__gte=timezone.now(),
status=Auction.STATUS_ACTIVE)
else:
auctions = Auction.objects.all()
serializer = AuctionSerializer(auctions, many=True, context={'request': request})
return Response(serializer.data)
serializer.py
class AuctionSerializer(serializers.ModelSerializer):
winning_bid = serializers.SlugRelatedField(many=False, read_only=True, slug_field='amount', allow_null=True)
seller = serializers.SlugRelatedField(many=False, read_only=True, slug_field='username', allow_null=False)
class Meta:
model = Auction
fields = ('id', 'url', 'title', 'description', 'deadline', 'min_price', 'winning_bid', 'seller')
in my Settings.py
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly'
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
],
}
Lastly I can mention that the authentication works when I use the API via Postman.
If the other solution doesn't work, add this line too
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
),
You should remove BasicAuthentication from the settings.py and it should work as expected. BasicAuthentication works by base64 encoding the user login information and attaching them to HTTP Authorization Header. That's why your requests via Postman work normally.
By default Django uses Session based authentication, which depends on cookies on the client to store the user session information once the user is logged in. Obviously username and password are not stored in cookie with BasicAuthentication.
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly'
],
}

Django REST Framework's APIClient sends None as 'None'

In my tests, I send mock data of models that I've passed through the serializer. The serializer.data looks something like this
{
"field": None
}
However, the data that my API receives is formatted like
{
"field": "None"
}
which is a problem because I'm trying to specify a foreign key that is allowed to be null. Shouldn't the APIClient convert None into null instead of unicode?
Is there any way to fix this or get around it?
Here's my serializer
class MyModelSerializer(serializers.ModelSerializer):
field = serializers.PrimaryKeyRelatedField(
queryset=OtherModel.objects.all(), required=False, allow_null=True)
And my create method in a viewset
def create(self, request):
model = MyModel()
serializer = MyModelSerializer(model, data=request.data)
if serializer.is_valid():
serializer.save(owner=request.user)
return Response(serializer.data, status=201)
return Response(serializer.errors, status=406)
Also my model class
class MyModel(models.Model):
field= models.OneToOneField(
OtherModel, blank=True, null=True)
In addition to what Kevin already said, you can force the APIClient to send JSON using the parameter format='json'.
See the documentation.
The problem here is that the APIClient is sending data to the view as form-data by default, which doesn't have a concept of None or null, so it is converted to the unicode string None.
The good news is that Django REST framework will coerce a blank string to None for relational fields for this very reason. Alternatively, you can use JSON and actually send None or null, which should work without issues.
In addition to existing answers,
if you are expecting a null, this probably means you expect your api to receive json.
If that's the case, you may want to configure the test request default format to json instead of form-data:
In your setting.py:
REST_FRAMEWORK = {
...
'TEST_REQUEST_DEFAULT_FORMAT': 'json'
}
This way, no need to add format='json' to each request

Swagger API documentation

I saw swagger documentation of Flask and Django. In Flask I can design and document my API hand-written.(Include which fields are required, optional etc. under parameters sections).
Here's how we do in Flask
class Todo(Resource):
"Describing elephants"
#swagger.operation(
notes='some really good notes',
responseClass=ModelClass.__name__,
nickname='upload',
parameters=[
{
"name": "body",
"description": "blueprint object that needs to be added. YAML.",
"required": True,
"allowMultiple": False,
"dataType": ModelClass2.__name__,
"paramType": "body"
}
],
responseMessages=[
{
"code": 201,
"message": "Created. The URL of the created blueprint should be in the Location header"
},
{
"code": 405,
"message": "Invalid input"
}
]
)
I can chose which parameters to include, and which not. But how do I implement the same in Django? Django-Swagger Document in
not good at all. My main issue is how do I write my raw-json in Django.
In Django it automates it which does not allows me to customize my json. How do I implement the same kind of thing on Django?
Here is models.py file
class Controller(models.Model):
id = models.IntegerField(primary_key = True)
name = models.CharField(max_length = 255, unique = True)
ip = models.CharField(max_length = 255, unique = True)
installation_id = models.ForeignKey('Installation')
serializers.py
class ActionSerializer(serializers.ModelSerializer):
class Meta:
model = Controller
fields = ('installation',)
urls.py
from django.conf.urls import patterns, url
from rest_framework.urlpatterns import format_suffix_patterns
from modules.actions import views as views
urlpatterns = patterns('',
url(r'(?P<installation>[0-9]+)', views.ApiActions.as_view()),
)
views.py
class ApiActions(APIView):
"""
Returns controllers List
"""
model = Controller
serializer_class = ActionSerializer
def get(self, request, installation,format=None):
controllers = Controller.objects.get(installation_id = installation)
serializer = ActionSerializer(controllers)
return Response(serializer.data)
My questions are
1) If I need to add a field say xyz, which is not in my models how do I add it?
2) Quiet similar to 1st, If i need to add a field which accepts values b/w 3 provided values,ie a dropdown. how do I add it?
3) How I add an optional field? (since in case of PUT request, I might only update 1 field and rest leave it blank, which means optional field).
4) Also how do I add a field that accepts the json string, as this api does?
Thanks
I can do all of these things in Flask by hardcoding my api. But in Django, it automates from my models, which does not(as I believe) gives me the access to customize my api. In Flask, I just need to write my API with hands and then integrate with the Swagger. Does this same thing exist in Django?
Like I just need to add the following json in my Flask code and it will answer all my questions.
# Swagger json:
"models": {
"TodoItemWithArgs": {
"description": "A description...",
"id": "TodoItem",
"properties": {
"arg1": { # I can add any number of arguments I want as per my requirements.
"type": "string"
},
"arg2": {
"type": "string"
},
"arg3": {
"default": "123",
"type": "string"
}
},
"required": [
"arg1",
"arg2" # arg3 is not mentioned and hence 'opional'
]
},
Would this work:
class TriggerView(APIView):
"""
This text is the description for this API
mykey -- My Key parameter
"""
authentication_classes = (BasicAuthentication,)
permission_classes = (IsAuthenticated,)
def post(self, request, format=None):
print request.DATA
return Response(status=status.HTTP_202_ACCEPTED)
the POST request:
Authorization:Basic YWRtaW46cGFzcw==
Content-Type:application/json
{"mykey": "myvalue"}

Categories