Change a django form class before it is instantiated - python

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.

Related

django get view method name on permission classes like actions

I have a custom permission like:
def has_permission(self, request, view):
print("view", view)
I am calling list() method of ListModelMixin.
Here when I print the value of veiw it gives me class name of the view. But what I want is the name of the method that is being called, in this case list.
In view set we can get method name from action attribute.
Is there anyway I can get name of the method not the class ??
You probably can't do what you want to do...
I think it's unlikely you're going to be able to achieve what you want here without some kind of a hack. The reason is, that list isn't really the view that is called. Look at for example ListAPIView:
class ListAPIView(mixins.ListModelMixin,
GenericAPIView):
"""
Concrete view for listing a queryset.
"""
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
get is fundamentally the view that is called, and it then calls list. You could try and emulate what the dispatch method does to find the view that gets called (it uses request.method.lower()) but that's still just going to give you essentially the http-method.
But you probably don't want to do it either...
My senses tell me that, you probably don't want to decide what kind of permissions are appropriate based off of if you are doing list or retrieve. Even if at the moment, it so happens that all of your lists require a certain behaviour, and all of you retrieves require a different behaviour. Almost certainly, a little bit down the road this will no longer be the case and this permission will become a complicated unmaintainable mess.
Alternatives
Abstract away all of the logic about permissions into helper functions, and then write DRY permission classes for each View as needed.
Alternatively, you can always check permissions as you call the appropriate list and retrieve methods. Write a decorator for each behaviour, and just decorate the relevant methods. e.g
class MyView(ListAPIView):
#my_permission_decotator
def list(request, *args, **kwargs):
return super().list(*args, **kwargs)

Different users get the same search results

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)

How to manipulate form fields in Django dynamically within ModelAdmin?

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

Difference between returning modified class and using type()

I guess it's more of a python question than a django one, but I couldn't replicate this behavior anywhere else, so I'll use exact code that doesn't work as expected.
I was working on some dynamic forms in django, when I found this factory function snippet:
def get_employee_form(employee):
"""Return the form for a specific Board."""
employee_fields = EmployeeFieldModel.objects.filter(employee = employee).order_by ('order')
class EmployeeForm(forms.Form):
def __init__(self, *args, **kwargs):
forms.Form.__init__(self, *args, **kwargs)
self.employee = employee
def save(self):
"Do the save"
for field in employee_fields:
setattr(EmployeeForm, field.name, copy(type_mapping[field.type]))
return type('EmployeeForm', (forms.Form, ), dict(EmployeeForm.__dict__))
[from :http://uswaretech.com/blog/2008/10/dynamic-forms-with-django/]
And there's one thing that I don't understand, why returning modified EmployeeForm doesn't do the trick?
I mean something like this:
def get_employee_form(employee):
#[...]same function body as before
for field in employee_fields:
setattr(EmployeeForm, field.name, copy(type_mapping[field.type]))
return EmployeeForm
When I tried returning modified class django ignored my additional fields, but returning type()'s result works perfectly.
Lennart's hypothesis is correct: a metaclass is indeed the culprit. No need to guess, just look at the sources: the metaclass is DeclarativeFieldsMetaclass currently at line 53 of that file, and adds attributes base_fields and possibly media based on what attributes the class has at creation time. At line 329 ff you see:
class Form(BaseForm):
"A collection of Fields, plus their associated data."
# This is a separate class from BaseForm in order to abstract the way
# self.fields is specified. This class (Form) is the one that does the
# fancy metaclass stuff purely for the semantic sugar -- it allows one
# to define a form using declarative syntax.
# BaseForm itself has no way of designating self.fields.
__metaclass__ = DeclarativeFieldsMetaclass
This implies there's some fragility in creating a new class with base type -- the supplied black magic might or might not carry through! A more solid approach is to use the type of EmployeeForm which will pick up any metaclass that may be involved -- i.e.:
return type(EmployeeForm)('EmployeeForm', (forms.Form, ), EmployeeForm.__dict__)
(no need to copy that __dict__, btw). The difference is subtle but important: rather than using directly type's 3-args form, we use the 1-arg form to pick up the type (i.e., the metaclass) of the form class, then call THAT metaclass in the 3-args form.
Blackly magicallish indeed, but then that's the downside of frameworks which do such use of "fancy metaclass stuff purely for the semantic sugar" &c: you're in clover as long as you want to do exactly what the framework supports, but to get out of that support even a little bit may require countervailing wizardry (which goes some way towards explaining why often I'd rather use a lightweight, transparent setup, such as werkzeug, rather than a framework that ladles magic upon me like Rails or Django do: my mastery of deep black magic does NOT mean I'm happy to have to USE it in plain production code... but, that's another discussion;-).
I just tried this with straight non-django classes and it worked. So it's not a Python issue, but a Django issue.
And in this case (although I'm not 100% sure), it's a question of what the Form class does during class creation. I think it has a meta class, and that this meta class will finalize the form initialization during class creation. That means that any fields you add after class creation will be ignored.
Therefore you need to create a new class, as is done with the type() statement, so that the class creation code of the meta class is involved, now with the new fields.
It's worth noting that this code snippet is a very poor means to the desired end, and involves a common misunderstanding about Django Form objects - that a Form object should map one-to-one with an HTML form. The correct way to do something like this (which doesn't require messing with any metaclass magic) is to use multiple Form objects and an inline formset.
Or, if for some odd reason you really want to keep things in a single Form object, just manipulate self.fields in the Form's __init__ method.

How to get a subclassed object of a django model

When I have a given django model class like this:
class BaseClass(models.Model):
some_field = models.CharField(max_length = 80)
...
and some subclasses of it, for example
class SomeClass(BaseClass):
other_field = models.CharField(max_length = 80)
Then I know I can get the derived object by calling
base = BaseClass.objects.get(pk=3)
my_obj= base.someclass
Now the problem arises that I have multiple subclasses, and all I have is an instance of the base class. How can I get to the subclassed object without knowing it's class in advance?
The idea is to load a corresponding view and let that do the stuff. My project features only have a limited set of default actions for these models, like view, edit, delete etc. What I don't want is to expose the type of the given object via URL, so the "normal way" is not available
There isn't a built-in way.
Perhaps the best thing to do is to define a derived_type field on your base class, which is set automatically on save of the derived class. You could then have a get_derived method on the base which checks the value of derived_type and returns the actual derived object.
How can I get to the subclassed object without knowing it's class in advance?
Why would this be useful? If you don't know what class you want, you also won't know which methods to call or which attributes can be inspected.
The idea is to load a corresponding view and let that do the stuff. My project features only have a limited set of default actions for these models, like view, edit, delete etc. What I don't want is to expose the type of the given object via URL, so the "normal way" is not available
If you know the set of model subclasses ahead of time, or are willing to register them with a central view list, you can do something like this:
VIEWS = [('subclass_a', a_views), ('subclass_b', b_views)]
def edit(request):
base = (get base somehow)
for attrname, views in VIEWS:
if getattr(base, attrname) is not None:
return views['edit']
Depending on how many different kinds of views you have, you might abstract the search out into a separate function, so the end view is something like:
def edit(request):
return generic_base_view(request, 'edit')
For people looking into this answer a bit later than the question was posted and the accepted answer was given, in the newer versions of django, this is possible more directly, see:
django documentation about multi table inheritance
look at the example of places and restaurants. The base object is a place, and the subclass is a restaurant. You can get the subclass by place.restaurant which gives an exception you can catch if the place is not a restaurant.
I add this because the answer accepted might be a bit outdated and led me to the wrong track.
If you use the InheritanceManager from django-model-utils then you can select the subclasses when you query without knowing what they are ahead of time.
https://django-model-utils.readthedocs.org/en/latest/managers.html#inheritancemanager

Categories