For a slew of reasons that detailing will make this post too long, I'm using a custom user model, alongside django-registration, and guardian.
Everything is working quite alright, but I'm trying to make it so every user registered is automatically set to active, so "they" (right now it's only me emulating users) can login instantly. The way I did so far is by manually setting that field to "1" (True) in the SQL table.
It seems that no matter how I arrange things, the is_active flag is always False until I change it manually in SQL, even though I'm printing it to my console and it's showing True when I register.
I did get it to work by changing user.save() to user.save(commit=True), that does make the is_active flag True and allows the user to log-in, but it always throws an error that says:
TypeError: save() got an unexpected keyword argument 'commit'
Of course, this is all for testing purposes and I do intend to introduce email verification, but I'm very intrigued on why I'm not able to simply set the flag and save().
Here's my register/forms.py:
class GeneralUserForm(UserCreationForm):
business_name = forms.CharField(required=True)
class Meta:
model = GeneralUser
fields = ['username', 'email', 'password1',
'password2', 'business_name']
# TODO: only create all objects if no error is found
def save(self, commit=True):
user = super(GeneralUserForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
# TODO: create permissions, including groups.
if commit:
user.is_active = True # remove before deployment
print(user.is_active)
user.save() # LINE I CHANGED
business = Business.objects.create(name=self.cleaned_data['business_name'], owner=user)
business.save()
mng_grp = create_perms_for_biz(business, user)
user.groups.add(mng_grp)
return user
And my register/models.py:
class GeneralUser(AbstractUser, GuardianUserMixin):
username = models.CharField(
('username'),
max_length=150,
unique=True,
help_text=('Required. 150 characters or fewer. Letters, digits and #/./+/-/_ only.'),
error_messages={
'unique': ("A user with that username already exists."),
},
)
password = models.CharField(max_length=100)
email = models.EmailField(('email address'), blank=True)
is_active = models.BooleanField(
('active'),
default=True,
help_text=(
'Designates whether this user should be treated as active. '
'Unselect this instead of deleting accounts.'
),
)
objects = UserManager()
date_joined = models.DateTimeField(('date joined'), default=timezone.now)
business_name = models.CharField(('business name'), max_length=150, blank=True)
USERNAME_FIELD = 'username'
def email_user(self, subject, message, from_email=None, **kwargs):
"""
Sends an email to this User.
"""
send_mail(subject, message, from_email, [self.email], **kwargs)
def get_custom_anon_user(User):
return User(
username='AnonymousUser',
)
You are confusing ModelForm.save() method that do take a commit argument with the Model.save() method that does not.
Related
I have a custom user model in my Django project and when I create an instance of request.user in my views PyCharm doesn't seem to recognize the user instance correctly. The available methods/attributes suggested by PyCharm still point to the built-in Django user model but not to my new one.
Is there any way to set this up properly?
Example:
# settings.py
AUTH_USER_MODEL = 'user.UserProfile'
# models.py custom user model
class UserProfile(AbstractBaseUser, PermissionsMixin):
# Email and name of the user
email = models.EmailField(max_length=255, unique=True)
name = models.CharField(max_length=255)
# Privilege and security booleans
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
is_active = models.BooleanField(default=False)
email_confirmed = models.BooleanField(default=False)
# Company on whose behalf the user acts on
company = models.ForeignKey('company.Company', on_delete=models.CASCADE, blank=True, null=True)
objects = UserProfileManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
def email_user(self, subject, message, from_email=None, **kwargs):
"""Send mail to user - Copied from original class"""
send_mail(subject, message, from_email, [self.email], **kwargs)
def __str__(self):
return self.email
# views.py
def render_dashboard_benefits(request):
# Get current user instance
current_user = request.user
# Typing...
current_user.company
# Pycharm suggests 'first_name' and 'last_name' depending on the initial user model
# but not e.g. "email" or "company" according to the new user model
return render(request, 'test.html')
Re dudulus answer, this indeed works but raises:
current_user: UserProfile = request.user
so still I think this is an IDE bug?
You can use like this.
current_user: UserProfile = request.user
I'm using a custom user model with allauth and I need to have the username field omitted. I've already seen the docs and a whole bunch of stackoverflow answers about using ACCOUNT_USER_MODEL_USERNAME_FIELD = None but all of this still leads my database to have an username field.
Now since the db still has an username field with the unique constraint set on and allauth will not put a username in the field with the aforementioned setting set to None, this causes me to face IntegrityError after the very first user creation. I know I can solve this by just having the aforementioned setting be set to 'username' but I'm curious, how do I just not make the username, because I'm never using it.
My model:
class CustomUser(AbstractUser):
# Custom user model for django-allauth
first_name = None
last_name = None
def delete(self):
# Custom delete - make sure user storage is also purged
# Purge the user storage
purge_userstore(self.email)
# Call the original delete method to take care of everything else
super(CustomUser, self).delete()
It doesn't really do much except override the delete function. This override is irrelevant in this topic but I included it just for the sake of completeness. It also sets first_name and last_name to None, which works perfectly and removes those fields from the database as expected. I've tried setting user to None but that does nothing. I've also tried setting username to None but that will raise a FieldNotFound error with ACCOUNT_USER_MODEL_USERNAME_FIELD = None
My settings (the relevant bit):
AUTHENTICATION_BACKENDS = (
"django.contrib.auth.backends.ModelBackend",
"allauth.account.auth_backends.AuthenticationBackend",
)
AUTH_USER_MODEL = 'custom_account.CustomUser'
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_UNIQUE_EMAIL = True
ACCOUNT_AUTHENTICATION_METHOD = 'email'
My migration file:
migrations.CreateModel(
name='CustomUser',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and #/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
],
....
This migration generation confuses me to no end. Why is the username field still there? Why is it set to be the only unique constraint even though I've clearly set ACCOUNT_UNIQUE_EMAIL = True?
Note: This migration file is generated from a clean slate, this is the first and only migration file generated from the code I have presented.
At first I thought, my settings were simply not being read. But I checked django.conf.settings and allauth.account.app_settings (in the shell) for these changes and they were all updated. What's going on here?
Note: Amongst the many stackoverflow questions I've searched, this question in particular seems to explain my issue perfectly. With one small problem, the answer by the creator of allauth himself suggested to use ACCOUNT_USER_MODEL_USERNAME_FIELD = "username" as the model in question was "clearly using the username field". But the answer does not explain what to do when you don't want to use the username field at all.
Looks like the only way to get rid of the username field is to override the AbstractUser's username field and/or use a completely custom model from the ground up. Thought overriding AbstractBaseUser should work too, albeit AbstractBaseUser provides less functionality.
class CustomUser(AbstractUser):
# Custom user model for django-allauth
# Remove unnecessary fields
username = None
first_name = None
last_name = None
# Set the email field to unique
email = models.EmailField(_('email address'), unique=True)
# Get rid of all references to the username field
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
This model will remove the username field and make the email field unique, as well as change all references to the USERNAME_FIELD to 'email'. Do note that REQUIRED_FIELDS should be empty as the USERNAME_FIELD cannot be in there. When using allauth, this is not a problem, the email and password requirement are managed by allauth anyway.
The settings that I've mentioned in the question should remain the same, specifically-
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_EMAIL_REQUIRED = True
I want to capture the username of the user currently logged in, NOT pk.
incident.username = request.user doesn't work
incident.username = request.user.username doesn't work
incident.username = request.username doesn't work
Uggg. This can't be this hard.
models.py
class Incident(models.Model):
username = models.ForeignKey(User) ## field in db will be auto-populated by view.py
date_reported = models.DateField() ## field in db will be auto-populated by view.py
date_occurred = models.DateField()
number_of_samples_affected = models.IntegerField()
capa = models.CharField(max_length=9)
title = models.CharField(max_length=100)
description = models.TextField()
status = models.ForeignKey(Status) ## field in db will be auto-populated by view.py to "Open" at form submission
category = models.ForeignKey(Category)
lab = models.TextField(Lab)
views.py
from submit_app.forms import IncidentForm
from submit_app.models import Incident, Status
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
import datetime
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
#login_required(login_url='/login/')
def submit(request):
if request.GET:
form = IncidentForm()
template = 'submit.html'
context = {'form': form}
return render(request, template, context)
# if this is a POST request we need to process the form data
if request.POST:
# create a form instance and populate it with the data from the request:
form = IncidentForm(request.POST)
# check whether it's valid:
if form.is_valid():
incident = form.save(False) # creating an incident object, but not saving it to the db just yet
incident.username = request.user # auto capture logged in user
incident.date_reported = datetime.date.today() # auto capture the incident submission date
incident.status = Status.objects.get(status="open") # trying to auto populate status with 'Open' upon submission (foreign key)
incident.save()
return HttpResponseRedirect(reverse('dashboard_app:dashboard'))
form = IncidentForm()
template = 'submit.html'
context = {'form': form}
return render(request, template, context)
Right now, your model has a foreign key relation to the User model, which by default relates to the primary key field. To change that and relate to the username field itself, add a to_field keyword argument to your model, makemigrations, and migrate.
username = models.ForeignKey(User,to_field='username')
Afterwards, you'll be able to access the user for the current request via request.user.username, assuming that username is a field/attribute of User (and not a related model).
...
However, there's generally no need to do this. You can still relate to the User model (relation built via PK) and access the username from there. Easiest way to do this is perhaps to create a method to read the username.
class Incident(models.Model):
user = models.ForeignKey(User, related_name='incidents')
def username(self):
if self.user:
return getattr(self.user,'username',None) # attempt to access username
...
>>> first_incident = Incident.objects.create(user=User.objects.get(username='a'))
>>> print(first_incident.username())
a
There is some obvious confusion. Incident.username is a foreign key to a User model, so it needs to be assigned a User object, not just a username. For that incident.username = request.user should work. You can later access the user name by accessing incident.username.username, although I would rename the field to user to avoid confusion. If this doesn't work, something is not working as it should. It would help if you posted the error you are getting.
You should use custom user model and specify usename field to be primary key. But in django abstract base classes for models can't have "overriden fields" so you will need to sublcass AbstractBaseUser instead of AbstractUser. You may eventually end up with a copy of AbstractUser code (https://github.com/django/django/blob/1.8.9/django/contrib/auth/models.py#L378)
with just one line changed:
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, validators, UserManager
class MyUser(AbstractBaseUser, PermissionsMixin):
username = models.CharField(_('username'), max_length=30, unique=True,
primary_key=True, ## the only difference from AbstractUser help_text=_('Required. 30 characters or fewer. Letters, digits and '
'#/./+/-/_ only.'),
validators=[
validators.RegexValidator(r'^[\w.#+-]+$',
_('Enter a valid username. '
'This value may contain only letters, numbers '
'and #/./+/-/_ characters.'), 'invalid'),
],
error_messages={
'unique': _("A user with that username already exists."),
})
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
email = models.EmailField(_('email address'), blank=True)
is_staff = models.BooleanField(_('staff status'), default=False,
help_text=_('Designates whether the user can log into this admin '
'site.'))
is_active = models.BooleanField(_('active'), default=True,
help_text=_('Designates whether this user should be treated as '
'active. Unselect this instead of deleting accounts.'))
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
objects = UserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
abstract = True
def get_full_name(self):
"""
Returns the first_name plus the last_name, with a space in between.
"""
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
"Returns the short name for the user."
return self.first_name
def email_user(self, subject, message, from_email=None, **kwargs):
"""
Sends an email to this User.
"""
send_mail(subject, message, from_email, [self.email], **kwargs)
After this you will be able to point FK fields to username field. But do you realy need this? Why do you need to have such FK? Primary keys should better be "static". By using username as primary key you will have problems changing your usernames.
I can imagine several reasons for such a requirement:
You want your incidents to point specific username instead of actual user (maybe your instances of User may be deleted and later recreated with same username?). This is strange but can be done: use username = CharField(...) and also specify property for user with getter and setter.
class Incident(models.Model):
username = models.CharField(max_length=30)
#property
def user(self):
return User.objects.get(username=self.username)
#user.setter
def user(self, user):
if user.is_authenticated():
self.username = user.username
else:
self.username = '#anonymous' # by default '#' is disallowed in username. You can also make your username nullable
you want to "optimize" database calls (to not query users table). In this case you'd better use prefetching or denormalization:
from django.db import models
# prefetch user with only "username" field. Assuming that you have `Incident.user = models.ForeignKey(...)`. Read https://docs.djangoproject.com/en/1.9/topics/db/managers/ and https://docs.djangoproject.com/en/1.9/ref/models/querysets/#prefetch-related
class IncidentManager(models.Manager):
def get_queryset(self):
return super(IncidentManager, self).get_queryset().prefetch_related(models.Prefetch('user', queryset=User.objects.all().only('username'))
class Incident(models.Model):
user = models.ForeignKey(User)
# ...
objects = IncidentManager()
In case of denormalization you should create receiver for post_save and post_delete signals for User model which should update Incident.username field with actual username. You must also create similar signal receivers for Incident's post_save/post_delete (or you can modify Incident.save and Incident.delete methods). You may also create signal receiver for admin.models.LogAction post_save signal (from django.contrib.admin.models import DELETEION; if instance.action_flag == DELETEION and instance.content_type_id=get_content_type_for_model(Incident).pk:) because mass deletion from django-admin does not call Incident.delete and does not trigger post_delete for deleted incidnets. And even after this denormalized data may be invalid if you use User.object.update(username=something) anywhere in your project or if data is changed directly in database.
I am tying to build a custom user model in a Django app, instead of using the built-in one.
models.py
from django.contrib.auth.models import AbstractBaseUser
from django.db import models
from django.contrib.auth.models import BaseUserManager
class AccountManager(BaseUserManager):
def create_user(self, email, password=None, **kwargs):
if not email:
raise ValueError('Users must have a valid email address.')
if not kwargs.get('username'):
raise ValueError('Users must have a valid username.')
account = self.model(
email=self.normalize_email(email),
username=kwargs.get('username')
)
account.set_password(password)
account.save()
return account
def create_superuser(self, email, password, **kwargs):
account = self.create_user(email, password, **kwargs)
account.is_admin = True
account.save()
return account
class Account(AbstractBaseUser):
email = models.EmailField(unique=True)
username = models.CharField(max_length=40, unique=True)
first_name = models.CharField(max_length=40, blank=True)
last_name = models.CharField(max_length=40, blank=True)
tagline = models.CharField(max_length=140, blank=True)
is_admin = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
objects = AccountManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
def __unicode__(self):
return self.email
def get_full_name(self):
return ' '.join([self.first_name, self.last_name])
def get_short_name(self):
return self.first_name
When I run the python manage.py makemigrations commands, I get the following error:
You are trying to add a non-nullable field 'password' to account
without a default; we can't do that (the database needs something to
populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows)
2) Quit, and let me add a default in models.py
Note, I have added this in settings.py
AUTH_USER_MODEL = 'authentication.Account'
The app is called authentication btw.
How do I fix this? Thanks
The error you're getting is from the database. You can't create a non-nullable column without a default value when there are already rows for that column.
You would either need to set a default for the password field or delete all the users you already have in that table before running this migration.
I am using Django 1.7.1 with Python 3.4. I created a custom user model and now I have a need for users to be able to update their details. What I need is that, when users go to the form to update their details, the form is pre-populated with their data i.e. username, email and so on. So far, the form is showing but not with the current user data.
I have the following code:
models.py
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
... # Some code left out for brevity
class AuthUser(AbstractBaseUser, PermissionsMixin):
"""
A fully featured User model with admin-compliant permissions that uses
a full-length email field as the username.
Email and password are required. Other fields are optional.
"""
username = models.CharField(_('username'), max_length=30, unique=True,
help_text=_('Required. 30 characters or fewer. Letters, numbers and #/./+/-/_ characters'),
validators=[validators.RegexValidator(re.compile('^[\w.#+-]+$'), _('Enter a valid username.'), _('invalid'))])
email = models.EmailField(_('email address'), max_length=254, unique=True)
is_staff = models.BooleanField(_('staff status'), default=False,
help_text=_('Designates whether the user can log into this admin site.'))
is_active = models.BooleanField(_('active'), default=True,
help_text=_('Designates whether this user should be treated as '
'active. Unselect this instead of deleting accounts.'))
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
objects = AuthUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username'] # Not needed since it has been mentioned in USERNAME_FIELD and is required by default and cannot be listed in REQUIRED_FIELDS
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
def get_absolute_url(self):
return "/users/%s/" % urlquote(self.username)
def __str__(self):
return self.username
def get_full_name(self):
# The user is identified by their email address
return self.email
def get_short_name(self):
# The user is identified by their username
return self.username
def email_user(self, subject, message, from_email=None):
"""
Sends an email to this User.
"""
send_mail(subject, message, from_email, [self.email])
forms.py
from django.contrib.auth.forms import UserChangeForm
from .models import AuthUser
class AuthUserChangeForm(UserChangeForm):
"""
A form for updating users. Includes all the fields on the user, but
replaces the password field with admin's password hash display field.
"""
password = ReadOnlyPasswordHashField(label="password",
help_text="""Raw passwords are not stored, so there is no way to see this
user's password, but you can change the password using <a href=\"password/\">
this form</a>""")
class Meta:
model = AuthUser
fields = ('username', 'email', 'password', 'is_active', 'is_staff', 'is_superuser', 'user_permissions')
widgets = {
'email': TextInput(),
}
def clean_password(self):
# Regardless of what the user provides, return the initial value.
# This is done here, rather than on the field, because the field does
# not have access to the initial value
return self.initial["password"]
views.py
class UpdateUserView(LoginRequiredMixin, FormView):
template_name = 'users/update_user.html'
form_class = AuthUserChangeForm
# get current user object
def get_object(self, queryset=None):
return self.request.user
urls.py
url(r'^profile/update/', UpdateUserView.as_view(), name='update_profile'),
What I'm I missing?
FormView is not the appropriate base class here: it doesn't know about model forms and doesn't define a get_object method. Use UpdateView instead.