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
Related
I have two tables of users and I am creating a form to store the user information.
Models.py
class MyUser(User):
address = models.CharField(max_length=200)
city = models.CharField(max_length=200)
expire_date = models.DateField()
This creates a table with user_ptr_id to the auth_user table of django.
I created two serializers: one for the User:
class UserSerializer(serializers.ModelSerializer):
first_name = serializers.CharField(min_length=2, required=True)
last_name = serializers.CharField(min_length=2, required=True)
email = serializers.EmailField(min_length=5, required=True)
password = serializers.CharField(min_length=8, write_only=True, required=True)
class Meta:
model = User
fields = ('email', 'first_name', 'last_name', 'password')
def create(self, validated_data):
return UserSerializer(**validated_data)
And MyUser class:
class MyUserSerializer(serializers.ModelSerializer):
address = serializers.CharField(max_length=200)
city = serializers.CharField(max_length=200)
class Meta:
model = MyUser
fields = ('city', 'address')
def create(self, validated_data):
return MyUser(**validated_data)
As I am using Django Rest-Framework-Auth, I craeted a serializer to catch the data, but I don't know how to let the things work together. In the "MyUserSerializer" class, I also perform many validate checks, that I omitted here to keep the code clean.
Code below doesn't work
class UserSignup(serializers.Serializer):
user = UserSerializer()
my_user = MyUserSerializer()
confirm_psw = serializers.CharField(min_length=8, write_only=True, required=True)
def validate(self, data):
if not data["user"].get('password') or not data.get('confirm_psw'):
raise serializers.ValidationError("Please enter a password and confirm it.")
if data["user"].get('password') != data.get('confirm_psw'):
raise serializers.ValidationError("Those passwords don't match.")
return data
def save(self, validated_data):
user_data = self.validated_data["user"]
my_user = self.validated_data["my_user"]
return user_data, my_user
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create(user_data)
my_user = validated_data.pop('my_user')
my_user["user_ptr_id"] = user.id
MyUser.objects.create(**ua_user)
return user
How do I merge the two serializers to correctly perform the user registration?
Thanks
I extended the Django AbstratUser so that users can use email to sign in and signup, these work perfectly. The problem I am facing, however, is that the extra information on the extended model is not storing the information in the database, even though the user gets created. Once I hit the submit button, the user and extended model get created, and while the user model stores the information, the extended model is always empty.
I have tried using both signals and #transaction_atomic, yet, I have not been able to figure it out. Maybe I am missing out something, I do not know.
Models.py
class Company(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True)
name= models.CharField(_('Company name'), max_length=250)
...
#more information
...
class Meta:
verbose_name = _('Company')
verbose_name_plural = _('Companies')
def __str__(self):
return self.name
forms.py
class CompanySignUpForm(CustomUserCreationForm):
name = forms.CharField(widget=TextInput(attrs={'placeholder': 'Company name'}))
...
#more fields
...
class Meta(CustomUserCreationForm.Meta):
model = User
#transaction.atomic
def save(self):
user = super().save(commit=False)
user.is_company = True
user.save()
company = Company.objects.create(user=user)
company.name = self.cleaned_data.get('name')
...
#more information
...
return user
Views.py
def company_signup(request):
if request.method == 'POST':
form = CompanySignUpForm(request.POST)
if form.is_valid():
form.save()
return render(request, 'accounts/templates/company_success.html')
else:
form = CompanySignUpForm()
return render(request, 'accounts/templates/company_signup.html', context={
'title': _('Create a Company Account'),
'form': form,
})
Edit:
Thanks to #Mandrup, I was able to extend his solution to fit my need.
forms.py
class CompanySignUpForm(CustomUserCreationForm):
name = forms.CharField(widget=TextInput(attrs={'placeholder': 'Company name'}))
number_of_employees = forms.CharField(widget=NumberInput(attrs={'placeholder': 'Number of employees'}))
phone = forms.CharField(widget=TextInput(attrs={'placeholder': 'Contact Number'}))
country = forms.ModelChoiceField(queryset=Country.objects.all(), required=True, empty_label="Country")
class Meta(CustomUserCreationForm.Meta):
model = User
#transaction.atomic
def save(self, commit=True):
user = super(CompanySignUpForm, self).save(commit=False)
if commit:
user.is_company = True
user.save()
name = self.cleaned_data.get('name')
number_of_employees = self.cleaned_data.get('number_of_employees')
phone = self.cleaned_data.get('phone')
country = self.cleaned_data.get('country')
company = Company(user=user, name=name, number_of_employees=number_of_employees, phone=phone, country=country)
company.save()
return user
Edit:
This worked for me when i tried to create an extended user profile. I changed it to fit your needs.
Model:
class Company(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True)
name= models.CharField(max_length=250)
...
#more information
...
def __str__(self):
return self.name
Form:
class RegisterUserForm(UserCreationForm):
class Meta:
model = User
fields = ["username", "email", "password1", "password2"]#add whatever fields you want to here
def save(self, commit=True):
user = super(RegisterUserForm, self).save(commit=False)
if commit:
user.save()
company = Company(user=user, name='Company name')
company.save()
return user
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 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'
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.