#python #django #django-models
#python #django #django-модели
Вопрос:
Мне было интересно преобразовать поле электронной почты из AbstractBaseUser в моей пользовательской реализации в собственную модель. Идея в том, что я могу потребовать от пользователей зарегистрироваться, указав адрес электронной почты, а затем, как только они подтвердят, я смогу продолжить и записать создание записи пользователя в таблицу User.
Однако я сталкиваюсь с ошибкой с django/db/models/fields/related_descriptors.py когда я пытаюсь создать суперпользователя (я уже настроил UserManager так, чтобы не normalize_email для моего внешнего ключа электронной почты). Ошибка гласит:
ValueError: Cannot assign "1": "User.email" must be a "Email" instance
Я не уверен, как конкретно исправить эту ошибку, и буду признателен за любую помощь. Ниже приведена моя реализация:
* Кроме того, является ли разбиение электронной почты на собственную таблицу излишне сложным?
Models.py
from django.db import models
from django.contrib.auth import get_user_model
from django.contrib.auth.models import PermissionsMixin #, UserManager
from django.contrib.auth.validators import UnicodeUsernameValidator
from django.contrib.auth.base_user import AbstractBaseUser
from django.core.validators import EmailValidator, MinLengthValidator
from django.core.mail import send_mail
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from .utils import CustomUsernameValidator, username_blacklist_validator
from .managers import EmailManager, UserManager
from phonenumber_field.modelfields import PhoneNumberField
from enum import Enum
def avatar_upload_to(instance, filepath):
return 'user_images/{username}/avatar/{filepath}'.format(username=instance.user.id, filepath=filepath)
class GenderChoice(Enum):
"""Subclass of Enum for gender profile choices"""
MALE = "Male"
FEMALE = "Female"
F2M = "F2M"
M2F = "M2F"
OTHER = "Other"
class Gender(models.Model):
"""
Gender dimension table
"""
#gender = models.CharField(
# _('gender'),
# max_length=32,
# choices=[(tag.name, tag.value) for tag in GenderChoice],
# default=GenderChoice.UNSPECIFIED.value
# null=True,
# blank=True
# )
gender = models.CharField(_('gender'), max_length=32, null=True, blank=True)
class Email(models.Model):
"""
User email address information
"""
# Validators
email_validator = EmailValidator()
# Model fields
email = models.EmailField(
_('email address'),
unique=True,
validators=[email_validator]
)
signup_date = models.DateTimeField(_('date joined'), default=timezone.now)
is_verified = models.BooleanField(
_('verified'),
default=False,
help_text=_(
'Designates whether the email address is active in the system via a verification link.'
),
)
verification_sent_datetime = models.DateTimeField(
_('verification sent date'),
default=timezone.now
)
verification_expiration_datetime = models.DateTimeField(
_('verification expiration date'),
default=timezone.now() timezone.timedelta(hours=2)
)
verification_sent_count = models.IntegerField(
_('verification sent count'),
default=1
)
objects = EmailManager()
EMAIL_FIELD = 'email'
REQUIRED_FIELDS = ['email']
class Meta:
ordering = ['email']
verbose_name = _('email')
verbose_name_plural = _('emails')
def __str__(self):
"""
Returns a string representation of this 'User'. This string is used when a 'User' is printed in the console.
"""
return self.email
def email_user(self, subject, message, from_email=None, **kwargs):
"""
Send an email to this user.
"""
send_mail(subject, message, from_email, [self.email], **kwargs)
class PhoneNumber(models.Model):
"""
User phone number information
"""
phone_number = PhoneNumberField(null=True, blank=False) #default=' 10000000000')
associated_account_count = models.IntegerField(_('associated account count'), default=0)
class User(AbstractBaseUser, PermissionsMixin):
"""
Custom user to be used for the Titan project
"""
# Validators
username_validator = UnicodeUsernameValidator()
custom_username_validator = CustomUsernameValidator()
# Default values
avatar_default = 'user_images/_default/avatar/default.jpg'
# Model fields
email = models.OneToOneField(
Email, on_delete=models.CASCADE, primary_key=False
)
phone_number = models.ForeignKey(PhoneNumber, on_delete=models.SET_NULL, blank=False, null=True, primary_key=False)
username = models.CharField(
_('username'),
max_length=20,
unique=True,
help_text=_('Letters, numbers, dashes, and underscores only. Username must be between 3 and 20 characters.'),
validators=[
username_validator,
custom_username_validator,
username_blacklist_validator,
MinLengthValidator(3),
],
error_messages={
'unique': _('A user with that username already exists.'),
},
)
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)
name = models.CharField(_('name'), max_length=100, blank=True)
birth_date = models.DateField(null=True, blank=True)
city = models.CharField(max_length=128, null=True, blank=True)
country = models.CharField(max_length=128, null=True, blank=True)
gender = models.ForeignKey(Gender, on_delete=models.SET_NULL, null=True)
bio = models.TextField(max_length=500, null=True, blank=True)
avatar = models.ImageField(max_length=255, null=False, blank=False, default=avatar_default) #avatar = models.ImageField(upload_to=upload_to, max_length=255, default=avatar_default)
website = models.CharField(max_length=255, null=True, blank=True)
is_private = models.BooleanField(_('private'), default=False, help_text=_(
'Designates whether this user profile is private.'
)
)
friend_count = models.IntegerField(_('friend count'), null=False, blank=False, default=0)
follower_count = models.IntegerField(_('follower cou nt'), null=False, blank=False, default=0)
is_verified = models.BooleanField(_('verified'), null=False, blank=False, default=False, help_text=_(
'Designates whether the user has gone through government issued id verification.'
)
)
objects = UserManager()
EMAIL_FIELD = 'email__email'
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
class Meta:
ordering = ['username']
verbose_name = _('user')
verbose_name_plural = _('users')
def __str__(self):
"""
Returns a string representation of this 'User'. This string is used when a 'User' is printed in the console.
"""
return self.username
def get_name(self):
"""
Returns the name of the user.
"""
return name.strip()
def email_user(self, subject, message, from_email=None, **kwargs):
"""
Send an email to this user.
"""
send_mail(subject, message, from_email, [self.email_id__email], **kwargs)
# The following are class bits based off of the nnmware NnmwareUser model
def _ctype(self):
return ContentType.objects.get_for_model(get_user_model())
class PhoneNumberVerification(models.Model):
"""
Phone number verification table. We allow phone numbers to be associated with multiple
accounts unlike email addresses so use a separate table to track verification.
"""
phone_number = models.ForeignKey(PhoneNumber, on_delete=models.CASCADE, null=False, blank=False)
user = models.ForeignKey(User, on_delete=models.CASCADE, null=False, blank=False)
signup_date = models.DateTimeField(_('date joined'), default=timezone.now)
is_verified = models.BooleanField(
_('verified'),
default=False,
help_text=_(
'Designates whether the email address is active in the system via a verification link.'
),
)
verification_sent_datetime = models.DateTimeField(_('verification sent date'),
default=timezone.now
)
verification_expiration_datetime = models.DateTimeField(
_('verification expiration date'),
default=timezone.now() timezone.timedelta(hours=2)
)
verification_sent_count = models.IntegerField(
_('verification sent count'),
default=1
)
Managers.py
from django.contrib.auth.base_user import BaseUserManager
class EmailManager(BaseUserManager):
def _create_email(self, email, **extra_fields):
"""
Create and save a user email address.
"""
if not email:
raise ValueError('The given email must be set')
email = self.normalize_email(email)
#extra_fields.setdefault('is_verified', False)
#extra_fields.setdefault('verification_expiration_datetime', timezone.now timedelta(hours=2)
useremail.save(using=self._db)
return useremail
class UserManager(BaseUserManager):
use_in_migrations = True
def _create_user(self, username, email, password, **extra_fields):
"""
Create and save a user with the given username, email, and password.
"""
if not username:
raise ValueError('The given username must be set')
if not self.verify_is_integer(email):
raise ValueError('The given email is not an integer')
email = email #email = self.normalize_email(email)
username = self.model.normalize_username(username)
user = self.model(username=username, email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(self, username, email=None, password=None, **extra_fields):
extra_fields.setdefault('is_staff', False)
extra_fields.setdefault('is_superuser', False)
return self._create_user(username, email, password, **extra_fields)
def create_superuser(self, username, email, password, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
if extra_fields.get('is_staff') is not True:
raise ValueError('Superuser must have is_staff=True.')
if extra_fields.get('is_superuser') is not True:
raise ValueError('Superuser must have is_superuser=True.')
return self._create_user(username, email, password, **extra_fields)
def verify_is_integer(self, field):
return isinstance(field, int)
Ответ №1:
Я думаю, вы можете обновить свой _create_user
метод следующим образом:
def _create_user(self, username, email, password, **extra_fields):
"""
Create and save a user with the given username, email, and password.
"""
if not username:
raise ValueError('The given username must be set')
if not self.verify_is_integer(email):
raise ValueError('The given email is not an integer')
username = self.model.normalize_username(username)
user = self.model(username=username, email_id=email, **extra_fields). # using email_id instead of email
user.set_password(password)
user.save(using=self._db)
return user
Здесь email
поле ожидает экземпляр электронной почты, но здесь вы создаете экземпляр модели электронной почты и передаете его первичный ключ в качестве параметра. Поэтому использование email_id
вместо email
должно решить вашу проблему.
Комментарии:
1. Блестяще — большое вам спасибо! Я полностью пропустил эту строку в методе _create_user.