python - 为复杂的 View 生成器函数定义 API(具有许多可配置项)

标签 python django

我正在为我的 Django 项目编写一个 View 生成器。我有大量来自遗留应用程序的模型(约 150 个模型),它们都需要相同的基本 CRUD 操作(提供管理员访问权限显然是不够的)。

所以我正在编写一个生成器,它为每个模型返回 5 个 View ,当然每个 View 都可能采用大量选项,我正在尝试为我的生成器定义合理的 API/默认参数格式。

我目前的发电机:

def generate_views(model_class, **kwargs):
    """
    For a given model, returns a dict of generic class-based views
    """
    ###
    # Forms
    #   Optionally generate form classes if not already provided
    ###

    # Append these fields with either "create_" or "update_" to have them only
    # apply to that specific type of form
    form_override_args = ['fields', 'exclude', 'form_method', 'form_class',
                          'form_layout', 'widgets', 'media_css', 'media_js']

    if 'form_class' not in kwargs and 'create_form_class' not in kwargs:
        create_form_kwargs = kwargs.copy()
        for arg in form_override_args:
            if f'create_{arg}' in kwargs:
                create_form_kwargs[arg] = kwargs[f'create_{arg}']
        kwargs['create_form_class'] = forms.FormFactory(model_class, **create_form_kwargs).form()

    if 'form_class' not in kwargs and 'update_form_class' not in kwargs:
        update_form_kwargs = kwargs.copy()
        for arg in form_override_args:
            if f'update_{arg}' in kwargs:
                update_form_kwargs[arg] = kwargs[f'update_{arg}']
        kwargs['update_form_class'] = forms.FormFactory(model_class, **update_form_kwargs).form()

    if 'form_class' not in kwargs:
        kwargs['form_class'] = forms.FormFactory(model_class, **kwargs).form()

    ###
    # Tables
    #   Optionally generate table classes if not already provided
    ###

    # Append these fields with "table_" to have them only
    # apply to the table view
    table_override_args = ['fields', 'exclude']

    if 'table_class' not in kwargs:
        update_table_kwargs = kwargs.copy()
        for arg in table_override_args:
            if f'table_{arg}' in kwargs:
                update_table_kwargs[arg] = kwargs[f'table_{arg}']
        kwargs['table_class'] = tables.TableFactory(model_class, **update_table_kwargs).table()

    ###
    # Views
    #   Generate 5 generic views based on the provided model
    ###
    view_factory = views.ViewFactory(model_class, **kwargs)

    return {
        'list_view': view_factory.list_view(),
        'detail_view': view_factory.detail_view(),
        'create_view': view_factory.create_view(),
        'update_view': view_factory.update_view(),
        'delete_view': view_factory.delete_view()
    }

我目前依赖于 kwargs,我想定义一个完全填写的 kwargs 字典应该是什么样子。有点像

{
    'forms': {
        'all': {

        },
        'create': {

        },
        'update': {

        }
    },
    'tables': {
        'all': {

        },
        'list': {

        }
    },
    'views': {
        'all': {

        },
        'list': {

        },
        'detail': {

        },
        'create': {

        },
        'update': {

        },
        'delete': {

        }
    }
}

而且看起来有点过度劳累。我主要是在寻找有关可能更好的设计的建议(因为我只是在研究它而对它产生了反感)。

最佳答案

看来您正在与 Django 在 class-based views 中构造离散功能/配置的方式作斗争。 .

Django’s generic class-based views are built out of mixins providing discrete functionality.

因此,我的建议是:使用 mixins 将 tableform 类合并到 View 中以进行 CRUD 操作。在生成器中,所有可配置参数都应该只传递给 View

背景知识

让我们看看如何django.views.generic.edit.CreateView被设计。它继承了以下方法和属性: SingleObjectTemplateResponseMixin, BaseCreateViewModelFormMixin。 只需几行代码即可将其绑定(bind)到模型:

from myapp.models import Author
class AuthorCreateView(CreateView):
    model  = Author
    fields = ['FirstName','FamilyName','BirthDay']
    def form_valid(self, form):
        # Saves the form instance, sets the current object for the view, and redirects to get_success_url().

这里 model 属性由所有 mixins 共享以完成它们的工作,而 fieldsform_valid 特定于 ModelFormMixin 。 虽然所有可配置的参数/方法都放在 View 类下,但每个 mixin 只是选择它需要的那些。

重新设计 API

牢记这一点,让我们开始简化您的 View 生成器/工厂。对于此示例,假设您有以下包含通用(默认)设置的基类:

from django.views.generic.edit import CreateView, DeleteView, UpdateView
from django.views.generic import ListView, DetailView
from django_tables2 as SingleTableMixin

class TableListView(SingleTableMixin, ListView):
    table_pagination = { 'per_page': 10 }
    # add common configurable parameters here

class MyOwnCreateView(CreateView):
    success_url = "/yeah"
    # Introduce a configurable method `form_valid_hook`
    def form_valid(self, form):
        if hasattr(self,'form_valid_hook'):
            self.form_valid_hook(form)
        return super().form_valid(form)

下面是所有 5 个 View 的简化生成器函数。

BaseViews= {'create': MyOwnCreateView,
            'delete': DeleteView,
            'update': UpdateView,
            'list'  : TableListView,
            'detail': DetailView }

def generate_views(model_class, **kwargs):
    """
    Generate views for `model_class`

    Keyword parameters:
        {action}=dict(...)
        {action}_mixins=tuple(...)
        where `action` can be 'list', 'detail', 'create', 'update', 'delete'.
    """
    NewViews = {}
    for action, baseView in BaseViews.items():
        viewName = model_class.__name__ + baseView.__name__
        viewAttributes = kwargs.get(action,{})
        viewBaseCls = (baseView,) + kwargs.get(f"{action}_mixins",tuple())
        v = type(viewName, viewBaseCls, viewAttributes) # create a subclass of baseView
        v.model = model_class # bind the view to the model
        NewViews[f'{action}_view'] = v
    return NewViews

你看,生成器函数被简化为只有 10 行的代码。 此外,API 将变得更加简洁:

def validate_author(self, form):
    send_email(form)

AuthorViews = generate_views(Author, 
                             create=dict(
                                 success_url='/thanks/',
                                 form_valid_hook=validate_author), 
                             ... )

如何在此 API 中使用 mixin

在上面的示例中,我使用钩子(Hook)/回调函数form_valid_hook 在保存表单数据之前注入(inject)一个电子邮件发送过程。这很丑陋,因为电子邮件的可配置项将在模块范围内。最好将其重构为一个 mixin 类。

from django.core.mail import send_mail

class FormEmailMixin:
    from_email = 'info@example.com'
    subject_template = 'We hear you'
    message_template = 'Hi {username}, ...'

    def form_valid(self, form):
        user_info = dict( username = self.request.user.username
                          to_email = ... )
        send_mail(subject_template.format(**user_info),
                  message_template.format(**user_info)
                  self.from_email , [user_info['to_email'],] )
        return super().form_valid(form)

然后你就可以在 API 调用中使用这个 mixin 类了。

AuthorViews = generate_views( Author, 
                  create={ 'message_template': 'Dear Author {username}, ...' }, 
                  create_mixins = (FormEmailMixin,) )

关于python - 为复杂的 View 生成器函数定义 API(具有许多可配置项),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55288143/

相关文章:

python - 停止 kivy 视频

django - “SubfieldBase的处理方法已弃用。请改用Field.from_db_value。”

python - django session 以防止用户多次投票

python - 绘图时,想要 'hold' x 区间内的 y 值。 [不是 'bar plot' 问题]

python - 如何在Linux环境下读取Windows文件?

python - 用于解析格式错误的多项式的正则表达式

python - 让两条重叠的线看起来不更粗

python - 获取查询结果作为元组进行替换

python - 'InMemoryUploadedFile' 对象没有属性 'encode'

python - 无法在我的 matplotlib 饼图上设置背景