I was wondering if anyone knows why is it that DRF doesn't capitalise the first letter of the user object in the error message and is there a simple way to correct this?
Error message for create user:
{
"password": [
"This field is required."
],
"email": [
"user with this email already exists."
]
}
models.py
class User(AbstractBaseUser, PermissionsMixin):
created = models.DateTimeField(auto_now_add=True)
email = models.EmailField(max_length=255, unique=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
def get_full_name(self):
return self.email
def get_short_name(self):
return self.email
def __str__(self):
return self.email
serializers.py
class RegisterUserSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
fields = ('id', 'password', 'email')
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = models.User(email=validated_data['email'])
user.set_password(validated_data['password'])
user.save()
return user
views.py
class UserListView(generics.ListCreateAPIView):
queryset = User.objects.all()
def create(self, request, *args, **kwargs):
serializer = RegisterUserSerializer(data=request.data)
if serializer.is_valid():
self.perform_create(serializer)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
The easiest way to do that is
class User(AbstractBaseUser, PermissionsMixin):
created = models.DateTimeField(auto_now_add=True)
email = models.EmailField(max_length=255, unique=True,error_messages={
'unique': "User with this email already exists.",
},)
...
To answer your question:
why is it that DRF doesn't capitalise the first letter of the user object
If not explicitly provided , DRF constructs the unique error message like this:
unique_error_message = unique_error_message % {
'model_name': model_field.model._meta.verbose_name,
'field_label': model_field.verbose_name
}
And for the django's user model, verbose name is:
>>> from django.contrib.auth.models import User
>>> User.Meta.verbose_name
'user'
Related
I'm building a login and signup api with django rest frame work.
I created a custom user, and I'm using django-rest-knox library for authentication.
I'm getting error "AuthToken.user" must be a "User" instance.
Custom User Definition
class User(AbstractBaseUser):
email = models.EmailField(verbose_name='email_address', max_length=255, unique=True,)
full_name = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
staff = models.BooleanField(default=False)
admin = models.BooleanField(default=False)
phone_number = PhoneNumberField(region='NG', blank=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['full_name', 'phone_number'] # Email & Password are required by default.
def get_full_name(self):
# The user is identified by their email address
return self.full_name
def get_short_name(self):
# The user is identified by their email address
return self.email
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
"Does the user have a specific permission?"
# Simplest possible answer: Yes, always
return True
def has_module_perms(self, app_label):
"Does the user have permissions to view the app `app_label`?"
# Simplest possible answer: Yes, always
return True
#property
def is_staff(self):
"Is the user a member of staff?"
return self.staff
#property
def is_admin(self):
"Is the user a admin member?"
return self.admin
objects = UserManager()
Register Serializer
class RegisterSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'full_name', 'email', 'phone_number', 'password')
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = User.objects.create_user(validated_data['email'], validated_data['full_name'], validated_data['phone_number'], validated_data['password'])
return user
Register API
class RegisterAPI(generics.GenericAPIView):
serializer_class = RegisterSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
token = AuthToken.objects.create(user)[1]
return Response({
"User": UserSerializer(user, context=self.get_serializer_context()).data,
"Token": token
})
I am creating a login method using Django-rest-Knox. I have created a custom user in app using AbstractBaseUser.
This is my views.py
class LoginView(GenericAPIView):
serializer_class = LoginSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data) # Load request body to serializer
serializer.is_valid(raise_exception=True) # Validate data in serializer and raise an error if one is found
user = serializer.validated_data # Get the validated data from the serializer
token = AuthToken.objects.create(user)[1] # Create an Authentication Token for the user
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data, # Get serialized User data
"token": token
})
Serializer.py
class LoginSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField()
# class Meta:
# model = User
# fields = ('username', 'password')
def Validate(self, data):
user = authenticate(**data)
if user:
return user
raise serializers.ValidationError("Incorrect Credentials")
This is my custom user model
class User(AbstractBaseUser):
_id = models.AutoField
email = models.EmailField(verbose_name='email', max_length=255, unique=True)
username = models.CharField(verbose_name='username', max_length = 100, unique=True)
name = models.CharField(max_length = 100)
date_joined = models.DateTimeField(verbose_name="date-joined", auto_now_add=True)
last_login = models.DateTimeField(verbose_name="last-login", auto_now=True)
category = models.CharField(max_length=50, default= "teacher")
is_admin = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
is_teacher = models.BooleanField(default=False)
is_parent = models.BooleanField(default=False)
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email',]
objects = UserManager()
def __str__(self):
# return self.email
return self.username
def has_perm(self, perm, obj=None):
return self.is_admin
def has_module_perms(self, app_label):
return True
I am getting an Value error when submitting login as Cannot assign "OrderedDict([('username', 'api_test'), ('password', 'test#123')])": "AuthToken.user" must be a "User" instance.
I think somewhere Django/Knox is not able to get user and user.is_active in my LoginSerializer. I have initiated my user model from initial migrations.
Any help to resolve this issue would be great. Thanks.
In this part of your code :
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data, # Get serialized User data
"token": token
})
You need to replace the token by an instance of the user on which this token belong so for example :
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data, # Get serialized User data
"token": User.objects.get(token=token)
})
I have a UserProfile model which has one to one relationship with User model.
I am facing problem in uploading image by rest-framework API endpoint to nested model.
Problem is: Though I am explicitly using #action() decorator, then also it is calling inbuilt create() method for saving image by POST.
Then I have explicitly mentioned which method to call in urls.py. Now it shows errors.
There are errors in the implementation in serializer.py and views.py.
So if possible then please correct my mistakes and direct me in the correct direction. Its more than two days but still I am struck.
In models.py:
def user_image_upload_file_path(instance, filename):
"""Generates file path for uploading user images"""
extension = filename.split('.')[-1]
file_name = f'{uuid.uuid4()}.{extension}'
date = datetime.date.today()
initial_path = f'pictures/uploads/user/{date.year}/{date.month}/{date.day}/'
full_path = os.path.join(initial_path, file_name)
return full_path
class UserManager(BaseUserManager):
def create_user(self, email, password, username, **extra_kwargs):
"""Creates and saves a new user"""
if not email:
raise ValueError(_('Email cannot be empty'))
user = self.model(email=self.normalize_email(email), **extra_kwargs)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password, username, **extra_kwargs):
"""Creates and saves a new user with superuser permission"""
user = self.create_user(
email, password, username, **extra_kwargs)
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
class User(AbstractBaseUser, PermissionsMixin):
"""Creates user model that supports using email as username"""
email = models.EmailField(_('Email'), max_length=255, unique=True)
created_date = models.DateTimeField(
_('Created Date'), default=timezone.now, editable=False)
objects = UserManager()
USERNAME_FIELD = 'email'
def __str__(self):
"""String representation of user model"""
return self.email
class UserProfile(models.Model, Languages):
"""Creates user profile model"""
user = models.OneToOneField(
'User',
related_name='profile',
on_delete=models.CASCADE
)
first_name = models.CharField(
_('First Name'), max_length=255, blank=True)
last_name = models.CharField(
_('Last Name'), max_length=255, blank=True)
image = models.ImageField(
_('Image'),
upload_to=user_image_upload_file_path,
null=True,
blank=True,
max_length=1024
)
#receiver(post_save, sender=User)
def user_is_created(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
else:
instance.profile.save()
In serializer.py:
class UserSerializer(serializers.ModelSerializer):
"""Minimal serializer for supporting user image upload field"""
class Meta:
model = get_user_model()
fields = ('username', )
class UserImageUploadSerializer(serializers.ModelSerializer):
"""Serializer for user profile"""
user = UserSerializer(read_only=True)
class Meta:
model = UserProfile
fields = ('id', 'user', 'image', )
read_only_fields = ('id', 'user', )
In views.py:
class UserImageUploadView(viewsets.ModelViewSet):
serializer_class = serializer.UserImageUploadSerializer
authentication_classes = [authentication.TokenAuthentication, ]
permission_classes = [permissions.IsAuthenticated, ]
queryset = get_user_model().objects.all()
def get_queryset(self):
"""Return object for only authenticated user"""
return self.queryset.filter(id=self.request.user)
#action(detail=True, methods=['POST'], url_path='user-upload-image')
def image_upload(self, pk=None):
"""Save the uploaded picture and profile data"""
user = self.get_object()
profile = user.profile
data = {'user': user, 'id': profile.pk, 'data': request.data}
serializer_ = serializer.UserImageUploadSerializer(data=data)
if serializer_.is_valid():
serializer_.save()
return Response(serializer_.data, status=status.HTTP_200_OK)
else:
return Response(serializer_.errors, status=status.HTTP_400_BAD_REQUEST)
In urls.py:
urlpatterns = [
path('<int:pk>/upload-image/', views.UserImageUploadView.as_view(
{'get': 'list', 'post': 'image_upload', }), name='user-image-upload')
]
To achieve this task I have manually created the image saving feature using APIView.
Please update if any efficient code exists
In my urls.py file:
urlpatterns = [
path('upload-image/', views.UserImageUploadView.as_view(), name='user-image-upload'),
]
In views.py:
from rest_framework.parsers import FormParser, MultiPartParser, JSONParser
from rest_framework.views import APIView
from . import serializer
from core import models
class UserImageUploadView(APIView):
"""View to upload or view image for user"""
serializer_class = serializer.TempSerializer
authentication_classes = [authentication.TokenAuthentication, ]
permission_classes = [permissions.IsAuthenticated, ]
parser_classes = [JSONParser, MultiPartParser]
def get(self, request, format=None):
"""To get user profile picture"""
user = get_user_model().objects.get(email=request.user)
user_profile = models.UserProfile.objects.get(user=user)
# Preparing the data manually as per our serializer
data = {'user': {'username': user.username},
'image': user_profile.image or None}
# Serializing our prepared data
ser = serializer.TempSerializer(
user_profile, data=data, context={"request": request})
# Returning appropriate response
if ser.is_valid():
return_ser_data = {'id': ser.data.get('id'),
'image': ser.data.get('image')}
return Response(return_ser_data, status=status.HTTP_200_OK)
else:
return Response(ser.errors, status=status.HTTP_400_BAD_REQUEST)
def post(self, request, format=None):
"""To save the profile picture"""
user = get_user_model().objects.get(email=request.user)
user_profile = models.UserProfile.objects.get(user=user)
# Formatting the data to as per our defined serializer
data = {'user': {'username': user.username},
'image': request.data.get('image')}
# Serializing our data
ser = serializer.TempSerializer(
user_profile, data=data, context={"request": request})
if ser.is_valid():
if ser.validated_data:
# Deleting the old image before uploading new image
if user_profile.image:
user_profile.image.delete()
# Saving the model
ser.save(user=user)
return_ser_data = {'id': ser.data.get('id'),
'image': ser.data.get('image')}
return Response(return_ser_data, status=status.HTTP_200_OK)
else:
return Response(ser.errors, status=status.HTTP_400_BAD_REQUEST)
In serializer.py:
class UserSerializer(serializers.ModelSerializer):
"""Minimal serializer for supporting user image upload field"""
class Meta:
model = get_user_model()
fields = ('username', )
class TempSerializer(serializers.ModelSerializer):
"""Serializer for user image upload"""
user = UserSerializer(read_only=True)
image = serializers.ImageField(allow_null=True, use_url=True)
class Meta:
model = UserProfile
fields = ('id', 'user', 'image')
read_only_fields = ('id', 'user')
I've added a view to create a new user, this takes a username, password, email and a slug field to link to a permission.
{
"username" : "TestUsername",
"email" : "TestUsername#outlook.com",
"password" : "Password01",
"group" : "partial-permission"
}
The view for this request is;
class CreateUserSerializer(serializers.ModelSerializer):
group = serializers.SlugRelatedField(queryset=CompanyGroup.objects.all(), slug_field='slug_field')
class Meta:
model = User
fields = ['company', 'email', 'username', 'password', 'token', 'group']
read_only_fields = ['token']
write_only_fields = ('password',)
def create(self, validated_data):
return User.objects.create_user(**validated_data)
I'm trying to use the SlugRelatedField to link automatically to the Group passed in the slug field and pass this onto my create_user method in my model.
class UserManager(BaseUserManager):
def get_queryset(self):
return UserQuerySet(self.model, using=self._db).active_and_not_deleted()
def create_user(self, username, email, password, group=None, company=None):
user = self.model(username=username, email=self.normalize_email(email), company=company)
user.set_password(password)
return user
When doing this I'm getting the exception:
AttributeError at /users/
Got AttributeError when attempting to get a value for field `group` on serializer `CreateUserSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.
Original exception text was: 'User' object has no attribute 'group'.
I understand that this exception explains exactly what my problem is, but I'm trying to avoid having it on the User object and manually looking up the Group from the objects.
Edit:
Models as request;
class User(AbstractUser):
# Date the User was created
created_at = models.DateTimeField(auto_now_add=True)
# Date the User info was last updated
updated_at = models.DateTimeField(auto_now=True)
# Date the User last logged into the app
last_active = models.DateTimeField(auto_now=True)
# The company which this user is associated with.
company = models.ForeignKey(Company, on_delete=models.CASCADE, null=True)
# Indicates whether this user has been deleted or not
is_deleted = models.BooleanField(default=False)
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
objects = UserManager()
def __str__(self):
return self.username
#property
def token(self):
return self._generate_jwt_token()
def _generate_jwt_token(self):
dt = datetime.now() + timedelta(days=60)
token = jwt.encode({
'id': self.pk,
'exp': int(dt.strftime('%s'))
}, settings.SECRET_KEY, algorithm='HS256')
return token.decode('utf-8')
And group;
class CompanyGroup(Group):
slug_field = models.SlugField()
objects = GroupManager()
def __str__(self):
return self.name
I think there needs to have a ManyToMany Relation between User and GroupCompany(as far as I understood from comments). Because as per documentation:
SlugRelatedField may be used to represent the target of the relationship using a field on the target.
So, you can add it like this:
class User(AbstractUser):
# rest of the fields
group = models.ManyToManyField(GroupCompany)
Need to make sure, slug_field is unique in GroupCompany:
slug_field = models.SlugField(unique=True)
Also, you need to change the create_user model manager method as well:
def create_user(self, username, email, password, company=None):
user = self.model(username=username, email=self.normalize_email(email), company=company)
user.set_password(password)
user.save()
return user
And update the serializer as well:
class CreateUserSerializer(serializers.ModelSerializer):
group = serializers.SlugRelatedField(queryset=CompanyGroup.objects.all(), slug_field='slug_field', many=True) # added many True
def create(self, validated_data):
group = validated_data.pop('group')
user = User.objects.create_user(**validated_data)
user.group.add(group)
return user
Update
class CreateUserSerializer(serializers.ModelSerializer):
company_group = serializers.CharField(write_only=True) # added many True
def create(self, validated_data):
group = validated_data.pop('company_group')
user = User.objects.create_user(**validated_data)
group, _ = CompanyGroup.objects.create(slug_field=company_group)
user.group.add(group)
return user
I have a custom user model
class User(AbstractUser):
username = None
email = models.EmailField( unique=True)
phone = models.CharField( max_length=15)
is_pro = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['phone']
objects = UserManager()
#property
def token(self):
"""
Allows us to get a user's token by calling `user.token` instead of
`user.generate_jwt_token().
The `#property` decorator above makes this possible. `token` is called
a "dynamic property".
"""
return self._generate_jwt_token()
def _generate_jwt_token(self):
"""
Generates a JSON Web Token that stores this user's ID and has an expiry
date set to 60 days into the future.
"""
import jwt
from datetime import datetime, timedelta
from django.conf import settings
dt = datetime.now() + timedelta(days=60)
token = jwt.encode({
'id': self.pk,
'exp': int(dt.strftime('%s'))
}, settings.SECRET_KEY, algorithm='HS256')
return token.decode('utf-8')
Now I try make SignIn API with Django Rest Framework using this tutorial https://thinkster.io/tutorials/django-json-api/authentication
serializer.py
class RegistrationSerializer(serializers.ModelSerializer):
password = serializers.CharField(
max_length=128,
min_length=8,
write_only=True
)
token = serializers.CharField(max_length=255, read_only=True)
class Meta:
model = User
fields = ['email', 'phone', 'password', 'token']
def create(self, validated_data):
# Use the `create_user` method we wrote earlier to create a new user.
return User.objects.create_user(**validated_data)
views.py
class RegistrationAPIView(APIView):
# Allow any user (authenticated or not) to hit this endpoint.
permission_classes = (AllowAny,)
serializer_class = RegistrationSerializer
def post(self, request):
user = request.data.get('user', {})
serializer = self.serializer_class(data=user)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
Then I create a new user an error occured "This field is required." for all my User fields, like email, phone, password.
Screenshot picture http://joxi.ru/12MOMkZt4eeBEr
This is because you set this fields not nullable and required inside User model. To fix it you can add blank=True arguments to the fields which may be blank, like phone:
class User(AbstractUser):
username = None
email = models.EmailField(unique=True)
phone = models.CharField(max_length=15, blank=True)
is_pro = models.BooleanField(default=False)
After this run makemigrations and migrate to apply changes at DB level.
UPD
In view you need to get data from request.data directly:
def post(self, request):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
request.data doesn't contain user key, so request.data.get('user', {}) return empty dict.