I have a field (slug) that is "required" in the model, but want to change the field in the ModelAdmin class to be optional. If the user doesn't fill it in, it is automatically filled in by another field (name).
class SomeModel(model.Model):
name = model.CharField(max_length=255)
slug = model.SlugField(unique=True, max_length=255)
I tried to do this various ways, such as overriding get_form() within ModelAdmin or using the ModelForm class and specifying the form specifically.
class SomeModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(self.__class__, self).get_form(request, obj, **kwargs)
form.slug.required = False
return form
However, neither solution worked for me. Beyond manually creating the form, is there any other quicker solution?
I have a lot of these forms, and doing it by hand might be tedious and hard to maintain.
Found this page through Google when wrestling with the same problem myself. The following will also work in the ModelAdmin:
def get_form(self, *args, **kwargs):
form = super(SomeModelAdmin, self).get_form(*args, **kwargs)
form.base_fields['slug'].required = False
return form
Subsequent forms created from the updated ModelFormMetaclass will have the slug field unrequired.
This works better in my situation, where I have only a single class in which I need to unrequire the field, and don't need to do any data transformation on save. GoogleDroid's solution is better if you have a lot of classes, or where the data transformations are necessary.
In your get_form method, form.fields['slug'].required should work.
But the proper way to do this is to simply provide a custom ModelForm.
class SomeModelForm(forms.ModelForm):
slug = forms.CharField(required=False)
class SomeModelAdmin(admin.ModelAdmin):
form = SomeModelForm
Incidentally, please don't do super(self.__class__, self). You should always explicitly name the current class when using super, otherwise any subclass that inherits from yours and in turn calls super will break.
Edit form.fields, not forms.fields.
By saying self.__class__, you are explicitly stopping Python from working out the inheritance - because it always refers to the concrete class - ie the bottom of the inheritance tree. But if your method is the middle of that tree, then referring to the concrete class in super is wrong - because you want it to call the next level up from where you are, not one up from the bottom. That's why you should always name the class you're in - in this case, super(SomeModelAdmin, self).
I just wanted to report back in case others might find this useful.
I was never able to in get_form method do form.fields['slug'].required and never figured out why. However, I solved my problem by creating a new form inheriting from ModelForm.
I had to override init() to set self.fields['slug'].required = False after calling the parent constructor, then override clean_slug() to modify the slug field content if required by accessing self.data['slug'].
Hope this helps someone
Related
I am trying to override the helptext that is set in my model in the wagtail admin (an edit view). I have tried the code below but the text is not changed, why is that?
class MemberRegistrationEditView(EditView):
def get_form(self):
form = super().get_form()
form.base_fields.get("own_email").help_text = "Test"
return form
Thanks for posting your question. I understand what you're trying to achieve i think.
Generally speaking though, and others may disagree, but I would do this inside of the forms dunder init function (__init__)
class MyForm(forms.Form):
def __init__(self, *args, **kwargs):
self.base_fields['own_email'].help_text = "Test"
super().__init__(*args, **kwargs)
Its worth noting that this will replace the help text for every time this particular form is used.
If you want to do something specific while in a certain view, then I would recommend simply inheriting the form you want instead of forms.Form and then replace the form attribute on the view for your new form.
The point of my question is the following. I have Django form with a field, which inherits the Selet2 field:
class Select2ModelField(MyBaseSelect2ModelField, AutoModelSelect2Field):
'''
Select2ModelField, that uses ajax to get autocomplete options.
Should be used by default.
'''
widget = Select2ChoiceWidget
class LimitedDepartmentChoiceField(Select2ModelField):
def __init__(self, *args, **kwargs):
super(LimitedDepartmentChoiceField, self).__init__(*args, **kwargs)
And then I use it in my form, creating this field in a views.py, because the content of this field depends on the request data:
form = RepresentativeCreateEditForm(request.POST)
form.fields['department'] = LimitedDepartmentChoiceField(label=u'Department',
queryset=Department.objects.filter(
id__in=all_deps_ids))
The problem is that when two different users enter this page at the same time, they both have the same list of options, exactly the one which the user, who first load the page, has. And this behaviour is incorrect, they should have the different lists of options.
Please, could anyone tell me how I can solve this problem?
It sounds like a value is getting set as a class attribute (somewhere, on one of your classes), rather than as an attribute of a particular instance of a class. There's a lot of inheritance going on, so you might have to do some digging to see exactly where the problem is. My guess is that it's the Select2ChoiceWidget class.
From your code example it looks like all instances of Select2ModelField and its subclasses are sharing a single Select2ChoiceWidget class between themselves. I would think this would be the cause of the problem.
I don't know a whole lot about the Django classes you're using, but maybe try something along these lines?
class Select2ModelField(MyBaseSelect2ModelField, AutoModelSelect2Field):
'''
Select2ModelField, that uses ajax to get autocomplete options.
Should be used by default.
'''
def __init__(self, *args, **kwargs):
# Not sure if this is the proper way to instantiate this class,
# but doing so would help avoid leaking data across the instances
# of Select2ModelField and its subclasses.
self.widget = Select2ChoiceWidget()
# Do the parent class(es) for good measure.
super(Select2ModelField, self).__init__(self, *args, **kwargs)
I'm wondering if it's possible to alter or change a form class before it's instantiated.
As a concrete example, I have a payment form which needs to be modified based on the payment system being used.
Ideally I'd rather not create different form classes and then choose different ones based on the payment system; instead the payment system object will "tell" the form class what changes it need to make -- for example, making certain fields optional or instructing them to use different widgets.
Is this possible? The alternative is to pass an object representing the payment system into the form, and then having it modify the form after instantiation, but that just seems clumsy somehow to have it run in the form class rather than the view. I feel like the Django "view" is closer to a controller, and it seems like this is where something like this should happen. I also feel like modifying it'd be better to modify the form_class object rather than the form instance; I'm not even sure if when you add fields after the fact like this it will handle validation and form fill-in correctly. Will it?
Anywhere, here's some sample code of how it would work passing the payment object into a form instantiation call:
payment_system.py:
class ExamplePaymentSystem(BasePaymentSystem):
def modify_form(self, form):
for fld in self.optional_fields:
form.fields[fld].required = False
…etc…
forms.py:
class ModifiablePaymentForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.payment_system = kwargs.pop("payment_system", None)
super(ModifiablePaymentSystem, self).__init__(*args, **kwargs)
self.payment_system.modify_form(self)
You should not modify global static data (classes defined at module scope are static), because if you run your code in many threads per process (which is often done) one thread may modify form class used by the other threads.
If you your payment systems are static (you do not add new on the fly, while server is running) I'd define one form per payment system.
If not, you can always, define new form types on the fly like that:
def get_form_type(payment_system):
class DynamicForm(BasePaymentForm):
... add change fields etc...
return DynamicForm
or modify instances like that:
class PaymentForm(BasePaymentForm):
def __init__(self, ..., payment_system):
self.fields['foo'].required = False # <--- I'm writing code
#from the memory, so possibly you'll need t edit it
#but this is doable and easy to do.
How to remove field in forms (per OP request).
When you subclass:
This is hard and I think you'll need to browse through form internals and modify them by hand after subclass creation. This is a wild guess...
def get_form_type(payment_system):
class DynamicForm(BasePaymentForm):
... add change fields etc...
del DynamicForm.base_fields['foo']
return DynamicForm
When you modify instance:
I'm not 100% sure, but I peeked into django source code (unfortunately these details are not in docs). But i guess that you should:
class PaymentForm(BasePaymentForm):
def __init__(self, ..., payment_system):
del self.fields['foo']
The fields are a dict (or I guess -- OrderedDict for that matter) and to delete field you need to remove whole key-vaule mapping.
What I'm trying to do is create a dynamic ModelForm that generates extra fields based on one of its class-attributes to use in a ModelAdmin. Something like:
class MyModelForm(forms.ModelForm):
config_fields = ('book_type', 'is_featured', 'current_price__is_sale')
class MyModelAdmin(admin.ModelAdmin):
form = MyModelForm
In this case, MyModelForm would generate fields based on the config_fields attribute by performing some introspection. My approach so far looks something like this (based on this answer https://stackoverflow.com/a/6581949/677985):
class ConfigForm(type):
def __new__(cls, name, bases, attrs):
if 'config_fields' in attrs:
for config_field in attrs['config_fields']:
# ... (removed for clarity)
attrs.update(fields)
return type(name, bases, attrs)
class MyModelForm(forms.ModelForm):
__metaclass__ = ConfigForm
config_fields = ('book_type', 'is_featured', 'current_price__is_sale')
This approach works well enough, but I'm not quite happy with it for several reasons:
The validation doesn't seem to work, but this is a minor concern for now
I'm not quite sure why the "if config_field in attrs:"-condition is needed, but it is
I would prefer for MyModelForm to inherit instead of setting the __metaclass__ attribute, the base-class could then be easily reused and would allow me to easily override the clean- and __init__-methods.
I tried implementing the third item, the result being that the extra-fields did not show up in the admin-form. I'd be grateful if someone could help me figure this out, or at least point me in the right direction.
I am aware that using a metaclass for this probably overkill, and would guess that part of the problem is that ModelForm already has one or two metaclasses in its inheritance-chain. So if anyone has an alternate solution that accomplishes the same, that would make me just as happy.
I believe that the ModelForm already has a metaclass, but you're overwriting it by setting your own. That's why you're not getting validation or any of the other built in goodness of modelforms.
Instead, you should be able to use type directly to create your ModelForm, which will describe the type you want, but still cause the ModelForms metaclass to do its thing.
Example:
config_fields = ('book_type', 'is_featured', 'current_price__is_sale')
# the below is an example, you need more work to construct the proper attrs
attrs = dict((f, forms.SomeField) for f in config_fields)
ConfigModelForm = type('DynamicModelForm', (forms.ModelForm,), attrs)
class MyModelAdmin(admin.ModelAdmin):
form = ConfigModelForm
You can wrap the first part up in a function if need be, and invoke it for your form attribute in your ModelAdmin.
See my answer here for links and discussion on using type.
How about this,
Basically any form that extends your StepForm will also have the metaclass you wanted in the case below it's StepFormMetaclass, please note that if you have the form defined in some form.py file, you will need to import the form in the ___init___.py so that it will execute it during django starting sequence.
from django.forms.forms import DeclarativeFieldsMetaclass
class StepFormMetaclass(DeclarativeFieldsMetaclass):
.......
def __new__(meta_class, name, bases, attributes):
.....
return DeclarativeFieldsMetaclass.__new__(meta_class, name, bases, attributes)
class StepForm(six.with_metaclass(StepFormMetaclass, forms.Form, StepFormMixin)):
def __init__(self, *args, **kwargs):
super(StepForm, self).__init__(*args, **kwargs)
def as_p(self):
return ......
class Test(forms.Form):
def set_choices(self, choices):
self.choices = choices
def get_choices(self):
return self.choices
options = forms.ChoiceField(choices=get_choices())
f = Test()
f.set_choices(...)
Why isn't this possible?
How else can I achieve the goal of passing data into class Test?
Thanks in advance.
This is a basic Python issue. You need to think about the order these commands are executed in, and their scope.
First, you define a form class called Test. That class has three attributes: a set_choices method, a get_choices method, and an options field. These definitions are evaluated when the class itself is defined. The definition of options calls get_choices(). However, there is no get_choices method in scope at that point, because the class is not yet defined.
Even if you somehow managed to sort out the scope issue, this would still not do what you want, because the definition of choices for options is done at define time. Even if you later call set_choices, options still has the value of get_choices that was returned when the field was defined.
So, what do you actually want to do? It seems like you want to set dynamic choices on the options field. So, you should override the __init__ method and define them there.
class Test(forms.Form):
options = forms.ChoiceField(choices=())
def __init__(self, *args, **kwargs):
choices = kwargs.pop('choices', None)
super(Test, self).__init__(*args, **kwargs)
if choices is not None:
self.fields['options'].choices = choices
Extending __init__ is a good way to add options to ChoiceField dynamically as Daniel Roseman explains in his answer.
Just to add to that ... adding options at run time is hackish (at best). Here is the note about best practices (straight from Django ChoiceField documentation) -
Finally, note that choices can be any
iterable object -- not necessarily a
list or tuple. This lets you construct
choices dynamically. But if you find
yourself hacking choices to be
dynamic, you're probably better off
using a proper database table with a
ForeignKey. choices is meant for
static data that doesn't change much,
if ever.