django: data migrate permissions - django

I have a bunch of new permissions which I need to migrate. I tried doing it through data migration but complains about ContentType not being available.
Doing quick research I found out that ContentType table is populated after all the migrations applied.
I even tried using update_all_contenttypes() from from django.contrib.contenttypes.management import update_all_contenttypes
which causes migration to load data which is not consistent to the fixture.
What is the best way to migrate permission data in Django?

Here is a quick and dirty way to ensure all permissions for all apps have been created:
def add_all_permissions(apps=None, schema_editor=None):
from django.contrib.auth.management import create_permissions
if apps is None:
from django.apps import apps
for app_config in apps.get_app_configs():
app_config.models_module = True
create_permissions(app_config, verbosity=0)
app_config.models_module = None
class Migration(migrations.Migration):
dependencies = [('myapp', '0123_do_the_thing')]
operations = [
migrations.RunPython(add_all_permissions,
reverse_code=migrations.RunPython.noop)
# ...
]
NOTE: edited to include ruohola's excellent suggestion

There are 2 ways to solve this:
1) The ugly way:
Run manage.py migrate auth before your wanted migration
2) Recommended way:
from django.contrib.auth.management import create_permissions
def add_permissions(apps, schema_editor):
apps.models_module = True
create_permissions(apps, verbosity=0)
apps.models_module = None
# rest of code here....

Here are steps for adding custom permissions to the User model:
First create a migration file, for example under your authentication application,
Here i named it 0002_permission_fixtures.py:
account (your authentication application)
|_migrations
|__ 0001_initial.py
|__ 0002_permission_fixtures.py
|__ __init__.py
Then adding your permission objects, as follow:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations
def forwards_func(apps, schema_editor):
# Get models that we needs them
user = apps.get_model("auth", "User")
permission = apps.get_model("auth", "Permission")
content_type = apps.get_model("contenttypes", "ContentType")
# Get user content type object
uct = content_type.objects.get_for_model(user)
db_alias = schema_editor.connection.alias
# Adding your custom permissions to User model:
permission.objects.using(db_alias).bulk_create([
permission(codename='add_sample', name='Can add sample', content_type=uct),
permission(codename='change_sample', name='Can change sample', content_type=uct),
permission(codename='delete_sample', name='Can delete sample', content_type=uct),
])
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '__latest__'),
]
operations = [
migrations.RunPython(
forwards_func,
),
]
To run this migration, first migrate contenttype model, and then migrate your application (here is account).
$ python manage.py migrate contenttypes
$ python manage.py migrate account

Related

How to generate a function-based index in my Python model migration?

I'm using Django, Python 3.7 and PostGres 9.5. To speed along a particular query, I want to create this functional index, which I'd normally do in PostGres like so ...
CREATE INDEX my_article_idx ON article (regexp_replace(url, '\?.*$', ''))
However in the world of Django and auto-generated migrations, I'm not sure how to annotate my class in my models.py file so that this function-based index would be auto-generated. The field in question in my model looks like this ...
class Article(models.Model):
...
url = models.TextField(default='', null=False)
You have to create a data migration. Read more about them in docs
Step 1 - create an empty data migration file
python manage.py makemigrations --empty yourappname
Step 2 - Add custom sql to the migration:
# -*- coding: utf-8 -*-
# Generated by Django 1.11.14 on 2018-09-20 08:01
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('yourapp', '0001_name_of_depending_migration'),
]
operations = [
migrations.RunSQL(
sql="CREATE INDEX my_article_idx ON article (regexp_replace(url, '\?.*$', ''))",
reverse_sql='DROP INDEX my_article_idx ON article'
)
]

How to add a permission to a user/group during a django migration?

I would like to execute the following migration:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.contrib.auth.models import Permission
from django.db import migrations
from django.conf import settings
from django.contrib.auth.models import Group, User
def add_api_group(apps, schema_editor):
Group.objects.create(name=settings.API_USER_GROUP)
# get_or_create returns a tuple, not a Group
group = Group.objects.get(name=settings.API_USER_GROUP)
permissions = Permission.objects.filter(codename__in = [
'add_topic',
])
group.permissions.add(*permissions)
def add_api_user(apps, schema_editor):
user = User.objects.create_user(username=settings.API_USER, password=settings.API_USER_PASSWORD)
group = Group.objects.get(name=settings.API_USER_GROUP)
user.groups.add(group)
class Migration(migrations.Migration):
dependencies = [
('nd_content', '0001_initial'),
]
operations = [
migrations.RunPython(add_api_group),
migrations.RunPython(add_api_user)
]
At the last line of the migration, I issued an error to stop execution and look at the database state. The problem is the table auth_permission still has not the permissions of a model of another module, although this other module is registered as a dependecy of this migration.
I can confirm missing permissions seem to be added only after all migrations have been executed.
AttributeError: 'StateApps' object has no attribute 'label' in Django 1.10
There is a solution:
for app_config in apps.get_app_configs():
app_config.models_module = True
create_permissions(app_config, verbosity=0)
app_config.models_module = None
EDIT 2018-01-31
This answer will only work until Django 1.9. For Django 1.10 an up, please refer to the answer provided by #anton-lisenkov
Original Answer (Django<1.10)
It turns out I could do the following:
from django.contrib.auth.management import create_permissions
def add_permissions(apps, schema_editor):
apps.models_module = True
create_permissions(apps, verbosity=0)
apps.models_module = None
Thanks #elad-silver for his answer: https://stackoverflow.com/a/34272647/854868
If you don't have to attach your permission to a personal model you can do it this way:
from django.contrib.auth.models import Permission, ContentType
def add_permission(apps, schema_editor):
content_type = ContentType.objects.get(app_label='auth', model='user') # I chose user model but you can edit it
permission = Permission(
name='Your permission description',
codename='your_codename',
content_type=content_type,
)
permission.save()

right way to create a django data migration that creates a group?

I would like to create data migrations that create Permissions and Groups, so that my other developers can just run the migrations and get everything set up. I was able to create the migrations and run them just fine, but now I'm getting an error when running my tests.
But if I do this:
from django.contrib.auth.models import Group
def add_operations_group(apps, schema_editor):
Group.objects.get_or_create(name='operations')
I get:
django.db.utils.OperationalError: no such table: auth_group
If I do this:
def add_operations_group(apps, schema_editor):
Group = apps.get_model("django.contrib.auth", "group")
Group.objects.get_or_create(name='operations')
I get:
LookupError: No installed app with label 'django.contrib.auth'
Is there a way to do this? Or is there a "Django Way" to make sure things like permissions and groups are created?
This is how I do it:
from django.db import models, migrations
def apply_migration(apps, schema_editor):
Group = apps.get_model('auth', 'Group')
Group.objects.bulk_create([
Group(name=u'group1'),
Group(name=u'group2'),
Group(name=u'group3'),
])
def revert_migration(apps, schema_editor):
Group = apps.get_model('auth', 'Group')
Group.objects.filter(
name__in=[
u'group1',
u'group2',
u'group3',
]
).delete()
class Migration(migrations.Migration):
dependencies = [
('someapp', 'XXXX_some_migration'),
]
operations = [
migrations.RunPython(apply_migration, revert_migration)
]
Although, there must be a more Djangonic way.
Answer from César is correct. To make it more Django create the migration file automatically by going to your django app root folder and entering:
python manage.py makemigrations <yourappname> --empty
Note: You may need python3 instead of python depending on your system configuration.
This creates an empty migration file in a sub directory of your app called 0001_initial.py
You can then alter it as per César instructions. Which worked correctly with Django 2.2

Adding django admin permissions in a migration: Permission matching query does not exist

I wanted to add some groups and assign permissions to them in a manually written migration but if I run it on a clean DB it creates permissions only after running all migrations.
I've found this ticket: https://code.djangoproject.com/ticket/23422
but I cannot comment there (it's possible I was banned after expressing some discontent with GeoDjango docs), so I'll share an improvement over the solution there below.
In django 1.10 the following code could be used:
from django.contrib.auth.management import create_permissions
def migrate_permissions(apps, schema_editor):
for app_config in apps.get_app_configs():
app_config.models_module = True
create_permissions(app_config, apps=apps, verbosity=0)
app_config.models_module = None
Django <= 1.9
see another answer for Django 1.10+
It's enough to call create_permissions:
from django.contrib.auth.management import create_permissions
apps.models_module = True
create_permissions(apps, verbosity=0)
apps.models_module = None
The whole migration being something like this
# coding:utf-8
from django.db import migrations
from django.contrib.auth.models import Permission, Group
from django.contrib.auth.management import create_permissions
from django.contrib.contenttypes.models import ContentType
from django.conf import settings
MODERATORS_PERMISSIONS = ['change_modelname', ]
def add_permissions(apps, schema_editor):
apps.models_module = True
create_permissions(apps, verbosity=0)
apps.models_module = None
moderators_group = Group.objects.get_or_create(
name=settings.MODERATORS_GROUP)[0]
for codename in MODERATORS_PERMISSIONS:
permission = Permission.objects.get(codename=codename)
moderators_group.permissions.add(permission)
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('thisappname', '0001_initial'),
]
operations = [
migrations.RunPython(add_permissions),
]
And if you want something that will work on any version (or that will keep working when you upgrade):
from django.contrib.auth.management import create_permissions
version = django.VERSION
if version[0] >= 1 and django.VERSION[1] > 9:
for app_config in apps.get_app_configs():
app_config.models_module = True
create_permissions(app_config, apps=apps, verbosity=0)
app_config.models_module = None
else:
apps.models_module = True
create_permissions(apps, verbosity=0)
apps.models_module = None
Trying to get an permission during migrations causes an exception(Permission matching query does not exist) in Django. It's an old problem in Django.
In 1.6 version I solved it via #int_ua's snippet but in 1.11 version it doesn't work(I'm not sure why).
I used this workaround in 1.11 version:
def _assign_group_permissions(permission_codenames, apps, group_name):
permission_list = []
Permission = apps.get_model('auth', 'Permission')
for permission_codename in permission_codenames:
for permission in Permission.objects.all():
if permission.codename == permission_codename:
permission_list.append(permission)
Group = apps.get_model('auth', 'Group')
group = Group.objects.get(name=group_name)
group.permissions.add(*permission_list)
Instead of Permission.objects.get(codename='your_code_name') it's possible to iterate over all permissions and choose suitable one by codename.
Django 3.2
Here is a version for Django 3.2, which you can run from the command line:
./manage.py fix_permissions
# app_label/management/commands/fix_permissions.py
from django.contrib.auth.models import Permission
from django.contrib.auth.management import create_permissions
from django.core.management.base import BaseCommand
from django.apps import apps
class Command(BaseCommand):
help = 'Recreate permissions from scratch'
def handle(self, *args, **options):
# Run this method via shell whenever any amendments in any of the tables is made
print("Deleting existing user permissions...")
Permission.objects.all().delete()
for app_config in apps.get_app_configs():
print(f"Adding user permissions for {app_config}...")
app_config.models_module = True
create_permissions(app_config, apps=apps, verbosity=0)
app_config.models_module = None
print("DONE.")

Django Cant access auth User, Group object in custom data migration

My migration file looks like this:
from __future__ import unicode_literals
from django.db import migrations
from project.tools import do_nothing
def create_can_receive_group(apps, schema_editor):
Group = apps.get_model("django.contrib.auth", 'Group')
# Group operation added here
class Migration(migrations.Migration):
dependencies = [
('poreceiving', '0004_auto_20150616_0846'),
('django.contrib.auth', '0006_require_contenttypes_0002')
]
operations = [
migrations.RunPython(create_can_receive_group, do_nothing),
]
Here I want to access Group object of django.contrib.auth.
I get the following exception.
*** LookupError: No installed app with label 'django.contrib.auth'.
I found somewhere that if we want to use other object which is not in app in which the migration is present then we should add latest migration of other app.
When I add django.contrib.auth latest migration to the dependency I get following :
django.db.migrations.graph.NodeNotFoundError: Migration poreceiving.0005_create_can_receive_group dependencies reference nonexistent parent node (u'django.contrib.auth', u'0006_require_contenttypes_0002')
Try something like this (look at the migrations.swappable_dependency part in the dependencies):
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations
from project.tools import do_nothing
def create_can_receive_group(apps, schema_editor):
Group = apps.get_model("auth", "Group")
# Group operation added here
class Migration(migrations.Migration):
dependencies = [
('poreceiving', '0004_auto_20150616_0846'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RunPython(create_can_receive_group, do_nothing),
]