How to reduce Django class based view boilerplate

人走茶凉 提交于 2019-12-12 04:24:02

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!