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

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.")

Related

How to apply migrations in django, pytest, postgres

How to auto-apply migrations to test database?
Error
django.db.utils.ProgrammingError: relation "users" does not exist
E LINE 1: ...ers"."phone_confirmed", "users"."last_login" FROM "users" WH...
pytest.ini
[pytest]
DJANGO_SETTINGS_MODULE = ma_saas.settings
addopts = --migrations
database_fixture.py
import pytest
from django.db import connections
import psycopg2
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
def run_sql(sql):
conn = psycopg2.connect(database='postgres')
conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
cur = conn.cursor()
cur.execute(sql)
conn.close()
#pytest.yield_fixture(scope='session')
def django_db_setup():
from django.conf import settings
settings.DATABASES['default']['NAME'] = 'test_db'
run_sql('DROP DATABASE IF EXISTS test_db')
run_sql('CREATE DATABASE test_db')
yield
for connection in connections.all():
connection.close()
run_sql('DROP DATABASE test_db')
I think it is better to create a database and apply its migrations in the database_fixture better.
It was not so obvious.
But we should use django_db_blocker to run commands.
from django.core.management import call_command
#pytest.yield_fixture(scope='session') # or #pytest.fixture(scope='session')
def django_db_setup(django_db_blocker):
from django.conf import settings
test_database_name = 'test_db'
settings.DATABASES['default']['NAME'] = test_db
run_sql(f'DROP DATABASE IF EXISTS {test_db}')
run_sql(f'CREATE DATABASE {test_db}')
with django_db_blocker.unblock():
call_command('migrate', '--noinput')
yield
for connection in connections.all():
connection.close()
run_sql(f'DROP DATABASE {test_db}')

Define models in seperate file when using app factory flask

I am creating a flask application where I am using the app factory method. I have a file in the application folder __init__.py which has the create_app function with the following code
def create_app(test_config=None):
app = Flask(__name__,instance_relative_config=True)
app.config.from_mapping(
SECRET_KEY='dev',
)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:#localhost/database'
db = SQLAlchemy(app)
if test_config == None:
app.config.from_pyfile('config.py',silent=True)
else:
app.config.form_mapping(test_config)
from flaskr import models
try:
os.makedirs(app.instance_path)
except OSError:
pass
class User(db.Model):
id = db.Column(db.Integer,primary_key=True)
uname = db.Column(db.String(50))
#app.route('/hello')
def hello():
return json.dumps({'message':'hello','status':True})
#app.route('/getusers')
def getusers():
u = User.query.get(1)
return json.dumps({'uname':u.uname})
return app
What I want is to define the models in a seperate file. How can I do this?
I have tried defining in a seperate file and importing it. But the problem is the model inherits the db.Model which is then not available in the imported file.
Leave the creation of db object outside create_app without passing any app instance and use the SQLAlchemy.init_app method to configure and init. your db object, this way you can import it from any file:
db = SQLAlchemy()
#...
def create_app(test_config=None):
app = Flask(__name__,instance_relative_config=True)
#...
db.init_app(app)
More on this topic can be found at flask's documentation
I've fought with this problem a few hours too. Couldn't fix it until I came to the realization, that I have to return the app inside the app.app_context()
model.py:
from flask_user import UserMixin
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class User(db.Model, UserMixin):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255, collation='NOCASE'), nullable=False,
unique=True)
# ...
__init__.py:
from flask import Flask
from flask_migrate import Migrate
def create_app(test_config=None):
app = Flask(__name__, instance_relative_config=True)
app.config.from_pyfile('config.py', silent=True)
migrate = Migrate()
from appname.model import db
db.init_app(app)
migrate.init_app(app, db)
with app.app_context():
# Create all database tables
db.create_all()
import appname.routes
# Apply the blueprints to the app
from appname import bp1, bp2
appname.register_blueprint(bp1.bp)
appname.register_blueprint(bp2.bp)
return app
Run with:
cd ~/Folder/Webapp
. appname/bin/activate
export FLASK_APP=appname
export FLASK_ENV=development
flask run
(in Linux terminal)
or create this file and run it in python shell:
from appname import create_app
if __name__ == '__main__':
app = create_app()
app.run(debug=True)

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()

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),
]

django: data migrate permissions

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