I've only been using Django for a couple of weeks now, so I may be approaching this all kinds of wrong, but:
I have a base ModelForm that I put some boilerplate stuff in to keep things as DRY as possible, and all of my actual ModelForms just subclass that base form. This is working great for error_css_class = 'error' and required_css_class = 'required' but formfield_callback = add_css_classes isn't working like I would expect it to.
forms.py
# snippet I found
def add_css_classes(f, **kwargs):
field = f.formfield(**kwargs)
if field and 'class' not in field.widget.attrs:
field.widget.attrs['class'] = '%s' % field.__class__.__name__.lower()
return field
class BaseForm(forms.ModelForm):
formfield_callback = add_css_classes # not working
error_css_class = 'error'
required_css_class = 'required'
class Meta:
pass
class TimeLogForm(BaseForm):
# I want the next line to be in the parent class
# formfield_callback = add_css_classes
class Meta(BaseForm.Meta):
model = TimeLog
The end goal is to slap some jquery datetime pickers on forms with a class of datefield/timefield/datetimefield. I want all of the date time fields within the app to use the same widget, so I opted to do it this way than explicitly doing it for each field in every model. Adding an extra line to each form class isn't that big of a deal, but it just bugged me that I couldn't figure it out. Digging around in the django source showed this is probably doing something I'm not understanding:
django.forms.models
class ModelFormMetaclass(type):
def __new__(cls, name, bases, attrs):
formfield_callback = attrs.pop('formfield_callback', None)
But I don't know how __init__ and __new__ are all intermangled. In BaseForm I tried overriding __init__ and setting formfield_callback before and after the call to super, but I'm guessing it needs to be somewhere in args or kwargs.
__new__ is called before object construction. Actually this is a factory method that returns the instance of a newly constructed object.
So there there are 3 key lines in ModelFormMetaclass:
formfield_callback = attrs.pop('formfield_callback', None) #1
fields = fields_for_model(opts.model, opts.fields,
opts.exclude, opts.widgets, formfield_callback) #2
new_class.base_fields = fields #3
In the class we attach base_fields to our form.
Now let's look to ModelForm class:
class ModelForm(BaseModelForm):
__metaclass__ = ModelFormMetaclass
This means that ModelFormMetaclass.__new__(...) will be called when we create a ModelForm instance to change the structure of the future instance. And attrs of __new__ (def __new__(cls, name, bases, attrs)) in ModelFormMetaclass is a dict of all attributes of ModelForm class.
So decision is to create new InheritedFormMetaclass for our case (inheriting it from ModelFormMetaclass). Don't forget to call new of the parent in InheritedFormMetaclass. Then create our BaseForm class and say:
__metaclass__ = InheritedFormMetaclass
In __new__(...) implementation of InheritedFormMetaclass we could do all we want.
If my answer is not detailed enough please let me know with help of comments.
You may set widgets class like this:
class TimeLogForm(BaseForm):
# I want the next line to be in the parent class
# formfield_callback = add_css_classes
class Meta(BaseForm.Meta):
model = TimeLog
widgets = {
'some_fields' : SomeWidgets(attrs={'class' : 'myclass'})
}
For what you're trying to accomplish, I think you're better off just looping through the fields on form init. For example,
class BaseForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(BaseForm, self).__init__(*args, **kwargs)
for name, field in self.fields.items():
field.widget.attrs['class'] = 'error'
Clearly you'll need a little more logic for your specific case. If you want to use the approach that sergzach suggested (overkill for your particular problem I think), here's some code for you that will call formfield_callback on the base class in the case the subclass doesn't define it.
baseform_formfield_callback(field):
# do some stuff
return field.formfield()
class BaseModelFormMetaclass(forms.models.ModelFormMetaclass):
def __new__(cls, name, bases, attrs):
if not attrs.has_key('formfield_callback'):
attrs['formfield_callback'] = baseform_formfield_callback
new_class = super(BaseModelFormMetaclass, cls).__new__(
cls, name, bases, attrs)
return new_class
class BaseModelForm(forms.ModelForm):
__metaclass__ = OrganizationModelFormMetaclass
# other form stuff
Finally, you might wanna look into crispy forms: https://github.com/maraujop/django-crispy-forms
sergzach is correct that you have to use metaclasses; overriding __init__ is not enough. The reason is that the metaclass for ModelForm (which will be called for all ModelForm subclasses unless you specify another metaclass in a subclass) takes the class definition, and using the values in the class definition creates a class with class attributes. For example, both META.fields and our formfield_callback is used to create form Fields with various option (like which widget).
That means AFAIU formfield_callback is a parameter to the metaclass used when creating your custom model form class, not some value used at runtime when actual form instances are created. That makes placing formfield_callback in __init__ useless.
I solved a similiar problem with a custom metaclass like
from django.forms.models import ModelFormMetaclass
class MyModelFormMetaclass(ModelFormMetaclass):
def __new__(cls,name,bases,attrs):
attrs['formfield_callback']=my_callback_function
return super(MyModelFormMetaclass,cls).__new__(cls,name,bases,attrs)
and in the base class for all my model forms setting the metaclass
class MyBaseModelForm(ModelForm):
__metaclass__=MyModelFormMetaclass
...
which can be used like (at least in Django 1.6)
class MyConcreteModelForm(MyBaseModelForm):
# no need setting formfield_callback here
...
Related
I have two models in models.py
class Inner(models.Manager):
name = models.CharField(max_length=256)
class Outer(models.Manager):
name = models.CharField(max_length=256)
inner = models.ForeignKey(Data)
I then have a ModelForm for Outer.
class OuterModelForm(ModelForm):
class Meta:
model = Outer
fields = ['name', 'inner']
My question is what gets called by ModelForm when displaying the possible inner values in the drop-down in the generated form.
I have ruled out the following by overriding the objects with a custom models.Manager. (just by adding print in there and seeing what is called)
values
get
get_queryset
all
filter
This is the responsibility of the form field, which will call its queryset attribute. By default, this is simply the related class's default manager; in your case, Data.objects.all().
To change this, redefine the field with an explicit queryset:
class OuterModelForm(ModelForm):
inner = forms.ModelChoiceField(queryset=Data.objects.filter(myparam='whatever'))
or, if you need it to depend on some other parameter, explicitly set that attribute within the __init__ method:
class OuterModelForm(ModelForm):
def __init__(self, *args, **kwargs):
param = kwargs.pop('myparam', None)
super(OuterModelForm, self).__init__(*args, **kwargs)
self.fields['inner'].queryset = Data.objects.filter(myparam=param)
I need to add a field validator through an abstract class.
The code I'm working with supposes that every class inheriting from this abstract class has a field name, but this field itself isn't defined in the abstract class unfortunately.
So I tried the following, but I'm not sure what is the good way of finding the field in self._meta.fields, since this is a list..??
class AbsClass(models.Model):
class Meta:
abstract = True
def __init__(self, *args, **kw):
super(AbsClass, self).__init__(*args, **kw)
name_field = [f for f in self._meta.fields if f.name == 'name'][0] # Is there another way?
name_field.validators.append(my_validator)
You need Options.get_field:
...
name_field = self._meta.get_field('name')
name_field.validators.append(my_validator)
...
Your approach, however, doesn't seem like a good idea: you model's field instances are shared between all instances of your model (their references are stored in a class attribute, not in instance attributes). This means that every time you instantiate an object of your model, you'll be adding another copy of my_validator to the field's validators, because you're adding it to the same field instance.
You could implement a metaclass for your abstract base class and add the validator at compile time instead of tampering with field instances at runtime, something along the lines of this (not tested):
from django.utils.six import with_metaclass
from django.db.models.base import ModelBase
# inherit from Django's model metaclass
class AbsClassMeta(ModelBase):
def __new__(cls, name, bases, attrs):
if 'name' in attrs:
attrs['name'].validators.append(my_validator)
elif name != 'AbsClass':
# is it an error to not have a "name" field in your subclasses?
# handle situation appropriately
return super(AbsClassMeta, cls).__new__(cls, name, bases, attrs)
class AbsClass(with_metaclass(AbsClassMeta, models.Model)):
...
Note that your AbsClass class itself will also be created using this metaclass. If you decide to throw an exception in AbsClassMeta.__new__ if the class doesn't have a name field, you need to take this into account, since your AbsClass class doesn't have a name field.
In Django 2.x
instead of
name_field = self._meta.get_field('name')
name_field.validators.append(my_validator)
you can do:
name_field = self.fields['name']
name_field.validators.append(my_validator)
Let's suposse I have the following model:
class Example(models.Model):
name = models.CharField(max_length=40)
I want its form to have an initial value for the field 'name', so it could be:
class ExampleForm(forms.ModelForm):
name = forms.CharField(initial="initial_name")
That's good enought for this simple example but in case I have more complex ModelFields (i.e. with overwritten widgets) I'm missing all when re-assigning the field 'name' with the basic forms.CharField.
My question: Is there a way to set the initials in the Meta class, in the same way the widgets can be? something like...
class ExampleForm(forms.ModelForm):
class Meta:
initials = {
'name': 'initial_name',
}
These are the options that I would checkout:
Option 1: Provide initial form data when instantiating the form.
This is the most basic way to do it. In your views.py, simply provide the data with the initial keyword argument.
views.py
from forms import ExampleForm
INITIAL_DATA = {'name': 'initial_name'}
def my_view(request):
...
form = ExampleForm(initial=INITIAL_DATA)
...
Not too tricky. The only downside would be if you use and abuse that form and get tired of passing in the initial data.
Option 2 (ideal for your case): Provide the initial data in the __init__ method of the class.
Forms in Django are designed to accept initial data at instantiation, so you can mess with those values in the __init__ method. Without testing it, this what I imagine it would look like:
forms.py
class ExampleForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
"""If no initial data, provide some defaults."""
initial = kwargs.get('initial', {})
initial['name'] = 'initial_name'
kwargs['initial'] = initial
super(ExampleForm, self).__init__(*args, **kwargs)
This would be optimal because you can ignore the initial={...} in the view code.
This seems like a bug but I just want to make sure I'm consuming the API properly.
It seems that support for django's modelform isn't supported on neo4django. Here's what I have:
Simple class:
from neo4django.db import models
class Person(models.NodeModel):
name = models.StringProperty()
The modelform:
class PersonForm(forms.ModelForm):
class Meta:
model = Person
Will trigger exception:
'super' object has no attribute 'editable'
I posted details as an issue:
https://github.com/scholrly/neo4django/issues/135
Because when Django goes to lookup field information using the model's _meta information, it finds a BoundProperty instead of a StringProperty or Property (which has a member called 'editable', but BoundProperty doesn't).
Is there a workaround, or is this an actual bug? Any ideas on how to fix the bug? I'm not familiar with the library codebase.
Thanks!
Below is a reasonable (and quick) workaround for anyone using neo4j with Django.
This solution requires that field names on the form have the exact same name as the attributes of the model.
Inherit the form from this class and set the model under the form class Meta class:
class NeoModelForm(forms.Form):
def __init__(self, *args, **kwargs):
super(NeoModelForm, self).__init__(*args, **kwargs)
self._meta = getattr(self, 'Meta', None)
if not self._meta:
raise Exception('Missing Meta class on %s' % str(self.__class__.__name__))
if not hasattr(self._meta, 'model'):
raise Exception('Missing model on Meta class of %s' % str(self.__class__.__name__))
def save(self, commit=True):
if not self.is_valid():
raise Exception('Failed to validate')
instance = self._meta.model(**self.cleaned_data)
if commit:
instance.save()
return instance
Now you can create a form class like this:
class PersonForm(NeoModelForm):
name = forms.CharField(widget=forms.TextInput())
class Meta:
model = Person
And still be able to save a model instance from a valid form:
form = formclass(request.POST)
if form.is_valid():
obj = form.save()
Plus the commit argument will give you the same solution as django's modelform class- but I didn't bother to implement to save_m2m functionality (which doesn't seem relevant for neo4j as a backend).
Question : What is the recommended way to specify an initial value for fields if one uses model inheritance and each child model needs to have different default values when rendering a ModelForm?
Take for example the following models where CompileCommand and TestCommand both need different initial values when rendered as ModelForm.
# ------ models.py
class ShellCommand(models.Model):
command = models.Charfield(_("command"), max_length=100)
arguments = models.Charfield(_("arguments"), max_length=100)
class CompileCommand(ShellCommand):
# ... default command should be "make"
class TestCommand(ShellCommand):
# ... default: command = "make", arguments = "test"
I am aware that one can used the initial={...} argument when instantiating the form, however I would rather store the initial values within the context of the model (or at least within the associated ModelForm).
My current approach
What I'm doing at the moment is storing an initial value dict within Meta, and checking for it in my views.
# ----- forms.py
class CompileCommandForm(forms.ModelForm):
class Meta:
model = CompileCommand
initial_values = {"command":"make"}
class TestCommandForm(forms.ModelForm):
class Meta:
model = TestCommand
initial_values = {"command":"make", "arguments":"test"}
# ------ in views
FORM_LOOKUP = { "compile": CompileCommandFomr, "test": TestCommandForm }
CmdForm = FORM_LOOKUP.get(command_type, None)
# ...
initial = getattr(CmdForm, "initial_values", {})
form = CmdForm(initial=initial)
This feels too much like a hack. I am eager for a more generic / better way to achieve this. Suggestions appreciated.
Updated solution (looks promising)
I now have the following in forms.py which allow me to set Meta.default_initial_values without needing extra boilerplate code in views. Default values are used if user does not specify initial={...} args.
class ModelFormWithDefaults(forms.ModelForm):
def __init__(self, *args, **kwargs):
if hasattr(self.Meta, "default_initial_values"):
kwargs.setdefault("initial", self.Meta.default_initial_values)
super(ModelFormWithDefaults, self).__init__(*args, **kwargs)
class TestCommandForm(ModelFormWithDefaults):
class Meta:
model = TestCommand
default_initial_values = {"command":"make", "arguments":"test"}
I don't see that much use in setting initial_values on form's meta if you then have to send to the form init.
I would rather create a subclass of ModelForm that overrides the constructor method and then use that subclass as parent class of the other forms.
e.g.
class InitialModelForm(forms.ModelForm):
#here you override the constructor
pass
class TestCommandForm(InitialModelForm):
#form meta
class CompileCommandForm(InitialModelForm):
#form meta