Django: auto capturing username from the User model. not pk - python

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.

Related

PyCharm doesn't recognize user instance when using custom user model in Django

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

Django problem overriding all auth Signup forms

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

Django: Do not allow users to see pages when login details do not match with custom models details

I have custom model where login details have been stored:
class UserRegistration(models.Model):
# Auto updated when data is inserted
created_at = models.DateTimeField(auto_now_add=True, auto_now=False)
# Auto updated when data is altered
updated_at = models.DateTimeField(auto_now_add=False, auto_now=True)
username = models.CharField(max_length=255, null=True)
password = models.CharField(max_length=255, null=True)
first_name = models.CharField(max_length=255, null=True)
last_name = models.CharField(max_length=255, null=True)
def __str__(self):
return self.first_name
urls.py is
url(r'^login/$', view.login_page, name='login_page'),
views.py is
def login_page(request):
if request.method == 'GET':
return render(request, "login_page.html")
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
print username, password
credentials_matches = False # considering credentials are wrong
all_users = UserRegistration.objects.all()
for user in all_users:
if user.username == username and user.password == password:
credentials_matches = True
break
if credentials_matches:
return HttpResponse('success')
# return redirect("http://127.0.0.1:8000/lmtech")
else:
return HttpResponse('fail')
I have other views that I do not wish to show to user if they have not logged in. I though about #login_required() but in this case, user details are in custom model. How do I solve this problem?
You must not make a custom user model like this. Writing your own authentication system is always insecure.
You should use the standard built-in auth framework - you can substitute your own user model but it must inherit from AbstractBaseUser, see the docs. However there doesn't seem to be much reason to do this as you don't add any custom functionality.
On your custom model, you want the first name, last, name, username and password, in User model from django you only have the email more which is optional. So what you can do is to créate a User with the django's model and then use a relationship to have the two other attributes you want.
Here is an example
class User(models.Model):
username = models.CharField()
password
...
class UserMore(models.Model):
user = models.OneToOneField(User)
created_at = ...
updated_at = ...
def __str__(self):
return self.user.username
In this case you then can use the #login_required() of django.
Personnaly, it's how I do.
But if you really want to keep your custom model without the User model of Django, maybe you can keep the user in a session that you initiate in your login view and then in your logout you delete it. In this way you can easily check if the request.session['username'] is defined or not.

Removing case sensitivity from Email in Django login form

I've created a custom UserModel and used Email as main authenticating id instead of username.
The problem that it is case sensitive, as it counts test#gmail.com,Test#gmail.com as 2 different accounts.
I need to force it to deal with both as 1 account ignoring if it upper or lower case.
Here are my files :
models.py
class UserModelManager(BaseUserManager):
def create_user(self, email, password, pseudo):
user = self.model()
user.name = name
user.email = self.normalize_email(email=email)
user.set_password(password)
user.save()
return user
def create_superuser(self, email, password):
'''
Used for: python manage.py createsuperuser
'''
user = self.model()
user.name = 'admin-yeah'
user.email = self.normalize_email(email=email)
user.set_password(password)
user.is_staff = True
user.is_superuser = True
user.save()
return user
class UserModel(AbstractBaseUser, PermissionsMixin):
## Personnal fields.
email = models.EmailField(max_length=254, unique=True)
name = models.CharField(max_length=16)
## [...]
## Django manage fields.
date_joined = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
REQUIRED_FIELD = ['email', 'name']
objects = UserModelManager()
def __str__(self):
return self.email
def get_short_name(self):
return self.name[:2].upper()
def get_full_name(self):
return self.name
signup view in views.py
def signup(request):
if request.method == 'POST':
signup_form = SignUpForm(request.POST)
if signup_form.is_valid():
signup_form.save()
username = signup_form.cleaned_data.get('username')
raw_password = signup_form.cleaned_data.get('password1')
user = authenticate(username=username, password=raw_password)
return redirect('signup_confirm')
else:
signup_form = SignUpForm()
context = {
'signup_form': signup_form,
}
return render(request, 'fostania_web_app/signup.html', context)
def signup_confirm(request):
return render(request, 'fostania_web_app/signup_confirm.html')
the sign up form in forms.py:
class SignUpForm(UserCreationForm):
email = forms.CharField(required=True, help_text='البريد الإلكترونى الخاص بك - يجب ان يكون حقيقى (يستخدم لتسجيل الدخول) ')
name = forms.CharField(required=True, help_text='إسمك الحقيقى - سيظهر كأسم البائع')
password1 = forms.CharField(widget=forms.PasswordInput,
help_text='كلمة المرور - حاول ان تكون سهلة التذكر بالنسبة لك')
password2 = forms.CharField(widget=forms.PasswordInput,
help_text='تأكيد كلمة المرور - إكتب نفس كلمة المرور السابقة مرة أخرى')
class Meta:
model = UserModel
fields = ('email','name', 'password1', 'password2', )
labels = {
'name': 'إسمك الحقيقى - سيظهر كأسم البائع',
'email': 'البربد الإلكترونى Email',
'password1': 'كلمة المرور',
'password2': 'تأكيد كلمة المرور'
}
All that I need now is to make it simply ignores the case sensitivity.
update
here is my login files
urls.py
path('login/', auth_views.login, name='login'),
registartion/login.html
<form method="post">
{% csrf_token %}
البريد الإلكترونى E-Mail <Br>{{ form.username }}
<br><br>
كلمة المرور <Br>{{ form.password }}
<Br><br>
<div align="center">
<button class ="btn btn-primary" submit>تسجيل الدخول</button><br><br>
<a href="{% url 'signup' %}"><button type="button" class="btn btn-warning">
إنشاء حساب جديد</button></a>
</div>
</form>
A cleaner approach might be to override the model field itself if you don't mind losing the formatting of how the user entered their email.
This worked better for me because I only had to change it in one place. Otherwise, you might have to worry about Signup, Login, User Update, API views, etc.
The only method you will have to overwrite is to_python. This will just lowercase anything being saved to the UserModel.email field.
from django.db import models
class LowercaseEmailField(models.EmailField):
"""
Override EmailField to convert emails to lowercase before saving.
"""
def to_python(self, value):
"""
Convert email to lowercase.
"""
value = super(LowercaseEmailField, self).to_python(value)
# Value can be None so check that it's a string before lowercasing.
if isinstance(value, str):
return value.lower()
return value
Your user model would then just be..
# Assuming you saved the above in the same directory in a file called model_fields.py
from .model_fields import LowercaseEmailField
class UserModel(AbstractBaseUser, PermissionsMixin):
email = LowercaseEmailField(unique=True)
# other stuff...
Addendum 2023:
According to this RFC, the local part of the address before the # is to be handled by the mail host at its own terms, it mentions nothing about a standard requiring the local part to be lowercase, neither to handle addresses in a case insensitive way, even if most mail servers will do so nowadays.
This means that a mail server actually can discard mails sent to user.name#mail.tld if the account is set up as User.Name#mail.tld. This is also reflected in the original normalize_email method of Django's BaseUser class, it only forces the domain part to be lowercase for this reason. Considering this, we have to store the local part of the address in is original format.
That being said, we can still enforce a case insensitive uniqueness check before saving the user to the DB. If we make an educated guess, we can assume that even if a mail server was set up to handle addresses case sensitive for historical reasons, that it has started to validate their uniqueness in a case insensitive way to avoid impersonation.
Django 4.0 and beyond
Django 4.0 comes with Functional unique constraints which is probably the most portable and specific solution:
from django.db.models import UniqueConstraint
from django.db.models.functions import Lower
class User:
# ...
class Meta:
constraints = [
UniqueConstraint(
Lower("email"),
name="user_email_ci_uniqueness",
),
]
Django before 4.0
This is not tested and syntax is from memory (also probably you have to display the validation error manually instead on the field because it will be hidden in the form) but it should convey the potential workaround.
from django.db import models
class User:
# ...
email = models.EmailField(
blank=False,
null=False,
)
email_lower = models.EmailField(
blank=False,
null=False,
unique=True,
editable=False,
error_messages={
"unique": "A user is already registered with this email address",
},
)
def clean(self):
super().clean()
self.email_lower = self.email.lower()
Original Post:
You don't need to change much to accomplish this - in your case you just need to change the form and make use of Django's built-in form data cleaners or by making a custom field.
You should use the EmailField instead of a CharField for built-in validation. Also you did not post your AuthenticationForm, but i presume you have changed it to include email instead of username.
With data cleaners:
class SignUpForm(UserCreationForm):
# your code
email = forms.EmailField(required=True)
def clean_email(self):
data = self.cleaned_data['email']
return data.lower()
class AuthenticationForm(forms.Form):
# your code
email = forms.EmailField(required=True)
def clean_email(self):
data = self.cleaned_data['email']
return data.lower()
With a custom field:
class EmailLowerField(forms.EmailField):
def to_python(self, value):
return value.lower()
class SignUpForm(UserCreationForm):
# your code
email = EmailLowerField(required=True)
class AuthenticationForm(forms.Form):
# your code
email = EmailLowerField(required=True)
This way you can make sure that each email is saved to your database in lowercase and that for each login attempt the email is lowercased before compared to a database value.
When creating the account entry, interpret the email as email.lower() to create a normalised entry in your database.
When parsing the email/username from a log in form, you should also use email.lower() to match the database entry.
Note: the normalize_email only sanitises the domain portion of an email address:
classmethod normalize_email(email)
Normalizes email addresses by lowercasing the domain portion of the email address.
For anyone using Postgres and django 2 or higher:
There is a CIEmailField that stores and retrieves the email address case sensitive but compares it case insensitive. This has the benefit of preserving case in the address (should a user want their address to look like JohnDoe#ImportantCompany.com) without allowing multiple accounts with the same email.
Usage is simple:
from django.contrib.postgres.fields import CIEmailField
# ...
class User(AbstractBaseUser, ...):
email = CIEmailField(unique=True, ...)
# ...
Except on migrating I got the error:
django.db.utils.ProgrammingError: type "citext" does not exist
To solve this, follow this so answer and set up the CITextExtension before the first CreateModel migration operation:
from django.contrib.postgres.operations import CITextExtension
class Migration(migrations.Migration):
...
operations = [
CITextExtension(),
...
]
Note: both in the so answer and in the official documentation is stated that you should add it before the first CreateModel migration operation. For me it was sufficient to add it in the first migration that uses the CIEmailField.
class UserManager(BaseUserManager):
def create_user(self, email, name, tc, password=None):
if not email:
raise ValueError('Users must have an email address')
user = self.model(
email=self.normalize_email(email).lower(),
name = name,
tc = tc
)
user.set_password(password)
user.save(using=self._db)
return user

Updating Custom User Model in Django with Class Based UpdateView

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.

Categories