Reputation: 8367
maybe I am exhausted and don't see something simple, but in Django 1.9.7, while doing the migration I found something strange, and I am looking for an explanation.
While getting a model class by apps
(it is (django.db.migrations.state.StateApps
) in RunPython operation I have AttributeError
for the field which exists.
My model:
class Weight(models.Model):
INF = 2**31-1
minimum = models.PositiveIntegerField()
maximum = models.PositiveIntegerField()
carrier = models.ForeignKey(Carrier)
class Meta:
ordering = ['carrier__name', 'minimum']
in migration method runned from RunPython
, I have:
Weight = apps.get_model('calc.Weight')
then have exception, but only for some fields.
from debugging (inside method runned by RunPython):
>>> Weight.maximum
Traceback (most recent call last):
File "<pudb command line>", line 1, in <module>
AttributeError: type object 'Weight' has no attribute 'maximum'
>>> Weight.minimum
Traceback (most recent call last):
File "<pudb command line>", line 1, in <module>
AttributeError: type object 'Weight' has no attribute 'minimum'
>>> Weight.INF
Traceback (most recent call last):
File "<pudb command line>", line 1, in <module>
AttributeError: type object 'Weight' has no attribute 'INF'
but:
>>> Weight.carrier
<django.db.models.fields.related_descriptors.ForwardManyToOneDescriptor object at 0x7f8dcca692d0>
>>> Weight._meta.fields
(<django.db.models.fields.AutoField: id>, <django.db.models.fields.PositiveIntegerField: minimum>,
<django.db.models.fields.PositiveIntegerField: maximum>, <django.db.models.fields.related.ForeignKey: carrier>)
type(Weight)
<class 'django.db.models.base.ModelBase'>
so somehow only carrier field is available, why?
update: my migration file is:
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
def add_weights(app, *args):
Carrier = app.get_model('calc.Carrier')
Weight = app.get_model('calc.Weight')
# import pudb;pu.db
carrier_obj = Carrier.objects.get(name='MainCarrier')
Weight.objects.create(carrier=carrier_obj, minimum=1, maximum=400) # OK, yes it works within `create`
Weight.objects.create(carrier=carrier_obj, minimum=401, maximum=800) # OK
Weight.objects.create(carrier=carrier_obj, minimum=800, maximum=Weight.INF) # here is AttributeError
class Migration(migrations.Migration):
dependencies = [
('calc', '0012_auto_20170622_1310'),
]
operations = [
migrations.CreateModel(
name='Weight',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('minimum', models.PositiveIntegerField()),
('maximum', models.PositiveIntegerField()),
('carrier', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='calc.Carrier')),
],
options={
'ordering': ['carrier__name', 'minimum'],
},
),
migrations.RunPython(add_weights)
]
btw: after all I can place INF outside class body, and have workaround, but knowledge what is happening is more important to me.
Upvotes: 10
Views: 4854
Reputation: 8367
FYI, I resolved my issue by putting non-model fields and custom methods into the definition of a new class
in general:
class MyModelInterface(object): ..
class MyModel(models.Model, MyModelInterface): ..
where the MyModelInterface
is a mixin for the model, but if needed I can use that separately.
I found it as a good practice for Django models, so in special needs like migration, I can assess interface class directly. Also, it's helpful to avoid a very long model's body with many custom methods, properties, ...
Upvotes: 1
Reputation: 1213
I think the reason of this error is the database changes and the state changes have different aspects. Documentation says
A highly specialized operation that let you mix and match the database (schema-changing) and state (autodetector-powering) aspects of operations.
It accepts two list of operations, and when asked to apply state will use the state list, and when asked to apply changes to the database will use the database list. Do not use this operation unless you’re very sure you know what you’re doing.
https://docs.djangoproject.com/en/1.9/ref/migration-operations/#separatedatabaseandstate
The proper update for the case as below;
operations = [
migrations.SeparateDatabaseAndState(
[
migrations.CreateModel(
name='Weight',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('minimum', models.PositiveIntegerField()),
('maximum', models.PositiveIntegerField()),
('carrier', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='calc.Carrier')),
],
options={
'ordering': ['carrier__name', 'minimum'],
},
)
],
[
migrations.RunPython(add_weights)
]
)
]
However I would do those 2 migrations in seperate files, since they have different purposes and reverting can be a pain in the future.
Upvotes: 0
Reputation: 814
The app.get_model(...)
call will return a django.db.models.base.ModelBase
instance, not your Weight
model, that's why you can't see INF
.
Import it with an alternative name (so it doesn't shadow your Weight
variable), and you'll be able to use it:
from myapp.models import Weight as WeightModel
...
...
maximum = WeightModel.INF
Upvotes: 11