Django ModelForm inheritance and Meta inheritance - python

I have this ModelForm:
class Event(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(Event, self).__init__(*args, **kwargs)
##Here make some changes such as:
self.helper = FormHelper()
self.helper.form_method = 'POST'
##Many settings here which **i don't want to rewrite in 10 child classes**
class Meta:
model = Event
exclude = something...
widgets = some settings here also.
And this child ModelForm:
class UpgradedEvent(Event):
def __init__(self, *args, **kwargs):
super(UpgradedEvent,self).__init__(*args,**kwargs)
class Meta(Event.Meta):
model = UpgradedEvent
UpgradedEvent is a child of Event model but has some extra fields.
How can i inherit all the settings from the Event FORM into UpgradedEvent FORM?
When running the above code, it renders the Event form. Is there a way to inherit only the settings inside __init__ ?
EDIT
Check out the answer, it works great but keep in mind:
you need to create another instance of FormHelper in your child class, otherwise it won't work. So child class should look something like:
class UpgradedEvent(Event):
def __init__(self, *args, **kwargs):
super(UpgradedEvent,self).__init__(*args,**kwargs)
self.helper = FormHelper()
class Meta(Event.Meta):
model = UpgradedEvent

You can obtain the fields the Meta above, and extend the lists, etc.:
class UpgradedEventForm(EventForm):
def __init__(self, *args, **kwargs):
super(UpgradedEventForm,self).__init__(*args,**kwargs)
# some extra settings
# ...
# for example
self.fields['extra_field'].initial = 'initial value of extra field'
class Meta(EventForm.Meta):
model = UpgradedEvent
exclude = EventForm.Meta.exclude + ['extra_exclude1', 'extra_exclude2']
fields = EventForm.Meta.fields + ['extra_field']
So by using inheritance, we can add extra procedures to the __init__ function by performing some extra actions after the super(UpgradedEventForm, self) call, and wwe can access the attributes of our parent, and extend these.
Note that you better name your forms with a Form suffix, since now your models clash with your forms. As a result, your Form seems to have as model a reference to the Form itself. By using proper "nomenclature", you avoid a lot of mistakes.

Create FormWithSettings which will hold common settings for you form classes and inherit it
class FormWithSettings(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(FormWithSettings, self).__init__(*args, **kwargs)
##Here make some changes such as:
self.helper = FormHelper()
self.helper.form_method = 'POST'
##Many settings here which **i don't want to rewrite in 10 child classes**
class Meta:
exclude = something...
widgets = some settings here also.
class EventForm(FormWithSettings):
def __init__(self, *args, **kwargs):
super(EventForm, self).__init__(*args,**kwargs)
class Meta(FormWithSettings.Meta):
model = Event
class UpgradedEventForm(FormWithSettings):
def __init__(self, *args, **kwargs):
super(UpgradedEventForm, self).__init__(*args,**kwargs)
class Meta(FormWithSettings.Meta):
model = UpgradedEvent

Related

Django FormSetView with custom validation

I'm using the ModelFormSetView class in django-extra-views to create a formset view of all WorkerStatus entries connected to a Worker. I'd also like to use custom validation on the formset, so I've defined my own formset_class and form_class in the view. Here's the view definition:
class WorkerStatusUpdateView(ModelFormSetView):
model = WorkerStatusEntry
formset_class = WorkerStatusFormSet
form_class = WorkerStatusForm
template_name = 'staff/workers/worker_status_update.tmpl'
can_delete = True
can_order = False
fields = ['status', 'start_date']
def dispatch(self, request, *args, **kwargs):
self.worker = Worker.objects.get(pk=self.kwargs['worker_pk'])
return super(WorkerStatusUpdateView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, *args, **kwargs):
ctx = super(WorkerStatusUpdateView, self).get_context_data(*args, **kwargs)
ctx['worker'] = self.worker
return ctx
def get_queryset(self, *args, **kwargs):
return self.worker.statuses.all()
...and here are the definitions of the form and formset respectively:
class WorkerStatusForm(forms.ModelForm):
class Meta:
model = WorkerStatusEntry
fields = ['status', 'start_date']
class WorkerStatusFormSet(BaseModelFormSet):
class Meta:
model = WorkerStatusEntry
def __init__(self, queryset, *args, **kwargs):
super(WorkerStatusFormSet, self).__init__(*args, **kwargs)
def clean(self):
print "Cleaning"
This results in a page where EVERY WorkerStatusEntry in the database is shown in the formset, regardless of get_queryset(). One thing you'll notice is that WorkerStatusFormSet.__init__ takes a queryset argument: I put that there because there was a queryset argument passed to it from the ModelFormSetView, but I don't know what to do with it.
Another thing to note: if I take formset_class = WorkerStatusFormSet out of the view definition, the correct queryset shows up in the formset. However I need to use my own formset class to validate across the whole formset. Unless there's another way?
The problem is your WorkerStatusFormSet.__init__ method. Looking at the code for BaseModelFormSet, the __init__ method already takes a queryset parameter. Since you aren't doing anything in your __init__ method except calling super(), the easiest fix is to remove it.
It's not a good idea to change the signature of the __init__ method as you have done for two reasons
def __init__(self, queryset, *args, **kwargs):
super(WorkerStatusFormSet, self).__init__(*args, **kwargs)
You have changed the order of the arguments. If you look at the code for BaseModelFormset, the first argument is data. That means that data might be incorrectly assigned to queryset if somebody calls WorkerStatusFormSet(data, ...)
You do not do anything with queryset or pass it to super(), so it is lost.

django: subclass FormView twice and override form_class

Im trying to build a FormView for an app that needs to be subclassed afterwards. Sadly I was not able to set the formclass by the subclass.
My Code:
class EventCreateView(FormView):
template_name='Events/add_event_form.html'
success_url = reverse_lazy('events_list')
form_class = None # replaced by __init__ function
def __init__(self, *args, **kwargs):
self.form_class=EventForm
return super(EventCreateView, self).__init__(*args, **kwargs)
#other functions, not shown here ..
class TrainingCreateView(EventCreateView):
def __init__(self, *args, **kwargs):
self.form_class=TrainingForm
return super(TrainingCreateView, self).__init__(*args, **kwargs)
urls.py:
urlpatterns = patterns('',
url(r'event/event/add/$', EventCreateView.as_view(), name='event_add'),
url(r'event/training/add/$', TrainingCreateView.as_view(), name='training_add'),
)
What am I doing wrong?
Try this instead:
class EventCreateView(FormView):
template_name='Events/add_event_form.html'
success_url = reverse_lazy('events_list')
form_class = EventForm
...
class TrainingCreateView(EventCreateView):
form_class = TrainingForm
This doesn't work for the TrainingCreateView because the __init__ view does the following
It sets self.form_class = TrainingForm
super(TrainingCreateView, self).__init__(*args, **kwargs) calls the __init__ of EventCreateView ...
Which sets self.formclass = EventForm
You can get around this by changing the order of your __init_ method. Note that the method doesn't have to return anything.
class TrainingCreateView(EventCreateView):
def __init__(self, *args, **kwargs):
super(TrainingCreateView, self).__init__(*args, **kwargs)
self.form_class = TrainingForm
However, from the code you've written, it is not clear why you need to set self.form_class in the __init__ method, rather than just setting it as a class attribute. If you need to set it dynamically, a better option might be to override get_form_class instead.

Error composing layouts with django-crispy

I want to build several forms in Django with django-crispy using a common layout. I read the crispy documentation about composing layouts, but I cannot made it all by myself, because I get the message error:
append() takes exactly one argument (2 given).
See my code below:
# a class with my common element
class CommonLayout(forms.Form, Layout):
code = forms.CharField(
label='Serial Number',
max_length=12,
required=True,
)
def __init__(self, *args, **kwargs):
super(CommonLayout, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_method = 'POST'
self.helper.layout = Layout (
Field('code', css_class='form-control', placeholder='Read the Serial Number'),
)
#the class with the form
class CollectionForms(forms.Form):
def __init__(self, *args, **kwargs):
super(CollectionForms, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_action = 'collection'
self.helper.layout.append(
CommonLayout(),
FormActions(
StrictButton('Pass', type="submit", name="result", value="True", css_class="btn btn-success"),
)
)
So, I need help to get this right and pass to another forms.
You're creating a CommonLayout form class and you're trying to have other forms inherit the layout of that form.
One way to achieve this is to make CollectionForms inherit from CommonLayout, like so:
#the class with the form
class CollectionForms(CommonLayout):
def __init__(self, *args, **kwargs):
super(CollectionForms, self).__init__(*args, **kwargs)
self.helper.form_action = 'collection'
self.helper.layout.append(
FormActions(
StrictButton('Pass', type="submit", name="result", value="True", css_class="btn btn-success"),
)
)
Notice that this inherits the Layout() object from the CommonLayout form, and extends this. You aren't re-initializing a FormHelper object within your CollectionForms class, you're modifying the FormHelper object created from the CommonLayout form class. Your previous example did not inherit the FormHelper from CommonLayout, it created a new Layout() object, which is the root of your problem.

django : Change default value for an extended model class

I posted a similar question a while earlier, but this one is different. I have a model structure of related classes like:
class Question(models.Model):
ques_type = models.SmallIntegerField(default=TYPE1, Choices= CHOICE_TYPES)
class MathQuestion(Question):
//Need to change default value of ques_type here
// Ex: ques_type = models.SmallIntegerField(default=TYPE2, Choices= CHOICE_TYPES)
I want to change the default value of ques_type in the derived class. How should i accomplish this?
First, in this use of inheritance it is (at least according to my tests) not possible to change the default of the field in the child class. MathQuestion and Question share the same field here, changing the default in the child class affects the field in the parent class.
Now if what only differs between MathQuestion and Question is the behaviour (so, MathQuestion doesn't add any fields besides those defined in Question), then you could make it a proxy model. That way, no database table is created for MathQuestion.
from django.db import models
class Question(models.Model):
ques_type = models.SmallIntegerField(default=2)
class MathQuestion(Question):
def __init__(self, *args, **kwargs):
self._meta.get_field('ques_type').default = 3
super(MathQuestion, self).__init__(*args, **kwargs)
class Meta:
proxy = True
Test:
In [1]: from bar.models import *
In [2]: a=Question.objects.create()
In [3]: a.ques_type
Out[3]: 2
In [4]: b=MathQuestion.objects.create()
In [5]: b.ques_type
Out[5]: 3
Examples above are for proxy models. If you need to change default for model inherited from non-abstract base model you can do following:
from django.db import models
class Base(models.Model):
field_name = models.CharField(...)
class Child(Base):
def __init__(self, *args, **kwargs):
kwargs['field_name'] = kwargs.get('field_name') or 'default value'
super().__init__(*args, **kwargs)
Which will set default if it wasn't passed directly on Model(...) or Model.objects.create(...).
This is easy to do using a closure.
from django.db import models
# You start here, but the default of 2 is not what you really want.
class Question(models.Model):
ques_type = models.SmallIntegerField(default=2)
class MathQuestion(Question):
def __init__(self, *args, **kwargs):
self._meta.get_field('ques_type').default = 3
super(MathQuestion, self).__init__(*args, **kwargs)
class Meta:
proxy = True
The closure allows you to define it how you like it.
from django.db import models
def mkQuestion(cl_default=2):
class i_Question(models.Model):
ques_type = models.SmallIntegerField(default=cl_default)
class i_MathQuestion(i_Question):
def __init__(self, *args, **kwargs):
super(MathQuestion, self).__init__(*args, **kwargs)
return i_MATHQUESTION
MathQuestion = mkQuestion()
MathQuestionDef3 = mkQuestion(3)
# Now feel free to instantiate away.
Use a Form or ModelForm, on which you can override the field. For models, set the default value in it's __init__ method like so:
class Question(models.Model):
ques_type = models.SmallIntegerField(default=2)
class MathQuestion(Question):
def __init__(self, *args, **kwargs):
super(MathQuestion, self).__init__(*args, **kwargs)
self.ques_type = 3
class Meta:
proxy = True
Note that this has to be done after calling the parent class init.
https://docs.djangoproject.com/en/dev/topics/forms/modelforms/

Automatically passing extra attributes to Widget

I have a custom model fields, that can have 'chain' argument.
from django.db import models
class ChainField(object):
def __init__(self, *args, **kwargs):
chain = kwargs.get('chain', False)
if chain:
self.chain = chain
del kwargs['chain']
super(self.__class__.__mro__[2], self).__init__(*args, **kwargs)
class DateTimeField(ChainField, models.DateTimeField):
pass
And now the question: how I can automatically pass 'chain' argument of model field to widget class when initializing ModelForm? I neen that in html it become 'class="chainxxx"' attribute of form field.
Override __init__ of the ModelForm like this:
class MyClass(ModelForm):
def __init__(self, *args, **kwargs):
super(MyClass, self).__init__(*args, **kwargs)
chain_value = self.fields['name_of_the_field'].chain
self.fields['name_of_the_field'].widget = CustomWidget(chain=chain_value)

Categories

Resources