暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

使用django-filter和django-tables2打造美观的管理后台

牛建邦 2021-12-29
1315

第一步 安装项目依赖

#pip安装

Django==3.2.2

django-filter==2.4.0 自定义过滤字段

django-tables2==2.3.4  自定义表格显示字段

django-widget-tweaks==1.4.8 用户美化表单

 

第二步 创建项目和应用

先使用django-admin startproject myproject创建一个名为myproject的应用,接着使用python manage.py startappusers 创建一个名为users的app,并把它和其它三个项目依赖加入到settings.py的INSTALLED_APPS中去。

INSTALLED_APPS = [

    'django.contrib.admin',

    'django.contrib.auth',

    'django.contrib.contenttypes',

    'django.contrib.sessions',

    'django.contrib.messages',

    'django.contrib.staticfiles',

    'widget_tweaks'项目依赖

    'django_tables2',项目依赖

    'django_filters',项目依赖

    'users'自建应用

 

]

然后把app下的urls路径加入到项目文件夹的urls.py里去。

from django.contrib import admin

from django.urls import path, include

urlpatterns = [

    path('admin/', admin.site.urls),

    path('myadmin/', include('users.urls'))

]

使用Django自带的User模型,无需创建模型,但考虑到后续存在切换自定义用户模型的可能性,我们使用get_user_model方法获取用户模型。

修改models.py

from django.contrib.auth import get_user_model

 

User = get_user_model()

 

第三步 编写URLs和视图

在users 目录下新建urls.py

from django.urls import path, re_path

from . import views

 

app_name = "users"

 

urlpatterns = [

    path('', views.UserAdminTableView.as_view(), name='user_admin'),

    path('users/create/', views.UserCreateView.as_view(), name='user_create'),

    path('users/<int:pk>/update/', views.UserUpdateView.as_view(), name='user_update'),

    path('users/<int:pk>/delete/', views.UserDeleteView.as_view(), name='user_delete'),

]

编写与各个url路由对应的视图,它们位于user目录下views.py

核心Django模块,比如Mixins和通用视图

from django.contrib.auth.mixins import LoginRequiredMixin

from django.views.generic import CreateView, UpdateView, DeleteView

from django.urls import reverse_lazy

from .models import User

 

# django fitlerdjango_tables2提供的Mixins

from django_tables2 import SingleTableView, RequestConfig

from django_filters.views import FilterView

 

# filters.py定义了用哪些字段对模型进行过滤

from .filters import UserFilter

 

# tables.py定义了用哪些字段将在表格中展示

from .tables import UserTable

 

# forms.py定义了用哪些字段创建或更新用户

from .forms import UserForm

 

管理用户主界面

class UserAdminTableView(LoginRequiredMixinSingleTableViewFilterView):

    filter = None

    使用UserFilter过滤

    filter_class = UserFilter

    使用UserTable展示数据

    table_class = UserTable

    template_name = 'users/user_admin.html'

 

    获取过滤后的查询集

    def get_queryset(self, **kwargs):

        qs = User.objects.all().order_by('-id')

        self.filter = self.filter_class(self.request.GET, queryset=qs)

        return self.filter.qs

 

    将查询集与table实例集合,提供filtertable两个变量前端渲染

    每页5条记录

    def get_context_data(self, **kwargs):

        context = super().get_context_data(**kwargs)

        t = self.table_class(data=self.get_queryset())

        RequestConfig(self.request, paginate={"per_page"5}).configure(t)

        context['filter'] = self.filter

        context['table'] = t

        return context

 

# create

class UserCreateView(LoginRequiredMixinCreateView):

    model = User

    template_name = 'users/user_form.html'

    form_class = UserForm

    success_url = reverse_lazy('users:user_admin')

 

# update

class UserUpdateView(LoginRequiredMixinUpdateView):

    model = User

    template_name = 'users/user_form.html'

    form_class =  UserForm

    success_url = reverse_lazy('users:user_admin')

 

# Delete

class UserDeleteView(LoginRequiredMixinDeleteView):

    model = User

    template_name = 'users/user_confirm_delete.html'

    success_url = reverse_lazy('users:user_admin')

在视图中我们分别使用了UserFilter, UserTable和UserForm, 它们分别位于users目录下的filters.py,tables.py和 forms.py,

# users/filters.py

import django_filters

from django.db.models import Q

from .models import User

 

class UserFilter(django_filters.FilterSet):

    '''

    Filter user by username, email, is_staff, ect

    '''

    自定义过滤字段

    query = django_filters.CharFilter(method='my_custom_filter'label='关键词')

 

    def my_custom_filter(selfquerysetqvalue):

        return queryset.filter(Q(username__icontains=value) | Q(email__icontains=value))

 

    class Meta:

        使用哪个模型和哪些字段过滤

        model = User

        fields = {

            'is_staff',

            'is_superuser',

            'is_active'

        }

# users/tables.py

import django_tables2 as tables

from django.utils.html import format_html

from django.urls import reverse

from .models import User

 

class UserTable(tables.Table):

    id_select = tables.CheckBoxColumn(accessor="id"orderable=Falseexclude_from_export=True)

    actions = tables.Column(empty_values=(), verbose_name="操作"orderable=Falseexclude_from_export=True)

 

    class Meta:

        使用哪个模型

        model = User

        表格中显示哪些字段

        fields = ['username''email''first_name''last_name''is_staff']

        表格中字段显示顺序

        sequence = ['id_select'] + fields + ['actions']

        表格模板

        template_name = "users/bs4_tables2.html"

        表格样式

        attrs = {"class""table table-striped table-sm text-nowrap"}

        排序字段

        order_by_field = 'sort_by' # default: sort

        # page_field = 'page'

 

    自定义操作链接

    def render_actions(selfvaluerecord):

        return format_html(

        '<a class="btn btn-sm badge badge-pill badge-warning ml-2" href= "' +

        reverse("users:user_update"args=[str(record.pk)]) + '">' + '编辑' + '</a>'

        + '<a class=" btn  btn-sm badge badge-pill badge-danger ml-2" href= "' +

        reverse("users:user_delete"args=[str(record.pk)]) + '">' + '删除' + '</a>'

        )

# users/forms.py

from django import forms

from .models import User

 

class UserForm(forms.ModelForm):

    class Meta:

        自定义使用哪个模型和哪些字段来创建和更新用户

        model = User

        fields = ('username''email''password''first_name''last_name')

 

        widgets = {

            'password':forms.PasswordInput(),

        }

 

第四步 编写模板

本项目一共使用到6个模板文件,它们位于users\templates\users目录下:

base.html

<!DOCTYPE html>

<html lang="en">

 

<head>

    <meta charset="utf-8">

    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <meta name="viewport" content="width=device-width, initial-scale=1">

    <meta name="description"

        content="">

    <title>Django Filter + Django Tables 2 Admin</title>

 

    <!-- using online links -->

   <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">

   <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.0/font/bootstrap-icons.css">

   <style>

    @import url(http://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,500,700);

    bodytablethtrtdp

    {

      font-family'Source Sans Pro'sans-serif;

      color:#666;

    }

    body a { color:#666 }

    body a:hover { text-decoration:none;}

 

</style>

 

</head>

 

<body class="bg-light">

        <!-- head  -->

         <div class="bg-secondary">

             <div class="container-fluid px-0" style="max-width: 1200px;">

                 <!-- right-top-navbar  -->

                <nav class="navbar row">

                   <div class="col-6">

                     <a class="navbar-brand text-white" href="#">

                        大江狗Django Filter + Tables2管理后台

                    </a>

                    </div>

 

                    <!-- User profile dropdown -->

                    <div class="dropdown col-6 text-right">

                        {% if not request.user.is_authenticated %}

                             <small><a href="/admin/" class="btn btn-sm btn-info">Login</a></small>

                        {% else %}

                             <a class="dropdown-toggle text-white" href="#" id="dropdown-user" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">

                                {{ request.user.username }}

                            </a>

                            <div class="dropdown-menu dropdown-menu-right mr-2 mt-2" aria-labelledby="dropdown-user">

                                <a class="dropdown-item text-muted" href="/admin/logout/">Log Out</a>

                            </div>

                        {% endif %}

                    </div>

 

                  </nav>

             </div>

        </div>

         <!-- End of head  -->


        <div class="container-fluid py-0 mt-3" style="max-width: 1200px;">

              <!-- Start of x_box 1 -->

              <div class="mx-md-3">

                  {% block content %}

 

                {% endblock %}

                </div><!-- End of x_box -->

          </div>

 

    <!-- using online scripts -->

    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js"

        integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous">

    </script>

    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>

 

</body>

</html>

bs_form.html

{% load widget_tweaks %}

 

{% if form.non_field_errors %}

<div class="alert alert-warning alert-dismissible fade in" role="alert">

          <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span>

          </button>

    {% for error in form.non_field_errors %} {{ error }} {% endfor %}

</div>

{% endif %}

 

{% for hidden_field in form.hidden_fields %} {{ hidden_field }} {% endfor %}

{% for field in form.visible_fields %}

 

      <div class="form-group{% if field.errors %} invalid{% endif %}">

            {{ field.label_tag }}

             {% render_field field class="form-control"%}

              {% if field.help_text %}

                  <small class="form-text text-muted">{{ field.help_text }}</small>

            {% endif %}

              {% for error in field.errors %}

              <p class="text-danger small">{{ error }}</p>

            {% endfor %}

        </div>

{% endfor %}

bs4_tables2.html

{% load django_tables2 %}

{% load i18n %}

{% block table-wrapper %}

<div class="table-wrapper mt-2">

    {% block table %}

    <div class="table-responsive">

        <table {% render_attrs table.attrs class="table" %}>

            {% block table.thead %}

            {% if table.show_header %}

                <thead {{ table.attrs.thead.as_html }}>

                <tr>

                {% for column in table.columns %}

                    <th {{ column.attrs.th.as_html }}>

                        {% if column.orderable %}

                        <a href="{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}"><i class="bi bi-chevron-expand"></i>{{ column.header }} </a>

                        {% else %}

                            {{ column.header }}

                        {% endif %}

                    </th>

                {% endfor %}

                </tr>

                </thead>

            {% endif %}

            {% endblock table.thead %}

            {% block table.tbody %}

                <tbody {{ table.attrs.tbody.as_html }}>

                {% for row in table.paginated_rows %}

                    {% block table.tbody.row %}

                    <tr {{ row.attrs.as_html }}>

                        {% for column, cell in row.items %}

                            <td {{ column.attrs.td.as_html }}>{% if column.localize == None %}{{ cell }}{% else %}{% if column.localize %}{{ cell|localize }}{% else %}{{ cell|unlocalize }}{% endif %}{% endif %}</td>

                        {% endfor %}

                    </tr>

                    {% endblock table.tbody.row %}

                {% empty %}

                    {% if table.empty_text %}

                    {% block table.tbody.empty_text %}

                        <tr><td colspan="{{ table.columns|length }}">{{ table.empty_text }}</td></tr>

                    {% endblock table.tbody.empty_text %}

                    {% endif %}

                {% endfor %}

                </tbody>

            {% endblock table.tbody %}

            {% block table.tfoot %}

            {% if table.has_footer %}

            <tfoot {{ table.attrs.tfoot.as_html }}>

                <tr>

                {% for column in table.columns %}

                    <td {{ column.attrs.tf.as_html }}>{{ column.footer }}</td>

                {% endfor %}

                </tr>

            </tfoot>

            {% endif %}

            {% endblock table.tfoot %}

        </table>

    </div>

    {% endblock table %}

 

    {% block pagination %}

        {% if table.page and table.paginator.num_pages > 1 %}

        <nav aria-label="Table navigation ">

            <ul class="pagination justify-content-center float-md-left">

            {% if table.page.has_previous %}

                {% block pagination.previous %}

                <li class="previous page-item">

                    <a href="{% querystring table.prefixed_page_field=table.page.previous_page_number %}" class="page-link">

                        <span aria-hidden="true">&laquo;</span>

 

                    </a>

                </li>

                {% endblock pagination.previous %}

            {% endif %}

            {% if table.page.has_previous or table.page.has_next %}

            {% block pagination.range %}

            {% for p in table.page|table_page_range:table.paginator %}

                <li class="page-item{% if table.page.number == p %} active{% endif %}">

                    <a class="page-link" {% if p !'...' %}href="{% querystring table.prefixed_page_field=p %}"{% endif %}>

                        {{ p }}

                    </a>

                </li>

            {% endfor %}

            {% endblock pagination.range %}

            {% endif %}

            {% if table.page.has_next %}

                {% block pagination.next %}

                <li class="next page-item">

                    <a href="{% querystring table.prefixed_page_field=table.page.next_page_number %}" class="page-link">

                        <span aria-hidden="true">&raquo;</span>

                    </a>

                </li>

                {% endblock pagination.next %}

            {% endif %}

            </ul>

 

              <div class="float-md-right d-none d-md-block">

             <p>{% trans 'Per page' %}: [ <a href="{% querystring 'per_page'=20 %}">20</a> |

               <a href="{% querystring 'per_page'=50 %}">50</a>

                | <a href="{% querystring 'per_page'=100 %}">100</a> ]

            </p>

           </div>

        </nav>

 

        {% endif %}

 

    {% endblock pagination %}

</div>

{% endblock table-wrapper %}

user_admin.html

{% extends 'users/base.html' %}

{% load django_tables2 %}

 

{% block content %}

     <div class="align-items-center row">

             <div class="col-6 pt-2">

                    <h5>管理用户</h5>

             </div>

 

             <div class="col-6">

                     <span class="dropdown float-right">

                        <a href="{% url 'users:user_create' %}"><button type="button" id="modal-create-btn" class="btn btn-sm btn-success"

                                 data-toggle="tooltip" data-placement="top" title="添加用户">

                         <i class="bi bi-plus"></i>添加用户</button></a>

 

                    </span>

 

              </div>

     </div>

 

    <div class="align-items-center py-1 row bg-white">

             <div class="col-sm-6 col-md-3">

                 <form class="" action="" method="get">

                     <div class="input-group">

                         <input type="text" name="query" class="form-control search-menu"

                            autocomplete="off" placeholder="关键词" value="{{ request.GET.query }}">

                         <div class="input-group-append">

                                <button class="btn btn-primary" type="submit">

                                    <i class="bi bi-search" aria-hidden="true"></i>

                                </button>

                         </div>

                     </div>

                 </form>

             </div>

 

              <div class="col-sm-6 col-md-9 mt-1">

                  <span class="dropdown float-md-right">

                        <button type="button" class="btn btn-sm btn-light dropdown-toggle my-0" data-toggle="dropdown" aria-expanded="false">

                         <i class="bi bi-funnel"></i> 过滤

                     </button>

                     <div class="dropdown-menu dropdown-menu-right px-3 mt-2" role="menu" style="width:280px">

                         <form class="form-horizontal" role="form" autocomplete="off">

                             {% include 'users/bs_form.html' with form=filter.form %}

                             <button type="submit" class="btn btn-sm btn-info form-control">确定</button>

                         </form>

                     </div>

 

                      <a href="{{ request.path }}" class="btn btn-light btn-sm">

                      <i class="bi bi-backspace-reverse"></i> 重置

                      </a>

 

                    </span>

             </div>

 

         </div>

        {% render_table table %}

 

{% endblock %}

User_confirm_delete.html

{% extends 'users/base.html' %}

{% block content %}

 

 <div class="x_title align-items-center row bg-white rounded-top">

     <div class="col-6 pt-2">

         <h5>删除用户</h5>

     </div>

 </div>

 

<div class="align-items-center py-1 row bg-white pl-3 pb-3 border-top border-gray rounded-bottom">

    <form method="post">{% csrf_token %}

        <p>你确定要删除用户"{{ user }}"?</p>

        <input type="submit" value="Confirm" class="btn btn-warning" />

    </form>

</div>

 

{% endblock %}

user_form.html

{% extends 'users/base.html' %}

{% block content %}

 <div class="align-items-center row bg-white rounded-top">

     <div class="col-6 pt-2">

         <h5>{% if object %}编辑用户 {% else %} 添加用户 {% endif %}</h5>

     </div>

 </div>

 

<div class="align-items-center py-1 row bg-white pl-3 pb-3 border-top border-gray rounded-bottom">

    <form action="" method="post" enctype="multipart/form-data">

    {% csrf_token %}

     {% include 'users/bs_form.html' with form=form %}

    <p><input type="submit" class="btn btn-success" value="Submit"></p>

    </form>

</div>

{% endblock %}

 

第五步 查看效果

连续运行如下命令,

python manage.py makemigrations

python manage.py migrate

python manage.py createsuperuser

python manage.py runserver

登录/admin/添加一些用户,在访问127.0.0.0.1:8000/myadmin


文章转载自牛建邦,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论