Problems extending my Django user model - python

I have a simple django app, and am trying to set up a custom Django User Model so that I can have users log in with their email field. I think I set everything up well enough, its all fairly straightforward, however when trying to actually run the migrations I get this error:
django.db.migrations.exceptions.InconsistentMigrationHistory: Migration admin.0001_initial is applied before its dependency accounts.0001_initial
Seems weird?
I'm having trouble understanding why it's having trouble understanding.
Heres the User model Im implementing
from __future__ import unicode_literals
from django.db import models
from django.core.mail import send_mail
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.base_user import AbstractBaseUser
from django.utils.translation import ugettext_lazy as _
from .managers import UserManager
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(_('email address'), unique=True)
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
display_name = models.CharField(_('display name'), max_length=30, blank=True)
bio = models.TextField(blank=True)
date_joined = models.DateTimeField(_('date joined'), auto_now_add=True)
is_active = models.BooleanField(_('active'), default=True)
avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
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)

"Using a custom user model when starting a project"
If you’re starting a new project, it’s highly recommended to set up a custom user model, even if the default User model is sufficient for you. This model behaves identically to the default user model, but you’ll be able to customize it in the future if the need arises." reference->django docs
Then you have to specify to setting.py that you are using a new user model (again before you migrate for the first time.)
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
pass
# in settings.py
AUTH_USER_MODEL = 'myapp.MyUser'
I highly recommend checking out the link above.
Now to solve your problem you can delete your migrations in your app path under the migrations directory and reset your database and follow the instructions above.
Changing user model mid project is difficult in django and requires manually editing your schema so since your project is for educational purposes just reset it

Basically, you have already created some User accounts or at least have created superuser account. Since you are changing the User model after I assume you have already made migrations at least once, Django has conflict with old user model with the new one.
Solution is to delete all accounts in admin including superuser. Then delete your migrations files in migrations folder. And also I think delete your sqlite data file. Run migrations again and it should work.
You should have created custom user model before any migration.

One solution is to truncate the django_migrations table.
To do this you have to:
login to your database (In case you use Mysql) using mysql -u root -p
use your_db_name;
SET FOREIGN_KEY_CHECKS=0; to prevent relation errors
DELETE FROM django_migrations; truncates the django_migrations table
SET FOREIGN_KEY_CHECKS=1;
Then you can run your migrations!
Another solution is to run these commands before your migration to avoid migrations error:
python manage.py migrate admin zero --fake
python manage.py migrate auth zero --fake
python manage.py migrate contenttypes zero --fake
python manage.py migrate sessions zero --fake
Warning :
This solution may leave you with a database schema that's
inconsistent with the migrations applied, so only do this if
you know what you're doing. Thanks to #AKX
You can also use this solution too!

Related

Django: Create a superuser in a data migration

Goal: automatically creating a superuser
I'm trying to create a default user, specifically a superuser, in an early data migration, so whenever my Django application is run in my Docker container, it already has a superuser with which I can access the admin site.
I had already tried different options for creating said superuser, and although I have some functioning ones (based on the command parameter of my docker-compose file), I've seen when adding initial data to a Django project, the best practice is to do it through a Data Migration.
My custom user
In my Django project I've extended the AbstactBaseUser so I can change the default username field requirement for the email field. My User is as such:
class UserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
# Implementation...
def create_superuser(self, email, password, **extra_fields):
# Implementation...
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(
verbose_name="email address",
max_length=255,
unique=True,
)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
def __str__(self):
return self.email
Failed attempts
By following the Django documentation here I tried making my superuser in a data migration with the following code, located in a file called 0002_data_superuser in the migrations folder of my app:
def generate_superuser(apps, schema_editor):
User = apps.get_model("users.User")
User.objects.create_superuser(
email=settings.DJANGO_SUPERUSER_EMAIL,
password=settings.DJANGO_SUPERUSER_PASSWORD,
)
print("\nInitial superuser created\n")
class Migration(migrations.Migration):
dependencies = [
('users', '0001_initial'),
]
operations = [
migrations.RunPython(generate_superuser)
]
When running my docker-compose, however, I run into this error:
AttributeError: 'Manager' object has no attribute 'create_superuser'
I've tried debugging by printing the Manager and, indeed, it does not have the create_superuser necessary for this. My next thought was to try to reproduce what create_superuser does myself, but I find it quite impossible given that a lot of methods for managing passwords, hashing, normalizing emails and stuff are not available to me.
I gather from all of this that the problem is that the Manager, for some reason, is not available during migrations, and I'd like to know if there's a solution to this.
Add use_in_migrations = True to your custom manager to make it available during migrations
class UserManager(BaseUserManager):
use_in_migrations = True
...
Then you should be able to use User.objects.create_superuser in your data migration
Be careful though, there are some implications for doing this here in the docs
Reproducing create_user functionality manually
Checking out a bit more the source code of Django I found at there is a way to reproduce the functionality from create_superuser. By importing BaseUserManager and the classmethod make_password from django.contrib.auth.hashers, I came up with this:
from django.contrib.auth.models import BaseUserManager
from django.contrib.auth.hashers import make_password
def generate_superuser(apps, schema_editor):
User = apps.get_model("users.User")
email = settings.DJANGO_SUPERUSER_EMAIL
password = settings.DJANGO_SUPERUSER_PASSWORD
user = User()
user.email = BaseUserManager.normalize_email(email)
user.password = make_password(password)
user.is_staff = True
user.is_superuser = True
user.save()
which does the trick.
Still, I don't like it much as a solution given that it requires reeimplementing an already existing Django method that just is not accessible at this point of the program.
The Django documentation provides clear guide on doing something similar Data Migrations.
First create a empty migration in your user app
python manage.py makemigrations --empty yourappname
This will create a empty migration file, then
import logging
import environ
from django.db import migrations
logger = logging.getLogger(__name__)
def generate_superuser(apps, schema_editor):
from django.contrib.auth import get_user_model
env = environ.Env()
USERNAME = env.str("ADMIN_USERNAME")
PASSWORD = env.str("ADMIN_PASSWORD")
EMAIL = env.str("ADMIN_EMAIL")
user = get_user_model()
if not user.objects.filter(username=USERNAME, email=EMAIL).exists():
logger.info("Creating new superuser")
admin = user.objects.create_superuser(
username=USERNAME, password=PASSWORD, email=EMAIL
)
admin.save()
else:
logger.info("Superuser already created!")
class Migration(migrations.Migration):
dependencies = [("users", "0009_user_full_name")]
operations = [migrations.RunPython(generate_superuser)]
This workflow works well with Docker, once python migrate gets run this will generate a new user.

django.db.utils.OperationalError: no such table: MainApp_user when i try to create superuser

I make some web application, and almost in the end of development process i decided to customize user model to make profile page with many other info. Recently i have found a video where example from django docs is explained, and i made the same code as there was, but at first i delete my file db.sqlite3 and now when i try to create a superuser i do always catch the next error:
django.db.utils.OperationalError: no such table: MainApp_user
Here is my models.py:
class MyUserManager(BaseUserManager):
def create_user(self, username, password):
if not username:
raise ValueError("Mailname field is empty")
if not password:
raise ValueError("You have to set password")
user = self.model(
username=username,
)
user.set_password(password)
user.save(using=self._db)
return user
class User(AbstractBaseUser):
username = models.CharField(max_length=30, unique=True)
password = models.CharField(max_length=64)
name = models.CharField(max_length=30, blank=True)
surname = models.CharField(max_length=30, blank=True)
avatar = models.ImageField(width_field=512, height_field=512)
email_link = models.CharField(max_length=64, blank=True, null=True)
bio = models.CharField(max_length=512, blank=True, null=True)
registered = models.DateField(auto_now_add=True)
USERNAME_FIELD = "username"
REQUIRED_FIELDS = ['password']
objects = MyUserManager()
def __str__(self):
return self.username
Also i've added the next variable in settings.py:
AUTH_USER_MODEL = "MainApp.User"
Why this error happens and how to solve it, help please.
***Ofc, i did made migrations to database
I did found the solution. In my case, when i have deleted db.sqlite3 file i've also deleted "migrations" folder in the app. Of course, i've created it again, but the issue was just in that i forgot to add init.py in migrations folder. And django after migrations applying made only system tables in database(auth_users, contenttypes and other), it didn't save my own models. So that if you ever come up with any similar problem, remember about init.py in migrations folder. Good luck !

Custom User model changes never migrate

I made the mistake of not using a custom user model for the first migration and I paid the price by having to delete all the migrations and makemigrations and migrate again as I switched the project to use my custom user.
models.py
"""
Changes are never picked by makemigrations
"""
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
username = models.CharField(max_length=120, unique=True, blank=False,
validators=[MinLengthValidator(2)])
email = models.EmailField('Email Address', unique=True, blank=False)
FCM_ID = models.CharField(max_length=300, blank=True)
def __str__(self):
return self.username
Everytime I made any changes to the custom user, I have had to delete all migrations related files and apply migrations again just to get past the errors- this SO answer and this.
Now I realized that I needed a newer change, but migrations are not detected after changing the User model. The only solution I have is to delete the migrations again! So far I have done this chore like 5 times and I am tired, what am I doing wrong?
I have tried python3 manage.py makemigration <app_name>, and of course, the custom user model is defined in settings.py using AUTH_USER_MODEL.

Natural Key causes Django tests to fail

Consider a clean django 1.7.7 project with one app called testrunner.
The models look like this:
class Contact(AbstractBaseUser, PermissionsMixin, models.Model):
relation = models.ForeignKey('tests.Relation', related_name='contacts')
username = models.CharField(max_length=200, unique=True)
first_name = models.CharField(max_length=30, null=True, blank=True)
last_name = models.CharField(max_length=30, null=True, blank=True)
email = models.EmailField(null=True, blank=True, 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.')
USERNAME_FIELD = 'username'
def get_full_name(self):
pass
def get_short_name(self):
pass
def get_name(self):
pass
class Relation(models.Model):
name = models.CharField(max_length=200, unique=True)
created_by = models.ForeignKey('tests.Contact', null=True, related_name='created_%(class)s')
modified_by = models.ForeignKey('tests.Contact', null=True, related_name='modified_%(class)s')
def natural_key(self):
return (self.name,)
In the settings.py I've set 'tests.Contact' to by the AUTH_USER_MODEL.
This setup is a clean test to replicate the error I get within a larger environment. The problem is that I cannot run the django tests without it failing on the creation of the test database:
manage.py test
Testing started at 14:39 ...
Creating test database for alias 'default'...
CommandError: Can't resolve dependencies for tests.Contact, admin.LogEntry, tests.Relation in serialized app list.
Process finished with exit code 1
Empty test suite.
When I remove the def natural_key(self) from the Relation model everything works fine.
We would like to use the natural_key on the Relation model for our fixtures, but are unable to get it to work with django tests.
What am I doing wrong?
A very simple solution, yet took me some time to come up with.
tl;dr
Dynamically import serializers.
Dynamically define natural_keys method & assign to model.
def method_where_serializers_is_being_used():
# import serlializers
from django.core import serializers
# define natural_key method
def natural_key(self):
return some_unique_id
DjangoModel.natural_key = natural_key
It's a kind of a hack, but its working fine
The problem here is a circular dependency involving natural keys that Django fails to handle properly. Since this post was made, the loading of such data was already much improved, but serialization still breaks on a (now pretty obsolete) check for circular dependencies.
This is an open bug and reported here: https://code.djangoproject.com/ticket/31051

Django ForeignKey to AbstractBaseUser

I'm trying to set up an app that will handle reviews about registered users. So in my Review model, I want to have a ForeignKey to my User model.
I'm using a custom user profile that looks like this:
#In /profiles/models.py
class MyUser(AbstractBaseUser):
email = models.EmailField(
verbose_name='email address',
max_length=255,
unique=True,
)
company = models.CharField(default="", max_length=200)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
objects = MyUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['company']
I have included it with settings.py AUTH_USER_MODEL = "profiles.MyUser". It works fine with registration, creating users etc. So I know its working.
In my review model I write the following:
class Review(models.Model):
company = models.ForeignKey(settings.AUTH_USER_MODEL)
reviewer = models.ForeignKey(Reviewer)
rating = models.IntegerField(default=0)
review = models.TextField()
pub_date = models.DateTimeField('date published')
Instead of settings.AUTH_USER_MODEL I have also tried writing profiles.MyUser, 'profiles.MyUser' and MyUser.
I can successfully use the python manage.py makemigrations reviews command. But when I do python manage.py migrate I get errors no matter what version I use above.
The error I get is the following:
ValueError: Lookup failed for model referenced by field reviews.Review.company: profiles.MyUser
nejc92 comment was correct. I had migrated my database earlier before I set AUTH_USER_MODEL for the first time.
I removed my whole database and created new migrations for all apps and migrated everything again from scratch. It then worked.
Sounds like a bug(?) to me.

Categories

Resources