If I may suggest a better approach: - Make the code as ImageField does (see it's width_field and height_field) - Then focus on writing the migration for this field
Unfortunately, I don't see a way for custom fields to provide hints to the migrations framework. So a field that automatically creates another field is hard to deal with. Another different approach is to use a custom ForeignKey [1]. Then you create only one field, but at the cost of a join. [1] https://github.com/furious-luke/django-address/blob/master/address/models.py#L289 On Wed, Sep 13, 2017 at 2:05 PM, Michal Petrucha <[email protected]> wrote: > On Tue, Sep 12, 2017 at 12:34:26AM -0700, [email protected] wrote: >> Hi, >> >> I don't know if crossposting with stackoverflow is allowed here but >> essentially my problem is explained >> in >> https://stackoverflow.com/questions/46162104/django-dynamic-model-fields-and-migrations. >> What I want to create is a custom ModelField of type DecimalField, that >> dynamically adds another ModelField (CharField) to the source model. As far >> as I can tell, this is nicely explained >> in >> https://blog.elsdoerfer.name/2008/01/08/fuzzydates-or-one-django-model-field-multiple-database-columns/. >> >> Let's assume I start with a fresh project and app and add this code to >> models.py: >> >> from django.db import models >> from django.db.models import signals >> >> >> _currency_field_name = lambda name: '{}_extension'.format(name) >> >> >> class PriceField(models.DecimalField): >> >> def contribute_to_class(self, cls, name): >> # add the extra currency field (CharField) to the class >> if not cls._meta.abstract: >> currency_field = models.CharField( >> max_length=3, >> editable=False, >> null=True, >> blank=True >> ) >> cls.add_to_class(_currency_field_name(name), currency_field) >> # add the original price field (DecimalField) to the class >> super().contribute_to_class(cls, name) >> >> # TODO: set the descriptor >> # setattr(cls, self.name, FooDescriptor(self)) >> >> >> class FooModel(models.Model): >> >> price = PriceField('agrhhhhh', decimal_places=3, max_digits=10, >> blank=True, null=True) >> >> >> If I then call *./manage.py makemigrations* the following migration file >> for the app is created: >> >> # Generated by Django 1.11.4 on 2017-09-11 18:02 >> from __future__ import unicode_literals >> >> from django.db import migrations, models >> import testing.models >> >> >> class Migration(migrations.Migration): >> >> initial = True >> >> dependencies = [ >> ] >> >> operations = [ >> migrations.CreateModel( >> name='FooModel', >> fields=[ >> ('id', models.AutoField(auto_created=True, primary_key=True, >> serialize=False, verbose_name='ID')), >> ('price', testing.models.PriceField(blank=True, >> decimal_places=3, max_digits=10, null=True, verbose_name='agrhhhhh')), >> ('price_extension', models.CharField(blank=True, >> editable=False, max_length=3, null=True)), >> ], >> ), >> ] >> >> >> All good, as far as I can tell. The problem comes up if I then call >> *./manage.py >> migrate* which errors with the following exception: >> >> ./manage.py migrate testing >> Operations to perform: >> Apply all migrations: testing >> Running migrations: >> Applying testing.0001_initial...Traceback (most recent call last): >> File >> "/usr/local/var/pyenv/versions/stockmanagement-3.6.2/lib/python3.6/site-packages/django/db/backends/utils.py", >> line 63, in execute >> return self.cursor.execute(sql) >> File >> "/usr/local/var/pyenv/versions/stockmanagement-3.6.2/lib/python3.6/site-packages/django/db/backends/sqlite3/base.py", >> line 326, in execute >> return Database.Cursor.execute(self, query) >> sqlite3.OperationalError: duplicate column name: price_extension >> > [...] >> As said, I have a fresh project with no exisiting DB and tables so far. Why >> is it complaining that there allready exista a column named >> "price_extension"?. The migration file only contains one field called >> "price_extension"? > > I'm not quite certain about this, but I believe that when the > migration runner reconstructs a version of your FooModel, it iterates > over the list of fields, and adds each of them to the reconstructed > model class one by one. So what happens is that your custom PriceField > gets added, which in turn creates its price_extension field, and then > next thing, the migration runner adds the price_extension field one > more time. So you end up with two instances of the price_extension > field on the same model, and when eventually the create_model schema > operation is executed, it adds each of them as another column to the > table, which is obviously wrong. > > As for what you could do to avoid this situation – I'd suggest that > you add an extra argument to PriceField which tells the field that it > shouldn't create the additional extension field. Then, you implement > the deconstruct method on the field, and include this additional flag. > That way, auto-detected migrations will include the extra argument to > the field instance, and there shouldn't be any duplication of the > extension field on the resulting models reconstructed by migrations. > > Good luck, > > Michal > > -- > You received this message because you are subscribed to the Google Groups > "Django users" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to [email protected]. > To post to this group, send email to [email protected]. > Visit this group at https://groups.google.com/group/django-users. > To view this discussion on the web visit > https://groups.google.com/d/msgid/django-users/20170913120505.GO8762%40koniiiik.org. > For more options, visit https://groups.google.com/d/optout. -- Melvyn Sopacua -- You received this message because you are subscribed to the Google Groups "Django users" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To post to this group, send email to [email protected]. Visit this group at https://groups.google.com/group/django-users. To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/CA%2Bgw1GUsF%3DjNxMbKUgc3ntg3XPJ59et5cc4sZX36aMb8CP_kMw%40mail.gmail.com. For more options, visit https://groups.google.com/d/optout.

