Using django-custom-user in existing django-cms project - django

I have an existing django-cms ver. 3.1.3 project where I would like to replace the default django User model with the one found in django-custom-user (to have email as user name for my users). I have added the custom_user app to my INSTALLED_APPS and set AUTH_USER_MODEL = 'custom_user.EmailUser'. Finally I applied the migrations.
Everything seems to work fine in my custom models, but the django-cms models that have a reference to the auth_user table (GlobalPagePermission, PagePermission, PageUser and UserSettings) are not updated to have a foreign-key reference to the new custom user table.
The django-cms documentation says that is generally advisable to add custom user models in the beginning of a project, but here I am, in the middle of a project, and would very much like to avoid having to delete my cms models (with data) to make it work.
If I were in the beginning of a project, and had added the custom user model before migrating the django-cms models, would the django-cms models actually get a reference to the custom user model table instead of the default one?
Is there any way for me to make migrations for the django-cms models, so they use the new custom user model instead of the default one?
UPDATE
Im trying to implement what yakky suggested:
from __future__ import unicode_literals
from django.conf import settings
from django.db import models, migrations
import django.db.models.deletion
from django.utils.translation import ugettext_lazy as _
def up(apps, schema_editor):
PageUser = apps.get_model("cms", "PageUser")
db_alias = schema_editor.connection.alias
for pu in PageUser.objects.all():
pu['emailuser_ptr_id'] = pu['user_ptr_id']
pu.save()
def down(apps, schema_editor):
PageUser = apps.get_model("cms", "PageUser")
db_alias = schema_editor.connection.alias
for pu in PageUser.objects.all():
pu['user_ptr_id'] = pu['emailuser_ptr_id']
pu.save()
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
(‘myapp’, ‘previous-migration’),
]
operations = [
migrations.RenameField(
model_name='PageUser',
old_name='user_ptr_id',
new_name='user_ptr_id_old',
),
migrations.AddField(
model_name='PageUser',
name='emailuser_ptr_id',
field=models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, verbose_name=_('user'), blank=True),
preserve_default=True,
),
migrations.RunPython(up, down),
migrations.RemoveField(
model_name='PageUser',
name='user_ptr_id_old',
),
]
It fails with KeyError: ('myapp', u'pageuser') suggesting that it looks for the PageUser model in the my custom app and not in the cms app. How do I apply these migrations to the cms models?

Being said that replacing the Django User model it's not advisable in the middle of a project lifetime (see https://docs.djangoproject.com/en/1.9/topics/auth/customizing/#substituting-a-custom-user-model) as Django does not provide any way to modify the relations after their are being created, to replace the custom user model you can write a custom migration that alter the foreignkey in the django CMS models to point to theand create a new one to the new user model.
Something like:
...
migrations.RenameField(
model_name='globalpagepermission',
old_name='user',
new_name='user_old',
),
migrations.AddField(
model_name='globalpagepermission',
name='user',
field=models.ForeignKey(null=True, to=settings.AUTH_USER_MODEL, verbose_name=_('user'), blank=True),
preserve_default=True,
),
migrations.RunPython(copy_data),
migrations.RemoveField(
model_name='globalpagepermission',
name='user_old',
),
...
Repeat for any relevant model / field.
copy_data method will copy the value for the foreign keys from user_old to user
Code is completely untested but the general idea should work.
Alternatively you can write a RunSQL migration to get the same result.
Users with the same primary keys must exist also for the new model for this to work.

Thanks to https://code.djangoproject.com/ticket/25313 I managed to make it work. This is what I did:
Backed up my database!
Created a new app email_username to hold my user model (named User as recommended in the link). A new app was necessary because my main app (myapp) is dependent on django-cms, and if I put my new user model in myapp, django-cms would become dependent on my app - this would create a circular reference.
class User(AbstractEmailUser):
# Needed to add username, first_name and last_name here, because cms.PageUser model depends on these fields
username = models.CharField(max_length=100, verbose_name=_('username'))
first_name = models.CharField(max_length=100, verbose_name=_('first_name'))
last_name = models.CharField(max_length=100, verbose_name=_('last_name'))
class Meta:
db_table = 'auth_user' # the model will use the existing auth_user table (at first)
Set AUTH_USER_MODEL = ‘email_username.User’ in settings.py
Deleted all migrations of myapp and truncated the django_migrations table
Ran manage.py makemigrations and got
Cannot resolve bases for [] This can
happen if you are inheriting models from an app with migrations (e.g.
contrib.auth) in an app with no migrations; see
https://docs.djangoproject.com/en/1.8/topics/migrations/#dependencies
for more
... so I removed the cms app from settings.py, ran makemigrations again which now suceeded, then re-added and ran makemigrations again.
Ran manage.py migrate --fake, but got
Cannot resolve bases for [ModelState: 'djangocms_file.File', ModelState: 'djangocms_video.Video', ModelState: 'djangocms_link.Link', ModelState: 'djangocms_googlemap.GoogleMap', ModelState: 'djangocms_picture.Picture', ModelState: 'djangocms_teaser.Teaser']
... so I removed these apps as well from settings.py, ran migrate --fake, re-added the apps and migrate --fake again.
Removed the db_table setting from the new User model, ran makemigrations followed by migrate (without fake!)
Everything now seems to be in order. All foreign keys that previously referenced the standard auth_user table is now referincing the table of my new user model. Existing users have even been transferred because of the db_table trick.

Related

How to automatically fill pre-existing database entries with a UUID in Django

I have added a UUID to the following model:
class Post(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, editable=False)
...
But there already are entries in the database that were created without the uuid field.
When I run migrate, it adds the same UUID to all my previous objects.
Is there an easy way of populating the existing objects with a different UUID automatically?
I think the easiest way to fix this is to make a data migration [Django-doc]. You create a new migration file with:
python manage.py makemigrations --empty yourappname
This will create a new file in the migrations/ directory that does nothing. We can alter that migration file with something that looks like:
# Generated by Django 3.2 on 2021-05-01 12:49
from django.db import migrations
from uuid import uuid4
class Migration(migrations.Migration):
def populate_uuid(apps, schema_editor):
Post = apps.get_model('yourappname', 'Post')
posts = list(Post.objects.all())
for post in posts:
post.uuid = uuid4()
Post.objects.bulk_update(posts, ['uuid'])
dependencies = [
('yourappname', 'previous_migrationname'),
]
operations = [
migrations.RunPython(populate_uuid)
]
If you then migrate, it will load all Post objects, provide these each a unique uuid, and then update the items in bulk.

django simple history field indexing

How can I tell DSH to index particular field? Some queries that I do to historical models take too much time
I've base abstract model and all my models inherit from that model. The history field is also defined in this base model:
class BaseModel(models.Model):
class PublishingStatus(models.TextChoices):
DRAFT = 'draft', _('Draft')
ACCEPTED = 'accepted', _('Accepted'),
REJECTED = 'rejected', _('Rejected'),
MODIFIED = 'modified', _('Modified')
publishing_status = models.CharField(
max_length=9,
choices=PublishingStatus.choices,
default=PublishingStatus.DRAFT,
help_text=_("Publishing status represents the state of the object. By default it is 'draft'")
)
history = HistoricalRecords(inherit=True)
And I've also added indexes in this base model
class Meta:
abstract = True
indexes = [models.Index(fields=[
'publishing_status',
])]
It would be nice if Django Simple History could check which fields are indexed and create the same indexes in the historical models
Maybe there is a way to tell django simple history explicitly which field must be indexed additionally?
I hope there is a better solution, but I've ended up creating a custom migration file. First, I created a blank migration file for the app:
python manage.py makemigrations dosh --empty
I then modified this newly created empty migration file and added AddIndex for each historical model.
# Generated by Django 3.1.1 on 2020-10-15 06:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dosh', '0002_auto_20201008_2155'),
]
operations = [
# ...
migrations.AddIndex(
model_name='historicaltense',
index=models.Index(fields=['publishing_status'], name='htense_publish_status_idx')
),
migrations.AddIndex(
model_name='historicalsynonym',
index=models.Index(fields=['publishing_status'], name='hsynonym_publish_status_idx')
),
# ...
]
And finally, I've migrated those changes
python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, authtoken, comment, contenttypes, dosh, pinax_badges, sessions, social, users, vote
Running migrations:
Applying dosh.0003_auto_20201015_0849... OK

Django default entries on model creation

I'm sure this has been asked before, but I cannot find the answer. In django, if I have this model
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
How would one populate this model with a default Person entry when the first migrating the field?
I'm not talking about default values for the fields, but for default entries in the database.
thanks
You can make a data migration, that follows on the migration where you create the Person object. You can first let Django write the "skeleton" of the migration, this can be done with:
python manage.py makemigrations --empty appname
Next Django will make a file. In that file you can add RunPython item to the operations list. This then obtain the historical model (the model at that moment of the migration), where you then create a Person object in the database. For example with:
from django.db import migrations
class Migration(migrations.Migration):
def create_person(apps, schema_editor):
Person = apps.get_model('appname', 'Person')
Person.objects.create(first_name='will', last_name='mendil')
dependencies = [
('appname', 'migrationname'),
]
operations = [
migrations.RunPython(create_person)
]

Django: 'no such table' after extending the User model using OneToOneField

(Django 1.10.) I'm trying to follow this advice on extending the user model using OneToOneField. In my app 'polls' (yes, I'm extending the app made in the 'official' tutorial) I want to store two additional pieces of information about each user, namely, a string of characters and a number.
In my models.py I now have the following:
from django.contrib.auth.models import User
class Employee(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
stopien = models.CharField(max_length=100)
pensum = models.IntegerField()
and in admin.py the following:
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from polls.models import Employee
class EmployeeInline(admin.StackedInline):
model = Employee
can_delete = False
verbose_name_plural = 'employee'
class UserAdmin(BaseUserAdmin):
inlines = (EmployeeInline, )
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
When adding a user using the admin panel my two new fields display correctly. However, when I click 'save', or if I don't add any user and just click on the name of my sole admin user in the admin panel, I get the following error:
OperationalError at /admin/auth/user/1/change/
no such table: polls_employee
I see some questions and answers related to similar problems, but they seem to be relevant for older version of Django. Could anyone give me a tip as to what I should do? Ideally I'd want my two additional fields display in the admin panel, though I suspect this might be a task for the future.
I have to confess I do not understand this paragraph from the documentation just following the advice I'm using:
These profile models are not special in any way - they are just Django models that happen to have a one-to-one link with a User model. As such, they do not get auto created when a user is created, but a django.db.models.signals.post_save could be used to create or update related models as appropriate.
Do I need to tie this 'post-save' to some element of the admin panel?
I'd be very greatful for any help!
You need run makemigrations to create a migration for your new model, and then migrate to run the migration and create the database table.
./manage.py makemigrations
./manage.py migrate

IntegrityError after customizing user model

After customizing my user model in Django Oscar, I received the following error message:
IntegrityError at /
insert or update on table "basket_basket" violates foreign key constraint "basket_basket_owner_id_74ddb970811da304_fk_auth_user_id"
DETAIL: Key (owner_id)=(5) is not present in table "auth_user".
To customize my user model, I followed the instructions here.
First, I wrote the following models.py file, located within my project directory at apps/user/models.py.
from django.db import models
from oscar.apps.customer.abstract_models import AbstractUser
from django.contrib.postgres.fields import ArrayField
class User(AbstractUser):
acct_bal = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
purchased_items = ArrayField(models.IntegerField(), default=list)
The idea is that I want the user to have an account balance (which I will use for payment later) as well as a list of product numbers representing items that have already been purchased.
After making models.py, I edited the installed apps as follows:
INSTALLED_APPS = [...
'shopworld.apps.user',
] + get_core_apps()
And then put this at the bottom of my settings.py:
AUTH_USER_MODEL = 'user.User'
I then did ./manage.py migrate, but for some reason I am getting this error message. I also tried dropping the django_admin_log table as suggested here, but it did not work. Any help would be greatly appreciated.
I fixed this - the issue was that I was trying to migrate to a custom user model after already having done migrations with auth_user. This meant that auth_user didn't update correctly. I had to flush and re-sync the database, so that the initial migration captured the custom user model.