Django missing a migration in the 'auth' app - python

I'm trying to work on a Django project on a different computer than the one I usually do. However, when trying to run the following migration:
from django.conf import settings
import django.contrib.postgres.fields
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('auth', '0010_auto_20180727_1345'),
('lucy_web', '0183_auto_20180814_1505'),
]
operations = [
migrations.CreateModel(
name='GoogleCredentials',
fields=[
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('user', models.OneToOneField(limit_choices_to={'is_staff': True}, on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)),
('token', models.CharField(max_length=255, null=True)),
('refresh_token', models.CharField(max_length=255, null=True)),
('token_uri', models.CharField(max_length=255, null=True)),
('client_id', models.CharField(max_length=255, null=True)),
('client_secret', models.CharField(max_length=255, null=True)),
('scopes', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=255), null=True, size=None)),
],
options={
'abstract': False,
},
),
]
I get this error message:
django.db.migrations.exceptions.NodeNotFoundError: Migration lucy_web.0184_googlecredentials dependencies reference nonexistent parent node ('auth', '0010_auto_20180727_1345')
Following https://groups.google.com/forum/#!topic/django-users/m59NufO1GI8, I went into the django/contrib/auth/migrations directory and found that indeed, there are 9 migrations, but the 10th migration is not there:
Kurts-MacBook-Pro:auth kurtpeek$ ls migrations
0001_initial.py
0002_alter_permission_name_max_length.py
0003_alter_user_email_max_length.py
0004_alter_user_username_opts.py
0005_alter_user_last_login_null.py
0006_require_contenttypes_0002.py
0007_alter_validators_add_error_messages.py
0008_alter_user_username_max_length.py
0009_alter_user_last_name_max_length.py
__init__.py
__pycache__/
Kurts-MacBook-Pro:auth kurtpeek$ pwd
/Users/kurtpeek/.local/share/virtualenvs/lucy-web-oCa8O1zi/lib/python3.7/site-packages/django/contrib/auth
The problem is, I haven't checked the virtual env into version control, and I don't have access to the other computer at the moment. I also feel like I shouldn't have to check in the Django source code for the project, though.
My question is: how did this situation likely come to pass? What I suspect is that it is associated with the way the django.contrib.auth.User model is customized in the project. We have a lucy_web/models/user.py with content similar to the following:
from django.contrib.auth.models import User
from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver
from auditlog.registry import auditlog
from lucy_web.models import LucyGuide
from crequest.middleware import CrequestMiddleware
from lucy_web.lib import intercom
# overrides built in user to provide reasonable unique constraints
User._meta.get_field('username')._unique = True
# TODO(kayla): fix this, the unique constraint on email doesn't seem to be working in prod
# I suspect the username unique constraint only works because it's the default setting
User._meta.get_field('email')._unique = True
auditlog.register(User)
#property
def family(self):
if hasattr(self, 'employee_family'):
return self.employee_family
elif hasattr(self, 'partner_family'):
return self.partner_family
else:
return None
#property
def is_employee(self):
return hasattr(self, 'employee_family')
#property
def user_apn(self):
return self.userapn_set.order_by("created_at").last()
#property
def lucy_guide(self):
try:
return self.lucyguide
except LucyGuide.DoesNotExist:
return None
def user__str__(self):
"""
User model's __str__ method.
We use a different name than '__str__' because dunder names
are reserved by Python and subject to breakage without warning
(cf. https://www.python.org/dev/peps/pep-0562/#backwards-compatibility-and-impact-on-performance).
"""
return f"{self.first_name} {self.last_name}".strip()
#property
def profile(self):
if hasattr(self, 'user_profile'):
return self.user_profile
else:
return None
#property
def using_app(self):
if hasattr(self, 'user_profile'):
return self.user_profile.using_app
return False
#property
def activation_code(self):
if hasattr(self, 'user_profile'):
return self.user_profile.activation_code
return False
User.add_to_class('family', family)
User.add_to_class('is_employee', is_employee)
User.add_to_class('user_apn', user_apn)
User.add_to_class('lucy_guide', lucy_guide)
User.add_to_class('__str__', user__str__)
User.add_to_class('profile', profile)
User.add_to_class('using_app', using_app)
In short, we use the add_to_class method to add properties to Django's User model. This does not seem to be one of the recommended methods described in https://docs.djangoproject.com/en/2.1/topics/auth/customizing/#extending-user. Is this perhaps the source of the problem?

If you are using add_to_class to the Django User model it will detect a change in the external package and apply the migration in the external packages migrations directory (usually in your virtual environment).
It will be referenced in the migration file provided (lucy_web.0184_googlecredentials) at the top if the User model is required for the later migration to be created. The user migration number chosen is the last available at the time of creation to ensure compatibility.
As a bad workaround you could make migrations on the new computer which should create the new user model model migration. Find the file name and alter it temporarily in the migration file lucy_web.0184_googlecredentials

I also used add_to_class() and created new project in the same enviroment.
It need to delete all migrations and *.pyc in subfolders "migrations" in your env. Leave only __init__.py files.
...\Envs\blah-blah-env\Lib\site-packages\django\contrib\...
e.g. path (one of them):
c:\Users\user\Envs\blah-blah-env\Lib\site-packages\django\contrib\auth\migrations\
If you get after (source):
django.db.migrations.exceptions.InconsistentMigrationHistory: Migration admin.0001_initial is applied before its dependency account.0001_initial on database 'default'
delete db or clear migration table, delete migration files in your project. Good luck!

Related

Django TypeError: argument of type 'datetime.datetime' is not iterable

When I try to add a new field or modify my model in any way I get the following error when I try to migrate to the database:
File "C:\Users\uddin\Envs\web\lib\site-packages\django\db\models\fields\__init__.py", line 1913, in get_prep_value if value and ':' in value: TypeError: argument of type 'datetime.datetime' is not iterable
This is what my model.py file looks like
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
from django.urls import reverse
from PIL import Image
class Post(models.Model):
title = models.CharField(max_length=100)
title2 = models.CharField( max_length=100)
content = models.TextField()
content2 = models.TextField(default=None)
post_image = models.ImageField(upload_to='post_pics')
post_image2 = models.ImageField(upload_to='post2_pics')
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})
Full traceback of error
I've added the full traceback in a pastebin as not to make the post too long
front.0008_post_location file
# Generated by Django 3.1.5 on 2021-03-06 01:21
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('front', '0007_post_content'),
]
operations = [
migrations.AddField(
model_name='post',
name='location',
field=models.GenericIPAddressField(default=django.utils.timezone.now),
preserve_default=False,
),
]
Any help would be appreciated
The problem is that at some point you added a GenericIPAddressField named location, and gave as the default a callable that returns a datetime. Running the migration raises an exception since a datetime isn't a valid IP address.
When it didn't work, you just deleted the location field. The problem is that Django's migration system records a history of model changes, so removing it just created a new migration (when you ran makemgirations), keeping the bug forever enshrined in this migration file.
Next time, just fix the problem at the time and remove the buggy migration.
For now, you can simply edit the migration to make that line work. Since a later migration deletes the field, it doesn't really matter how you do that. I would simply replace the default with null=True.
Another option would be to use squashmigrations, which might (or might not) remove these redundant migrations that add and then remove a field. Or, if you're still at an early stage in your program, you can reset your migration history. There are various Stack Overflow questions on that subject.

How to use a user as a ForeignKey in a Django model

I've got this model in my Django application:
class ClubSession(models.Model):
location = models.CharField(max_length=200)
coach = models.ForeignKey('auth.User', on_delete=models.CASCADE)
date = models.DateTimeField(default=now)
details = models.TextField()
def __str__(self):
return self.title
I can run python manage.py makemigrations club_sessions without issue but when I thn run python manage.py migrate club_sessions I get ValueError: Field 'id' expected a number but got 'username'. username is a superuser and already exists.
How do I resolve this?
This is the latest migration:
# Generated by Django 3.0.6 on 2020-05-28 15:07
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('club_sessions', '0004_auto_20200528_1450'),
]
operations = [
migrations.AlterField(
model_name='clubsession',
name='coach',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='clubsession',
name='location',
field=models.CharField(max_length=200),
),
]
By default Django lets a ForeignKey refer to the primary key of the target model. This also has some advantages to make relations more uniform.
If you really want to save the username in the ForeignKey, you can specify a to_field=… parameter [Django-doc] and let it refer to a column that is unique (the username of the default User model is unique), so we can refer to it with:
from django.conf import settings
class ClubSession(models.Model):
location = models.CharField(max_length=200)
coach = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
to_field='username'
)
date = models.DateTimeField(default=now)
details = models.TextField()
def __str__(self):
return self.title
You will need to remove the already existing migration and make a new one in order to migrate the database properly.
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.

How to override the migrations of a third-party django package

All the previous answers I came across are not clear or old.
I have a third-party package installed and migrations ran.
Problem is the third-party package uses an Interger field to reference users(based on the assumption that the app is using the default django user), but in my case I am using a uuid for user IDs
package models.py
class UserDashboardModule(models.Model):
title = models.CharField(verbose_name=_('Title'), max_length=255)
user = models.PositiveIntegerField(verbose_name=_('user'))
column = models.PositiveIntegerField(verbose_name=_('column'))
order = models.IntegerField(verbose_name=_('order'))
collapsed = models.BooleanField(verbose_name=_('collapsed'), default=False)
...
# Migrations of third-party package
operations = [
migrations.CreateModel(
name='UserDashboardModule',
fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)),
('title', models.CharField(verbose_name='Title', max_length=255)),
('module', models.CharField(verbose_name='module', max_length=255)),
('app_label', models.CharField(verbose_name='application name', max_length=255, blank=True, null=True)),
('user', models.PositiveIntegerField(verbose_name='user')),
]
),
]
My user model is like this
class User(AbstractUser):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
...
this setup makes it impossible to use the third-party package and I will like to maintain my uuids on users table.
What I want to do is to somehow override this migration and turn the user field to a uuid instead of an interger field.
PS: I have tried creating an empty migration in my users app and wrote the migration but it didn't work.
don't know this is gonna work or not , you can set id of user model as whatever this app is comfortable to work with . then make a migration file that depends on the last migration of other app then convert your model and foreign key to whatever you want it to be .
This can be done with django's MIGRATION_MODULES setting.
For example, I needed to override the migrations in the puput application, for a very similar reason to yours (they hard coded the name of what should have been a swappable dependency model).
I copied all of puput's migration files to a directory in one of my own apps, in my case app/custom_puput_migrations and edited them as I saw fit.
Then in settings I put:
MIGRATION_MODULES = {'puput': 'app.custom_puput_migrations')
Then I recreated my database from scratch and away it went.
The downside of this is that you have to maintain those migrations; each time you upgrade the third party app version, you need to check for and copy across any migrations they may have made.

Limit custom migration to one model

I have decided to implement registration option for my website, I used this tutorial (signup with confirmation part). Following this material I have created Profile module to hold some info. Everything (seems to be) working now, but the problem is that old profiles throws relatedObjectDoesNotExist error. According to these two questions (first, second) I need to make a migration to create profiles for old user accounts. I tried to follow this doc as suggested in one of the answers, but then I try to run a migration I get following error: KeyError: ('stv', 'bazinekaina')
stv is the name of my app and bazinekaina is the name of the next model after the one I need to create profiles.
How to limit migration to only the first model?
My relevant models.py code:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
email_confirmed = models.BooleanField(default=False)
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.EmailField(max_length=254)
#receiver(post_save, sender=User)
def update_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.get(user=instance)
instance.profile.save()
#next model, this one throws an error, despite the fact it should not be touched at all
class BazineKaina(models.Model):
#bazines kainos modelis
bazka = models.DecimalField(max_digits=5, decimal_places=2)
data = models.DateField(auto_now=False, auto_now_add=True)
def __str__(self):
return str(self.bazka)
class Meta:
verbose_name_plural = "Bazinė kaina"
get_latest_by = 'data'
Migration file crated after using python manage.py makemigrations --empty stv command, named 0001_initial.py:
from django.db import migrations, models
def sukurti_profilius(apps, schema_editor):
Profile = apps.get_model("stv", "Profile")
for user in Profile.objects.all():
Profile.objects.get_or_create(user=user)
class Migration(migrations.Migration):
dependencies = [
]
operations = [
]
How and what I should fix to stop migrations from applying to the unrelated models (and throwing error)?
Sorry if it is basic question, but whole django is still very new to me.
If your migration is named 0001_initial then it means that you don't have a migration that actually creates the table for the profile model.
Remove that migration and run:
python manage.py makemigrations stv
python manage.py makemigrations stv --empty --name create_profiles
Then you should have a file 0002_create_profiles.py and put the logic to create profiles there.

Django fixtures save with default value

I'm using Django 1.7 and I have a problem with my fixtures.
I would like Django to use the default value or use the save() method to create unspecified values.
Here are my current objects:
# File: uuidable.py
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
class Uuidable(models.Model):
uuid = models.CharField(_('uuid'), blank=True,
null=False, unique=True,
max_length=64, default=uuid.uuid4()) # Tried here
class Meta:
abstract = True
def save(self, *args, **kwargs):
if self.pk is None:
self.uuid = uuid.uuid4() # Tried here also
super().save(*args, **kwargs)
# File: timestampable.py
from django.db import models
from django.utils.translation import ugettext_lazy as _
class Timestampable(models.Model):
created_at = models.DateTimeField(_('created at'), auto_now_add=True)
updated_at = models.DateTimeField(_('updated at'), auto_now=True)
class Meta:
abstract = True
# File: post.py
from project.lib.models.timestampable import Timestampable
from project.lib.models.uuidable import Uuidable
class Post(Timestampable, Uuidable):
title = models.CharField(_('title'), max_length=250, blank=False)
content = models.TextField(_('content'))
def __str__(self):
return self.title
As you can see, when I generate a new Post(), the created_at, updated_at and uuid values are automatically created on save(). But when I use fixtures, I get the following error:
[...]initial_data.yaml': Could not load post.Post(pk=None): UNIQUE constraint failed: post_post.uuid
If I specify a uuid in my fixture file, then I get an error on created_at and then on updated_at. So I have to specify the content of each field, even though I want it to be "automatic".
From the documentation (why is this in the django admin docs ?!), I know that the save() method is not called so this is why everything I put into the save() method doesn't work. But shouldn't the default or auto_now* features be enables/used ?
When fixture files are processed, the data is saved to the database as is. Model defined save() methods are not called, and any pre_save or post_save signals will be called with raw=True since the instance only contains attributes that are local to the model. You may, for example, want to disable handlers that access related fields that aren’t present during fixture loading and would otherwise raise an exception
Is there a way to "force" Django to automatically use the default or auto_now* features for fixtures ? I'm using manage.py syncdb to create all the tables etc.
I have searched on google and stack overflow but couldn't seem to find the right search keywords.
UPDATE-1: The following google group discussion says that objects are saved in raw mode, meaning that auto_now* features are not taken into account. I'm still searching to see if there is a way to hook some model function to the Django fixture saving.
The solution was to use django signals:
import uuid
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.db.models.signals import pre_save
from django.dispatch import receiver
class Uuidable(models.Model):
uuid = models.CharField(_('uuid'), blank=True,
null=False, unique=True,
max_length=64, default=uuid.uuid4())
class Meta:
abstract = True
#receiver(pre_save)
def set_uuid_on_save(sender, instance, *args, **kwargs):
if instance.pk is None:
instance.uuid = uuid.uuid4()
That way, the model/data is populated whatever way you create the model (via shell, fixtures, whatever).
Automatically loading initial data fixtures is deprecated in Django 1.7. One solution is via signals as you mentioned.
Another one that I prefer is to create a python script where you create all the needed data, and execute it in the shell:
python manage.py shell < create_initial_data.py
I think that the problem is when you put default=uuid.uuid4(). The parenthesis are too much, because they imply that you pass the result of uuid.uuid4() to default argument and not the function itself, so you should put default=uuid.uuid4.

Categories