Why declare field widget before super init? - django

I am a noob at Python and Django and attempting to learn it as I go along, however, there is one thing that confuses me.
In a custom field of mine I have:
def __init__(self, *args, **kwargs):
self.request = None
self.widget = ReCaptcha
super(ReCaptchaField, self).__init__(*args, **kwargs)
self.required = True
self.error_messages = {'required': self.default_error_messages['captcha_invalid']}
and I cannot seem to understand why declaring the field widget works where it is but not after:
super(ReCaptchaField, self).__init__(*args, **kwargs)
Instead it actually produces an error whereby:
def value_from_datadict(self, data, files, name):
In my widget actually is not passed self for some reason.
However:
self.error_messages = {'required': self.default_error_messages['captcha_invalid']}
only works after calling the super.
Why is this?

I currently have no way to confirm this, but I would provide a possible way to explain this.
It's clearer to check the source code for answer here. self.widget is expecting a class and widget will later be initialized as a class instance in __init__ in parent class:
widget = widget or self.widget
if isinstance(widget, type):
widget = widget() # the instance initialization
If you do the assignment after super, your widget remains a class and will never be initialized, thus it is not going to work.
On the other hand, error_messages is actually an optional parameter for __init__ method. If you provide that in __init__ function, it will take it to the self.error_messages. Otherwise, it's empty dict:
messages = {}
for c in reversed(self.__class__.__mro__):
messages.update(getattr(c, 'default_error_messages', {}))
# see here. Did you provide any error_messages? If no then {}
messages.update(error_messages or {})
# self.error_messages might be {} because the above code
self.error_messages = messages
So if you do self.error_messsages before the super, it will be overridden with {}.

Related

Pass argument to __init__ of object model class in iterate queryset

I have a model with overridden __init__ method like this:
class MyModel(models.Model):
...
def __init__(self, *args, **kwargs):
if not kwargs.get('skip', False):
do_something()
super().__init__(*args, **kwargs)
How can I pass skip argument to __init__, when I iter the queryset:
data = [obj for obj in MyModel.objects.all()]
I would like to implement this via method in custom manager, for use this something like this: queryset.with_skip()
I see that you don't remove the the argument skip from kwargs before passing it to super().__init__. That means that "skip" is a name of field, otherwise you got exception TypeError("'skip' is an invalid keyword argument for this function").
If you really need do_something() when the object is created before using so indispensably that nobody should forget to avoid all unsupported ways (??) then custom managers etc. are not enough.
Your problem is that models.Model.__init__(...) supports both *args and **kwargs arguments so perfectly that they should be interchangeable. You broke it and if the "skip" is passed by the complete tuple of positional arguments, you ignore it. That is if the object is created from the database. Read the docs Customizing model loading:
... If all of the model’s fields are present, then values are guaranteed to be in the order __init__() expects them. That is, the instance can be created by cls(*values)...
.
| #classmethod
| def from_db(cls, db, field_names, values):
| ...
| instance = cls(*values)
| ...
An easy way to fix it is to call do_something() after super().__init__ and read self.skip instead of implement parsing both kwargs and args.
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.skip:
do_something()
A problem could be the signal "post_init" that is sent at the end of super().__init__ if you need it.
The last possibility is to support *args (hacky, but still uses documented names someway):
def __init__(self, *args, **kwargs):
if kwargs:
skip = kwargs.get('skip', False)
else:
# check "val is True" in order to skip if val is DEFERRED
skip = any(field.name == 'skip' and val is True
for val, field in zip(args, self._meta.concrete_fields)
)
if not skip:
do_something()
super().__init__(*args, **kwargs)
EDIT: Maybe you you don't need what you wanted and a Proxy model that can do something extra sometimes over a basic model on the same data in the same database table is the right solution. ("Skip" doesn't look like a name describing the object data, but like a name describing a mode of object creation. It is easier to test and maintain a subclass than a mysterious switch inside.)

Django forms adding class attribute from the constructor

As you can see in the code sample below, I'm trying to add that multiple choice field from my constructor (instead of doing it like in the commented line) but it doesn't seem to work, doesn't matter if it's before or after the call of super().
Any advices on how i can add that attribute from my constructor?
class PageForm(forms.Form):
# answers = forms.ModelMultipleChoiceField(Answer.objects.all())
def __init__(self, *args, **kwargs):
self.answers = forms.ModelMultipleChoiceField(Answer.objects.all())
super(forms.Form, self).__init__(*args, **kwargs)
self.answers = forms.ModelMultipleChoiceField(Answer.objects.all())
P.S. I know it might be irrelevant for this example, but I need this thing for a more complex thing :D
Fields need to be added after super. Instead self.answers, try self.fields['answers']

Django form: name 'self' is not defined

I have a form in Django that looks like this
class FooForm(forms.ModelForm):
foo_field = forms.ModelChoiceField(widget=FooWidget(def_arg=self.data))
Where I call self.data, Python throws the exception name 'self' is not defined. How can I access self there?
As others have answered, there is no self to refer to at that point. Something like this does work though:
class FooForm(forms.ModelForm):
foo_field = forms.ModelChoiceField()
def __init__(self, *args, **kwargs):
super(FooForm, self).__init__(*args, **kwargs)
self.fields['foo_field'].initial = self.data
You can also access the widget in __init__ through self.fields['foo_field'].widget
you can't
at the time the class is created, there is no object instance. for this kind of dynamic behaviour, you need to override the __init__ method and create the field (or change some of its parameters) there
You can't; there is no self there. You'll need to do additional setup in __init__().

adding new form fields dynamically in admin

I am trying to add dynamically new form fields (I used this blog post), for a form used in admin interface :
class ServiceRoleAssignmentForm(forms.ModelForm):
class Meta:
model = ServiceRoleAssignment
def __init__(self, *args, **kwargs):
super(ServiceRoleAssignmentForm, self).__init__(*args, **kwargs)
self.fields['test'] = forms.CharField(label='test')
class ServiceRoleAssignmentAdmin(admin.ModelAdmin):
form = ServiceRoleAssignmentForm
admin.site.register(ServiceRoleAssignment, ServiceRoleAssignmentAdmin)
However, no matter what I try, the field doesn't appear on my admin form ! Could it be a problem related to the way admin works ? Or to ModelForm ?
Thank for any help !
Sébastien
PS : I am using django 1.3
When rendering your form in template, fields enumerating from fieldsets variable, not from fields. Sure you can redefine fieldsets in your AdminForm, but then validations will fail as original form class doesn't have such field. One workaround I can propose is to define this field in form definition statically and then redefine that field in form's init method dynamically. Here is an example:
class ServiceRoleAssignmentForm(forms.ModelForm):
test = forms.Field()
class Meta:
model = ServiceRoleAssignment
def __init__(self, *args, **kwargs):
super(ServiceRoleAssignmentForm, self).__init__(*args, **kwargs)
# Here we will redefine our test field.
self.fields['test'] = forms.CharField(label='test2')
I actually have a the same issue which I'm working through at the moment.
While not ideal, I have found a temporary workaround that works for my use case. It might be of use to you?
In my case I have a static name for the field, so I just declared it in my ModelForm. as normal, I then override the init() as normal to override some options.
ie:
def statemachine_form(for_model=None):
"""
Factory function to create a special case form
"""
class _StateMachineBaseModelForm(forms.ModelForm):
_sm_action = forms.ChoiceField(choices=[], label="Take Action")
class Meta:
model = for_model
def __init__(self, *args, **kwargs):
super(_StateMachineBaseModelForm, self).__init__(*args, **kwargs)
actions = (('', '-----------'),)
for action in self.instance.sm_state_actions():
actions += ((action, action),)
self.fields['_sm_action'] = forms.ChoiceField(choices=actions,
label="Take Action")
if for_model: return _StateMachineBaseModelForm
class ContentItemAdmin(admin.ModelAdmin):
form = statemachine_form(for_model=ContentItem)
Now as I mentioned before, this is not entirely 'dynamic', but this will do for me for the time being.
I have the exact same problem that, if I add the field dynamically, without declaring it first, then it doesn't actually exist. I think this does in fact have something to do with the way that ModelForm creates the fields.
I'm hoping someone else can give us some more info.
Django - Overriding get_form to customize admin forms based on request
Try to add the field before calling the super.init:
def __init__(self, *args, **kwargs):
self.fields['test'] = forms.CharField(label='test')
super(ServiceRoleAssignmentForm, self).__init__(*args, **kwargs)

Class based default value for field in Django model inheritance hierarchy

How can one accomplish class-based default value in following scheme? I mean, I would like to inherited classes set default value for "number" differently:
class OrderDocumentBase(PdfPrintable):
number = models.PositiveIntegerField(default=self.create_number())
#classmethod
def create_number(cls):
raise NotImplementedError
class Invoice(OrderDocumentBase):
#classmethod
def create_number(cls):
return 1
class CreditAdvice(OrderDocumentBase):
#classmethod
def create_number(cls):
return 2
I have looked at this stackoverflow question, but it doesn't address the same problem. The only thing I thought would work was overloading OrderDocumentBase's __init__ method like this:
def __init__(self, *args, **kwargs):
"""
Overload __init__ to enable dynamic set of default to number
"""
super(OrderDocumentBase, self).__init__(*args, **kwargs)
number_field = filter(lambda x: x.name == 'number', self._meta.fields)[0]
number = self.__class__.create_number()
number_field.default = number
This works, but only partially and behaves quite wierdly. In admin interface, I can see the default being set only after second or latter page refresh. On first try, None is being set :(
Second possibility is redefinition of number field in each class, but that doesn't seem too much pretty. Is there any other way?
Can someone help?
It does feel nicer to do this via default=, but anything you use there doesn't have a way to get at your class or specific model. To have it show up properly in places like the admin, you could set it in init() instead of save().
class OrderDocumentBase(PdfPrintable):
number = models.PositiveIntegerField()
def __init__(self, *args, **kwargs):
super(OrderDocumentBase, self).__init__(*args, **kwargs)
if not self.pk and not self.number:
self.number = self.DEFAULT_NUMBER
class Invoice(OrderDocumentBase):
DEFAULT_NUMBER = 2
class CreditAdvice(OrderDocumentBase):
DEFAULT_NUMBER = 3
There are a couple of problems here. First, the self.method is not going to work. There is no self in the context of the body of the class, which is where you are declaring the PositiveIntegerField.
Second, passing a callable will not work as the callable gets bound at compile time and does not change at runtime. So if you define say,
class OrderDocumentBase(PdfPrintable):
create_number = lambda: return 0
number = models.PositiveIntegerField(default=create_number)
class Invoice(OrderDocumentBase):
create_number = lambda: return 1
All Invoice instances will still get 0 as default value.
One way I can think of to tackle this is to override the save() method. You can check if the number has not been supplied and set it to a default before saving.
class OrderDocumentBase(PdfPrintable):
number = models.PositiveIntegerField()
def save(self, *args, **kwargs):
if not self.number:
self.number = self.DEFAULT
super(OrderDocumentBase, self).save(*args, **kwargs)
class Invoice(OrderDocumentBase):
DEFAULT = 2
class CreditAdvice(OrderDocumentBase):
DEFAULT = 3
I tested the above with a small change (made OrderDocumentBase abstract as I did not have PdfPrintable) and it worked as expected.