问题
I really hate boilerplate. However, I can't deny that code such as the following is a huge benefit. So my question, what does one do in Python to make up for the fact that it doesn't come with a macro (template) pre-processor?
One idea would be to write a factory function, but I'll willingly admit that I don't know where to start. (Please note that this is Django with its declarative classes and interesting "magic" metaclassy stuff going on underneath, which I know enough to recognise and not enough to understand or debug if I break it)
The other would be to turn this into a template and import it through a trivial pre-processor that implements something like ${var:-default}
in Bash. (see What is an alternative to execfile in Python 3? ),
with my_preprocessor("somefile.py") as f:
code = compile(f.read(), "somefile.py", 'exec')
exec(code) # in the current namespace
But there are lots of warnings about exec
that I've seen over the years. The cited SO answer mentions line numbers for debugging as an issue. Then there is this, http://lucumr.pocoo.org/2011/2/1/exec-in-python/ , warning of subtle problems including memory leaks. I suspect they won't apply to a code defining classes which are "never" deleted, but on the other hand I don't want the slightest risk of introducing obscure problems to a production setting.
Any thoughts or pointers welcome. Is the best thing to do is to accept cut and paste boilerplate? There are unlikely to be more than twenty paste-modifies of any such template, usually less than ten.
Example code. Lines marked #V are the only ones that would commonly be customized. The first two classes are used once only, by the third.
#--- this is boilerplate for a select-view ----
#--- just replace the string "User" by the relevant model and customize
class UserSelectPopupTable( tables.Table):
id = SelectorColumn( clickme='<span class="glyphicon glyphicon-unchecked"></span>' ) #V
class Meta:
model=User
attrs={ 'class':'paleblue' }
empty_text='Sorry, that search did not match anything.'
fields=( 'name','address', ) #V
sequence=('id','name','address',) #V
class UserFilter2(django_filters.FilterSet):
name = django_filters.CharFilter(lookup_expr='icontains') #V
address = django_filters.CharFilter(lookup_expr='icontains') #V
class Meta:
model = User
fields = ('name','address', ) #V (usually same as previous)
class UserSelectPopup( FilterTableView ):
model=User
table_class=UserSelectPopupTable
filterset_class=UserFilter2
template_name='redacted/select_in_popup.html'
#--- end boilerplate
回答1:
Python and Django are awesome.
I read and re-read the (quite short) documentation of the 3-argument form of type
that you use to dynamically create classes (https://docs.python.org/3/library/functions.html#type). I wrote a trivial helper routine Classfactory
to provide a better interface to type
, and translated the class structure into function calls, which was mostly cut and paste! I arrived at the following (which I think also proves that you can write Javascript in Python ... the instinct to insert semicolons was strong)
def Classfactory( classname, inheritsfrom=(object,), **kwargs):
inh = inheritsfrom if isinstance(inheritsfrom, tuple) else (inheritsfrom, )
return type( classname, inh, kwargs)
ThisPopupFilter = Classfactory( 'ThisPopupFilter', django_filters.FilterSet,
name = django_filters.CharFilter(lookup_expr='icontains') ,
address = django_filters.CharFilter(lookup_expr='icontains') ,
Meta = Classfactory( 'Meta',
model = User,
fields = ('name','address', ),
),
)
ThisPopupTable = Classfactory( 'ThisPopupTable', tables.Table,
id = SelectorColumn( clickme='<span class="glyphicon glyphicon-unchecked"></span>' ),
Meta = Classfactory( 'Meta', # default inherit from object
model=User,
attrs={ 'class':'paleblue' },
empty_text='Sorry, that search did not match anything.',
fields=( 'name','address', ) ,
sequence=('id','name','address',) ,
),
)
UserSelectPopup = Classfactory( 'UserSelectPopup', FilterTableView,
model=User,
table_class=ThisPopupTable,
filterset_class=ThisPopupFilter,
template_name='silson/select_in_popup.html', # this template handles any such view
)
Now I suddenly realized that it's not just Django Meta
classes that can be defined inside other classes. Any class that is not needed elsewhere can be nested in the scope where it is needed. So I moved the first two classes inside the third, and then with a bit more rearranging I was able to move to a factory function with arguments ...
def SelectPopupFactory( Model, fields, sequence=None,
clickme='<span class="glyphicon glyphicon-unchecked"></span>' ,
empty_text='Sorry, that search did not match anything.',):
return Classfactory( 'UserSelectPopup', FilterTableView,
model=Model,
template_name='silson/select_in_popup.html', # this template handles any such view
table_class=Classfactory( 'ThisPopupTable', tables.Table,
id = SelectorColumn( clickme=clickme ),
Meta = Classfactory( 'Meta', # default inherit from object
model=Model,
attrs={ 'class':'paleblue' },
empty_text=empty_text,
fields=fields,
sequence=sequence,
)),
filterset_class=Classfactory( 'ThisPopupFilter', django_filters.FilterSet,
name = django_filters.CharFilter(lookup_expr='icontains') ,
address = django_filters.CharFilter(lookup_expr='icontains') ,
Meta = Classfactory( 'Meta',
model = Model,
fields = ('name','address', ),
)),
)
UserSelectPopup = SelectPopupFactory( User,
fields=('name','address', ),
sequence=('id','name','address',) ,
)
Can anybody see anything fundamentally wrong with this? (I'm feeling slightly amazed that it all ran and did not crash at the first attempt, modulo typos)
UPDATE a workday later: I think this is OK as an example / proof of concept (it is code that ran without crashing) but there are several fine points to do with the actual django_filters and django_tables2 usage that aren't right here. My factory function has evolved and is more capable, but less easy to relate to the original non-factory class definitions.
来源:https://stackoverflow.com/questions/39765907/how-to-reduce-django-class-based-view-boilerplate