I'm still having this error when trying to do a post request from postman.
{
"username": [
"This field is required."
],
"password": [
"This field is required."
]
}
I can make the same post request successfully from my DRF localhost, but when i try on postman i get the error above.
How can I solve it?
Views.py
class PlayThingList(viewsets.ModelViewSet):
serializer_class = PlayThingSerializer
queryset = PlayThing.objects.all()
class UserViewset(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
Serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'password']
extra_kwargs = {'password': {
'write_only':True,
'required':True
}}
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
Token.objects.create(user)
return user
urls.py
router = DefaultRouter()
router.register('playthings', PlayThingList, basename='playthings')
router.register('users', UserViewset)
urlpatterns = [
path('playmates/', include(router.urls)),
]
Project urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework.authtoken.views import obtain_auth_token
app_name = 'playthings'
urlpatterns = [
path('admin/', admin.site.urls),
path("", include('playthings.urls')),
path('auth/', obtain_auth_token)
]
UPDATE
I made some changes based on the error messages and guides in the comments and I can now create users.
Problem is, after sending the user credentials in the form, i get this error in postman.
Try adding the following in your POSTMAN
Headers section:
KEY
Value
Accept
application/json
Body section (choose raw or x-www-form-urlencoded):
KEY
Value
username
(your username)
password
(your password)
In your userviewset, you are using UserSerializer. This way you can not create users. To create a user you will have to extend registeruser functionality.
Check out the code from rest-framework and use the same logic in your create method of userviewset. if you want to register a user.
Registeruser is all together a different thing.
Rest framework by default has a url to register users, use that url, it will handle everything for you.
Problem Solved!
The issue was with the token create() method. I changed
create(user) to create(user=user)
ref: serializers.py
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
Token.objects.create(user=user)
return user
Thank you to everyone that helped!
Related
I'm building a REST API with Django Rest Framework and Django Rest Auth.
My users have a consumer profile.
class UserConsumerProfile(
SoftDeletableModel,
TimeStampedModel,
UniversallyUniqueIdentifiable,
Userable,
models.Model
):
def __str__(self):
return f'{self.user.email} ({str(self.uuid)})'
As you can see it consists of some mixins that give it a UUID, a timestamp and an updated field and a OneToOne relationship to the user. I use this consumerprofile in relations to link data to the user.
This consumerprofile should get created as soon as a user signs up.
Here is the serializer that I wrote for the registration:
from profiles.models import UserConsumerProfile
from rest_auth.registration.serializers import RegisterSerializer
class CustomRegisterSerializer(RegisterSerializer):
def custom_signup(self, request, user):
profile = UserConsumerProfile.objects.create(user=user)
profile.save()
I connected this serializer in the settings:
REST_AUTH_REGISTER_SERIALIZERS = {
"REGISTER_SERIALIZER": "accounts.api.serializers.CustomRegisterSerializer"
}
It works flawlessly when the users signs up using his email. But when he signs up using facebook, no consumer profile gets created.
I thought the social view would also use the register serializer when creating users? How can I run custom logic after a social sign up?
EDIT for the bounty:
Here are the settings that I use for Django Rest Auth:
# django-allauth configuration
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_CONFIRM_EMAIL_ON_GET = True
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 1
ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
ACCOUNT_ADAPTER = 'accounts.adapter.CustomAccountAdapter'
SOCIALACCOUNT_ADAPTER = 'accounts.adapter.CustomSocialAccountAdapter'
SOCIALACCOUNT_PROVIDERS = {
'facebook': {
'METHOD': 'oauth2',
'SCOPE': ['email', 'public_profile', 'user_friends'],
'AUTH_PARAMS': {'auth_type': 'reauthenticate'},
'INIT_PARAMS': {'cookie': True},
'FIELDS': [
'id',
'email',
'name',
'first_name',
'last_name',
'verified',
'locale',
'timezone',
'link',
'gender',
'updated_time',
],
'EXCHANGE_TOKEN': True,
'LOCALE_FUNC': 'path.to.callable',
'VERIFIED_EMAIL': True,
'VERSION': 'v2.12',
}
}
# django-rest-auth configuration
REST_SESSION_LOGIN = False
OLD_PASSWORD_FIELD_ENABLED = True
REST_AUTH_SERIALIZERS = {
"TOKEN_SERIALIZER": "accounts.api.serializers.TokenSerializer",
"USER_DETAILS_SERIALIZER": "accounts.api.serializers.UserDetailSerializer",
}
REST_AUTH_REGISTER_SERIALIZERS = {
"REGISTER_SERIALIZER": "accounts.api.serializers.CustomRegisterSerializer"
}
And here are the custom adapters (in case they matter):
from allauth.account.adapter import DefaultAccountAdapter
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from allauth.utils import build_absolute_uri
from django.http import HttpResponseRedirect
from django.urls import reverse
class CustomAccountAdapter(DefaultAccountAdapter):
def get_email_confirmation_url(self, request, emailconfirmation):
"""Constructs the email confirmation (activation) url."""
url = reverse(
"accounts:account_confirm_email",
args=[emailconfirmation.key]
)
ret = build_absolute_uri(
request,
url
)
return ret
def get_email_confirmation_redirect_url(self, request):
"""
The URL to return to after successful e-mail confirmation.
"""
url = reverse(
"accounts:email_activation_done"
)
ret = build_absolute_uri(
request,
url
)
return ret
def respond_email_verification_sent(self, request, user):
return HttpResponseRedirect(
reverse('accounts:account_email_verification_sent')
)
class CustomSocialAccountAdapter(DefaultSocialAccountAdapter):
def get_connect_redirect_url(self, request, socialaccount):
"""
Returns the default URL to redirect to after successfully
connecting a social account.
"""
assert request.user.is_authenticated
url = reverse('accounts:socialaccount_connections')
return url
Lastly, here are the views:
from allauth.socialaccount.providers.facebook.views import \
FacebookOAuth2Adapter
from rest_auth.registration.views import SocialConnectView, SocialLoginView
class FacebookLogin(SocialLoginView):
adapter_class = FacebookOAuth2Adapter
class FacebookConnect(SocialConnectView):
adapter_class = FacebookOAuth2Adapter
I thought that if I connected the serializer like I did in the initial part of the question the register serializer logic would also get run when someone signs up using facebook.
What do I need to do to have that logic also run when someone signs up using facebook?
(If I can't fix it, I could make a second server request after each facebook sign up on the client side which creates the userconsumerprofile, but that would be kinda overkill and would introduce new code surface which leads to a higher likelihood of bugs.)
Looking briefly at the DefaultAccountAdapter and DefaultSocialAccountAdapter it may be an opportunity for you to override/implement the save_user(..) in your CustomAccountAdapter/CustomSocialAccountAdapter to setup the profile?
Looking just at code it seems that the DefaultSocialAccountAdapter.save_user will finally call the DefaultAccountAdapter.save_user.
Something like this maybe?
class CustomAccountAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=True):
user = super(CustomAccountAdapter, self).save_user(request, user, form,
commit)
UserConsumerProfile.objects.get_or_create(user=user)
return user
There are a few other "hooks"/functions in the adapters that may we worth to investigate if the save_user doesn't work for your scenario.
The REGISTER_SERIALIZER that you created is only used by the RegisterView.
The social login & connect views use different serializers: SocialLoginSerializer and SocialConnectSerializer, that cannot be overwritten per settings.
I can think of two ways to achieve your desired behavior:
create serializers for the social login & connect views (inherriting the default serializers) and set them as serializer_class for the view,
use Django signals, especially the post_save signal for the User model and when an instance is created, create your UserConsumerProfile.
I've created rest APIs using Django-rest-auth, in login, it's returning key and some user info, But I need to add some status like success and message and some other things. Is there any way to override view of django-rest-auth for login?
class TokenSerializer(serializers.ModelSerializer):
user = UserSerializer(many=False, read_only=True) # this is add by myself.
device = DeviceSerializer(many=True, read_only=True)
class Meta:
model = TokenModel
fields = ('key', 'user', 'device',)
Create a custom view class and use it
from rest_auth.views import LoginView
class CustomLoginView(LoginView):
def get_response(self):
orginal_response = super().get_response()
mydata = {"message": "some message", "status": "success"}
orginal_response.data.update(mydata)
return orginal_response
and change your urls.py as
urlpatterns = [
url(r'custom/login/', CustomLoginView.as_view(), name='my_custom_login')
]
now you should use the endpoint /custom/login/ instead of /rest-auth/login
I have a ViewSet:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
And appropriate urls:
from .users.api.views import UserViewSet
router = routers.DefaultRouter()
router.register('users', UserViewSet, 'user')
urlpatterns = [
url(r'^v1/', include(router.urls)),
]
It works, but I want to add username-password authentification to UserViewSet:
#list_route(methods=['post'], permission_classes=[AllowAny])
def login(self, request):
#check login and password
#creare and return token
Of cource I can write It by my-self, but I interest, how I can use rest_framework.authtoken.views.ObtainAuthToken for my goals.
Per the documentation, you can expose an API endpoint that takes a username/password and returns a token using rest_framework.authtoken.view.obtain_auth_token. See the rest framework Docs for more details. You urls.py would look like this:
from .users.api.views import UserViewSet
from rest_framework.authtoken import views
router = routers.DefaultRouter()
router.register('users', UserViewSet, 'user')
urlpatterns = [
url(r'^v1/', include(router.urls)),
url(r'^v1/login, views.obtain_auth_token)
]
If you really want this url to belong to the UserViewSet that you've already defined, you will need to define a detail_route and manually call authenticate and then generate a token for the authenticated user (if authenticate succeeds). I recommend using the first pattern I described as it's less code/customization.
I use httpie to test my api,when I text
localhost:8000/users/
it show the user list,then i text
localhost:8000/users/jack/
it still show the user list,not the user detail,it's something wrong with my code?
url.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^', include('rest_framework.urls',namespace='rest_framework')),
url(r'regist/', Regist.as_view()),
url(r'users/', UserList.as_view()),
url(r'users/(?P<username>[a-zA-Z0-9]+)/$', UserDetail.as_view()),
]
views.py
class UserDetail(generics.ListAPIView):
serializer_class= UserSeriallizer
def get_queryset(self):
username = self.kwargs['username']
user=User.objects.filter(username=username)
return user
class UserList(APIView):
def get(self, request):
users = User.objects.all()
serializer = UserSeriallizer(users, many=True)
return Response(serializer.data)
Problem in your urls, you need to close r'users/$, because Django can't go further users/ without $
And why you use ListAPIView for retrieving single object?
You need RetrieveAPIView or RetrieveUpdateAPIView if you want change the data. And change your view like so:
class UserDetail(RetrieveAPIView):
lookup_field = 'username'
queryset = User.objects.all()
You don't need get_queryset at all
About mixins
I try to create a view, that will accept POST requests and create new instances of my model(see bottom of post). I follow this tutorial. The problem is that when I access URL associated with view, which inherits from CreateAPIView I dont see a form in html representation of API for creation of new instances and I also see that it accepts GET requests, not POST as it mentioned in documentation.
Page looks like this
My views.py
from django.shortcuts import render
from rest_framework.generics import ListAPIView, CreateAPIView
from datingapp.models import Profile
from .serializers import ProfileSerializer, ProfileCreateSerializer
class ProfilesAPIView(ListAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
class ProfileCreateAPIView(CreateAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileCreateSerializer
My urls.py
from django.conf.urls import url
from django.contrib import admin
from datingapp.views import ProfilesAPIView, ProfileCreateAPIView
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'api/profiles/', ProfilesAPIView.as_view(), name='list'),
url(r'api/profiles/create/$', ProfileCreateAPIView.as_view(), name='create')
]
My serializers.py
from rest_framework.serializers import ModelSerializer
from datingapp.models import Profile
class ProfileSerializer(ModelSerializer):
class Meta:
model = Profile
fields = [
'name',
'age',
'heigth'
'location',
]
class ProfileCreateSerializer(ModelSerializer):
class Meta:
model = Profile
fields = [
'name',
'age',
'heigth'
'location',
]
In my settings.py I have crispy_forms installed.
What am I doing wrong ?
UPD: here is what I want to achieve
As you see there is a form and it accepts only POST and also says that GET is not allowed
The problem is in your router. The first pattern matches both api/profiles/ and api/profiles/create/ so the second one will never be evaluated. You are seeing the ProfilesAPIView instead of the create view.
url(r'api/profiles/', ProfilesAPIView.as_view(), name='list'),
url(r'api/profiles/create/$', ProfileCreateAPIView.as_view(), name='create')
To fix it, either swap the order of the urls, or add a $ to the end of the first pattern. r'api/profiles/$'
I was following a tutorial and had a similar problem. Probably I was not following the same version of Django Rest Framework and they had changes.
But I solved this problem doing this.
class AssetBundleList(generics.ListAPIView):
to
class AssetBundleList(generics.ListCreateAPIView):
Hope this helps someone.