I have defined a custom user model class as follows
class LabUserManager(BaseUserManager):
"""
A new user manager for both standard and administrative users
"""
def create_user(
self,
email,
first_name,
last_name,
university,
job_title,
bio,
password=None
):
"""
Creates a standard user with no administrative privledges
"""
if not email:
raise ValueError('Users must provide an email')
if not first_name:
raise ValueError('Users must provide a first name')
if not last_name:
raise ValueError('Users must provide a last name')
if not university:
raise ValueError('Users must provide an email address')
# Note: a biography and job_title are not required
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(
self,
email,
first_name,
last_name,
university,
job_title,
bio,
password=None
):
"""
Creates an administrative user
"""
user = self.create_user(
self,
email,
first_name,
last_name,
university,
job_title,
bio,
password=None
)
user.is_admin = True
user.save(using=self._db)
return user
class LabUser(AbstractBaseUser):
"""
Model for every user on the site
The only required fields are:
email,
first_name,
last_name,
university,
although, this will be discussed later
"""
email = models.EmailField(
verbose_name = 'email address',
max_length = 255,
unique = True,
db_index = True,
)
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
university = models.CharField(max_length=150)
job_title = models.CharField(max_length = 50)
bio = models.TextField()
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
created = models.DateTimeField(auto_now_add=True)
objects = LabUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = [
'first_name',
'last_name',
'university',
]
#property
def is_staff(self):
return self.is_admin
With the serializer
class NewUserSerializer(serializers.Serializer):
"""
Defines a new user serializer
"""
pk = serializers.Field()
email = serializers.EmailField()
first_name = serializers.CharField(max_length=50)
last_name = serializers.CharField(max_length=50)
university = serializers.CharField(max_length=150)
job_title = serializers.CharField(
max_length=50,
required=False
)
bio = serializers.CharField(
widget=widgets.Textarea,
required=False
)
password = serializers.CharField(
max_length=64,
widget=widgets.PasswordInput
)
def restore_object(self, attrs, instance=None):
if instance: # Update email university bio or job_title
user = instance
user.email = attrs['email']
user.university = attrs['university']
user.bio = attrs['bio']
user.job_title = attrs['job_title']
user.set_password(attrs.get('password'))
else:
user = LabUser(
email=attrs['email'],
first_name=attrs['first_name'],
last_name=attrs['last_name'],
university=attrs['university'],
job_title=attrs['job_title'],
bio=attrs['bio']
# Check custom user model page for other parameters
)
user.set_password(attrs.get('password'))
user.save()
return user
with the View
class NewUser(generics.CreateAPIView):
serializer_class = NewUserSerializer
But when I post a new user with a new email address, I receive the following error
IntegrityError at /users/new/
PRIMARY KEY must be unique
Request Method: POST
Request URL: http://127.0.0.1:8000/users/new/
Django Version: 1.5.1
Exception Type: IntegrityError
Exception Value:
PRIMARY KEY must be unique
At some point, you seem to submit and process a primary key value when creating new objects. It's only a guess, but I think this happens somewhere in your NewUserSerializer because you explicitly added a pk field, that is not read only and does not enforce a positive integer value. I would try using an IntegerField with read_only=True instead and see if that fixes the problem.
But I strongly recommend using ModelSerializer anyway. You could still have your custom widgets for each field, but it would also handle a few things a little more concise than your implementation does. And you'd have to write much less code.
Related
I am beginning a new project with Django Rest Framework, and I have a specifical need on the creation of a user: to create an account, you need to give information such as your birthdate, the birthdate of a friend, and several other info.
But it would NOT be relevant for a superuser to give such information, that's why I am looking for a way to require different info for user and for superuser. Do you know if it's possible ?
In the file models.py, I created 2 different classes :
class UserProfile(AbstractBaseUser, PermissionsMixin)
class SuperUserProfile(AbstractBaseUser, PermissionsMixin)
These two classes require different info to create an account.
In addition, I created a class to manage user profiles :
class UserProfileManager(BaseUserManager):
"""Manager for user profiles"""
def create_user(self, name1, firstName1, email1, name2, firstName2, birthDate2, password=None):
"""Create a new user profile"""
email1 = self.normalize_email(emailParrain)
user = self.model(emailParrain=emailParrain,
name1=name1,
firstName1=firstName1,
name2=name2,
firstNameUser=firstNameUser,
birthDateUser=birthDateUser)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, name, password):
"""Create and save a new superuser with given details"""
user = self.create_user(email,password)
user.is_superuser = True
user.is_staff = True
user.save(using=self._db)
return user
But when I do this, I cannot create a superuser with only the info sub-mentionned (email, name, password).
you just need to add the required fields and specifying the Manager in users models
class User (AbstractBaseUser, PermissionsMixin):
user_name = models.CharField(max_length=150, unique=True)
first_name = models.CharField(max_length=150)
last_name = models.CharField(max_length=150)
...
objects = UserProfileManager()
USERNAME_FIELD = 'user_name'
#the user_name and first_name are required
REQUIRED_FIELDS = ['user_name', 'first_name']
def __str__(self):
return self.user_name
I have a user model like below
class User(AbstractBaseUser, PermissionsMixin):
class Type:
FP = 'fp'
BRANCH = 'branch'
CHOICES = (
(FP, '채용공고 보기'),
(BRANCH, '채용공고 내기'),
)
class Sex:
MALE = 'male'
FEMALE = 'female'
CHOICES = (
(MALE, '남성'),
(FEMALE, '여성')
)
type = models.CharField(
max_length=10,
verbose_name='계정 종류',
choices=Type.CHOICES,
)
email = models.EmailField(
verbose_name='이메일',
unique=True,
)
name = models.CharField(
max_length=10,
verbose_name='이름'
)
nickname = models.CharField(
max_length=10,
verbose_name='닉네임',
unique=True,
)
phone = models.CharField(
max_length=20,
verbose_name='전화번호',
)
sex = models.CharField(
max_length=10,
verbose_name='성별',
choices=Sex.CHOICES,
)
company_name = models.CharField(
verbose_name='회사이름',
max_length=256,
)
dob = models.DateTimeField(
verbose_name='생년월일'
)
profile_img = models.ImageField(
verbose_name='프로필 이미지',
upload_to=user_profile_img_file_path,
)
sns_id = models.TextField(
verbose_name='SNS ID'
)
sns_type = models.CharField(
verbose_name='SNS 종류',
max_length=20,
)
objects = UserManager()
USERNAME_FIELD = 'email'
and custom manager like below
class UserManager(BaseUserManager):
def create_user(self, email, password, **extra_fields):
sys.stderr.write(repr(extra_fields))
if not email:
raise ValueError('이메일은 필수사항입니다')
user = self.model(email=self.normalize_email(email), **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password):
user = self.create_user(email, password)
user.is_superuser = True
user.save(using=self._db)
return user
and test looking like below
def test_create_valid_user_success(self):
"""
Test that creating user with valid payload is successful
"""
profile_img = self.generate_photo_file()
payload = {
'email': 'test#gmail.com',
'password': '1234567',
'type': 'fp',
'name': 'testname',
'nickname': 'testnickname',
'phone': '01012341234',
'sex': 'male',
'company_name': 'company_name',
'dob': '1992-11-23T10:22:38+0000',
'profile_img': profile_img,
'sns_id': '1234567',
'sns_type': 'facebook',
}
res = self.client.post(CREATE_USER_URL, payload)
sys.stderr.write(repr(res))
self.assertEqual(res.status_code, status.HTTP_201_CREATED)
user = get_user_model().objects.get(**res.data)
self.assertTrue(user.check_password(payload['password']))
self.assertNotIn('password', res.data)
unfortunately I am keep getting below error when I run the above test
Direct assignment to the forward side of a many-to-many set is prohibited. Use groups.set() instead.
I am not much of a django expert; however, as far as I know what I am doing above is pretty standard and I do not have any relationship in the user model.
Even stranger behaviour is that if I directly create the user with the same payload without using the APIClient, it works flawlessly. What's wrong with the test I have?
It's not normally recommend to write a custom user model and manager unless you really need it. If you are just looking to extend the user model then you would normally approach that by creating another model with a OneToOne relationship to the User model.
This is outlines within the official documents - https://docs.djangoproject.com/en/3.0/topics/auth/customizing/#extending-the-existing-user-model
I'm trying to obtain auth token using email instead of username, i have changed serialized to accept just email but i think that i have something wrong.
class AuthTokenSerializer(serializers.Serializer):
"""Serializer for user authentication object"""
email_or_username = serializers.CharField()
password = serializers.CharField(style = {'input_type' : 'password' }, trim_whitespace = False)
def validate(self, attrs):
"""Validate and authentiate the user"""
email_or_username = attrs.get('email')
password = attrs.get('password')
user = authenticate(request = self.context.get('request'), username = email, password = password)
if not user:
msg = _('Unable to authenticate with provided credentials.')
raise serializers.ValidationError(msd, code='authorization')
attrs['user'] = user
return attrs
And the View
class LoginViewSet(viewsets.ViewSet):
"""Check email and password and returns an auth token."""
serializer_class = serializers.AuthTokenSerializer
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
def create(self, request):
"""Use the ObtainAuthToken APIview to validate and create a token."""
return ObtainAuthToken().post(request)
when i make a test
def test_create_token_for_user(self):
"""Test that a token is created"""
parametrs = {'email' : 'test#noah-lc.com','email' : 'test#noah-lc.com', 'name' : 'test', 'password' : 'testPASS#123'}
create_user(**parametrs)
res = self.client.post(TOKEN_URL, parametrs)
print(res.data)
self.assertIn('token', res.data)
self.assertEqual(res.status_code, status.HTTP_200_OK)
I got this error
AssertionError: 'token' not found in {'username': [ErrorDetail(string='This field is required.', code='required')]}
Model of own user
class UserProfile(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(max_length=255, unique=True)
name = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
And Setting
AUTH_USER_MODEL = 'core.UserProfile'
DRF's ObtainAuthToken uses its AuthTokenSerializer which expects a username not an email. So if you want to use an email, I would advice you create your own ObtainAuthToken view or even better off put the logic of ObtainAuthToken directly into your LoginViewSet as it seems to serve the same purpose. You can them create a custom AuthTokenSerializer which accepts emails instead of username or both and use with it
I suppose you have an app named users, then define a new User model here,
class User(AbstractBaseUser, PermissionMixin):
first_name = models.CharField(max_length=100, blank=True, null=True)
last_name = models.CharField(max_length=100, blank=True, null=True)
email = models.EmailField(db_index=True, unique=True)
USERNAME_FIELD = 'email'
The important part is USERNAME_FIELD, that set email instead of username
then in settings.py add below line
AUTH_USER_MODEL = 'users.User'
I am using Django 1.5s Custom User Model. I want to let a user type their username in - and be logged in. NO PASSWORD (for testing anyway). My User Model doesnt have a password. But when i try to login to admin I get the following error:
OperationalError(1054, "Unknown column 'hrms.password' in 'field list'"
It seems to be trying to execute this query in the authenticate() method.
SELECT `myusers`.`password`, `myusers`.`last_login`, `myusers`.`id`, `myusers`.`user`, `myusers`.`name`, `myusers`.`firstname`, `myusers`.`lastname`, `myusers`.`organisation`, `myusers`.`unit`, `myusers`.`grade`, `myusers`.`email`, `myusers`.`position`, `myusers`.`manager` FROM `myusers` WHERE `myusers`.`user` = 'warrenm' "
I do not have the fields password, last_login - I dont know why its trying to get them.
Below is my code.
My Backend (auth.py)
from epmds.application.models import AuthUser
class MyBackend(object):
def get_user(self, user_id):
# get a user from the user_id
try:
return AuthUser.objects.get(pk=user_id)
except AuthUser.DoesNotExist:
return None
def authenticate(self, username=None, password=None):
# check the username/password and return a user
user = AuthUser.objects.get(user=username)
return user
MY Model
class AuthUser(AbstractBaseUser):
id = models.CharField(primary_key=True, max_length=15)
user = models.CharField('username', max_length=20, unique=True)
name = models.CharField(max_length=100)
firstname = models.CharField(max_length=100)
lastname = models.CharField(max_length=100)
organisation = models.CharField(max_length=100)
email = models.CharField(max_length=50, blank=True, null=True)
USERNAME_FIELD = 'user'
def get_full_name(self):
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
return self.first_name
class Meta:
ordering = ('lastname', 'firstname')
managed = False
db_table = 'myusers'
password is part of AbstractBaseUser so it should be added in your AuthUser model as well in table.
As you have managed=False for this model, you need to add that explicitly.
my goal is to create a custom user model in Django 1.5
# myapp.models.py
from django.contrib.auth.models import AbstractBaseUser
class MyUser(AbstractBaseUser):
email = models.EmailField(
verbose_name='email address',
max_length=255,
unique=True,
db_index=True,
)
first_name = models.CharField(max_length=30, blank=True)
last_name = models.CharField(max_length=30, blank=True)
company = models.ForeignKey('Company')
...
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['company']
I can't create a super user because of the company field (models.ForeignKey('Company') (python manage.py createsuperuser).
My question:
How can I create a super user for my application without a company.
I tried to make a custom MyUserManager without any success:
class MyUserManager(BaseUserManager):
...
def create_superuser(self, email, company=None, password):
"""
Creates and saves a superuser with the given email and password.
"""
user = self.create_user(
email,
password=password,
)
user.save(using=self._db)
return user
Or do I have to create a fake company for this user?
Thank you
There are three ways for you in this case
1) Make relation to company Not required company = models.ForeignKey('Company', null=True)
2) Add default company and provide it as default value to foreign key field company = models.ForeignKey('Company', default=1) #where 1 is id of created company
3) Leave model code as is. Add fake company for superuser named for example 'Superusercompany'
set it in create_superuser method.
UPD: according to your comment way #3 would be the best solution not to break your business logic.
Thanks to your feedback here is the solution I made:
A custom MyUserManager where I created a default company
def create_superuser(self, email, password, company=None):
"""
Creates and saves a superuser with the given email and password.
"""
if not company:
company = Company(
name="...",
address="...",
code="...",
city="..."
)
company.save()
user = self.create_user(
email,
password=password,
company=company
)
user.is_admin = True
user.save(using=self._db)
return user