返回顶部

Django之Stark组件开发小记

一. 简介

  网站后台无非就是对数据库表的增删改查。对于频繁的写url,写增删改查功能,写此组件帮助我们快速完成URL的编写和对表的增删改查。

 

功能预览:

 

 

 

二 . 前戏

1.路由分发的本质

include函数返回有三个元素的元组:(urls文件对象,app_name,namespace)

这个urls文件对象其实就是urls.patterns那个列表。

 

2. 单例模式

其实python中导入模块就是一个典型的单例模式,同一个项目中不同文件中,导入相同的模块对象时,其实都是同一个对象。

 

3.Django项目运行前,执行固定的文件

在项目的app文件:

from django.apps import AppConfig

from django.utils.module_loading import autodiscover_modules


class StarkConfig(AppConfig):
    name = 'stark'

    def ready(self):
        autodiscover_modules('stark')

在各个app中就可以创建stark文件,执行相关功能了。在此项目中,主要是为了生成url。

 

三. 自动生成URL

 

注意:path的路径

class StarkSite:

    def __init__(self):
        self._registry = []
        self.namespace = 'stark'
        self.app_name = 'stark'

    def register(self, model_class, handler_class=None, prev=None):
        if not handler_class:
            handler_class = StarkHandler
        self._registry.append(
            {'model_class': model_class, 'handler': handler_class(self, model_class, prev), 'prev': prev}
        )

    def get_urls(self):
        patterns = []
        for item in self._registry:
            model_class = item['model_class']
            handler = item['handler']
            prev = item['prev']
            app_label, model_name = model_class._meta.app_label, model_class._meta.model_name
            if prev:
                patterns.append(
                    path('%s/%s/%s/' % (app_label, model_name, prev), (handler.get_urls(), None, None))
                )
            else:
                patterns.append(
                    path('%s/%s/' % (app_label, model_name), (handler.get_urls(), None, None))
                )
        return patterns

    @property
    def urls(self):
        return self.get_urls(), self.app_name, self.namespace # 这就是include返回的三个元组


site = StarkSite()

通过一个装饰器,把每个试图函数的request赋值给self, 用于其他地方的使用。

def wrapper(self, func):
@functools.wraps(func)
def inner(request, *args, **kwargs):
self.request = request
return func(request, *args, **kwargs)

return inner


def get_urls(self): patterns = [ path('list/', self.wrapper(self.list_view), name=self.get_list_url_name), path('add/', self.wrapper(self.add_view), name=self.get_add_url_name), path('change/<int:pk>/', self.wrapper(self.change_view), name=self.get_change_url_name), path('delete/<int:pk>/', self.wrapper(self.delete_view), name=self.get_delete_url_name), ] patterns.extend(self.extra_url()) return patterns

name属性是拼接出来,通过model_class._meta.app_label等。

    def get_url_name(self, param):
        app_label, model_name = self.model_class._meta.app_label, self.model_class._meta.model_name
        if self.prev:
            return '%s_%s_%s_%s' % (app_label, model_name, self.prev, param)

        return '%s_%s_%s' % (app_label, model_name, param)

    @property
    def get_list_url_name(self):
        return self.get_url_name('list')

    @property
    def get_add_url_name(self):
        return self.get_url_name('add')

    @property
    def get_change_url_name(self):
        return self.get_url_name('change')

    @property
    def get_delete_url_name(self):
        return self.get_url_name('delete')

 

反向生成带参数的URL:

注意:request.GET和request.POST获取到的值是一个QueryDict类型的字典,默认是不允许修改的,可以通过把_mutable属性值改为True就可以修改了。

    def reverse_commen_url(self, name, *args, **kwargs):
        name = '%s:%s' % (self.site.namespace, name)
        base_url = reverse(name, args=args, kwargs=kwargs)
        if not self.request.GET:
            url = base_url
        else:
            param = self.request.GET.urlencode()
            query_dict = QueryDict(mutable=True)
            query_dict['_filter'] = param
            url = '%s?%s' % (base_url, query_dict.urlencode())
        return url

    def reverse_add_url(self, *args, **kwargs):
        return self.reverse_commen_url(self.get_add_url_name, *args, **kwargs)

    def reverse_change_url(self, *args, **kwargs):
        return self.reverse_commen_url(self.get_change_url_name, *args, **kwargs)

    def reverse_delete_url(self, *args, **kwargs):
        return self.reverse_commen_url(self.get_delete_url_name, *args, **kwargs)

    def reverse_list_url(self, *args, **kwargs):
        name = '%s:%s' % (self.site.namespace, self.get_list_url_name)
        base_url = reverse(name, args=args, kwargs=kwargs)
        param = self.request.GET.get('_filter')
        if not param:
            return base_url
        return '%s?%s' % (base_url, param)

 

四. 自定列展示

注意:

key_or_func代表这个可以是一个字段,也可以是一个方法,用于显示m2m,一对多,choice等字典的值。

     list_display = self.get_list_display(*args, **kwargs)  # ['title',]
        header_list = []
        if list_display:
            for key_or_func in list_display:
                if isinstance(key_or_func, FunctionType):
                    verbose_name = key_or_func(self, obj=None, is_header=True)
                else:
                    verbose_name = self.model_class._meta.get_field(key_or_func).verbose_name
                header_list.append(verbose_name)
        else:
            header_list.append(self.model_class._meta.model_name)

        # 处理表内容
        body_list = []
        for row in data_list:
            tr_list = []
            if list_display:
                for key_or_func in list_display:
                    if isinstance(key_or_func, FunctionType):
                        tr_list.append(key_or_func(self, obj=row, is_header=False, *args, **kwargs))
                    else:
                        tr_list.append(getattr(row, key_or_func))
            else:
                tr_list.append(row)
            body_list.append(tr_list)

通过一个闭包的形式来实现。

自定义类:

list_display = ['title', StarkHandler.display_edit_del]
def get_choice_text(title, field):
    def inner(self, obj=None, is_header=None, *args, **kwargs):
        if is_header:
            return title
        method = 'get_%s_display' % field
        return getattr(obj, method)()

    return inner


def get_m2m_text(title, field):
    def inner(self, obj=None, is_header=None, *args, **kwargs):
        if is_header:
            return title
        queryset = getattr(obj, field).all()
        text_list = [str(row) for row in queryset]
        return ','.join(text_list)

    return inner

def display_edit_del(self, obj=None, is_header=None, *args, **kwargs):
if is_header:
return '编辑|删除'
return mark_safe('<a href="%s">编辑</a>|<a href="%s">删除</a>' % (
self.reverse_change_url(pk=obj.pk), self.reverse_delete_url(pk=obj.pk)))

 

分页功能

代码:

"""
分页组件
"""


class Pagination(object):
    def __init__(self, current_page, all_count, base_url, query_params, per_page=20, pager_page_count=11):
        """
        分页初始化
        :param current_page: 当前页码
        :param per_page: 每页显示数据条数
        :param all_count: 数据库中总条数
        :param base_url: 基础URL
        :param query_params: QueryDict对象,内部含所有当前URL的原条件
        :param pager_page_count: 页面上最多显示的页码数量
        """
        self.base_url = base_url
        try:
            self.current_page = int(current_page)
            if self.current_page <= 0:
                raise Exception()
        except Exception as e:
            self.current_page = 1
        self.query_params = query_params
        self.per_page = per_page
        self.all_count = all_count
        self.pager_page_count = pager_page_count
        pager_count, b = divmod(all_count, per_page)
        if b != 0:
            pager_count += 1
        self.pager_count = pager_count

        half_pager_page_count = int(pager_page_count / 2)
        self.half_pager_page_count = half_pager_page_count

    @property
    def start(self):
        """
        数据获取值起始索引
        :return:
        """
        return (self.current_page - 1) * self.per_page

    @property
    def end(self):
        """
        数据获取值结束索引
        :return:
        """
        return self.current_page * self.per_page

    def page_html(self):
        """
        生成HTML页码
        :return:
        """
        # 如果数据总页码pager_count<11 pager_page_count
        if self.pager_count < self.pager_page_count:
            pager_start = 1
            pager_end = self.pager_count
        else:
            # 数据页码已经超过11
            # 判断: 如果当前页 <= 5 half_pager_page_count
            if self.current_page <= self.half_pager_page_count:
                pager_start = 1
                pager_end = self.pager_page_count
            else:
                # 如果: 当前页+5 > 总页码
                if (self.current_page + self.half_pager_page_count) > self.pager_count:
                    pager_end = self.pager_count
                    pager_start = self.pager_count - self.pager_page_count + 1
                else:
                    pager_start = self.current_page - self.half_pager_page_count
                    pager_end = self.current_page + self.half_pager_page_count

        page_list = []

        if self.current_page <= 1:
            prev = '<li><a href="#">上一页</a></li>'
        else:
            self.query_params['page'] = self.current_page - 1
            prev = '<li><a href="%s?%s">上一页</a></li>' % (self.base_url, self.query_params.urlencode())
        page_list.append(prev)
        for i in range(pager_start, pager_end + 1):
            self.query_params['page'] = i
            if self.current_page == i:
                tpl = '<li class="active"><a href="%s?%s">%s</a></li>' % (
                    self.base_url, self.query_params.urlencode(), i,)
            else:
                tpl = '<li><a href="%s?%s">%s</a></li>' % (self.base_url, self.query_params.urlencode(), i,)
            page_list.append(tpl)

        if self.current_page >= self.pager_count:
            nex = '<li><a href="#">下一页</a></li>'
        else:
            self.query_params['page'] = self.current_page + 1
            nex = '<li><a href="%s?%s">下一页</a></li>' % (self.base_url, self.query_params.urlencode(),)
        page_list.append(nex)
        page_str = "".join(page_list)
        return page_str
View Code
      all_count = queryset.count()
        query_param = request.GET.copy()
        query_param._mutable = True

        pager = Pagination(
            current_page=request.GET.get('page'),
            all_count=all_count,
            base_url=request.path_info,
            query_params=query_param,
            per_page=self.per_page_count,
        )

        data_list = queryset[pager.start:pager.end]

 

五. 增删改查


class StarkModelForm(forms.ModelForm):  # 给每一个字段定制样式
def __init__(self, *args, **kwargs):
super(StarkModelForm, self).__init__(*args, **kwargs)
for name, field in self.fields.items():
field.widget.attrs['class'] = 'form-control'

#
#######添加###### has_btn = True def get_add_btn(self, *args, **kwargs): if self.has_btn: return mark_safe('<a href="%s" class="btn btn-primary">添加</a>' % self.reverse_add_url(*args, **kwargs)) model_class_form = None def get_model_form(self, is_add, request, *args, **kwargs): if self.model_class_form: return self.model_class_form class Dynanmic_model_form(StarkModelForm): class Meta: model = self.model_class fields = '__all__' return Dynanmic_model_form

比较简单,通过modelform来实现。

    def save(self, request, form, is_update=None, *args, **kwargs):
        form.save()

    def add_view(self, request, *args, **kwargs):

        model_form_class = self.get_model_form(True, request, *args, **kwargs)
        if request.method == 'GET':
            form = model_form_class()
            return render(request, 'stark/change.html', {'form': form})
        form = model_form_class(data=request.POST)
        if form.is_valid():
            response = self.save(request, form, is_update=False, *args, **kwargs)
            return response or redirect(self.reverse_list_url(*args, **kwargs))
        all_error = form.errors.get('__all__')
        if all_error:
            all_error = all_error[0]
        return render(request, 'stark/change.html', {'form': form,
                                                     'all_error': all_error})

    def get_change_object(self, request, pk, *args, **kwargs):
        return self.model_class.objects.filter(pk=pk).first()

    def change_view(self, request, pk, *args, **kwargs):
        model_form_class = self.get_model_form(False, request, *args, **kwargs)
        current_obj = self.get_change_object(request, pk, *args, **kwargs)
        if not current_obj:
            return HttpResponse('修改的对象不存在!')
        if request.method == "GET":
            form = model_form_class(instance=current_obj)
            return render(request, 'stark/change.html', {'form': form})
        form = model_form_class(instance=current_obj, data=request.POST)
        if form.is_valid():
            response = self.save(request, form, is_update=True, *args, **kwargs)
            return response or redirect(self.reverse_list_url(*args, **kwargs))
        all_error = form.errors.get('__all__')
        if all_error:
            all_error = all_error[0]
        return render(request, 'stark/change.html', {'form': form,
                                                     'all_error': all_error})

    def delete_object(self, request, pk, *args, **kwargs):
        self.model_class.objects.filter(id=pk).delete()

    def delete_view(self, request, pk, *args, **kwargs):
        origin_url = self.reverse_list_url(*args, **kwargs)
        if request.method == 'GET':
            return render(request, 'stark/delete.html', {'cancel': origin_url})
        response = self.delete_object(request, pk, *args, **kwargs)

        return response or redirect(self.reverse_list_url(*args, **kwargs))

 

六. 排序和模糊搜索

注意:

1.可以对Django的Q对象(model中),进行拆分。

 ######## 排序#####
    order_list = []

    def get_order_list(self):
        return self.order_list or ['-id']

    ##############模糊搜索############
    search_list = []

    def get_search_list(self):
        return self.search_list

    ###########批量删除############
########################2. 模糊搜索#################
        search_list = self.get_search_list()
        search_value = request.GET.get('q', '')
        conn = Q()
        conn.connector = 'OR'
        if search_value:
            for item in search_list:
                conn.children.append((item, search_value))

        ########################3. 排序####################
        order_list = self.get_order_list()
        search_group_condition = self.get_search_group_condition()

        queryset = self.model_class.objects.filter(conn).order_by(*order_list)

        ######################## 4 分页 ####################

页面中:

{% if search_list %}
            <div style="margin: 5px 0;float: right">
                <form class="form-inline" method="get">
                    <div class="form-group">
                        <input type="text" class="form-control" name="q" value="{{ search_value }}"
                               placeholder="请输入关键字">
                        <button class="btn btn-primary" type="submit">
                            <i class="fa fa-search" aria-hidden="true"></i>
                        </button>
                    </div>

                </form>

            </div>
{% endif %}

 

七. 批量操作

###########批量删除############

    action_list = []

    def get_action_list(self):

        return self.action_list

    def multi_delete(self, request, *args, **kwargs):
        pk_list = request.POST.getlist('pk')
        self.model_class.objects.filter(id__in=pk_list).delete()

    multi_delete.text = '批量删除'

 

#######################1.批量操作##################
        action_list = self.get_action_list()
        action_func_name = request.POST.get('action')
        action_func_dict = {item.__name__: item.text for item in action_list}
        if action_func_name and action_func_name in action_func_dict:
            response = getattr(self, action_func_name)(request, *args, **kwargs)
            if response:
                return response

在表格的第一列定制一个checkbox

    def display_checkbox(self, obj=None, is_header=None, *args, **kwargs):
        if is_header:
            return '操作'
        return mark_safe('<input type="checkbox" name="pk" value="%s">' % obj.pk)

 

页面:

<form method="post">
            {% csrf_token %}
            {% if action_list %}
                <div style="float: left;margin: 5px">
                    <div class="form-inline">
                        <div class="form-group">
                            <select name="action" class="form-control">
                                <option>请选择操作</option>
                                {% for func_name, func_text in action_func_dict.items %}
                                    <option value="{{ func_name }}">{{ func_text }}</option>
                                {% endfor %}


                            </select>
                            <input type="submit" value="执行" class="btn btn-primary">
                        </div>
                    </div>

                </div>
            {% endif %}

            {% if add_btn %}
                <div style="margin: 5px 0;float: left">
                    {{ add_btn|safe }}
                </div>
            {% endif %}


            <table class="table table-bordered table-striped">
                <thead>
                <tr>
                    {% for item in header_list %}
                        <th>{{ item }}</th>
                    {% endfor %}
                </tr>
                </thead>
                <tbody>
                {% for item in body_list %}
                    <tr>
                        {% for per in item %}
                            <td>{{ per }}</td>
                        {% endfor %}

                    </tr>
                {% endfor %}
                </tbody>
            </table>

        </form>

 

 

 

八. 组合搜索

  search_group可以传一个option对象(field, db_condition=None,is_multi=None,text_func=None, value_func=None)
  通过option去获取queryset或者是choice字段值。

  通过option中的get_queryset_or_tuple方法,返回一个迭代器对象(SearchGroupRow),然后再网页中渲染。

  一个类中,自己实现了__iter__方法,那么它的对象就是一个可迭代对象。

  

##############组合搜索##############

    search_group = []

    def get_search_group(self):
        return self.search_group

    def get_search_group_condition(self):
        condition = {}
        for option_object in self.get_search_group():
            if option_object.is_multi:
                pk_list = self.request.GET.getlist(option_object.field)
                if not pk_list:
                    continue
                condition['%s__in' % option_object.field] = pk_list
            else:
                pk = self.request.GET.get(option_object.field)
                if not pk:
                    continue
                condition[option_object.field] = pk
        print(condition)
        return condition

class starkhandler中:

##############组合搜索###############
        search_group_row_list = []
        search_group = self.get_search_group()
        for option_object in search_group:
            row = option_object.get_queryset_or_tuple(self.model_class, request, *args, **kwargs)
            search_group_row_list.append(row)
class SearchGroupRow:

    def __init__(self, title, queryset_or_tuple, option, query_dict):
        self.title = title
        self.queryset_or_tuple = queryset_or_tuple
        self.option = option
        self.query_dict = query_dict

    def __iter__(self):
        yield '<div class="whole">'
        yield self.title
        yield '</div>'
        yield '<div class="others">'
        total_value_dict = self.query_dict.copy()
        total_value_dict._mutable = True

        origin_value_list = self.query_dict.getlist(self.option.field)
        if not origin_value_list:
            yield '<a href="?%s" class="active">全部</a>' % total_value_dict.urlencode()
        else:
            total_value_dict.pop(self.option.field)
            yield '<a href="?%s">全部</a>' % total_value_dict.urlencode()

        for item in self.queryset_or_tuple:
            text = self.option.get_text(item)
            value = str(self.option.get_value(item))
            query_dict = self.query_dict.copy()
            query_dict._mutable = True
            if not self.option.is_multi:
                query_dict[self.option.field] = value
                if value in origin_value_list:
                    query_dict.pop(self.option.field)
                    yield '<a href="?%s" class="active">%s</a>' % (query_dict.urlencode(), text)
                else:
                    yield '<a href="?%s">%s</a>' % (query_dict.urlencode(), text)
            else:
                multi_value_list = query_dict.getlist(self.option.field)
                if value in multi_value_list:
                    multi_value_list.remove(value)
                    query_dict.setlist(self.option.field, multi_value_list)
                    yield '<a href="?%s" class="active">%s</a>' % (query_dict.urlencode(), text)
                else:
                    multi_value_list.append(value)
                    query_dict.setlist(self.option.field, multi_value_list)
                    yield '<a href="?%s">%s</a>' % (query_dict.urlencode(), text)

        yield '</div>'


class Option:

    def __init__(self, field, db_condition=None, is_multi=None, text_func=None, value_func=None):
        self.field = field
        if not db_condition:
            db_condition = {}
        self.db_condition = db_condition
        self.is_multi = is_multi
        self.text_func = text_func
        self.value_func = value_func
        self.is_choice = False

    def get_queryset_or_tuple(self, model_class, request, *args, **kwargs):

        field_object = model_class._meta.get_field(self.field)
        title = field_object.verbose_name

        db_condition = self.db_condition
        if isinstance(field_object, related.RelatedField):
            return SearchGroupRow(title, field_object.related_model.objects.all(), self, request.GET)
        else:
            self.is_choice = True
            return SearchGroupRow(title, field_object.choices, self, request.GET)

    def get_text(self, field_object):
        if self.text_func:
            return self.text_func(field_object)
        if self.is_choice:
            return field_object[1]
        return str(field_object)

    def get_value(self, field_object):
        if self.value_func:
            return self.value_func(field_object)

        if self.is_choice:
            return field_object[0]
        return field_object.pk

注意:

1.   一个对象,要是能通过for循环进行迭代的话,内部一定实现了__iter__方法。

2. 对QueryDict进行赋值,setlist是先清空在赋值。

3. 判断一个对象是不是m2m,12m字段:

from django.db.models.fields import related
f isinstance(field_object, related.RelatedField):
return SearchGroupRow(title, field_object.related_model.objects.all(), self, request.GET)
else:
self.is_choice = True
return SearchGroupRow(title, field_object.choices, self, request.GET)

 

posted @ 2020-10-12 20:59  muguangrui  阅读(96)  评论(0编辑  收藏  举报