Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to migrate django custom base64 field. Field does not exist

I have a base64 field that is copied from the django snippet.

https://djangosnippets.org/snippets/1669/

class Base64Field(models.TextField):
    """
    https://djangosnippets.org/snippets/1669/
    Example use:
    class Foo(models.Model):
        data = Base64Field()

    foo = Foo()
    foo.data = 'Hello world!'
    print foo.data # will 'Hello world!'
    print foo.data_base64 # will print 'SGVsbG8gd29ybGQh\n'

    """

    def contribute_to_class(self, cls, name):

        if not self.db_column:
            self.db_column = name

        self.field_name =name+ '_base64'
        super(Base64Field, self).contribute_to_class(cls, self.field_name)
        setattr(cls, name, property(self.get_data, self.set_data))

    def get_data(self, obj):
        return base64.decodestring(getattr(obj, self.field_name))

    def set_data(self, obj, data):
        setattr(obj, self.field_name, base64.encodestring(data))

    def deconstruct(self):
        ame, path, args, kwargs = super(Base64Field, self).deconstruct()
        from pprint import pprint
        pprint(vars(self))
        return ame, path, args, kwargs

I am facing issues while migrating this field e.g.

class EmailStatus(models.Model):
    attachment = Base64Field(null=True, blank=True, db_column='attachment', name="attachment", verbose_name="attachment")

The error I am getting while migrating is

raise FieldDoesNotExist('%s has no field named %r' % (self.object_name, field_name))

django.core.exceptions.FieldDoesNotExist: EmailStatus has no field named u'attachment'

Now I can see why that is happening. But cant figure out a way around it. I think I might need to change something in the deconstruct field. I have tried multiple things for this but all of them broke.

e.g. removing the _base64. It does not work while saving and retrieving data.

I tried changing the name in the migrations file it does not work.

class Migration(migrations.Migration):

    initial = True

    dependencies = [
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
    ]

    operations = [
        migrations.CreateModel(name='EmailStatus',
            fields=[('attachment', gradsite.gradnotes.models.Base64Field(blank=True, null=True)),])]

I think the migrations auto-detecter is getting confused because of the change in name in contribute_to_class. I am not sure what can be a work around.

like image 878
Akamad007 Avatar asked Feb 01 '17 04:02

Akamad007


2 Answers

class Base64Field(models.TextField):
    def contribute_to_class(self, cls, name, private_only=False):
        if not self.db_column:
            self.db_column = name

        self.field_name = name + '_base64'
        super().contribute_to_class(cls,
                                    name)
        setattr(cls, self.field_name, property(self.get_data, self.set_data))

    def get_data(self, obj):
        return base64.b64encode(getattr(obj, self.name).encode('utf-8'))

    def set_data(self, obj, data):
        setattr(obj, self.field_name, base64.b64decode(data).decode('utf-8'))

This seems to work. There was a mix-up between self.field_name and name in contribute_to_class leading to the wrong value being used (hence makemigrations not picking up the field the second time around/when using migrate).

I have made python3 specific changes, namely the super calls and the use of base64 functions. The set_data method may be wrong (I didn't look into that too much, since you may be using python2 and encoding would differ), but migrations work.

Added bonus: the private_only argument was missing from your contribute_to_class method.

Here's what I'm getting:

from test_app import models

e = models.EmailStatus()
e.attachment = "Hello world!"

e.attachment  # Prints 'Hello world!'
e.attachment_base64  # Prints b'SGVsbG8gd29ybGQh'
like image 159
Raphaël Gomès Avatar answered Oct 13 '22 17:10

Raphaël Gomès


Useful link for you.

https://code.djangoproject.com/ticket/24563

HELPFUL TIPs:

The failing migration: ​https://github.com/codefisher/djangopress/blob/master/djangopress/forum/migrations/0011_auto_20150426_1821.py

The models: ​https://github.com/codefisher/djangopress/blob/master/djangopress/forum/models.py

It may be helps to others.

like image 34
bob marti Avatar answered Oct 13 '22 16:10

bob marti