Django-CRM项目学习(二)-模仿admin实现stark
开始今日份整理
1.stark模块基本操作
1.1 stark模块的启动
保证django自动的加载每一个app下的stark.py文件
- 创建django项目,创建stark项目,start app stark
- settings注册app
- stark.app中的apps.py创建def(函数名必须是ready才会自动执行)
from django.utils.module_loading import autodiscover_modules class StarkConfig(AppConfig): name = 'stark' def ready(self): autodiscover_modules('stark')
1.2 stark模块的注册
所谓注册就是仿照admin模块,对于注册的数据表进行记录,方便后面的url的增删改查
仿照admin在stark下创建一个包services,并创建一个sites.py文件,代码如下
from django.contrib import admin from django.urls import path from django.shortcuts import render,HttpResponse class ModelStark(object): list_display =("__str__") def __init__(self,model): self.model = model def list_view(self, request): return render(request,'stark/list_view.html',locals()) def add_view(self, request): return HttpResponse("add_view") def change_view(self, request, id): return HttpResponse("change_view") def delete_view(self, request, id): return HttpResponse("delete_view") @property def get_url(self,): temp = [ path("", self.list_view), path("add/",self. add_view), path("(\d+)/change/", self.change_view), path("(\d+)/delete/", self.delete_view), ] return (temp, None, None) class StarkSite: def __init__(self): self._registry ={} def register(self,model,admin_class=None,**options): admin_class = admin_class or ModelStark self._registry[model]=admin_class(model) def get_urls(self): temp =[] # 拿到已经注册的所有表 for model,config_obj in self._registry.items(): # 表名 model_name = model._meta.model_name # 项目名 model_label = model._meta.app_label temp.append( path("%s/%s/"%(model_label,model_name),config_obj.get_url) ) return temp @property def urls(self): return self.get_urls(),None,None site = StarkSite()
在app01中创建stark.py文件,并注册
from stark.services.sites import site,ModelStark from .models import Book,Publish,Author,Author_detail # 分别注册书籍,出版社以及作者 site.register(Book) site.register(Publish) site.register(Author) print(site._registry)
打印注册的列表,结果如下
{<class 'app01.models.Publish'>: <stark.services.sites.ModelStark object at 0x04B66B50>, <class 'app01.models.Book'>: <stark.services.sites.ModelStark object at 0x04B669B0>, <class 'app01.models.Author'>: <stark.services.sites.ModelStark object at 0x04B66970>}
这样就注册成功了
1.3 URL的分发功能以及页面样式理解(非常重要)
为了自定义的URL。所以我们才会有自定义页面,才会有配置类。
(1)在site中StarkSite类中创建一个URLS(self)方法,用@property方式,静态方法
(2)将二级分发功能放在配置类模块中
(3)配置类中self以及self.model的区别(超级重要)
self:是配置类对象
self.model:数据表对象,其实就是数据表的数据
通过上面:即可理解为什么在注册的时候有一个空字典,在每一个表对象进行注册时,对每一个表生成对应的配置类对象,如果一个表对象有自己的自定义样式,则会走自己自定义样式,无则会走默认样式。
这样就基本实现了url的分发功能,有一级也有二级分发。这块内容就是理解就会觉得东西少,不理解则东西好多!,只需要记住
self是配置类,self.model就是数据表对象就可以了。
对于默认类,self为默认配置类,其中self.model为传入的表对象,展示则使用默认类中的样式。
对于自定义类,self为自定义类,其中self.model为传入的表对象,自定义类继承默认类,优先使用自定义定义的类方法。
如下图展示
from django.contrib import admin from django.urls import path from django.shortcuts import render,HttpResponse class ModelStark(object): list_display =("__str__") def __init__(self,model): self.model = model def list_view(self, request): return render(request,'stark/list_view.html',locals()) def add_view(self, request): return HttpResponse("add_view") def change_view(self, request, id): return HttpResponse("change_view") def delete_view(self, request, id): return HttpResponse("delete_view") @property def get_url(self,): temp = [ path("", self.list_view), path("add/",self. add_view), path("(\d+)/change/", self.change_view), path("(\d+)/delete/", self.delete_view), ] return (temp, None, None) class StarkSite: def __init__(self): self._registry ={} def register(self,model,admin_class=None,**options): admin_class = admin_class or ModelStark self._registry[model]=admin_class(model) def get_urls(self): temp =[] # 拿到已经注册的所有表 for model,config_obj in self._registry.items(): # 表名 model_name = model._meta.model_name # 项目名 model_label = model._meta.app_label temp.append( path("%s/%s/"%(model_label,model_name),config_obj.get_url) ) return temp @property def urls(self): return self.get_urls(),None,None site = StarkSite()
访问顺序
path("stark/app01/book",BookConfig(Book).list_view) path("stark/app01/book/add",BookConfig(Book).add_view) path("stark/app01/publish",ModelAdmin(Publish).list_view) path("stark/app01/publish/add",ModelAdmin(Publish).add_view)
2.stark基础页面
2.1 stark的数据展示
2.1.1 普通数据与一对多数据展示
2.1.1.1 基于自定义类的数据展示
普通数据就是类似于数据库中book表中的title,price,出版日期,作者等基础数据,django天然对一对多支持
#目标数据类型 new_data=[ ["python",123], ["java",234] ]
视图层如下
def list_view(self, request): """ self:当前配置类 self.model:当前访问的表数据 :param request: :return: """ # 当前要展示的数据表的数据 querset = self.model.objects.all() print(querset) print(self.list_display) # 用于构建视图中的展示数据 new_data = [] for obj in querset: temp = [] # 构建列表嵌套中的小列表,使用的是自定义配置列表中要展示的内容 for field_or_info in self.list_display: vim = getattr(obj,field_or_info) temp.append(vim) new_data.append(temp) print('new_data', new_data) return render(request,'stark/list_view.html',locals())
模板层如下
对于Book表,是自定义展示配置类,展示如下图
2.1.1.2 基于默认类的数据展示
视图层以及模板层如上,不过需要注意的是
没有加逗号,django会默认认为是一个字符串,并不会以一个元祖来读取元素,最后导致报错
对于出版社展示如下,只会展示对象名,显示名称是由于模型类中有__str__方法
2.1.2 多对多数据的判断
在app01中添加展示列表中添加作者这个多对多字段
2.1.2.1 错误显示
添加多对多字段
模板展示
最后展示中,作者多对多数据显示为none,
2.1.2.2 多对多数据判断以及自定义错误报错
(1)知识补充:
使用名字访问一个model的实例:模型名._meta.get_field("publish")
展示模型类的的名字:模型名._meta.model_name
展示模型类对应的项目名称:模型名._meta.app_label
展示一个模型类对象的默认名称:模型对象.verbose_name
对于模型类中的参数,如果设置了verbose.name则会显示设置的名字,无则显示参数名称
(2)多对多的判断
对于多对多的判断,首先是导包,对于 list_display来说,普通属性从self.list_display拿到的是字符串,一对多和多对多则是拿到的对象,拿到对象的类型判断是否是多对多,如果是多对多则报错并报错
对于多对多来说,需要提示为多对多,需要使用自定义列,弹出错误,用于提示错误
2.1.3 模型类中choice数据的反射
数据的反射,例如book的出版状态只有已出版以及未出版,但在数据库中记录只有1和2,需要对1和2的内容取出并反射出具体的内容
注:这里主要使用的是”get_属性名_display方法”,这样最后在页面中展示就会变成以及出版或者是未出版,不在是数据库中1和2
2.1.4 模型类属性中的__str__处理方法
field_obj = self.model._meta.get_field(field_or_func) #获取模型对象
这句话,获取的是模型类中的方法,由于模型类中没有__str__方法,所以需要对其进行处理
2.1.5 自定义的展示多对多内容
2.1.5.1 APP内对stark进行自定义列设定
自定义函数,例如show_author,然后把函数名丢到list_display列表中
stites中获取返回值,然后加入到列表中传到前端
注:由于list_display中有字符串也有函数,所以需要用到callable来判断是都为函数名
这里的self 是类函数调用的方式.,因为原来类中需要穿参,现在增加一个参数而已,什么都行.但是最好是self
2.1.5.2 处理多对多内容
在callable 内容中传入obj对象,方便操作数据
在自定制配置类中,通过obj 获取对应的作者信息
2.2 表头数据的展示
2.2.1 默认配置类
非定制列 ,即只有 __str__,只需要返回到数据表名的大写即可
2.2.2 自定制配置类
如果是普通属性,则只需要丢到对应的head_list列表中即可,对于自定义列,传入为函数名的时候则需要对传入做判断
app中注册类书写
2.3 添加选择框,编辑以及删除到所有的展示页面中
2.3.1 在html中展示指定的内容
导入mark_safe包
from django.utils.safestring import mark_safe
可以是后台传入的标签内容不会被转化,直接成为前端代码
2.3.2 反向解析
导入包
from django.urls import reverse
定义类名以及表名
def __init__(self,model): self.model = model self.model_name = self.model._meta.model_name self.app_label = self.model._meta.app_label
视图层反向解析,设置name
@property def get_url(self,): # temp = [ # path("", self.list_view), # path("add/",self. add_view), # re_path("(\d+)/change/", self.change_view), # re_path("(\d+)/delete/", self.delete_view), # ] temp = [ path("", self.list_view, name="%s_%s_list" % (self.app_label, self.model_name)), path("add/", self.add_view, name="%s_%s_add" % (self.app_label, self.model_name)), re_path("(\d+)/change/", self.change_view, name="%s_%s_change" % (self.app_label, self.model_name)), re_path("(\d+)/delete/", self.delete_view, name="%s_%s_delete" % (self.app_label, self.model_name)), ] return (temp, None, None)
反向解析代码
# 反向解析当前访问表的增删改查URL def get_list_url(self): # 反向解析当前表的查询的URL list_url = reverse("%s_%s_list" % (self.app_label, self.model_name)) return list_url def get_add_url(self, obj): # 反向解析当前表的添加的URL add_url = reverse("%s_%s_delete" % (self.app_label, self.model_name)) return add_url def get_delete_url(self, obj): # 反向解析当前表的删除的URL delete_url = reverse("%s_%s_delete" % (self.app_label, self.model_name), args=(obj.pk,)) return delete_url def get_change_url(self, obj): # 反向解析当前表的修改的URL change_url = reverse("%s_%s_change" % (self.app_label, self.model_name), args=(obj.pk,)) return change_url
分析:反向解析名字为app名加表名,利用的是无名分组,注意无名分组为元祖传参,最后是三个默认列代码
# 三个默认列 # 选择框 def show_checkbox(self, obj=None, heade=False): if heade: return mark_safe("<input type='checkbox'>") return mark_safe("<input type='checkbox'>") # 删除框 def show_delbtn(self, obj=None, heade=False): if heade: return '删除' # return mark_safe("<a href='stark/app01/book/%s/delete'>删除</a>" % obj.pk) return mark_safe("<a href='%s'>删除</a>" % self.get_delete_url(obj)) # 编辑框 def show_editbtn(self, obj=None, heade=False): if heade: return '编辑' # return mark_safe("<a href='stark/app01/book/%s/change'>编辑</a>" % obj.pk) return mark_safe("<a href='%s'>编辑</a>" % self.get_change_url(obj)) ####同时构建新的list_display,如果需要在默认列表中都展示,需要设定新的list_display # 构建新的list_display def get_new_list_display(self): temp = [] temp.extend(self.list_display) temp.append(ModelStark.show_editbtn) temp.append(ModelStark.show_delbtn) temp.insert(0, ModelStark.show_checkbox) return temp
属性说明:
- self 为当前操作的模型配置类
- obj=None 让默认对象的值为None,即当获取表头的时候不用传值
- header=False 让默认的header 为False ,使调用数据的时候不用传值,不返回表头,只返回数据
获取表头中,是header =true 这样可以获取表头数据内容
完整site代码如下
from django.contrib import admin from django.urls import path,re_path from django.shortcuts import render,HttpResponse from django.utils.safestring import mark_safe from django.urls import reverse class ModelStark(object): list_display =("__str__",) def __init__(self,model): self.model = model self.model_name = self.model._meta.model_name self.app_label = self.model._meta.app_label # 反向解析当前访问表的增删改查URL def get_list_url(self): # 反向解析当前表的查询的URL list_url = reverse("%s_%s_list" % (self.app_label, self.model_name)) return list_url def get_add_url(self, obj): # 反向解析当前表的添加的URL add_url = reverse("%s_%s_delete" % (self.app_label, self.model_name)) return add_url def get_delete_url(self, obj): # 反向解析当前表的删除的URL delete_url = reverse("%s_%s_delete" % (self.app_label, self.model_name), args=(obj.pk,)) return delete_url def get_change_url(self, obj): # 反向解析当前表的修改的URL change_url = reverse("%s_%s_change" % (self.app_label, self.model_name), args=(obj.pk,)) return change_url # 三个默认列 # 选择框 def show_checkbox(self, obj=None, heade=False): if heade: return mark_safe("<input type='checkbox'>") return mark_safe("<input type='checkbox'>") # 删除框 def show_delbtn(self, obj=None, heade=False): if heade: return '删除' # return mark_safe("<a href='stark/app01/book/%s/delete'>删除</a>" % obj.pk) return mark_safe("<a href='%s'>删除</a>" % self.get_delete_url(obj)) # 编辑框 def show_editbtn(self, obj=None, heade=False): if heade: return '编辑' # return mark_safe("<a href='stark/app01/book/%s/change'>编辑</a>" % obj.pk) return mark_safe("<a href='%s'>编辑</a>" % self.get_change_url(obj)) # 构建新的list_display def get_new_list_display(self): temp = [] temp.extend(self.list_display) temp.append(ModelStark.show_editbtn) temp.append(ModelStark.show_delbtn) temp.insert(0, ModelStark.show_checkbox) return temp # 视图函数 def list_view(self, request): """ self:当前配置类 selfmodel:当前访问的表数据 :param request: :return: """ # 当前访问表的数据 querset = self.model.objects.all() print(querset) print(self.list_display) #用于展示头部文件 header_list=[] for field_or_info in self.get_new_list_display(): #判断是函数名或者是字符段 if callable(field_or_info): vim=field_or_info(self,heade=True) header_list.append(vim) else: # 获取指定字段的对象属性,并拿出verbose_name属性 if field_or_info=='__str__': #如果只有默认装饰类,只有__str__,则拿出他的表名作为头 vim = self.model._meta.model_name.upper() else: file_obj =self.model._meta.get_field(field_or_info) vim = file_obj.verbose_name header_list.append(vim) #用于构建视图中的展示数据 new_data=[] for obj in querset: temp=[] for field_or_info in self.get_new_list_display(): # 判断是函数还是字符段 if callable(field_or_info): vim = field_or_info(self,obj) else: try: from django.db.models.fields.related import ManyToManyField info_obj=self.model._meta.get_field(field_or_info) # 判断多对多字段 if type(info_obj)==ManyToManyField: raise Exception("list_distplay 不能是多不多字段") #判断是否是__str__ # if field_or_info=='__str': # vim=getattr(obj,field_or_info)() # 判断是否有choices字段 if info_obj.choices: vim = getattr(obj,'get_%s_display'%field_or_info)() else: vim =getattr(obj,field_or_info) except Exception as e: vim = getattr(obj, field_or_info)() temp.append(vim) new_data.append(temp) print('new_data',new_data) # 目标数据 # new_data=[ # ["python",123], # ["java",234] # ] return render(request,'stark/list_view.html',locals()) def add_view(self, request): return HttpResponse("add_view") def change_view(self, request, id): return HttpResponse("change_view") def delete_view(self, request, id): return HttpResponse("delete_view") @property def get_url(self,): # temp = [ # path("", self.list_view), # path("add/",self. add_view), # re_path("(\d+)/change/", self.change_view), # re_path("(\d+)/delete/", self.delete_view), # ] temp = [ path("", self.list_view, name="%s_%s_list" % (self.app_label, self.model_name)), path("add/", self.add_view, name="%s_%s_add" % (self.app_label, self.model_name)), re_path("(\d+)/change/", self.change_view, name="%s_%s_change" % (self.app_label, self.model_name)), re_path("(\d+)/delete/", self.delete_view, name="%s_%s_delete" % (self.app_label, self.model_name)), ] return (temp, None, None) class StarkSite: def __init__(self): self._registry ={} def register(self,model,admin_class=None,**options): admin_class = admin_class or ModelStark self._registry[model]=admin_class(model) def get_urls(self): temp =[] # 拿到已经注册的所有表 for model,config_obj in self._registry.items(): # 表名 model_name = model._meta.model_name # 项目名 model_label = model._meta.app_label temp.append( path("%s/%s/"%(model_label,model_name),config_obj.get_url) ) return temp @property def urls(self): return self.get_urls(),None,None site = StarkSite()
完整注册stark代码如下
from stark.services.sites import site,ModelStark from .models import * from django.utils.safestring import mark_safe class BookConfig(ModelStark): def show_authors(self,obj=None,heade=False): if heade: return "作者信息" return " ".join([author.name for author in obj.author.all()]) # # 选择框 # def show_checkbox(self,obj=None,heade=False): # if heade: # return mark_safe("<input type='checkbox'>") # return mark_safe("<input type='checkbox'>") # # # 删除框 # def show_delbtn(self, obj=None, heade=False): # if heade: # return '删除' # return mark_safe("<a href='stark/app01/book/%s/delete'>删除</a>"%obj.pk) # # # 编辑框 # def show_editbtn(self, obj=None, heade=False): # if heade: # return '编辑' # return mark_safe("<a href='stark/app01/book/%s/change'>编辑</a>" % obj.pk) list_display=["title","price","staes","publish",show_authors] # list_display=["title","price","staes","publish"] site.register(Book,BookConfig) site.register(Publish) print(site._registry)
a