How to create a django model field mixin - python

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.

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

Related

How is Django makemigrations triggered for changes to a custom model field?

I have a custom model field defined as:
class PriceField(models.DecimalField):
def __init__(self, *args, **kwargs):
kwargs['max_digits'] = 30
kwargs['decimal_places'] = 20
super().__init__(*args, **kwargs)
I want to change it so that it is also nullable/blankable so I add:
class PriceField(models.DecimalField):
def __init__(self, *args, **kwargs):
kwargs['max_digits'] = 30
kwargs['decimal_places'] = 20
kwargs['null'] = True
kwargs['blank'] = True
super().__init__(*args, **kwargs)
However, having made this addition and running makemigrations, no new migrations are created. Having read the documentation at https://docs.djangoproject.com/en/3.1/howto/custom-model-fields/#field-deconstruction I thought that the problem might be the lack of a deconstruct() method, but even after adding one as seems to be described in the docs (which I read as being to delete the kwargs you override in the init method):
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
del kwargs['max_digits']
del kwargs['decimal_places']
del kwargs['null']
del kwargs['blank']
return name, path, args, kwargs
...it still doesn't create any migrations to make the field nullable/blankable. Can anyone tell me what I'm missing?
Try to pass null and blank arguments to your field initialization inside the model definition like in any other standard django field

Django class view: __init__

I want to get <Model> value from a URL, and use it as an __init__ parameter in my class.
urls.py
url(r'^(?P<Model>\w+)/foo/$', views.foo.as_view(), name='foo_class'),
views.py
class foo(CreateView):
def __init__(self, **kwargs):
text = kwargs['Model'] # This is not working
text = kwargs.get('Model') # Neither this
Bar(text)
...
Clearly, I'm missing something, or my understanding of URL <> class view is wrong.
You should override dispatch method for such use cases.
class Foo(CreateView):
def dispatch(self, request, *args, **kwargs):
# do something extra here ...
return super(Foo, self).dispatch(request, *args, **kwargs)
For your specific scenario, however, you can directly access self.kwargs as generic views automatically assign them as an instance variable on the view instance.

How to make custom field from django field

I want to make my custom field extend from django foreign key.
class CustomField(models.ForeignKey):
def __init__(self, *args, **kwargs):
self.type=kwargs.pop('type', None)
super(CustomField, self).__init__(*args, **kwargs)
I am using like
CustomField('User', type="test")
This works correctly but i want to hard code model name in my field like this
super(CustomField, self).__init__('User', *args, **kwargs)
so that i can use
CustomField(type="test")
but then i get this error
__init__() got multiple values for keyword argument 'to'
The problem is that your are sending the to parameter used by models.ForeginKey in self and in the 'User' parameter when you make call super(CustomField, self).__init__('User', *args, **kwargs). You can try to do in this way:
class CustomField(models.ForeignKey):
def __init__(self, *args, **kwargs):
kwargs['to'] = 'User'
self.type = kwargs.pop('type', None)
super(CustomField, self).__init__(*args, **kwargs)

Use a method from other classes w/o inheritance

This question is about Python inheritance but is explained with a Django example, this should't hurt though.
I have this Django model, with Page and RichText models as well:
class Gallery(Page, RichText):
def save(self, *args, **kwargs):
# lot of code to unzip, check and create image instances.
return "something"
I'm only interested in using the save method in another class.
A solution could be:
class MyGallery(models.Model):
def save(self, *args, **kwargs):
# here goes the code duplicated from Gallery, the same.
return "something"
I'd like to avoid the code duplication and also I'm not interested in inheriting members from Page and RichText (so I don't want to do class MyGallery(Gallery):. If it would be legal I'd write something like this:
class MyGallery(models.Model):
# custom fields specific for MyGallery
# name = models.CharField(max_length=50)
# etc
def save(self, *args, **kwargs):
return Gallery.save(self, *args, **kwargs)
But it won't work because the save() in Gallery expects an instance of Gallery, not MyGallery.
Any way to "detach" the save() method from Gallery and use it in MyGallery as it were defined there?
EDIT:
I forgot to say that Gallery is given and can't be changed.
You can access the __func__ attribute of the save method:
class Gallery(object):
def save(self, *args, **kwargs):
return self, args, kwargs
class MyGallery(object):
def save(self, *args, **kwargs):
return Gallery.save.__func__(self, *args, **kwargs)
# or
# save = Gallery.save.__func__
mg = MyGallery()
print mg.save('arg', kwarg='kwarg')
# (<__main__.MyGallery object at 0x04DAD070>, ('arg',), {'kwarg': 'kwarg'})
but you're better off refactoring if possible:
class SaveMixin(object):
def save(self, *args, **kwargs):
return self, args, kwargs
class Gallery(SaveMixin, object):
pass
class MyGallery(SaveMixin, object):
pass
or
def gallery_save(self, *args, **kwargs):
return self, args, kwargs
class Gallery(object):
save = gallery_save
class MyGallery(object):
save = gallery_save
I'm not sure why you are against inheritance, particularly with regard to methods. I regularly create a MixIn class that is inherited by all of my Django models.Model, and it contains all manner of useful methods for URL creation, dumps, etc., etc. I do make the methods defensive in that they use hasattr() to make sure they apply to a particular class, but doing this is a real time saver.

django modelform inheritance

I've run into a django error that's got tearing out my hair. Background: I have a set of models inheriting from each other, and I'm trying to build a set of forms with a parallel structure.
Here's the base type for an object creation form:
class CreateSharedObjectForm(ModelForm):
def save(self, status, object_type, commit=True, *args, **kwargs):
print "*********Got here!!!**************"
shared_object = super(ModelForm,self).save( commit=False, *args, **kwargs)
shared_object.status = status
shared_object.object_type = object_type
if commit:
shared_object.save()
return shared_object
Here's an inherited form type:
class NewBatchForm(CreateSharedObjectForm):
def save(self, status, object_type, batch_options, commit=True, *args, **kwargs):
print "Checkpoint A"
batch = super(CreateSharedObjectForm,self).save( status, object_type, commit=False, *args, **kwargs )
print "Checkpoint B"
if commit:
batch.save(*args, **kwargs)
return analysis
class Meta:
model = batch
I call the inherited type from a view script:
form = NewAnalysisForm(request.POST, request.FILES)
new_analysis = form.save(
status = 'X',
object_type = 'Batch',
batch_type = 'temp',
)
And it throws this error:
save() takes at most 2 non-keyword arguments (4 given)
If I change the "super" line to this:
batch = super(CreateSharedObjectForm,self).save( status, object_type, commit=False, *args, **kwargs )
I get this error:
Exception Type: IntegrityError
Exception Value: null value in column "parent_project_id" violates not-null constraint
Even wierder, django's trace output gives me this:
Checkpoint A
Checkpoint B
Before returning a HTTP 500 error.
As far as I can tell, the super line in the save method in NewBatchForm is never calling CreateSharedObjectForm. I'm aware that the super method can be tricky, but this is just single inheritance, and I can't figure out why the method for the superclass never gets called.
What's going on here? How do I fix it?
are you sure you don't want super(NewBatchForm, self).save inside NewBatchForm?
(you have super(CreateSharedObjectForm, self)

Categories