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.
Related
I have a custom user model, I would like my CustomUser model to have a OneToOne relationship with the Person model; Since I want to first register persons and then create a username for them, since not all registered people need to have a username.
I have tried the code shown below; but what I get is the following error:
Cannot assign "1": "CustomUser.dni" must be a "Person" instance.
But, the ID number is already registered in the Person table
P.S. If anyone has a better suggestion for getting people to register first and then create a username for them when strictly necessary, I'd really appreciate it.
models.py
from .managers import CustomUserManager
# MODEL PERSON
class Person(models.Model):
dni = models.CharField('Número de DNI', max_length=8, unique=True)
...
# MODEL CUSTOMUSER
class CustomUser(AbstractBaseUser, PermissionsMixin):
dni = models.OneToOneField(Person, on_delete=models.CASCADE, unique=True)
email = models.EmailField('Correo electrónico', max_length=355, unique=True)
is_staff = models.BooleanField(default=True)
is_active = models.BooleanField(default=True)
is_superuser = models.BooleanField(default=False)
is_admin = models.BooleanField(default=False)
objects = CustomUserManager()
USERNAME_FIELD = 'dni'
REQUIRED_FIELDS = ['email']
...
managers.py
from django.contrib.auth.models import BaseUserManager
class CustomUserManager(BaseUserManager):
def create_user(self, dni, email, password=None):
if not dni:
raise ValueError('Debe ingresar el número de DNI.')
person = self.model(
dni=dni,
email=self.normalize_email(email),
)
person.set_password(password)
person.save(using=self._db)
return person
def create_superuser(self, dni, email, password=None):
person = self.create_user(
dni,
password=password,
email=email,
)
person.is_admin = True
person.is_superuser = True
person.save(using=self._db)
return person
settings.py
AUTH_USER_MODEL = 'accounts.CustomUser'
views.py
from accounts.forms import RegisterForm
class RegisterView(View):
form_class = RegisterForm
initial = {'key': 'value'}
template_name = 'accounts/register.html'
def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
return render(request, self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
form.save()
username = form.cleaned_data.get('username')
messages.success(request, f'Cuenta creada para exitosamente.')
return redirect(to='/')
return render(request, self.template_name, {'form': form})
I want to register a person in the Person table.
After creating the person, you should only be able to create their username and password, from the administration site; that is, an external person cannot register on the site, because the site will have many people in the database, but only some of them will be users of the system.
I hope this image helps clarify the idea.
Process flow
What you're probably looking to do is subclass the django registration form. Then you can change the fields that are required.
I believe the password field would be required by default so a NULL value would not be valid. However, you could quite easily set the password as a randomly generated UUID and then manually change it in the admin panel.
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
Thanks in advance, i'm learning Django and can't figure how to override all auth forms. Quick explanation first, I have a custom user model
class PersoUser(AbstractBaseUser):
email = models.EmailField(
verbose_name="Email Adress", max_length=200, unique=True)
username = models.CharField(
verbose_name="username", max_length=200, unique=True)
first_name = models.CharField(verbose_name="firstname", max_length=200)
last_name = models.CharField(verbose_name="lastname", max_length=200)
date_of_birth = models.DateField(verbose_name="birthday")
is_admin = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
objects = PersoUserManager()
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["date_of_birth", "username"]
....
and I would want to add date_of_birth field to my signup page, so I followed the official doc to override the specif form used by all auth SignupView https://django-allauth.readthedocs.io/en/latest/forms.html#signup-allauth-account-forms-signupform
which leads to ( in Book_store/forms.py )
from all auth.account.forms import SignupForm from users. models import PersoUser
class PersoUserRegisterForm(SignupForm):
class Meta:
model = PersoUser
fields = ["username", "email", "first_name",
"last_name", "date_of_birth", "password1", "password2"]
def save(self, request):
# Ensure you call the parent class's save.
# .save() returns a User object.
user = super(PersoUserRegisterForm, self).save(request)
# Add your processing here.
# You must return the original result.
return user
in my settings/base.py
ACCOUNT_FORMS = {'signup': 'Book_store.forms.PersoUserRegisterForm'}
My account/signup.html template just refers to {{form.as_p}} and it doesn't display the extra fields specified in PersouserRegisterForm just the default ones
I don't see what I'm missing, Thanks for reading
EDIT: And Signing up fail because it violates not-null constraint for date_of_birth
You are overriding the save method but not saving user with the date_of_birth field .
def save(self, request):
user = super(PersoUserRegisterForm, self).save(request)
user.date_of_birth = self.cleaned_data['date_of_birth']
user.save()
return user
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 customizes Django's authentication and now every time I try to create a new user using Django Admin this user's password is saved directly, without being hashed. I found out that create_user method from BaseUserManager class is not being called. When I create a superuser using bash it is done properly. Is there anything wrong with my model?
settings.py
AUTH_USER_MODEL = 'authentication.BaseAccount'
apps/authentication/models.py
class BaseAccountManager(BaseUserManager):
def create_user(self, email, password=None, **kwargs):
if not email:
raise ValueError('Users must have a valid email address.')
account = self.model(
email=self.normalize_email(email)
)
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.is_staff = True
account.save()
return account
class BaseAccount(AbstractBaseUser):
email = models.EmailField(unique=True)
first_name = models.CharField(max_length=40, blank=True)
last_name = models.CharField(max_length=40, blank=True)
is_admin = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
objects = BaseAccountManager()
USERNAME_FIELD = 'email'
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
def has_perm(self, perm, obj=None):
return self.is_admin
def has_module_perms(self, app_label):
return self.is_admin
For future readers, because this issue also got me banging my head against the wall...
To access that precious create_user() method from anywhere in your django project :
from django.contrib.auth import get_user_model
class SomeClass():
# This could be a form, for example...
def some_function():
get_user_model().objects.create_user()
If you call it from the save() method of some custom form, please take proper care of the commit parameter, and don't forget to register your custom user model in the settings.py file, as per the Django documentation.
I found an answer to your question about passwords, but I still don't know how to get the create_user function to be called correctly. The example removes username in favor of just an email. I am still looking for a way to hit the create_user function defined in the custom UserManager inerhiting from BaseUserManager. My custom manager sends an email with a password reset link and a one time token... which would avoid the need to actually set a hashed password from django admin.
https://www.caktusgroup.com/blog/2013/08/07/migrating-custom-user-model-django/
first create your own forms:
# appname/forms.py
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from appname.models import CustomUser
class CustomUserCreationForm(UserCreationForm):
"""
A form that creates a user, with no privileges, from the given email and
password.
"""
def __init__(self, *args, **kargs):
super(CustomUserCreationForm, self).__init__(*args, **kargs)
del self.fields['username']
class Meta:
model = CustomUser
fields = ("email",)
class CustomUserChangeForm(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.
"""
def __init__(self, *args, **kargs):
super(CustomUserChangeForm, self).__init__(*args, **kargs)
del self.fields['username']
class Meta:
model = CustomUser
then use those forms:
# appname/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.utils.translation import ugettext_lazy as _
from appname.models import CustomUser
from appname.forms import CustomUserChangeForm, CustomUserCreationForm
class CustomUserAdmin(UserAdmin):
# The forms to add and change user instances
# The fields to be used in displaying the User model.
# These override the definitions on the base UserAdmin
# that reference the removed 'username' field
fieldsets = (
(None, {'fields': ('email', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_name')}),
(_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
'groups', 'user_permissions')}),
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('email', 'password1', 'password2')}
),
)
form = CustomUserChangeForm
add_form = CustomUserCreationForm
list_display = ('email', 'first_name', 'last_name', 'is_staff')
search_fields = ('email', 'first_name', 'last_name')
ordering = ('email',)
admin.site.register(CustomUser, CustomUserAdmin)