I am trying to make a generic mixin for model fields (as opposed to form fields), the init for the mixin takes named arguments. I am running into trouble instantiating the mixin with another class.
Here is the code
class MyMixin(object):
def __init__(self, new_arg=None, *args, **kwargs):
super(MyMixin, self).__init__(*args, **kwargs)
print self.__class__, new_arg
class MyMixinCharField(MyMixin, models.CharField):
pass
...
class MyMixinModelTest(models.Model):
myfield = MyMixinCharField(max_length=512,new_arg="myarg")
Making the migration for this model produces the following output:
<class 'myapp.mixintest.fields.MyMixinCharField'> myarg
<class 'myapp.mixintest.fields.MyMixinCharField'> None
<class 'myapp.mixintest.fields.MyMixinCharField'> None
Migrations for 'mixintest':
0001_initial.py:
- Create model MyMixinModelTest
First, why is init running 3 times? Where does the kwarg 'new_arg' in the second two? How do I create a field mixin for django?
EDIT: As opposed to another question, this question asks about field mixins, the linked question refers to model mixins.
Model mixins are abstract classes that can be added as a parent class of a model. Python supports multiple inheritances, unlike other languages such as Java. Hence, you can list any number of parent classes for a model. Mixins ought to be orthogonal and easily composable.
To answer your question, with the new migration introduced in Django 1.7, in order to add a new field to a model you can simply add that field to your model and initialize migrations with ./manage.py makemigrations and then run ./manage.py migrate and the new field will be added to your DB. Save this answer.
mixins are classes that generally inherit from object (unless you are django core developer) mixins are narrow in scope as in they have single responsibility. They do one thing and do it really well. mixins provide plug-in functionality.
First, why is init running 3 times?
Although the models.py
is only imported once, the Field
objects created therein, such as...
myfield = MyMixinCharField(max_length=512, new_arg="myarg")
...are cloned several times, which involves calling the field constructor using the keyword args they were originally created with. You can use the traceback
module to see where it's happening...
import traceback
class MyMixin(object):
def __init__(self, new_arg=None, *args, **kwargs):
super(MyMixin, self).__init__(*args, **kwargs)
print self.__class__, new_arg
traceback.print_stack()
...which shows the following several times in the output...
File "django/db/migrations/state.py", line 393, in from_model
fields.append((name, field.clone()))
File "django/db/models/fields/__init__.py", line 464, in clone
return self.__class__(*args, **kwargs)
File "myproj/myapp/models.py", line 11, in __init__
traceback.print_stack()
Where is the kwarg 'new_arg' in the second two?
When you originally called...
myfield = MyMixinCharField(max_length=512, new_arg="myarg")
..."myarg"
is being passed in as the new_arg
parameter to...
def __init__(self, new_arg=None, *args, **kwargs):
...but because you don't pass that parameter to the underlying Field
constructor...
super(MyMixin, self).__init__(*args, **kwargs)
...it's not stored anywhere in the underlying Field
object, so when the field is cloned, the new_arg
parameter isn't passed to the constructor.
However, passing that option to the superclass constructor won't work, because the CharField
doesn't support that keyword arg, so you'll get...
File "myproj/myapp/models.py", line 29, in MyMixinModelTest
myfield = MyMixinCharField(max_length=512, new_arg="myarg")
File "myproj/myapp/models.py", line 25, in __init__
super(MyMixinCharField, self).__init__(*args, **kwargs)
File "django/db/models/fields/__init__.py", line 1072, in __init__
super(CharField, self).__init__(*args, **kwargs)
TypeError: __init__() got an unexpected keyword argument 'new_arg'
How do I create a field mixin for django?
Because of this cloning behavior, if you want to add custom field options, you have to define a custom deconstruct()
method so that Django can serialize your new option...
class MyMixin(object):
def __init__(self, new_arg=None, *args, **kwargs):
super(MyMixin, self).__init__(*args, **kwargs)
self.new_arg = new_arg
print self.__class__, new_arg
def deconstruct(self):
name, path, args, kwargs = super(MyMixin, self).deconstruct()
kwargs['new_arg'] = self.new_arg
return name, path, args, kwargs
class MyMixinCharField(MyMixin, models.CharField):
pass
class MyMixinModelTest(models.Model):
myfield = MyMixinCharField(max_length=512, new_arg="myarg")
...which outputs...
<class 'myapp.models.MyMixinCharField'> myarg
<class 'myapp.models.MyMixinCharField'> myarg
<class 'myapp.models.MyMixinCharField'> myarg
So I figured it out after lots of tinkering and re-reading the django docs on custom model fields
You need a deconstructor along with your init. Django fields need a deconstruct
method to serialize.
The mixin should have this method as well:
class MyMixin(object):
def __init__(self, new_arg=None, *args, **kwargs):
self.new_arg = new_arg
super(MyMixin, self).__init__(*args, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super(MyMixin, self).deconstruct()
if self.new_arg is not None:
kwargs['new_arg'] = self.new_arg
return name, path, args, kwargs
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With