Django Meta Class Access Outer Attribute - python

Folks,
I need to implement a form that may vary a little depending on a variable. My class that subclasses ModelForms looks like this
class ConstantVwModelForm(forms.ModelForm):
#couple attributes
def __init__(self, hasData, *args, **kwargs):
class Meta:
fields = ('xx', 'yy' ..)
I am looking for the very best way to access the variable hasData from the inner class Meta, it would be like
class ConstantVwModelForm(forms.ModelForm):
#couple attributes
def __init__(self, hasData, *args, **kwargs):
class Meta:
if hasData:
fields = ('xx', 'yy', ..)
else:
fields = ('hh', ..)
Any help is highly appreciated

You shouldn't do that and there's no way to achieve this. You can delete field on the fly in your __init__ function explicitly:
class ConstantVwModelForm(forms.ModelForm):
#couple attributes
def __init__(self, hasData, *args, **kwargs):
if hasData:
del self.fields['hh']
else:
del self.fields['xx']
del self.fields['yy']
class Meta:
model = ConstantVwModel

Related

How to remove fields on Django ModelForm?

I would like to build a form with dynamically fields depends on needs and i have tried this code but doesn't work, the model form show all fields.
forms.py:
class CustomModelForm(forms.ModelForm):
class Meta:
model = app_models.CustomModel
fields = '__all__'
def __init__(self, excluded_fields=None, *args, **kwargs):
super(CustomModelForm, self).__init__(*args, **kwargs)
for meta_field in self.fields:
if meta_field in excluded_fields:
# None of this instructions works
-> del self.fields[meta_field]
-> self.fields.pop(meta_field)
-> self.fields.remove(meta_field)
Anybody could help me ?
Thanks in advance.
Alternatively, could you use the modelform_factory?
from django.forms import modelform_factory
CustomModelForm = modelform_factory(MyModel, exclude=('field_1', 'field_2'))
That way you could determine the exclude fields before creating the form, and just pass them into the factory, no need to override the constructor then.
The problem was the list of excluded_fields came from model._meta.get_fields(), not is a list of strings, and the if condition didnt matched well because self.fields is a python ordereddict.
This code solve the problem:
class CustomModelForm(forms.ModelForm):
class Meta:
model = app_models.CustomModel
fields = '__all__'
def __init__(self, excluded_fields=None, *args, **kwargs):
super(CustomModelForm, self).__init__(*args, **kwargs)
show_fields = []
for field in excluded_fields:
show_fields.append(field.name)
for meta_field in list(self.fields):
if meta_field not in show_fields:
del self.fields[meta_field]

Django ModelForm inheritance and Meta inheritance

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

Python - make class decorator work on derived classes

In the app we're developing using Django, in some cases we need to automatically assign permissions to users for some models, that has owners (there is no rule for field's name, it can be "user", "owner", "coach" etc., also there can by more than one field.) My solution is to create a decorator containing those fields names, that will be put before model definition, like this (not using django-specific code in samples):
#auto_assign_perms('owner', 'user')
class Test(Base):
pass
Let's assume that Base is an abstract class deriving after Django's Model class, where I add functionality to assign permissions after object is saved. For now I only print a list of users assigned to the class. Below you can find code for the decorator and Base class:
class auto_assign_perms(object):
def __init__(self, *users):
self.users = users
def __call__(self, cls):
cls.owners.update(self.users)
return cls
class Base(object):
owners = set()
def save(self, *args, **kwargs):
for owner in self.owners:
print owner,
print
And my models could look like this:
#auto_assign_perms('owner', 'user')
class Test(Base):
pass
#auto_assign_perms('coach')
class Test2(Base):
pass
The problem is that both child classes contains all three fields ('owner', 'user', 'coach'), altough print self.__class__.__name__ in Base.save() method properly shows "Test" or "Test2". I tried to add classmethod get_owners() in Base class and then iterating over its results, but it doesn't helps.
How can I solve this? Maybe I should use metaclasses (I don't get them yet)? Thanks in advance.
You need to set the list of owners, not update:
class auto_assign_perms(object):
def __init__(self, *users):
self.users = users
def __call__(self, cls):
cls.owners = set(self.users) # <- here
return cls
#some tests
#auto_assign_perms('owner', 'user')
class Test(Base):
pass
#auto_assign_perms('coach')
class Test2(Base):
pass
t = Test()
t.save()
t = Test2()
t.save()
>>>
owner user
coach
You are using owners as a class variable of Base so whenever you change owners the change will be seen in all the derived classes.
To fix that you should define the owners variable as class variable of the derived classes:
class Base(object):
def save(self, *args, **kwargs):
for owner in self.owners:
print owner,
print
#auto_assign_perms('owner', 'user')
class Test(Base):
owners = set()
#auto_assign_perms('coach')
class Test2(Base):
owners = set()
Call me paranoia but i find this solution more elegant and that because i don't think you need owners to be a class variable at all:
def auto_assign_perms(*users):
def class_wrapper(cls):
class ClassWrapper(cls):
def __init__(self, owners=users):
super(cls, self).__init__(owners=owners)
ClassWrapper.__name__ = cls.__name__
ClassWrapper.__module__ = cls.__module__
return ClassWrapper
return class_wrapper
class Base(object):
def __init__(self, owners=None):
if owners is None:
owners = set()
self.owners = owners
def save(self, *args, **kwargs):
for owner in self.owners:
print owner,
print
#auto_assign_perms('owner', 'user')
class Test1(Base):
pass
#auto_assign_perms('coach')
class Test2(Base):
pass
class Test3(Base):
pass
t = Test1(); t.save() # owner user
t = Test2(); t.save() # coach
t = Test3(); t.save() #

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/

Dynamically add base class?

Let's say I have a base class defined as follows:
class Form(object):
class Meta:
model = None
method = 'POST'
Now a developer comes a long and defines his subclass like:
class SubForm(Form):
class Meta:
model = 'User'
Now suddenly the method attribute is lost. How can I "get it back" without forcing the user to inherit his meta class from mine? Can I dynamically add a base class to Form.Meta in the initializer, or in a metaclass's __new__ func?
As long as they won't override your __init__, or it will be called (ie by super), you can monkey-patch the Meta inner class:
class Form(object):
class Meta:
model = None
method = "POST"
def __init__(self, *args, **kwargs):
if self.__class__ != Form:
self.Meta.__bases__ += (Form.Meta,)
# other __init__ code here.
class SubForm(Form):
class Meta:
model = 'User'
Do you really need Meta to be defined that way? If you only need to access it as form.Meta.method, why wouldn't you just use a dotdict?
class dotdict(dict):
def __getattr__(self, attr):
return self.get(attr, None)
__setattr__= dict.__setitem__
__delattr__= dict.__delitem__
Then you can do this:
class Form(object):
def __init__(self):
self.Meta = dotdict()
self.Meta.model = None
self.Meta.method = 'POST'
class SubForm(Form):
def __init__(self):
Form.__init__(self)
self.Meta.model = 'User'
Maybe you could use a metaclass like this:
class _Meta:
model = None
method = "Post"
class MetaForm(type):
def __init__(cls, name, bases, dct):
super(MetaForm, cls).__init__(name, bases, dct)
if hasattr(cls, 'Meta'):
meta = getattr(cls, 'Meta')
for k,v in _Meta.__dict__.items():
check = meta.__dict__.get(k)
if not check:
meta.__dict__[k] = v
else:
setattr(cls, "Meta", _Meta)
class Form(object):
__metaclass__ = MetaForm
class SubForm(Form):
class Meta:
model = 'User'
class Sub2Form(Form):
pass
sub_form = SubForm()
sub2_form = Sub2Form()
print sub_form.Meta.method # prints "Post"
print sub2_form.Meta.model # prints None
The code is really simple and maybe you need to suit it to your needs.
You can check for method attribute in the __init__ method of a parent object and update it if needed. Of course this will work only if the programmer you are protecting your code from will call it in his constructor.
class Form(object):
def __init__(self):
if not getattr(self.Meta,'method',False):
self.Meta.method='POST'
class Meta:
model = None
method = 'POST'
class SubForm(Form):
class Meta:
model = 'User'
Maybe I could omit the default Meta class inside Form and use a default dict instead?
meta_defaults = {'model':None, 'method':'POST'}
meta_vars = meta_defaults
meta_vars.update(Form.Meta.__dict__)

Categories

Resources