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

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

Related

How to install PostGIS for Django?

So I'm following the DOCS and just want to make sure I'm understanding correctly.
https://docs.djangoproject.com/en/1.10/ref/contrib/gis/install/postgis/
Do I just create a file called migrations.py with:
from django.contrib.postgres.operations import CreateExtension
from django.db import migrations
class Migration(migrations.Migration):
operations = [
CreateExtension('postgis'),
...
]
and drop it in my project directory? And then run python manage.py makemigrations ?
Still the better way ist to create extension directly by making a sql query:
CREATE EXTENSION postgis;
After that you just have to navigate to your project-root (there is a manage.py file inside) and run python manage.py migrate (since django 1.9 - before v.1.9 first run python manage.py makemigrations and after that python manage.py migrate)
But if you want to use your code, you have to add it to "models.py".
This is the file called by "python manage.py migrate"
So your models.py looks like:
from django.contrib.gis.db import models
from django.contrib.postgres.operations import CreateExtension
from django.db import migrations
class Migration(migrations.Migration):
operations = [
CreateExtension('postgis'),
]
class model1(models.Model):
geom = models.GeometryField(srid=4326,blank=True,null=True)
name = models.TextField(null=True)

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

ValueError: Related model u'app.model' cannot be resolved

I have two applications (ook and eek say) and I want to use a foreign key to a model in ook from a model in eek. Both are in INSTALLED_APPS with ook first.
In ook.models.py, i have:
class Fubar(models.Model):
...
In eek.models.py, I have:
class monkey(models.Model):
external = models.ForeignKey('ook.Fubar', blank=True, null=True)
...
The migration generated is:
class Migration(migrations.Migration):
dependencies = [
('eek', '0002_auto_20151029_1040'),
]
operations = [
migrations.AlterField(
model_name='monkey',
name='external',
field=models.ForeignKey(blank=True, to='ook.Fubar', null=True),
),
]
When I run the migration, I get this error:
...
1595 raise ValueError('Foreign Object from and to fields must be
the same non-zero length')
1596 if isinstance(self.rel.to, six.string_types):
-> 1597 raise ValueError('Related model %r cannot be resolved' % self.rel.to)
1598 related_fields = []
1599 for index in range(len(self.from_fields)):
ValueError: Related model u'ook.Fubar' cannot be resolved
What am I doing wrong?
Because You have ForeignKey in operations, You must add a ook to dependencies:
dependencies = [
('ook', '__first__'),
('eek', '0002_auto_20151029_1040'),
]
Django migrations have two "magic" values:
__first__ - get module first migration
__latest__ - get module latest migration
Try running migrations one by one for every model.
This way you can debug the app you are facing problem with
python manage.py migrate appmname
I just got the same error, but referring to a model that was declared as part of the same migration. It turned out that the first migrations.CreateModel(...) referred to a not yet declared model. I manually moved this below the declaration of the referred model and then everything worked fine.
In my case, It was the cache and previous migrations that resulted in this error. I removed __pycache__ and migrations folder and then re-run the migrations command and it worked.
Remember, when you'll do python manage.py makemigrations it won't see any new migrations and will console output no changes detected. You'll have to do python manage.py makemigrations your_app_name instead to make things work.
I encountered this error when trying to use a child model of a base model as a foreign key. It makes sense that it didn't work because there's not an id field on the child model. My fix was to use the parent on the key. Unfortunately this was not immediately intuitive and set me back a couple hours.
I have found that it looks like this bug was not fixed yet when you scroll down to the bottom.
Django ValueError: Related model cannot be resolved Bug
I am using 1.11.7, they are talking about 1.9.3.
It worked everything on localhost, but was always failing on Heroku, so I tested all the options/answers above and nothing worked.
Then I have noticed, localhost DB in Admin I had 1 profile created (1 DB record), went to Heroku and DB has 0 records for Profile table so I have added 1, pushed the migration, python manage.py migrate and all it went OK.
That validates that I did not need to change any of those migrations manually that all is working.
Maybe it will help to someone.
migrations
# -*- coding: utf-8 -*-
# Generated by Django 1.11.7 on 2017-11-23 21:26
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('blog', '0005_blog_author'),
]
operations = [
migrations.AlterField(
model_name='blog',
name='author',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
to='core.Profile'),
),
]
order of dependencies is too important.
in your case, ook must be created first then depend eek on it.
dependencies= [
('ook', '0001_initial'),
('eek', '0002_auto_20151029_1040'),
]

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

Django South Postgres Datamigration Issue

I'm adding a new model, running a schemamigration. Then adding data to that table with a datamigration. I'm running this on a Postgres db. I've run it successfully on a SQLite db locally, so I'm guessing it's a db-specific issue. The error that comes up in the datamigration is:
Error in migration: app:0109_add_reservation_rates
DatabaseError: column "rate_currency" specified more than once
LINE 1: ...rate_currency", "rate", "reservation_id", "date", "rate_curr...
^
There is no better error that South gives me and I haven't found a way to produce the SQL that South is supposed to be running. I've checked the schemamigration, datamigration and there is no repeated field name...
Here are the actual migrations:
0108_etc...py
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'ReservationRate'
db.create_table(u'app_reservationrate', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('created_on', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('updated_on', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
('reservation', self.gf('django.db.models.fields.related.ForeignKey')(related_name='reservation_rates', to=orm['app.Reservation'])),
('room', self.gf('django.db.models.fields.related.ForeignKey')(related_name='reservation_rates', to=orm['app.Room'])),
('date', self.gf('django.db.models.fields.DateField')(db_index=True)),
('rate_currency', self.gf('djmoney.models.fields.CurrencyField')(default='USD')),
('rate', self.gf('djmoney.models.fields.MoneyField')(max_digits=10, decimal_places=2, default_currency='USD')),
))
db.send_create_signal(u'app', ['ReservationRate'])
# Adding unique constraint on 'ReservationRate', fields ['reservation', 'room', 'date']
db.create_unique(u'app_reservationrate', ['reservation_id', 'room_id', 'date'])
0109_etc...py
coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
class Migration(DataMigration):
def forwards(self, orm):
for reservation in orm.Reservation.objects.all():
# Loop through some code, get rates, and dates for creating ReservationRate objects...
orm.ReservationRate.objects.create(reservation=reservation, room=stay.room, date=date, rate=rate)
Anybody have a clue how to fix this?
Not exactly sure why, but running ./manage.py migrate -v2 app made everything work correctly. I believe that option is supposed to run the migration and output more info on what it's running.