前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python自动化开发学习-Django

Python自动化开发学习-Django

作者头像
py3study
发布2020-01-09 16:43:01
1.7K0
发布2020-01-09 16:43:01
举报
文章被收录于专栏:python3python3

django amdin是django提供的一个后台管理页面,该管理页面提供完善的html和css,使得你在通过Model创建完数据库表之后,就可以对数据进行增删改查。

准备工作

创建一个项目,或者是用已有的项目 使用下面的命令创建生成数据库,这里虽然还没有创建任何的表结构,但是django本身是有一些库要创建的

代码语言:javascript
复制
python manage.py migrate

这个命令一般是搭着 python manage.py makemigrations 之后用的,不过这里我们自己还一个表都还没创建呢。 启动 django 服务,然后默认用这个地址 http://127.0.0.1:8000/admin 就可以打开登陆界面了。

创建超级管理员

使用下面的命令,创建超级管理员账户:

代码语言:javascript
复制
python manage.py createsuperuser

根据提示,输入用户名和密码后,创建成功后,就可以去Web界面登录了。

本地化配置

去settings.py文件里修改下面2项,主要是改了 LANGUAGE_CODE ,这样后台管理能显示中文了。时区的设置这里不影响,不过顺便改一下吧。

代码语言:javascript
复制
# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'zh-hans'

# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Shanghai'

现在在去看看admin的管理页面,已经是中文的页面的。

创建表结构

先创建2张简单的表,有一个简单的外键关联:

代码语言:javascript
复制
class UserInfo(models.Model):
    name = models.CharField(max_length=32)
    age = models.PositiveSmallIntegerField()
    gender_choice = ((1, "男性"), (2, "女性"))
    gender = models.SmallIntegerField(choices=gender_choice)
    dept = models.ForeignKey('Dept', models.CASCADE)

class Dept(models.Model):
    name = models.CharField(max_length=32)

创建完表后要执行一下下面的命令,更新到数据库:

代码语言:javascript
复制
python manage.py makemigrations
python manage.py migrate

要在admin的管理界面里看到这些表,必须要在admin.py文件里注册一下:

代码语言:javascript
复制
from app01 import models

admin.site.register(models.UserInfo)
admin.site.register(models.Dept)

现在已经,可以在管理页面里看到我们的表的。但是英文看的不舒服。

显示自定制的表名和字段名

修改一下之前的表结构,添加verbose_name参数:

代码语言:javascript
复制
class UserInfo(models.Model):
    name = models.CharField(max_length=32, verbose_name="员工姓名")
    age = models.PositiveSmallIntegerField("年龄")
    gender_choice = ((1, "男性"), (2, "女性"))
    gender = models.SmallIntegerField("性别", choices=gender_choice)
    dept = models.ForeignKey('Dept', models.CASCADE, verbose_name="部门")

    class Meta:
        verbose_name = "员工信息"
        verbose_name_plural = "员工信息表"

class Dept(models.Model):
    name = models.CharField(max_length=32, verbose_name="部门名称")

    class Meta:
        verbose_name = "部门"
        verbose_name_plural = "部门表"

字段里面,部分是有位置参数的,所以就省略了 verbose_name= 这些内容。 表名有2个变量,verbose_name就是自定制表名了,但是显示的时候会在表名后面加个“s”,这是英文的做法。verbose_name_plural这个参数,就是替换掉英文加s的显示的内容了,这里我统一在后面加了个“表”。

正确显示记录

现在添加好记录的后,看到的是 UserInfo object (1)Dept object (1) 这样。这里其实和print打印出来的结果一一样的,直接显示了对象。为对象添加 __str__ 方法后,就能正常显示了。最后修改后的表结构如下:

代码语言:javascript
复制
class UserInfo(models.Model):
    name = models.CharField(max_length=32, verbose_name="员工姓名")
    age = models.PositiveSmallIntegerField("年龄")
    gender_choice = ((1, "男性"), (2, "女性"))
    gender = models.SmallIntegerField("性别", choices=gender_choice)
    dept = models.ForeignKey('Dept', models.CASCADE, verbose_name="部门")

    class Meta:
        verbose_name = "员工信息"
        verbose_name_plural = "员工信息表"

    def __str__(self):
        return self.name

class Dept(models.Model):
    name = models.CharField(max_length=32, verbose_name="部门名称")

    class Meta:
        verbose_name = "部门"
        verbose_name_plural = "部门表"

    def __str__(self):
        return self.name

创建用户及权限分配

这里是用admin的后台管理页面创建用户,直接点击默认的“认证和授权”下面的“用户”表,就可以创建记录。 这里要注意,输入了新用户的用户名、密码和确认密码后,就完成了用户的创建,但是这个用户并不能登录。随后会有一个修改用户的界面,都是中文就不细说了。就是这里要勾选一下 “职员状态(指明用户是否可以登录到这个管理站点。)” 这个复选框,该账号才可以登录。 只勾选了上面的复选框,可以实现登录,但是近来是什么也看不到的。在往下还有 “用户权限” ,默认所有的账户都是一张表的权限都没有的,包括超级管理员。但是超级管理员的账户勾选了 “超级用户状态(指明该用户缺省拥有所有权限。)” 所以无视这个设置。 普通用户就需要在这里添加权限了。这里有包括django默认的表以及我们自己创建的表。权限比较粗,基本上就是控制这个用户可以操作那些表,我没找到只读权限。 这里只要给账户 “auth|用户|Can change user” 这一个权限,它就可以为所欲为了,包括把自己变成超管或者把别的超管去掉。

admin.py的设置

这里除了注册我们自己的表以外, 还可以通过继承并重构 admin.ModelAdmin 里面的部分属性,获得更好的管理效果。

设定默认显示

点开员工信息表,能看到现在只显示一列。默认显示的属性是 list_display = ('__str__',) ,所以原本显示的是对象,这里我们已经在类里重构了 __str__ 方法,现在显示的name了。但是只显示name还不够,现在要把所有的字段都显示出来:

代码语言:javascript
复制
from app01 import models

class UserInfoAdmin(admin.ModelAdmin):
    list_display = ('name', 'age', 'gender', 'dept')

admin.site.register(models.UserInfo, UserInfoAdmin)
admin.site.register(models.Dept)

这里就是创建一个类,继承admin.ModelAdmin这个类,然后用自己的 list_display 属性覆盖掉原来的。然后注册的函数后面再把这个自己的类作为参数加上,就可以按照我们的设置显示字段和内容了。

开启搜索功能

继续在类里添加下面的属性:

代码语言:javascript
复制
    search_fields = ('name', 'age')

添加了搜索的属性以后,就会出现一个搜索框。直接搜内容按搜索吧。这里没把部门的字段加进去所以不会按部门搜。另外这里也没加性别的字段,如果有性别的字段,那么也只能搜索数据库里的值,也就是数字1和2。

开启过滤器

继续在类里添加下面的属性:

代码语言:javascript
复制
    list_filter = ('gender', 'dept')

添加了过滤器后,右边就会出现一个过滤器的部件,也可以帮助我们筛选记录。选项特别适合用过滤器来筛选。

开启分页

就是限制每页显示的记录数 继续在类里添加下面的属性:

代码语言:javascript
复制
    list_per_page = 3

防止记录太多,记得设置分页。

修改外键字的的管理方法

继续在类里添加下面的属性,这里只能把外键加进去:

代码语言:javascript
复制
    raw_id_fields = ('dept',)

原本外键的位置是一个下拉的select列表,现在变成了input框,里面是对应的数据库的值(即id)。不过后面有个搜索按钮,可以点开来选择对象的选项。单选并且选项多的时候,可以提升使用的体验。 如果是多对多的外键,需要用这个:

代码语言:javascript
复制
    filter_horizontal = ()  # 这里并没有多对多的字段,就空着吧

这个的效果可以参考用户权限分配里的用户组合用户权限的操作,多选的情况这么设置可以有更好的体验。 这里的两个方法,就是提供多选和单选操作的方便性。

在显示列表中直接修改

继续在类里添加下面的属性:

代码语言:javascript
复制
    list_display = ('name', 'age', 'gender', 'dept')
    list_editable = ('age', 'gender', 'dept')

这里要搭配list_display一起用,就是显示出来的列表中,哪些字段是可以直接在列表中修改的,这种就不用一个一个点进去改了。不过list_display里的第一个元素是不能修改的,否则会报错。

自定义action

默认每张表都是一个Delete的action,另外这个action是可以自己定制的,现在在类里这么加:

代码语言:javascript
复制
    actions = ('test_action',)

    def test_action(self):
        pass

此时再打开表,查看Action的下拉列表就能看到自定制的方法的名称了。至少选中1条记录,然后点go,Web上会报这么个错误 test_action() takes 1 positional argument but 3 were given ,需要3个参数,但是只提供了一个。所以把上面的内容这么改一下看看:

代码语言:javascript
复制
    actions = ('test_action',)

    def test_action(self, request, queryset):
        print(self)
        print(request)
        print(queryset)

# 输出如下:
# crm.CustomerAdmin
# <WSGIRequest: POST '/admin/crm/customer/'>
# <QuerySet [<Customer: 基佬_767676>, <Customer: 小哥_1123081>, <Customer: 姗姗_33312333>]>

参数分别是请求和QuerySet。 另外如果方法是公用的,也可以把方法写到类外面去。并且是支持自定义显示的名字的,如果没有设置,默认显示的就是方法名。定义actions可以使用字符串的函数名,也可以直接引用函数:

代码语言:javascript
复制
# 函数可以写在类的外面,作为一个公共的方法
def test_action(self, request, queryset):
    print(self)
    print(request)
    print(queryset)

test_action.short_description = "中文显示自定义Actions"

# 下面是是在类里定义action,可以用函数名,或者直接引用函数
    actions = (test_action,)

使用admin的认证来做网站的认证

这部分内容讲的不是很系统,下面是官网的文档连接: https://docs.djangoproject.com/zh-hans/2.0/topics/auth/customizing/#customizing-authentication-in-django

django有一张自己的认证表 auth_user ,直接用这张表记录用户的基本认证信息。更加详细的用户信息,就做一个一对一的外键,也就是下面的UserProfile表,来记录自己的更加详细的用户信息。 这里另起炉灶,重新建2张表,和上面的讲的每关系了。 下面是一种实现方法,并没有细讲,先贴上表结构:

代码语言:javascript
复制
ffrom django.contrib.auth.models import User

class UserProfile(models.Model):
    """账号"""
    user = models.OneToOneField(User, models.CASCADE)
    name = models.CharField(max_length=32)
    roles = models.ManyToManyField('Role', blank=True)

    def __str__(self):
        return self.name

class Role(models.Model):
    """角色表"""
    name = models.CharField(max_length=32, unique=True)

    def __str__(self):
        return self.name

新创建的表结构,记得去执行下面的两个步骤:

  • 执行2条命令,同步到数据库
  • admin.py 文件里注册这个新建的表

这里创建了一张自己的表,就是存放用户信息的,比如这里用户信息就一个字段 name 。并且和django的User表做了一对一的关联。也就是用户的认证信息和用户其他信息拆开来,认证信息直接使用django的User表。另外这里还有个角色表,留着做账号的权限管理的。貌似没什么用,但是作为一个结构就一起放上来了。

还可以做更加深度的自定义,文档后面还有很多内容。再深入下去就是要用使用自己的表(比如:crm_myuser表)替代django提供的auth_user这张表了,需要注意下面几点:

  • 去setting.py里设置一下自定义的表,加上这么一行: AUTH_USER_MODEL = 'crm.MyUser'
  • 去admin.py里注册你自己的这个表

登录验证

下面用的都是django帮我么封装好的方法来实现账号的登录和验证等操作:

代码语言:javascript
复制
from django.contrib.auth import authenticate  # 验证用户名和密码的方法
from django.contrib.auth import login  # 登录,上面只是验证,这个才是登录的动作,会帮我么创建session

下面是例子:

代码语言:javascript
复制
from django.contrib.auth import authenticate, login

def acc_login(request):
    """用户登录验证"""
    if request.method == 'POST':
        _username = request.POST.get('username')
        _password = request.POST.get('password')
        user_obj = authenticate(username=_username, password=_password)
        if user_obj is not None:
            print(user_obj)  # 如果认证通过,返回的是用户对象,否则是None
            login(request, user_obj)
            return redirect('/index/')
    return render(request, 'login.html')

登出

使用logout()方法就可以登出

代码语言:javascript
复制
from django.contrib.auth import logout

def logout_view(request):
    logout(request)
    return redirect('/accounts/login/')

登出后跳转到登录页面,django默认的登录也的目录是上面的 '/accounts/login/‘ 。

登录验证装饰器

这个直接用就好了:

代码语言:javascript
复制
from django.contrib.auth.decorators import login_required

@login_required
def my_view(request):
    pass

只要没有认证就会跳转到登录页面,默认的登录页面的url是 '/accounts/login/‘ ,也可以通过设置改成别的。在settings.py里加一个参数,指定登录页面的url:

代码语言:javascript
复制
LPGIN_URL = '/login/'

上面是全局的改变登录页面url方法,装饰器本身也有参数,可以指定url:

代码语言:javascript
复制
@login_required(login_url='/accounts/login/')

上面在跳转到登录页面的同时,也会保存当前请求页面的url,默认是放在next参数里的。这需要我们写登录处理函数的时候,在认证成功后,能够用 request.GET.get('next') 获取一下这个参数,如果有就跳转到参数指定的url。这个参数是一个get请求的参数,名字默认是next,也可以指定成别的名字:

代码语言:javascript
复制
@login_required(redirect_field_name='my_redirect_field')

登录本身仍然是一个POST请求,但是依然可以带GET请求的参数,通过GET请求的参数获取的方法是能够通过解析url获取到参数的。

自定义权限

默认每张表都有 add、change、delete 这3个权限。也可以添加自定义的权限,随便找个Model,一般就是用户信息的Model。在Meta里定义Permissions,前面是权限的名字,后面的权限的描述:

代码语言:javascript
复制
class Task(models.Model):
    ...
    class Meta:
        permissions = (
            ("view_task", "允许浏览"),
            ("change_task_status", "Can change the status of tasks"),
            ("close_task", "Can remove a task by setting its status as closed"),
        )

设置完Model后,需要同步一下数据库 makemigrations、migrate。之后自定义的权限会添加到django自己的权限表 auth_perssion 里,也可以的admin的权限设置里设置这个权限了。 permissions写哪里 测试下来写在任意一个Model里都是可以的,应该可以写一个空的class,或者放到用户相关的class里。

验证权限 首先获取到用户对象(只有这个对象才有has_perm()方法),然后调用has_perm()方法,参数是[app名字].[权限名]。如果该用户有这个权限,就返回True,如果用户没有这个权限,或者根本没这个权限名,都是返回False。

代码语言:javascript
复制
user.has_perm('app.view_task')

下面是测试的验证,期间去admin里修改一下权限再看看:

代码语言:javascript
复制
D:\>python manage.py shell
Python 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on
 win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.contrib.auth.models import User
>>> user = User.objects.get(id=2)
>>> user.has_perm('crm.view_task')
True
>>> user.has_perm('crm.close_task')
False
>>> user.has_perm('crm.close_task')
False
>>> User.objects.get(username='Adam').has_perm('crm.close_task')
True
>>> user.has_perm('crm.close_task')
False
>>> User.objects.get(username='Adam').has_perm('crm.close_task')
False
>>> User.objects.get(username='Adam').has_perm('crm.close_task')
True
>>> user.has_perm('crm.close_task')
False
>>>

获取到user之后,权限都在里面了,这时候再去修改权限user里的权限也不会变,重新get一下user的话就能获取到最新的权限了。这是django提供的方法,所以也支持用户组。 只要能拿到User对象和权限名,就是获取到一个True或False的结果。应该也可以写在if里选择性的不给前端返回某些重要信息,模板语言里也可以判断后不显示这部分内容和一些按钮。 也可以给处理函数加上装饰器,下面是django提供的装饰器:

代码语言:javascript
复制
from django.contrib.auth.decorators import permission_required

@permission_required('crm.view_task')
def my_view(request):
    ...

装饰器的文档在这里:https://docs.djangoproject.com/zh-hans/2.0/topics/auth/default/

更加精细的自定义权限

讲师的博客:https://www.cnblogs.com/alex3714/articles/6661911.html 下面大概都是废话了,另外博客里的最后一节是再提供一个自定义函数的钩子,让用户可以通过自定义的函数实现动态的业务逻辑的权限控制。

如果是用装饰器来控制权限的话,django提供的装饰器就是装饰处理view函数的,有权限就可以进入这个处理函数,没有权限就跳转。 更加精细的权限是这样的情况:

  • 同一个处理函数,可能对应多个页面。比如用户信息,只能修改自己的信息,不如进入别人的信息修改页面
  • 同一个处理函数,可能对应多个方法。比如只能发GET请求,不能发POST请求

控制权限的维度:

  • 限制url的访问
  • 限制url里的参数(貌似没有这个,或者不需要控制这个?)
  • 限制请求的方法
  • 限制请求使用的参数:必须包含某系参数,但是不关心值是多少,把参数记录在一个列表里
  • 限制请求使用的参数和值:必须包含特定的参数,并且值也必须匹配,把键值对记录在一个字典里

不过也不是所有的权限都是可以用装饰器来实现的。有些太精细的可能要放到业务逻辑里 继续用django的自定义权限来分配权限。自己搞一个权限的数据结构,记录更精细的权限设置。使用自己写的装饰器:

  1. 按上面自定义权限里说的(写在Meta里的),先在django里自定义一些权限,然后可以把这些权限分配给用户或组
  2. 自己的精细权限字典里,你的key就要是上面django里面定义的权限的名字(或者能对应上的),value就是上面说的控制权限的4个维度
  3. 首先认证用户是否登录
  4. 拿到用户请求的url,解析到各个参数,去自定义的权限字典里匹配,找到一个匹配的名字(Key)
  5. 把权限字典里匹配到的名字转成django里的权限名,以这个权限名作为参数,调用ser.has_perm(),看看是否给用户分配了这个权限

上面的3、4、5就是我们自己要写的那个通用的权限控制的装饰器。实现后给各个视图装饰上就好了。分配权限就是上面1、2那两个步骤进行设置。 可能需要用到下面这些:

代码语言:javascript
复制
request.path  # 路径的属性,但是不包括get请求的参数部分
request.get_full_path()  # 完整的路径,报告get请求的参数
from urllib.parse import urlparse  # 解析url的,get请求的参数要通过它来转码
from django.urls import resolve  # 解析url,分解出各种参数
view, args, kwargs = resolve(urlparse(next)[2])  # 看下面的说明

django的做法是,在跳转到另外一个页面做某些操作但是完成后需要跳转回来的时候,会把当前的url作为跳转的get请求的next参数。当完成操作需要跳回之前的页面的时候,读取这个next参数(这里肯定要转码),然后就知道该跳转到哪里了。

代码语言:javascript
复制
# 如果你有request,就不需要调用resolve方法了,django已经帮我们把结果封装到了request.resolver_match里了。
# 下面2个的结果是一样的
print(resolve(request.path))
print(request.resolver_match)
# 里面还有这些常用的属性
print(request.resolver_match.app_name, request.resolver_match.namespace)
print(request.resolver_match.url_name, request.resolver_match.view_name)

king_admin 开发

到这里,课上要做一个自己的类似 django admin 那样的后台管理界面。基本就是看着 django admin 的样子,反推它的实现方法,然后自己写一个一模一样的(差不多样子的)。 首先,另外创建一个app:

代码语言:javascript
复制
python manage.py startapp [app的名字]

然后在app里建立自己的admin配置文件,默认系统会自动生成一个admin.py,所以我们的文件可以叫 [app的名字]_admin.py 。然后就是照着admin.py的样子进行注册和配置,另外我们自己的admin的基类也放在这里把。 下面主要把其中的一些坑记录下来

通过表名获取app的name

用下面的方法进入django的python,然后在你的项目里测试,找到你要的东西。

代码语言:javascript
复制
(django) D:\PycharmProjects\LowCRM>python manage.py shell
Python 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from crm import models
>>> models.UserProfile
<class 'crm.models.UserProfile'>
>>> models.UserProfile._meta
<Options for UserProfile>
>>> dir(models.UserProfile._meta)
['FORWARD_PROPERTIES', 'REVERSE_PROPERTIES', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribu
te__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__rep
r__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_expire_cache', '_forward_fields_map', '_get_fields', '_get_fields_cache
', '_ordering_clash', '_populate_directed_relation_graph', '_prepare', '_property_names', '_relation_tree', 'abstract', 'add_field', 'add_manager', 'app_c
onfig', 'app_label', 'apps', 'auto_created', 'auto_field', 'base_manager', 'base_manager_name', 'can_migrate', 'concrete_fields', 'concrete_model', 'contr
ibute_to_class', 'db_table', 'db_tablespace', 'default_apps', 'default_manager', 'default_manager_name', 'default_permissions', 'default_related_name', 'f
ields', 'fields_map', 'get_ancestor_link', 'get_base_chain', 'get_field', 'get_fields', 'get_latest_by', 'get_parent_list', 'get_path_from_parent', 'get_p
ath_to_parent', 'has_auto_field', 'index_together', 'indexes', 'installed', 'label', 'label_lower', 'local_concrete_fields', 'local_fields', 'local_manage
rs', 'local_many_to_many', 'managed', 'managers', 'managers_map', 'many_to_many', 'model', 'model_name', 'object_name', 'order_with_respect_to', 'ordering
', 'original_attrs', 'parents', 'permissions', 'pk', 'private_fields', 'proxy', 'proxy_for_model', 'related_fkey_lookups', 'related_objects', 'required_db
_features', 'required_db_vendor', 'select_on_save', 'setup_pk', 'setup_proxy', 'swappable', 'swapped', 'unique_together', 'verbose_name', 'verbose_name_pl
ural', 'verbose_name_raw']
>>> models.UserProfile._meta.app_label
'crm'
>>> models.UserProfile._meta.model_name
'userprofile'

所有的信息都在 _meta 里面。比如上面的app名称 app_label 和表名 model_name 。测试出了要的信息的位置,就可以到代码里放心用了。 下面是steed_admin.py里的内容:

代码语言:javascript
复制
from crm import models

enabled_admins = {}

class BaseAdmin(object):
    """steed_admin的基类"""
    list_display = []
    list_filter = []

class CustomerAdmin(BaseAdmin):
    list_display = ['qq', 'name']

class CustomerFollowUpAdmin(BaseAdmin):
    list_display = ['customer', 'consultant', 'data']

def register(model_class, admin_class=None):
    """注册要使用admin的表"""
    app_name = model_class._meta.app_label  # 通过表拿到app的name
    model_name = model_class._meta.model_name  # 通过表拿到表名
    if app_name not in enabled_admins:
        enabled_admins[app_name] = {}
    # admin_class.model = model_class  # 这样往类里添加另一个类是有问题的
    # enabled_admins[app_name][model_name] = admin_class  # 这2句用下面用下面的3句来实现
    # 这里先要实例化一个对象,然后再往对象里添加。这样可以正常返回给前端
    # 如果只是类不实例化,后端打印没问题,但是前端取不到内容
    admin_obj = admin_class()
    admin_obj.model = model_class
    enabled_admins[app_name][model_name] = admin_obj  # 现在存的是对象了,不是类。

register(models.Customer, CustomerAdmin)
register(models.CustomerFollowUp, CustomerFollowUpAdmin)

在模板语言里显示上面的app的name

这里有个坑,根据上面的测试。处理函数像下面这样如下:

代码语言:javascript
复制
def index(request):
    # print(steed_admin.enabled_admins['crm']['customer'].model)
    return render(request, 'steed_admin/index.html', {'table_list': steed_admin.enabled_admins})

前端的模板语言如下,这里要用到自定义函数,所以引用了tags:

代码语言:javascript
复制
{% load tags %}

{% block panel_table %}
    {% for app_name, app_tables in table_list.items %}
    <table class="table table-hover">
    <thead>
    <tr>
        <th>{{ app_name }}</th>
    </tr>
    </thead>
    <tbody>
    {% for table_name, admin in app_tables.items %}
        <tr>
        <td><a href="{% url 'table_objs' app_name table_name %}">{% render_app_name admin %}</a></td>
        <td>添加</td>
        <td>编辑</td>
        </tr>
    {% endfor %}
    </tbody>
    </table>
    {% endfor %}
{% endblock %}

这里有个坑,按着上面的测试,要显示表的名字,可以像下面这样写:

代码语言:javascript
复制
        <td><a href="{% url 'table_objs' app_name table_name %}">{{ admin.model._meta.verbose_name_plural }}</a></td>

上面的用法在后端测试了,没问题。但是前端用不了,因为_meta前端不只是下划线开头。不过可以把这个写到模板语言的自定义函数里,避免在前端用到_meta。所以这里补上自定义函数的文件tags.py的内容:

代码语言:javascript
复制
from django import template

register = template.Library()

@register.simple_tag
def render_app_name(admin_class):
    return admin_class.model._meta.verbose_name_plural

把选项的内容显示出来

在显示选项的时候,需要拿着字段的名字(字符串),判断一下是不是有选项,如果是选项,需要显示出对应的选项的内容。否则显示的只是选项的数字。 这里首先要通过字段名,在表里查到这个字段的类型,然后判断一下里面的choices属性:

代码语言:javascript
复制
(django) D:\PycharmProjects\LowCRM>python manage.py shell
Python 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from crm import models
>>> models.Customer._meta.get_field('qq')
<django.db.models.fields.CharField: qq>
>>> print(models.Customer._meta.get_field('qq'))
crm.Customer.qq
>>> models.Customer._meta.get_field('qq').choices
[]
>>> models.Customer._meta.get_field('source').choices
((1, '转介绍'), (2, 'QQ群'), (3, '官网'), (4, '51CTO'), (5, '市场推广'))
>>>

这里要获取到的是字段的类,然后看看里面的choices属性。所有的表结构的基类都是Field,并且都用choices属性:

代码语言:javascript
复制
        self.choices = choices or []

如果是空列表,表示定义类型的时候没有给他choices属性。否则,就不能显示数据库的记录的值,而是要先出这个值所关联的choices里的内容。这时通过 get_FOO_display 就可以拿到选项里的内容了。 即{{ obj.get_level_display}},如果是在后端,这是个方法,最后要加()来调用一下。 在tags.py里的自定义函数如下:

代码语言:javascript
复制
@register.simple_tag
def build_table_row(obj, admin_class):
    """直接生成表格所有行的html返回
    obj: admin_class.model.objects.all() 查询到的所有数据
    admin_class: 获取要显示哪些列 admin_class.list_display
    """
    row_ele = ""
    for column in admin_class.list_display:
        # 判断是否有关联,显示关联的内容
        field_obj = obj._meta.get_field(column)
        if field_obj.choices:  # field_obj.choices == []
            column_data = getattr(obj, 'get_%s_display' % column)()
        else:
            column_data = getattr(obj, column)
        # 判断遇到的数据格式,如果是日期,转化一下
        if type(column_data).__name__ == 'datetime':
            column_data = column_data.strftime("%Y-%m-%d %H:%M:%S")
        row_ele += '<td>%s</td>' % column_data
    return mark_safe(row_ele)

上面还有对时间日期格式做了转化。

Django的分页

先去django的官网搜索一下:https://docs.djangoproject.com 搜一下分页的关键字 “Pagination ” 。就照着例子写就好了 下面是项目里的代码:

代码语言:javascript
复制
def display_table_objs(request, app_name, table_name):
    # print(app_name, table_name)
    admin_class = steed_admin.enabled_admins[app_name][table_name]
    # print(admin_class.model._meta.verbose_name_plural)
    # 下面是分页的实现
    contact_list = admin_class.model.objects.all()
    paginator = Paginator(contact_list,  3)  # 每页显示3条
    page = request.GET.get('page')
    contacts = paginator.get_page(page)
    return render(request, 'steed_admin/table_objs.html', {'admin_class': admin_class, 'contacts': contacts})

然后是前端的部分:

代码语言:javascript
复制
{% block panel_table %}
    <table class="table table-hover">
    <thead>
    <tr>
        {% for column in admin_class.list_display %}
            <th>{{ column }}</th>
        {% endfor %}
    </tr>
    </thead>
    <tbody>
{#    {% get_query_sets admin_class as query_sets %}#}
{#    {% for obj in query_sets %}#}
{#上面的这个没做分页,用下面的分页来做#}
    {% for obj in contacts %}
        <tr>
        {% build_table_row obj admin_class %}
        </tr>
    {% endfor %}
    </tbody>
    </table>
    <!-- 下面是django给的翻页的例子 -->
    <div class="pagination">
        <span class="step-links">
            {% if contacts.has_previous %}
                <a href="?page=1">« first</a>
                <a href="?page={{ contacts.previous_page_number }}">previous</a>
            {% endif %}

            <span class="current">
                Page {{ contacts.number }} of {{ contacts.paginator.num_pages }}.
            </span>

            {% if contacts.has_next %}
                <a href="?page={{ contacts.next_page_number }}">next</a>
                <a href="?page={{ contacts.paginator.num_pages }}">last »</a>
            {% endif %}
        </span>
    </div>
{% endblock %}

上面django给的并不是我们要的,自己搞个稍微好点的。下面的例子里做了2种:

代码语言:javascript
复制
    <!-- 下面是django给的翻页的例子 -->
    <nav aria-label="Page navigation">
        <ul class="pagination">
            <li>
                <a href="?page=1" aria-label="Previous">
                    <span aria-hidden="true">«</span>
                </a>
            </li>
            {% if contacts.has_previous %}
                <li><a href="?page={{ contacts.previous_page_number }}">上一页</a></li>
            {% else %}
                <li class="disabled"><a href="#">上一页</a></li>
            {% endif %}
            <li class="active"><a href="#">{{ contacts.number }}</a></li>
            {% if contacts.has_next %}
                <li><a href="?page={{ contacts.next_page_number }}">下一页</a></li>
            {% else %}
                <li class="disabled"><a href="#">下一页</a></li>
            {% endif %}
            <li>
                <a href="?page={{ contacts.paginator.num_pages }}" aria-label="Next">
                    <span aria-hidden="true">»</span>
                </a>
            </li>
        </ul>

        <ul class="pagination">
            <li>
                <a href="?page=1" aria-label="Previous">
                    <span aria-hidden="true">«</span>
                </a>
            </li>
            {% for loop_counter in contacts.paginator.page_range %}
                {% if loop_counter|render_page_ele:contacts %}
                    {{ loop_counter|render_page_ele:contacts }}
                {% endif %}
            {% endfor %}
            <li>
                <a href="?page={{ contacts.paginator.num_pages }}" aria-label="Next">
                    <span aria-hidden="true">»</span>
                </a>
            </li>
        </ul>
    </nav>

上面第二个分页,用到了自定义的函数如下:

代码语言:javascript
复制
@register.filter
def render_page_ele(loop_counter, contacts):
    """前端的页码"""
    if abs(contacts.number - loop_counter) <= 2:
        if contacts.number == loop_counter:
            return mark_safe('<li class="active"><a href="?page={0}">{0}</a></li>'.format(loop_counter))
        return mark_safe('<li><a href="?page={0}">{0}</a></li>'.format(loop_counter))

这里因为需要用if来判断自定义的函数的结果,所以必须用 @register.filter 。好在只有2个参数。课上的方法是不做判断,这样不满足条件的话最后会返回None,然后前端也会显示这个None。然后简单粗暴的在自定义函数最后 return '' 就是返回一个空字符串,反正效果一样的。

在Django中使用datetime模块

这里参考了 django/contrib/admin/filters.py 里面的用法,先导入这2个模块:

代码语言:javascript
复制
from django.utils import timezone
from datetime import timedelta

其中timedelta模块是用来做日期时间的加减法的。一般是配合datetime模块来用的。这里不用原生的datetime模块,而是用django的timezone。timezone源码也是调用了datetime模块,只是里面会读取settings.py里面有关时区的设置,输出的是一个按照配置文件里的时区设置的时间。 当前时间

代码语言:javascript
复制
now = timezone.now()  # 这个是UTC时间可以无视
# 下面在计算出来的就是当前时间
if timezone.is_aware(now):
    now = timezone.localtime(now)

今天、明天、下个月、明年

代码语言:javascript
复制
        if isinstance(field, models.DateTimeField):
            today = now.replace(hour=0, minute=0, second=0, microsecond=0)
        else:       # field is a models.DateField
            today = now.date()
        tomorrow = today + datetime.timedelta(days=1)
        if today.month == 12:
            next_month = today.replace(year=today.year + 1, month=1, day=1)
        else:
            next_month = today.replace(month=today.month + 1, day=1)
        next_year = today.replace(year=today.year + 1, month=1, day=1)

总结一下上面的思路,先计算出当前时间,这里就把时区的问题解决了。 在当前时间的基础上,计算出今天,这里把 DateField 和 DateTimeField 的差别也解决了。 然后再今天today的基础上,计算出其他各个需要的时间。源码写的就是好,值得借鉴。

输出标签的href属性的时候要转码

上面之前都是没转码的,因为没发现问题。正好做到了时间这里因为当前时间里有个“+”加号,这里不转码会被浏览器认作是空格。最后找到了原因。其实中文也是可能会遇到问题的。知道会有这个问题了,每次在自定义函数里写标签的时候都转一下就OK了。自己用的时候可能还需要str()一下,先转成字符串再处理:

代码语言:javascript
复制
from urllib.parse import quote

format_html('<a href="{}">{}</a>', quote(obj_url), obj)

所以之前的部分代码还要稍微修改一下

动态的创建类(type)

用type创建类的方法,这前在这篇里学过:https://blog.51cto.com/steed/2048162 现在有了应用场景。 首先不考虑动态,手动的创建类是这样的:

代码语言:javascript
复制
from django.forms import ModelForm
from crm import models

class CustomerModelForm(ModelForm):
    class Meta:
        model = models.Customer
        fields = '__all__'

然后继续看如何用上面的类,就是在Views.py里导入,然后实例化:

代码语言:javascript
复制
from crm import forms

def get_section(request):
    form_obj = forms.CustomerModelForm()
    return render(request, 'steed_admin/table_change.html', {'form_obj': form_obj})

现在的需求就是要为crm.models里的每一个类创建创建一个ModelForm类。其实不是为每个类创建ModelForm,而是在forms.py里只提供一个动态创建类的方法,然后要用的时候调用这个方法,生成一个类,然后直接实例化使用。下面就是这个动态的创建类的方法:

代码语言:javascript
复制
from django.forms import ModelForm
from crm import models

def create_model_form(request, admin_class):
    """动态生成ModelForm"""
    class Meta:
        model = admin_class.model
        fields = '__all__'

    # 这里先写个字典,下面再引用字典。之后这个类要添加什么方法都在这个字典里写
    members = {'Meta': Meta}
    # 左边是类名
    # 右边的参数:类的类型名字,继承哪些基类,类的所有成员
    model_form_class = type('DynamicModelForm', (ModelForm,), members)
    return model_form_class

上面只是提供了动态创建ModelForm类的方法。然后还是去Views.py里使用,这次没有线程的类可以用了,而是要用的时候就直接把类创建好,然后实例化:

代码语言:javascript
复制
from steed_admin import steed_admin
from steed_admin.forms import create_model_form

def change_table_obj(request, app_name, table_name, obj_id):
    admin_class = steed_admin.enabled_admins[app_name][table_name]  # 拿到要创建的crm.models里的类
    model_form_class = create_model_form(request, admin_class)  # 创建类
    obj = admin_class.model.objects.get(id=obj_id)  # 通过id,查到具体的一条记录
    form_obj = model_form_class(instance=obj)  # 实例化,然后传入默认值
    return render(request, 'steed_admin/table_change.html', {'admin_class': admin_class, 'form_obj': form_obj})

方法里的第一行是拿到了一个admin_class,这个在项目里其他地方已经定义好了。admin_class.model这个属性就是crm.models里某一个对应的class的类。在动态方法里,这个在Meta的model属性里要赋值给model。 所有参数都准备好了,就创建类。 然后实例化前,先通过id把对应的记录查到。 现在实例化,并且把查到的记录传给instance参数。 最后返回给前端,前端可以先简单的用 {{ form_obj.as_p }} 看到生成的form表单以及里面填入的默认值。

给所有的字段加上样式(new)

接着上面的内容,现在要为所有字段加上widgets属性。里面加上class属性,在前端可以显示出样式。问题是动态的怎么做。这里要用到 __new__ 方法。new方法是在构造函数执行之前执行的方法,可以用来定制我们的类。都是以前讲过的内容,但是不好理解,还是直接上结果吧。 添加了new方法的动态创建ModelForm的函数如下:

代码语言:javascript
复制
def create_model_form(request, admin_class):
    """动态生成ModelForm"""
    class Meta:
        model = admin_class.model
        fields = '__all__'

    def __new__(cls, *args, **kwargs):
        # cls.base_fields['qq'].widget.attrs['class'] = 'form-control'  # 指定价某一个是这么来加
        # 下面是要动态的把所有字段都加上
        # print(cls.base_fields)  # 先看看,这是一个序字典OrderedDict
        for filed_name, field_obj in cls.base_fields.items():
            field_obj.widget.attrs['class'] = 'form-control'
        return ModelForm.__new__(cls)

    # 这里先写个字典,下面再引用字典。之后这个类要添加什么方法都在这个字典里写
    # 这里的成员会被继承的属性覆盖掉
    members = {'Meta': Meta, '__new__': __new__}
    # 左边是类名
    # 右边的参数:类的类型名字,继承哪些基类,类的所有成员
    model_form_class = type('DynamicModelForm', (ModelForm,), members)
    return model_form_class

下面放上把所有内容都写死的写法:

代码语言:javascript
复制
from django.forms import ModelForm
from django.forms import widgets as my_widgets
from crm import models

class CustomerModelForm(ModelForm):
    class Meta:
        model = models.Customer
        fields = '__all__'
        widgets = {‘qq’: my_widgets.CharField(attrs: {'class': 'form-control'})}

现在还是不明白具体是如何把widgets放到Meta外面,在new里做的。而且new里的 cls 和 cls.base_fields 是什么。先照着这么用了再说吧。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-08-27 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 准备工作
  • 创建超级管理员
  • 本地化配置
  • 创建表结构
    • 显示自定制的表名和字段名
      • 正确显示记录
      • 创建用户及权限分配
      • admin.py的设置
        • 设定默认显示
          • 开启搜索功能
            • 开启过滤器
              • 开启分页
                • 修改外键字的的管理方法
                  • 在显示列表中直接修改
                    • 自定义action
                    • 使用admin的认证来做网站的认证
                      • 登录验证
                        • 登出
                          • 登录验证装饰器
                            • 自定义权限
                              • 更加精细的自定义权限
                          • king_admin 开发
                            • 通过表名获取app的name
                              • 在模板语言里显示上面的app的name
                                • 把选项的内容显示出来
                                  • Django的分页
                                    • 在Django中使用datetime模块
                                      • 输出标签的href属性的时候要转码
                                        • 动态的创建类(type)
                                          • 给所有的字段加上样式(new)
                                          相关产品与服务
                                          访问管理
                                          访问管理(Cloud Access Management,CAM)可以帮助您安全、便捷地管理对腾讯云服务和资源的访问。您可以使用CAM创建子用户、用户组和角色,并通过策略控制其访问范围。CAM支持用户和角色SSO能力,您可以根据具体管理场景针对性设置企业内用户和腾讯云的互通能力。
                                          领券
                                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档