django modelform inheritance - python

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)

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

How to create a django model field mixin

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

How to filter model choice field options based on user in django?

Here is my form:
class RecipeForm(forms.Form):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super(RecipeForm, self).__init__(*args, **kwargs)
Recipebase_id = forms.ModelChoiceField(queryset=Recipebase.objects.filter(user = self.user))
title = forms.CharField(max_length=500)
instructions = forms.CharField(max_length=500)
I want to filter model choice field based on user as you can see from the filter. But it gives the following error:
name 'self' is not defined
Any suggestions would be highly appreciated.
The self. would work only for objects created from a class. In this case you are not creating one, so it would not work as you would expect.
Instead, you need to override the queryset in the __init__ like this:
class RecipeForm(forms.Form):
Recipebase_id = forms.ModelChoiceField(queryset=Recipebase.objects.none())
def __init__(self, *args, **kwargs):
user = kwargs.pop('user') #Throws an error if user is not present
super(RecipeForm, self).__init__(*args, **kwargs)
qs = Recipebase.objects.filter(user=user)
self.fields['Recipebase_id'].queryset = qs
Another way to achieve the same is to make user a required argument in the form
class RecipeForm(forms.Form):
Recipebase_id = forms.ModelChoiceField(queryset=Recipebase.objects.none())
def __init__(self, user, *args, **kwargs):
super(RecipeForm, self).__init__(*args, **kwargs)
qs = Recipebase.objects.filter(user=user)
self.fields['Recipebase_id'].queryset = qs
And the view code would look like this:
form = RecipeForm(request.POST, user=request.user) #user would be passed in as a kwarg to the form class.
Putting your code starting at "Recipebase_id" at the indentation level you have it causes python to execute it at the time the file is parsed/imported. Self is passed into a method when the class is instantiated and the instance method is called, so at parse time self does not exist.
It's unclear to me if you want the Recipebase_id, title and instructions set in the init method. If you do, indent them to the same level as the lines above it. If not, then you'll need to get the value of user from somewhere other than self.

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)

Passing values into django forms

I have googled around trying to figure out and understand how this works, yet I still haven't grasped this quite right. What I want to do is pass a value into a form to then use for a query. I have a session variable called menu_term, which determines the choices in the form.
from views.py
def manage_groups(request):
form = CourseGroupForm(request,current_term=request.session.get('menu_term'))
return render_to_response("accounts/group_management.html", {'form':form}, context_instance=RequestContext(request))
from forms.py
class CourseGroupForm(ModelForm):
def __init__(self, current_term, *args, **kwargs):
super(CourseGroupForm, self).__init__(*args, **kwargs)
courseslist = Course.objects.filter(term=current_term, num_in=settings.LAB_COURSES).order_by('description').distinct();
print(courseslist)
self.fields['courses'].queryset = forms.ChoiceField(label='Select Course', choices=courseslist)
class Meta:
model = CourseGroup
fields = ['name','courses'];
The error I am getting is:
__init__() got multiple values for keyword argument 'current_term'
For the benefit of anyone else coming across this, what are the proper ways of defining a form that takes a value passed in from outside?
Thanks,
Good Day
MJ
Its important to pop the kwarg you instantiate your form with before calling the forms super __init__
class CourseGroupForm(ModelForm):
def __init__(self, current_term, *args, **kwargs):
current_term = kwargs.pop('current_term')
super(CourseGroupForm, self).__init__(*args, **kwargs)
The above assumes current_term is always present.
as #vishen pointed out, check your arguments, you are initializing your form with request as the value for current_term
The error is happening because in your model form init decleration
class CourseGroupForm(ModelForm):
def __init__(self, current_term, *args, **kwargs)
current_term is the first argument that the form is expecting to find, but because you are passing through the request object first and then the current_term after that, your effiectely passing the following
form = CourseGroupForm(current_term=request,current_term=request.session.get('menu_term'))
Hence the multiple values for keyword argument 'current_term' error message.

Categories