[django項目] 爲後臺網站編寫自定義通用視圖

自定義通用視圖

web後端開發的工作就是對數據的增刪改查, 回顧前面的各種功能的代碼,會發現有很多的代碼冗餘。我們一直在做重複的事情。

django框架的一個強大的功能就是提供了一個即插即用的管理後臺,django-admin。我不會教大家去開發一個django-admin,這對於現階段的大家來說太過複雜和龐大。

先學會一點點的優化,不要嘗試一步到位。我們的目的是複用代碼, 將相同的視圖寫成即插即用的通用視圖

I. MyListView(列表視圖)

後臺管理功能中,總是需要以數據表的形式展示數據, 複雜一點會有過濾,查詢,分頁等操作。

但它們的業務邏輯一致,都是接收請求,然後去數據庫中獲取數據,再進行過濾,分頁,然後展示。

那麼我們就可以抽象出一個MyListView來完成這些通用功能,只需要做少量參數配置就可以實現對不同的模型的數據展示。

將相同的代碼抽取出來, 把它做成通用的

1>通用的類屬性

找出通用屬性,定義在類屬性中,繼承它的類通過覆蓋這些屬性來完成自定義。

# 創建myadmin/generic.py並寫入以下代碼
class MyListView(View):
    model = None            # 模型
    template_name = None    # 模板名稱
    is_paginate = False     # 是否分頁
    per_page = None         # 每頁條數
    page_header = None      # 頁頭大標題
    page_option = None      # 頁頭小標題
    table_title = None      # 內容標題

    fields = None           # 需要展示的字段

2>方法

2.1>get

get方法是用來響應get請求的,根據請求,調用相應模型,查詢數據然後渲染模板。

    def get(self, request):
        context = self.get_context_data()
        return render(request, self.get_template_name(), context=context)

2.2>get_template_name

獲取模板名稱,如果不提供類屬性template_name,會根據模型名稱拼接出默認的模板myadmin/model_name/model_name_list.html

    def get_template_name(self):
        """獲取模板名"""
        if self.template_name is None:
            self.template_name = 'myadmin/{0}/{0}_list.html'.format(self.model._meta.model_name)

        return self.template_name

2.3>get_context_data

獲取視圖的上下文變量,如果需要額外添加,可以複寫次方法。

    def get_context_data(self, **kwargs):
        """獲取視圖的上下文變量,如要添加額外變量,請複寫此方法"""
        queryset = self.get_queryset()
        if self.is_paginate:
            page_size = self.per_page

            if page_size:
                page = self.paginate_queryset(queryset, page_size)

            else:
                page = self.paginate_queryset(queryset, DEFAULT_PAGE_SIZE)
        else:
            page = queryset

        context = {
            'page_obj': page,
            'page_header': self.page_header,
            'page_option': self.page_option,
            'table_title': self.table_title
        }

        context.update(kwargs)
        return context
# 在myadmin/constants.py中添加
DEFAULT_PAGE_SIZE = 10

2.4>get_queryset

獲取查詢集,根據是否提供fields屬性,來確定返回的數據。如果有搜索,過濾,可以複寫該方法。

    def get_queryset(self):
        """獲取查詢集,如需過濾,請複寫此方法"""
        if self.model is not None:
            queryset = self.model._default_manager.all()
            # 私有方法_default_manager可以獲取到模型的管理器,
            # 這裏同樣也可以直接寫成objects,但可擴展性相對較低
        else:
            raise ImproperlyConfigured(
                "%(cls)s 是不是傻,你沒有指定model,我怎麼知道呢?" % {
                    'cls': self.__class__.__name__
                }
            )
        if self.fields:
            queryset = queryset.only(*self.fields)
        return queryset

2.5>paginate_queryset

分頁

    def paginate_queryset(self, queryset, page_size):
        """如果需要進行分頁"""
        paginator = Paginator(queryset, page_size)
        try:
            # 嘗試獲取頁碼,獲取不到默認爲1
            page_number = int(self.request.GET.get('page', 1))
        except Exception as e:
            # 若出現其他錯誤,也使其變爲1
            page_number = 1
        page = paginator.get_page(page_number)
        return page

2.6>應用在標籤列表

上面我們已經定義好了模板, 接下來我們就來使用它

# 在myadmin/views.py中添加以下代碼
class TagsListView(MyPermissionRequiredMixin, MyListView):
    permission_required = ('myadmin.news_tags_list', )
    model = Tags

    page_header = '系統設置'
    page_option = '新聞標籤管理'
    table_title = '新聞標籤列表'

    fields = ['name', 'is_delete']
# 在myadmin/urls.py中添加以下代碼
path('tags/', views.TagsListView.as_view(), name='tags_list'),

這樣一個列表頁面的後端部分就搞定了

3>模板

3.1>obj_list

接下來我們看模板, 在templates/myadmin/base中創建一個obj_list.html, 然後在模板中將需要自定義的地方挖坑obj_list.html

<!-- templates/myadmin/base/obj_list.html -->
{% load static %}
{% load news_template_filters %}
<section class="content-header">
    <h1>
        {{ page_header }}
        <small>{{ page_option }}</small>
    </h1>
</section>
<!-- Main content -->
<section class="content container-fluid">
    <div class="box">
        <div class="box-header with-border">
            <h3 class="box-title">{{ table_title }}</h3>
            <div class="box-tools">
                {% block add_button %}
                    <!-- 添加按鈕 -->
                {% endblock %}
            </div>
        </div>
        <!-- /.box-header -->

        <div class="box-body">
            {% block search_form %}
                <!-- 搜索功能表單 -->
            {% endblock %}
            <table class="table table-bordered">
                <tbody>
                {% block table_content %}
                    <!-- 表頭和表格內容 -->
                {% endblock %}
                </tbody>
            </table>
        </div>
        <!-- 分頁 -->
        {% if page_obj.paginator %}
            <div class="box-footer clearfix">
                <div class="row">
                    <div class="col-sm-6">
                        <div class="dataTables_info" id="example2_info" role="status" aria-live="polite">
                            總共:{{ page_obj.paginator.count }}條 第{{ page_obj.start_index }}到{{ page_obj.end_index }}條
                        </div>
                    </div>
                    <div class="col-sm-6">
                        <ul class="pagination pagination-sm no-margin pull-right">
                            <li {% if not page_obj.has_previous %}class="disabled"{% endif %}
                                data-page="{{ page_obj.number|add:-1 }}"><a href="#">«</a></li>
                            {% for n in page_obj|page_bar %}
                                <li {% if n == page_obj.number %}class="active" {% endif %} data-page="{{ n }}"><a
                                        href="#">{{ n }}</a></li>
                            {% endfor %}
                            <li {% if not page_obj.has_next %}class="disabled"{% endif %}
                                data-page="{{ page_obj.number|add:1 }}"><a href="#">»</a></li>
                        </ul>
                    </div>
                </div>

            </div>
        {% endif %}
    </div>
</section>
<!-- /.content -->
{% block script %}
{% endblock %}

4>自定義過濾器page_bar

上面obj_list.html中引用了一個自定義過濾器news_template_filters, 這個是在之前的代碼中定義過的, 作用是根據分頁頁面對象生成分頁頁碼, 代碼:

from django import template

register = template.Library()


@register.filter
def page_bar(page):
    page_list = []
    if page.number != 1:
        page_list.append(1)
    if page.number - 3 > 1:
        page_list.append('...')
    if page.number - 2 > 1:
        page_list.append(page.number - 2)
    if page.number - 1 > 1:
        page_list.append(page.number - 1)
    page_list.append(page.number)
    if page.paginator.num_pages > page.number + 1:
        page_list.append(page.number + 1)
    if page.paginator.num_pages > page.number + 2:
        page_list.append(page.number + 2)
    if page.paginator.num_pages > page.number + 3:
        page_list.append('...')
    if page.paginator.num_pages != page.number:
        page_list.append(page.paginator.num_pages)
    return page_list

5>應用在標籤模板

templates/myadmin中創建一個tags文件夾, 然後創建tags_list.html

<!-- templates/myadmin/tags/tags_list.html -->
{% extends 'myadmin/base/obj_list.html' %}

{% block table_content %}
    <tr>
        <th>#</th>
        <th>標籤名</th>
        <th>是否可用</th>
    </tr>
    {% for obj in page_obj %}
        <tr>
            <td style="width: 40px" data-url=""><a href="#" data-id="{{ obj.id }}">{{ forloop.counter }}</a>
            </td>
            <td>{{ obj.name }}</td>
            <td>{% if obj.is_active %}是{% else %}否{% endif %}</td>
        </tr>
    {% endfor %}
{% endblock %}

運行服務, 打開後臺管理, 我們需要先在菜單管理中創建標籤管理, 纔可以看到它

創建完成後的結果:

II. UpdateView

在列表視圖中, 需要設計出每項的詳情頁面, 用於管理每項的信息.

這樣我們就需要一個詳情頁的通用視圖, 將其命名爲UpdateView

1>通用的類屬性

找出通用屬性,定義在類屬性中,子視圖類通過覆蓋這些屬性來完成自定義。

class UpdateView(View):
    model = None        # 模型
    form_class = None   # 模型表單類
    template_name = None    # 模板名稱

    page_header = None  # 頁頭大標題
    page_option = None  # 頁頭小標題
    table_title = None  # 內容標題

    fields = None   # 需要修改的字段
    pk = None       # url路徑參數名,默認pk對象的pk字段

2>方法

2.1>get

get方法是用來響應get請求的,根據請求,獲取模型對象,生成表單,渲染頁面。

    def get(self, request, **kwargs):
        # 1. 獲取對象
        self.obj = self.get_obj(**kwargs)
        # 2. 獲取模板變量
        context = self.get_context_data()
        # 3. 返回渲染的模板
        return render(request, self.get_template_name(), context=context)

2.2>get_obj

根據傳入的對象主鍵獲取需要修改的對象

    def get_obj(self, **kwargs):
        """獲取需要修改的對象"""
        # 1. 拿到主鍵
        self.get_obj_id(**kwargs)
        # 2. 根據模型獲取對象
        if self.model is None:
            raise ImproperlyConfigured('沒有設置模型')
        obj = self.model.objects.filter(pk=self.obj_id).first()
        if not obj:
            raise ObjectDoesNotExist('找不到pk=%s的對象')
        # 3. 返回對象
        return obj

2.3>get_obj_id

獲取傳入的對象主鍵

    def get_obj_id(self, **kwargs):
        """獲取傳遞的主鍵"""
        if self.pk is None:
            # 如果沒有設置pk的名字, 那麼就獲取 變量名pk的值
            self.obj_id = kwargs.get('pk')
        else:
            # 如果設置了pk的名字, 那麼就獲取 對象pk的值
            self.obj_id = kwargs.get(self.pk)

2.4>get_context_data

獲取視圖的上下文變量,如果需要額外添加,可以複寫次方法。

    def get_context_data(self, **kwargs):
        """獲取上下文變量"""
        # 1. 獲取表單類
        self.form_class = self.get_form_class()
        # 2. 創建表單對象
        form = self.form_class(instance=self.obj)
        # 3. 構造變量對象
        context = {
            'form': form,
            'page_header': self.page_header,
            'page_option': self.page_option,
            'table_title': self.table_title
        }
        context.update(kwargs)
        return context

2.5>get_form_class

獲取表單類,如果沒有提供,通過給定字段屬性自動生成

    def get_form_class(self):
        """獲取表單類"""
        if self.form_class is None:
            # 如果沒有提供,通過給定字段屬性自動生成
            if self.fields is None:
                raise ImproperlyConfigured('未設置form和字段,無法生成表單')
            # modelform_factory接收參數幫你創建一個表單類
            return modelform_factory(self.model, fields=self.fields)
        else:
            return self.form_class

2.6>get_template_name

獲取模板名稱,如果不提供類屬性template_name,會根據模型名稱拼接出默認的模板myadmin/model_name/model_name_detail.html

    def get_template_name(self):
        """獲取模板名"""
        if self.template_name is None:
            # 如果未指定模板名, 則生成一個模板名
            self.template_name = 'myadmin/{0}/{0}_detail.html'.format(self.model._meta.model_name)
        return self.template_name

2.7>put

put方法是用來響應put請求的,根據請求,獲取模型對象,獲取參數,填充表單,校驗,修改對象。

    def put(self, request, **kwargs):
        # 1. 獲取對象
        self.obj = self.get_obj(**kwargs)
        # 2. 獲取表單類
        self.form_class = self.get_form_class()
        # 獲取模型對象
        form = self.form_class(QueryDict(request.body), instance=self.obj)
        if form.is_valid():
            # 校驗成功,保存表單數據
            self.save(form)
            return json_response(errmsg='修改數據成功!')
        else:
            # 校驗失敗,返回渲染錯誤的表單對象
            context = self.get_context_data(form=form)
            return render(request, self.get_template_name(), context=context)

2.8>save

保存對象,如果有額外操作,複寫此方法。

    def save(self, form):
        if form.has_changed():
            instance = form.save(commit=False)
            instance.save(update_fields=form.changed_data)

3>前端代碼

3.1>obj_detail

templates/myadmin/base中創建obj_detail.html, 然後在模板中將需要自定義的地方挖坑

<!-- templates\myadmin\obj_detail.html -->
{% load admin_customer_tags %}
{% load admin_customer_filters %}
<section class="content-header">
    <h1>
        {{ page_header }}
        <small>{{ page_option }}</small>
    </h1>

</section>

<!-- Main content -->
<section class="content container-fluid">
    <div class="box">
        <div class="box-header with-border">
            <h3 class="box-title">{{ table_title }}</h3>
        </div>

        <!-- /.box-header -->

        <div class="box-body">
            <form class="form-horizontal">
                {% csrf_token %}
                {% block form_content %}
                    {% for field in form %}
                        {% if field|is_checkbox %}
                            <div class="form-group">

                                <div class="col-sm-offset-1 col-sm-11">

                                    <div class="checkbox">
                                        <label for="{{ field.id_for_label }}">{{ field }}{{ field.label }}</label>
                                    </div>
                                </div>
                            </div>
                        {% elif field|is_url_field %}
                            <div class="form-group {% if field.errors %}has-error{% endif %}">
                                <label for="{{ field.id_for_label }}"
                                       class="col-sm-1 control-label">{{ field.label }}</label>
                                <div class="col-sm-11">
                                    {% for error in field.errors %}
                                        <label class="control-label"
                                               for="{{ field.id_for_label }}">{{ error }}</label>
                                    {% endfor %}
                                    <div class="input-group">
                                        {% add_class field 'form-control' %}
                                        <span class="input-group-btn"><input class="hidden" type="file">
                      <button type="button" class="btn btn-info btn-flat">上傳文件</button>
                    </span>
                                    </div>
                                </div>
                            </div>
                        {% else %}
                            <div class="form-group {% if field.errors %}has-error{% endif %}">

                                <label for="{{ field.id_for_label }}"
                                       class="col-sm-1 control-label">{{ field.label }}</label>

                                <div class="col-sm-11">
                                    {% for error in field.errors %}
                                        <label class="control-label"
                                               for="{{ field.id_for_label }}">{{ error }}</label>
                                    {% endfor %}
                                    {% add_class field 'form-control' %}
                                </div>
                            </div>
                        {% endif %}
                    {% endfor %}
                {% endblock %}
            </form>
        </div>
        <div class="box-footer">

            <button type="button" class="btn btn-default back">返回</button>
            <button type="button" {% if form.instance.id %}
                    data-url="{% block update_url %}{% endblock %}"
                    data-type="PUT"
            {% else %}
                    data-url="{% block add_url %}{% endblock %}"
                    data-type="POST"
            {% endif %}
                    class="btn btn-primary pull-right save">保存
            </button>
        </div>
    </div>
</section>
{% block script %}
    <script>
        $(() => {
            {% block back_button %}
                // 返回按鈕
                $('.box-footer button.back').click(() => {
                    let url = $('.sidebar-menu li.active a').data('url');
                    if (!url) {
                        return
                    }
                    $('#content').load(
                        url,
                        (response, status, xhr) => {
                            if (status !== 'success') {
                                message.showError('服務器超時,請重試!')
                            }
                        }
                    );
                });
            {% endblock %}

            {% block save_button %}
                $('.box-footer button.save').click(function () {
                    let url = $(this).data('url');
                    if (!url) {
                        return
                    }
                    $
                        .ajax({
                            url: url,
                            data: $('form').serialize(),
                            type: $(this).data('type')
                        })
                        .done((res) => {
                            if (res.errno === '0') {
                                message.showSuccess(res.errmsg);
                                $('#content').load(
                                    $('.sidebar-menu li.active a').data('url'),
                                    (response, status, xhr) => {
                                        if (status !== 'success') {
                                            message.showError('服務器超時,請重試!')
                                        }
                                    }
                                );
                            } else {
                                $('#content').html(res)
                            }
                        })
                        .fail((res) => {
                            message.showError('服務器超時,請重試!')
                        })
                });
            {% endblock %}

            {% block upload %}
                // 上傳文件input
                let $fileInput = $('.input-group-btn input');
                let $uploadBtn = $('.input-group-btn button');
                $uploadBtn.click(function () {
                        $(this).prev('input[type="file"]').click()
                    }
                );
                // 自動上傳文件
                $fileInput.change(function () {
                    $this = $(this);
                    if ($this.val() !== '') {
                        let formData = new FormData();
                        formData.append('upload', $this[0].files[0]);
                        formData.append('csrfmiddlewaretoken', $('input[name="csrfmiddlewaretoken"]').val());
                        $
                            .ajax({
                                url: '/admin/upload/',
                                // 使用ckeditor_uploader 就使用下面的url
                                // url: '/ckeditor/upload/&responseType=json',
                                type: 'POST',
                                data: formData,
                                processData: false,
                                contentType: false
                            })
                            .done((res) => {
                                if (res.uploaded === '1') {
                                    message.showSuccess('封面圖片上傳成功!');
                                    $this.parent().prev('input').val(res.url);
                                    // 清空一下
                                    $this.val('')
                                } else {
                                    message.showError('封面圖片上傳失敗!')
                                }
                            })
                            .fail(() => {
                                message.showError('服務器超時, 請重新嘗試!')
                            })
                    }
                });
            {% endblock %}

        });
    </script>
{% endblock %}
<!-- /.content -->

4>自定義過濾器和自定義標籤

模板中用到了以下幾個自定義過濾器和自定義標籤

4.1>is_checkbox

判斷字段是否checkbox 類型,決定渲染方式

# apps/myadmin/templatetags/admin_customer_filters.py
from django.template import Library
from django.forms.widgets import CheckboxInput

register = Library()


@register.filter()
def is_checkbox(field):
    return isinstance(field.field.widget, CheckboxInput)

4.2>is_url_field

判斷字段是否url類型,決定渲染方式

# apps/myadmin/templatetags/admin_customer_filters.py
@register.filter()
def is_url_field(field):
    return True if 'url' in field.label else False

4.3>add_class

給form字段添加class樣式

# apps/myadmin/templatetags/admin_customer_tags.py
from django.template import Library
from django.shortcuts import reverse

register = Library()


@register.simple_tag()
def add_class(field, class_str):
    return field.as_widget(attrs={'class': class_str})

5>應用到標籤詳情

創建tags/tags_detail.html, 並寫入以下代碼:

{% extends 'myadmin/base/obj_detail.html' %}
{% block update_url %}
	<!-- 詳情頁上, 保存按鈕的鏈接 -->
    {% url 'myadmin:tags_update' form.instance.id %}
{% endblock %}

頁面上的效果:

在這裏插入圖片描述

III. AddView

在列表視圖上做一個添加按鈕, 可以點擊它創建一個新的標籤

接下來我們將其做成一個通用的視圖模型

1>通用類屬性

class AddView(View):
    model = None
    form_class = None
    template_name = None
    fields = None       # 新建對象需要的字段
    page_header = None  # 頁頭大標題
    page_option = None  # 頁頭小標題
    table_title = None	# 表格標題

2>方法

2.1>get

渲染一個對於模型的表單頁面返回。

    def get(self, request):
        context = self.get_context_data()
        return render(request, self.get_template_name(), context=context)

2.2>post

根據post參數生成表單,校驗,創建新對象。

    def post(self, request):
        self.form_class = self.get_form_class()
        form = self.form_class(request.POST)
        if form.is_valid():
            self.save(form)
            return json_response(errmsg='添加數據成功!')
        else:
            context = self.get_context_data(form=form)

            return render(request, self.get_template_name(), context=context)

2.3>get_context_data

獲取視圖的上下文變量,如果需要額外添加,可以複寫次方法。

    def get_context_data(self, **kwargs):

        self.form_class = self.get_form_class()
        form = self.form_class()
        context = {
            'form': form,
            'page_header': self.page_header,
            'page_option': self.page_option,
            'table_title': self.table_title
        }
        context.update(kwargs)
        return context

2.4>get_template_name

獲取模板名稱,如果不提供類屬性template_name,會根據模型名稱拼接出默認的模板`myadmin/model_name/model_name_detail.html

    def get_template_name(self):
        """獲取模板名"""
        if self.template_name is None:
            self.template_name = 'myadmin/{0}/{0}_detail.html'.format(self.model._meta.model_name)

        return self.template_name

2.5>get_form_class

獲取表單類,如果沒有提供,通過給定字段屬性自動生成

    def get_form_class(self):
        if self.form_class is None:
            if self.fields is None:
                raise ImproperlyConfigured('你有不設置form,又不設置fields字段,那怎麼生成表單呢?')
            return modelform_factory(self.model, fields=self.fields)
        else:
            return self.form_class

2.6>save

保存對象,如果有額外操作,複寫此方法。

    def save(self, form):
        form.save()

3>模板

AddView的頁面模板與UpdateView一致, 除此之外, 還需要在tags_list.html中填坑添加按鈕

{% block add_button %}
    <button type="button" class="btn btn-primary btn-sm"
            data-url="{% url 'myadmin:tags_add' %}">添加
    </button>
{% endblock %}

4>應用到添加標籤

添加視圖類:

class TagsAddView(MyPermissionRequiredMixin, AddView):
    permission_required = ('myadmin.news_tags_add',)
    model = Tags
    page_header = '系統設置'
    page_option = '新聞標籤管理'
    table_title = '添加新聞標籤'

    fields = ['name', 'is_delete']

添加url路徑:

path('add_tag/', views.TagsAddView.as_view(), name='tags_add'),

現在已經可以訪問添加頁面了, 但是還不能添加標籤, 需要以下配置

//在tags/tags_detail.html中寫入以下代碼
{% block add_url %}
    {% url 'myadmin:news_tag_add' %}
{% endblock %}

IIII.刪除功能

1>添加刪除按鈕

如果想在某個模型中添加刪除功能, 需要在它的list模板中添加刪除按鈕, 例如此處在tags_list.html中添加

<!-- 添加到指定的tr標籤中 -->
<td style="width: 100px">
    <button type="button" class="btn btn-danger btn-xs delete"
            data-url="{% url 'myadmin:tags_update' obj.id %}">刪除</button>
</td>

2>編寫通用視圖

class UpdateView(View):
	...
    def delete(self, request, **kwargs):
        # 1. 獲取對象
        self.obj = self.get_obj(**kwargs)
        # 2. 判斷對象是否存在
        if self.obj:
            # 2.1 存在則刪除並提示成功
            self.obj.delete()
            return json_response(errmsg='刪除%s成功' % self.obj.name)
        else:
            # 2.2 否則報錯
            return json_response(errno=Code.NODATA, errmsg='%s不存在!' % self.obj.name)

3>編寫通用js

obj_list.html中添加以下代碼即可實現刪除功能

{% block script %}
    <script>
        $(() => {
        	...
            {% block delete %}
                $('button.delete').click(function () {
                    let $this = $(this);
                    let url = $this.data('url');

                    if (!url) {
                        return
                    }
                    $
                        .ajax({
                            url: url,
                            type: 'DELETE',
                            dataType: 'json'
                        })
                        .done((res) => {
                            if (res.errno === '0') {
                                // 刪除表格中的一行元素
                                $this.parent().parent().remove();
                                message.showSuccess(res.errmsg)
                            } else if (res.errno === '4105') {
                                message.showError(res.errmsg)
                            } else if (res.errno === '4101') {
                                message.showError(res.errmsg);
                                setTimeout(() => {
                                    window.location.href = res.data.url
                                }, 1500)
                            } else {
                                message.showError(res.errmsg)
                            }
                        })
                        .fail(() => {
                            message.showError('服務器超時,請重試!')
                        })
                });
            {% endblock %}
        });
    </script>
{% endblock %}

V. 繼續抽象

仔細觀察上面的三個視圖,發現有很多共同的方法,還需要將這些代碼進一步的抽離,然後通過繼承來達到減少冗餘和解耦的作用。

1>TemplateView

抽取公用的代碼;

class TemplateView(View):
    """
    此類視圖僅用於繼承, 不單獨使用
    """
    # 這些屬性是那三個視圖中公用的屬性
    model = None  # 模型
    template_name = None
    page_header = None  # 頁頭大標題
    page_option = None  # 頁頭小標題
    table_title = None  # 內容標題
    fields = None  # 需要展示的字段

    def get(self, request):
        context = self.get_context_data()
        return render(request, self.get_template_name(), context=context)

    def get_context_data(self, **kwargs):
        context = {
            'page_header': self.page_header,
            'page_option': self.page_option,
            'table_title': self.table_title
        }

        context.update(kwargs)
        return context

    def get_template_name(self):
        """獲取模板名"""
        if self.template_name is None:
            if isinstance(self, MyListView):
                # 如果self代表MyListView,就返回list模板
                self.template_name = 'myadmin/{0}/{0}_list.html'.format(self.model._meta.model_name)
            else:
                # 否則返回detail模板
                self.template_name = 'myadmin/{0}/{0}_detail.html'.format(self.model._meta.model_name)
        return self.template_name

2>DetailView

class DetailView(View):
    """
    此類視圖僅用於繼承, 不單獨使用
    """
    form_class = None

    def get_form_class(self):
        if self.form_class is None:
            if self.fields is None:
                raise ImproperlyConfigured('你有不設置form,又不設置fields字段,那怎麼生成表單呢?')
            return modelform_factory(self.model, fields=self.fields)
        else:
            return self.form_class

    def save(self, form):
        form.save()

3>MyListView

class MyListView(TemplateView):

    is_paginate = False                     # 是否分頁
    per_page = None                         # 每頁條數

    def get_queryset(self):
        """獲取查詢集,如需過濾,請複寫此方法"""
        if self.model is not None:
            queryset = self.model._default_manager.all()
        else:
            raise ImproperlyConfigured(
                "%(cls)s 是不是傻,你沒有指定model,我怎麼知道呢?" % {
                    'cls': self.__class__.__name__
                }
            )
        if self.fields:
            queryset = queryset.only(*self.fields)
        return queryset

    def get_context_data(self, **kwargs):
        """獲取視圖的上下文變量,如要添加額外變量,請繼承此方法"""
        queryset = self.get_queryset()
        if self.is_paginate:
            page_size = self.per_page

            if page_size:
                page = self.paginate_queryset(queryset, page_size)

            else:
                page = self.paginate_queryset(queryset, 10)
        else:
            page = queryset
        return super().get_context_data(page_obj=page)


    def paginate_queryset(self, queryset, page_size):
        """如果需要進行分頁"""
        paginator = Paginator(queryset, page_size)
        try:
            page_number = int(self.request.GET.get('page', 1))
        except Exception as e:
            page_number = 1
        page = paginator.get_page(page_number)
        return page

4>UpdateView

class UpdateView(TemplateView, DetailView):

    pk = None           # url路徑參數名,默認pk 對象的pk字段

    def get(self, request, **kwargs):
        self.obj = self.get_obj(**kwargs)
        return super().get(request)

    def put(self, request, **kwargs):
        self.obj = self.get_obj(**kwargs)
        self.form_class = self.get_form_class()
        form = self.form_class(QueryDict(request.body), instance=self.obj)
        if form.is_valid():
            self.save(form)
            return json_response(errmsg='修改數據成功!')
        else:
            context = self.get_context_data(form=form)

            return render(request, self.get_template_name(), context=context)

    def get_obj_pk(self, **kwargs):
        if self.pk is None:
            self.obj_id = kwargs.get('pk')
        else:
            self.obj_id = kwargs.get(self.pk)

    def get_obj(self, **kwargs):
        self.get_obj_pk(**kwargs)
        if self.model is None:
            raise ImproperlyConfigured('沒有設置模型')
        obj = self.model.objects.filter(pk=self.obj_id).first()
        if not obj:
            raise ObjectDoesNotExist('找不到pk=%s的對象' % self.obj_id)
        return self.model.objects.filter(pk=self.obj_id).first()

    def get_context_data(self, **kwargs):

        self.form_class = self.get_form_class()
        form = self.form_class(instance=self.obj)
        context = super().get_context_data(form=form)
        context.update(kwargs)
        return context

    def save(self, form):
        if form.has_changed():
            instance = form.save(commit=False)
            instance.save(update_fields=form.changed_data)

5>AddView

class AddView(TemplateView, DetailView):

    def post(self, request):
        self.form_class = self.get_form_class()
        form = self.form_class(request.POST)
        if form.is_valid():
            self.save(form)
            return json_response(errmsg='添加數據成功!')
        else:
            context = self.get_context_data(form=form)

            return render(request, self.get_template_name(), context=context)

    def get_context_data(self, **kwargs):

        self.form_class = self.get_form_class()
        form = self.form_class()
        context = super().get_context_data(form=form)
        context.update(kwargs)
        return context
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章