10

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?

  • a syntax and names are OK,
  • prepared migration (by Django) is OK as well (have all fields)

--------------------

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.

Sławomir Lenart
  • 7,543
  • 4
  • 45
  • 61

3 Answers3

11

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
DiegoG
  • 814
  • 8
  • 17
  • 1
    of course it is a ModelBase, but running in `python manage.py shell`: `from django.apps import apps`, then `apps.get_model('calc.Weight').INF` you will see value of this attribute instead of `AttributeError`. – Sławomir Lenart Dec 12 '18 at 18:26
  • btw, `ModelBase` is a metaclass, not a parent class. – Sławomir Lenart Dec 12 '18 at 18:29
  • If you do this, keep in mind the [warnings from the documentation](https://docs.djangoproject.com/en/3.1/topics/migrations/#historical-models). Also mentioned [here](https://stackoverflow.com/a/32103212). – djvg Feb 15 '21 at 15:54
1

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, ...

Sławomir Lenart
  • 7,543
  • 4
  • 45
  • 61
  • Is there an explanation or warning in the Django docs about this? I didn't find anything relevant. I had similar issues with some `@property` decorated functions that were used during migrations, and were solved by implementing them in a mixin. – m000 Aug 04 '21 at 13:51
0

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.

Community
  • 1
  • 1
Evren Kutar
  • 1,173
  • 1
  • 9
  • 12