Django 1.11 - Adding Permissions to existing users in production - django

Problem
I have a database in production with users and I'm creating new permissions in some models. Now I need to add these permissions to some users depending on a condition. How should I do this? Should I add code to the migration to check all users and add permissions to them accordingly?

You can create a DataMigration
This allow you to run your regular migrations and then apply some logic (preferablly using a python code) to do some changes in your DB records.
Example:
from django.db import migrations, models
def my_data_migration_code(apps, schema_editor):
my_model = apps.get_model('your_app', 'MyModel')
for instance in my_model.objects.all():
instance.name = instance.name + ' wow'
instance.save()
class Migration(migrations.Migration):
dependencies = [
('your_app', '0050_auto_20190207_1156'),
]
operations = [
migrations.AddField(
model_name='MyModel',
name='blabla',
field=models.BooleanField(default=True),
),
migrations.RunPython(my_data_migration_code)
]

Related

Django manual migration to add group

I am attempting to create a migration that will automatically add a group and permissions to the group when it is run. I adapted some code from the docs and an example. I can get the migration to add the group but not the permissions. I am getting the following error: TypeError: Direct assignment to the forward side of a many-to-many set is prohibited. Use permissions.set() instead.
I am not sure how to implement this suggestion. Any ideas?
The migration:
from django.db import migrations, models
from django.contrib.auth.models import Group, Permission
from django.contrib.auth.management import create_permissions
def add_group_permissions(apps, schema_editor):
for app_config in apps.get_app_configs():
create_permissions(app_config, apps=apps, verbosity=0)
# Employee
group, created = Group.objects.get_or_create(name='Employee')
if created:
permissions_qs = Permission.objects.filter(
codename__in=[
'can_add_how_to_entry',
'can_change_how_to_entry',
'can_view_how_to_entry',
]
)
group.permissions = permissions_qs
group.save()
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
]
operations = [
migrations.RunPython(add_group_permissions),
]
Remember that group.permissions is a related query manager, not a field, so if you assign something to that you will destroy it. So you need to do something like:
permissions_qs = Permission.objects ...
for permission in permissions_qs:
group.permissions.add(permission)
group.save()
Additionally, the Django way to create custom permission is through the Meta class, for example:
# Assuming that you have an "Entry" model
class Entry(models.Model):
...
class Meta:
...
permissions = [
('custom_permission_a', 'a description for permission a'),
('custom_permission_b', 'a description for permission b'),
]
After migrating the changes you will have two additional permissions attached to the Entry model.
Now you can create a group with the custom permissions like:
group, created = Group.objects.get_or_create(name='CustomGroup')
if created:
permissions = Permission.objects.filter(
codename__in=[
'custom_permission_a',
'custom_permission_b',
]
)
for p in permissions:
group.permissions.add(p)
group.save()
You can include that in a new migration or whatever you want, also if you don't want to use the Meta class to create your custom permissions, you can do it programmatically:
# Assuming that you have an Entry model
# Import Entry model
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
entryContentType = ContentType.objects.get_for_model(Entry)
# Create custom permission
custom_permission = Permission.objects.create(
codename ='custom_permission',
name ='description for the custom permission',
content_type = entryContentType
)
A custom_permission will be created and attached to the Entry model. Then you can include that permission in your custom group.

Pre-selection of data for migration to the database

Is there any way in Django to populate the database with multiple records during the migration, or after it, except for the manual method, or restore the backup.
For example:
I have a model with services, which after the creation of the database should already have 3 entries, because it is a binder.
How do I implement this in Django 2.x?
From django documentation on Data migrations
Django can’t automatically generate data migrations for you, as it
does with schema migrations, but it’s not very hard to write them.
Migration files in Django are made up of Operations, and the main
operation you use for data migrations is RunPython.
Example
from django.db import migrations
def combine_names(apps, schema_editor):
# We can't import the Person model directly as it may be a newer
# version than this migration expects. We use the historical version.
Person = apps.get_model('yourappname', 'Person')
for person in Person.objects.all():
person.name = '%s %s' % (person.first_name, person.last_name)
person.save()
class Migration(migrations.Migration):
dependencies = [
('yourappname', '0001_initial'),
]
operations = [
migrations.RunPython(combine_names),
]

How to define default data for Django Models?

I want my application to have default data such as user types.
What's the most efficient way to manage default data after migrations?
It needs to handle situations such as, after I add a new table, it adds the default data for it.
You need to create an empty migration file and Do your stuff in operations block, as explained in docs.
Data Migrations
As well as changing the database schema, you can also use migrations to change the data in the database itself, in conjunction with the schema if you want.
Now, all you need to do is create a new function and have RunPython use it
Docs explains this with an example to show ,how to communicate with your models.
From Docs
To create an empty migration file,
python manage.py makemigrations --empty yourappname
And this is the example how to update a newly added field.
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
def combine_names(apps, schema_editor):
# We can't import the Person model directly as it may be a newer
# version than this migration expects. We use the historical version.
Person = apps.get_model("yourappname", "Person")
for person in Person.objects.all():
person.name = "%s %s" % (person.first_name, person.last_name)
person.save()
class Migration(migrations.Migration):
initial = True
dependencies = [
('yourappname', '0001_initial'),
]
operations = [
migrations.RunPython(combine_names),
]
The accepted answer is fine. But, since OP asked the question in the context of adding new rows and not updating existing entries. Here is the code snippet for adding new entries :
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('loginmodule', '0002_login_avatar'),
]
def insertData(apps, schema_editor):
Login = apps.get_model('loginmodule', 'Login')
user = Login(name = "admin", login_id = "admin", password = "password", email = "admin#pychat.com", type = "Admin", avatar="admin.jpg")
user.save()
operations = [
migrations.RunPython(insertData),
]
Update:
most users are looking for data migration as suggested by #durdenk in https://stackoverflow.com/a/39742847/3627387. But what OP was asking is about a way to add data after migrations, that is why this is accepted answer.
Original answer:
I think what you are looking for is fixtures https://docs.djangoproject.com/en/1.10/howto/initial-data/
From docs
It’s sometimes useful to pre-populate your database with hard-coded data when you’re first setting up an app. You can provide initial data via fixtures.
Also read this https://code.djangoproject.com/wiki/Fixtures
Answer is given above just to show how to insert new rows to the table.
from django.db import migrations, models
from yourapp.models import <yourmodel>
def combine_names(apps, schema_editor):
obj = <yourmodel>(arrib=value)
obj.save()
For example let's say you have model Person
person = Person(first_name='raj', last_name='shah')
person.save()

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

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.

Django - How to make migrations locally within an application while overriding User or Group model?

I have an application 'registration' in which I am trying to add an extra field in Django auth groups. I have successfully implemented it using monkey patching. However, when I post this application to someone else and they run 'migrate', the build fails stating the reason that the newly added field does not exists. The reason being that when I created the migrations, the migration files were not created in my 'registration' application, instead, they were created in the Django.contrib.auth application.
How can I get past this problem?
I solved it by adding a dummy migration file in my application - 'registration'. Inside this migration, I created the column manually by executing an SQL Query so that the build does not fail. I'll post the code here.
def check_and_add_column(apps, schema_editor):
import sqlite3
conn = sqlite3.connect('db7.sqlite3')
cur = conn.cursor()
result = [True for i in cur.execute('PRAGMA table_info(auth_group)') if i[1] == 'is_auto_assign']
if not result:
cur.execute('ALTER TABLE auth_group ADD COLUMN is_auto_assign BOOLEAN DEFAULT FALSE')
class Migration(migrations.Migration):
dependencies = [
('auth', '0001_initial'),
]
operations = [
migrations.RunPython(check_and_add_column,),
# If you need to run SQL directly from here
# migrations.RunSQL("ALTER TABLE auth_group ADD COLUMN is_auto_assign BOOLEAN DEFAULT FALSE"),
]