当前位置: 首页 > news >正文

Django生态:Django-admin、Django-Compressor、Django-grappelli、django-rules

在Python生态下,虽然有Flask、FastAPI等后来者居上的Web框架;但不可否认的是,依然有很多企业、组织和开发者仍在使用Django框架,或是有遗留的项目在使用Django。

本文汇总整理几个Django生态的开发框架。

Django Admin

Django-Compressor

开源(GitHub,2.9K Star,608 Fork)静态资源压缩器,可在Django模板里把CSS、JS合并并压缩成可缓存的静态文件,也支持离线压缩,把资源处理从请求链路里提前挪到部署阶段,官方文档。

特点

  • 接入位置自然:直接工作在Django模板层。用{% compress css %}{% compress js %}包住资源片段,自动解析link、style、script,再输出压缩后的静态文件引用;
  • 缓存友好:压缩后的文件名和内容有关,内容变化才会生成新的文件名。浏览器可以放心做长缓存,线上资源更新时也不容易遇到“缓存没刷新”的问题。
  • 扩展空间足:支持CSS/JS过滤器、预编译器、不同HTML解析器、远程存储和离线压缩。简单项目可先用默认配置,复杂项目也能继续扩展到SASS、LESS、S3、CDN等场景。

适用场景

  • Django项目里CSS/JS较多,希望减少请求数和静态文件体积
  • 需要把资源文件名和内容绑定,方便浏览器做长缓存
  • 部署链路里可以提前执行manage.py compress
  • 项目需要接入SASS、LESS、JS minifier或自定义资源处理流程

实战

基于pip安装:pip install django-compressor

用模板标签压缩CSS和JS:当一个页面里同时引用多个 CSS/JS 文件和少量内联代码时,希望最终输出变成更少的静态资源请求,同时让文件名随内容变化。

frompathlibimportPathfromtempfileimportTemporaryDirectoryfromdjango.confimportsettingsimportdjangofromdjango.template.loaderimportrender_to_stringwithTemporaryDirectory()astmp:root=Path(tmp)static_dir=root/"static"out_dir=root/"compressed"tpl_dir=root/"templates"(static_dir/"css").mkdir(parents=True)(static_dir/"js").mkdir(parents=True)tpl_dir.mkdir()(static_dir/"css"/"base.css").write_text("body { color: #333; }\n.card { padding: 16px; }\n",encoding="utf-8")(static_dir/"js"/"app.js").write_text("function hello(name) { console.log('hello ' + name); }\nhello('mumu');\n",encoding="utf-8")(tpl_dir/"demo.html").write_text("""{% load compress %} {% compress css %} <link rel="stylesheet" href="/static/css/base.css"> <style>.title { font-weight: 700; color: #0b5; }</style> {% endcompress %} {% compress js %} <script src="/static/js/app.js"></script> <script>console.log("inline js");</script> {% endcompress %} """,encoding="utf-8")settings.configure(SECRET_KEY="demo",DEBUG=False,INSTALLED_APPS=["django.contrib.staticfiles","compressor"],STATIC_URL="/static/",STATICFILES_DIRS=[str(static_dir)],STATIC_ROOT=str(out_dir),COMPRESS_ROOT=str(out_dir),COMPRESS_URL="/static/",STATICFILES_FINDERS=["django.contrib.staticfiles.finders.FileSystemFinder","django.contrib.staticfiles.finders.AppDirectoriesFinder","compressor.finders.CompressorFinder",],TEMPLATES=[{"BACKEND":"django.template.backends.django.DjangoTemplates","DIRS":[str(tpl_dir)],"APP_DIRS":True,}],STORAGES={"default":{"BACKEND":"django.core.files.storage.FileSystemStorage"},"staticfiles":{"BACKEND":"django.contrib.staticfiles.storage.StaticFilesStorage"},"compressor":{"BACKEND":"compressor.storage.CompressorFileStorage"},},COMPRESS_ENABLED=True,COMPRESS_OFFLINE=False,)django.setup()print(render_to_string("demo.html").strip())

compressor.finders.CompressorFinder让compressor能找到静态资源,COMPRESS_ROOT决定压缩后的文件写到哪里。真实项目里通常还会配合collectstatic、CDN和静态文件存储一起使用。

功能二:把小段样式直接内联压缩

有些首屏关键样式很短,不一定值得单独生成文件。使用inline模式,让压缩结果以内联方式输出。

frompathlibimportPathfromtempfileimportTemporaryDirectoryfromdjango.template.loaderimportrender_to_stringwithTemporaryDirectory()astmp:root=Path(tmp)out_dir=root/"compressed"tpl_dir=root/"templates"tpl_dir.mkdir()(tpl_dir/"inline.html").write_text("""{% load compress %} {% compress css inline %} <style> .notice { color: #0b5; padding: 12px; } .notice strong { font-weight: 700; } </style> {% endcompress %} """,encoding="utf-8")//省略部分重复代码片段print(render_to_string("inline.html").strip())

内联模式适合很小的关键样式或特殊片段。不要把大量 CSS 都塞进 HTML 里,否则页面体积会变大,也会削弱浏览器缓存静态文件的收益。

线上环境更推荐把压缩过程提前到部署阶段,用manage.py compress生成压缩文件和manifest。这样请求到来时不必再临时解析模板和写文件,运行时行为更稳定。开启COMPRESS_OFFLINE=True,在部署时统一生成资源文件。

frompathlibimportPathfromtempfileimportTemporaryDirectoryfromdjango.core.managementimportcall_commandwithTemporaryDirectory()astmp:root=Path(tmp)static_dir=root/"static"out_dir=root/"compressed"tpl_dir=root/"templates"(static_dir/"css").mkdir(parents=True)tpl_dir.mkdir()(static_dir/"css"/"dashboard.css").write_text(".card { padding: 16px; color: #245; }\n",encoding="utf-8")(tpl_dir/"dashboard.html").write_text("""{% load compress %} {% compress css %} <link rel="stylesheet" href="/static/css/dashboard.css"> <style>.badge { border-radius: 999px; padding: 4px 8px; }</style> {% endcompress %} """,encoding="utf-8")settings.configure(//省略部分代码 COMPRESS_ENABLED=True,COMPRESS_OFFLINE=True,)django.setup()call_command("compress",verbosity=1,force=True)

离线压缩的核心产物是压缩文件和manifest。部署时需要把这些产物一起发布到静态文件存储或CDN。

注意事项

  1. 确认compressor.finders.CompressorFinder已加入STATICFILES_FINDERS
  2. 确认COMPRESS_ROOTSTATIC_ROOTCOMPRESS_URL和 CDN 地址一致。
  3. 线上启用离线压缩时,把manage.py compress放进部署流程。
  4. 检查压缩输出目录、manifest 和静态文件存储是否一起发布。
  5. 避免在模板里混入过多动态上下文,减少离线压缩找不到缓存的风险。

Django-Grappelli

官网,开源(GitHub,3.9K Star,651 Fork)Django Admin增强:一边把交互和布局做得更顺手,一边把很多后台常见的小痛点,比如原始字段联想、可拖拽 inline、dashboard 组合这些能力,一起收拢成了成体系的扩展点,官方文档。

特点

  • 后台体验:界面更顺眼,调整后台导航、关联选择、过滤器等交互;
  • 很多增强点都做成了可配置接口:从后台标题、切换用户,到 autocomplete 查找和 change list 模板,Grappelli 不是靠魔改 admin 模板硬怼,而是留了清晰的 settings、ModelAdmin 属性和 dashboard API。
  • 已有Django Admin项目很友好:迁移成本比重新做管理后台小得多。

适用场景

  • 已经在用 Django Admin,但觉得默认后台体验太素、太散
  • 想增强关联选择、inline 排序、dashboard 这些后台高频交互
  • 团队希望保留原生 admin 的稳定性,又想把管理后台做得更像产品
  • 需要一套比“纯换主题”更完整、但又不至于重做后台的方案

实战

基于pip安装:pip install django-grappelli

环境验证:

importdjangoimporttempfilefromdjango.confimportsettingsfrompathlibimportPathfromdjango.contribimportadminfromdjango.urlsimportinclude,path,reversefromgrappelliimportsettingsasgrp_settings DB=Path(tempfile.gettempdir())/"grappelli_demo1.sqlite3"ifDB.exists():DB.unlink()settings.configure(SECRET_KEY="demo",ROOT_URLCONF=__name__,STATIC_URL="/static/",USE_TZ=True,DATABASES={"default":{"ENGINE":"django.db.backends.sqlite3","NAME":str(DB)}},INSTALLED_APPS=["django.contrib.admin","django.contrib.auth","django.contrib.contenttypes","django.contrib.sessions","grappelli",],MIDDLEWARE=[],TEMPLATES=[{"BACKEND":"django.template.backends.django.DjangoTemplates","APP_DIRS":True,"OPTIONS":{"context_processors":[]}}],GRAPPELLI_ADMIN_TITLE="Mumu Ops Hub",GRAPPELLI_SWITCH_USER=True,)django.setup()urlpatterns=[path("grappelli/",include("grappelli.urls")),path("admin/",admin.site.urls),]print("admin title :",grp_settings.ADMIN_TITLE)print("admin index :",reverse("admin:index"))print("lookup url :",reverse("grp_related_lookup"))print("switch user :",reverse("grp_switch_user",args=[7]))

GrappelliSortableHiddenMixin

fromdjangoimportformsfromgrappelli.formsimportGrappelliSortableHiddenMixinclassDummyBase:defformfield_for_dbfield(self,db_field,request,**kwargs):widget=kwargs.get("widget")orforms.TextInput()returntype("Field",(),{"widget":widget})()classDemoInline(GrappelliSortableHiddenMixin,DummyBase):sortable_field_name="position"classDbField:def__init__(self,name):self.name=name inline=DemoInline()print("position widget:",type(inline.formfield_for_dbfield(DbField("position"),None).widget).__name__)print("title widget :",type(inline.formfield_for_dbfield(DbField("title"),None).widget).__name__)print("sortable field :",inline.sortable_field_name)

把后台首页当成了可以编排的 dashboard,而不是一块固定模板。你可以把快捷入口、监控分组、最近操作这些模块自由拼起来,做出更像“团队工作台”的管理首页。

原生 admin 首页通常更像入口列表,但很多团队真正想要的是“打开后台就先看到关键入口和关键状态”。Grappelli 的 dashboard API 正是为了把首页做成可组合模块。

fromdjango.confimportsettingsifnotsettings.configured:settings.configure(SECRET_KEY="demo",USE_I18N=False,INSTALLED_APPS=["grappelli"])importdjango django.setup()fromgrappelli.dashboardimportDashboard,modulesclassOpsDashboard(Dashboard):title="Ops Dashboard"def__init__(self,**kwargs):super().__init__(**kwargs)self.children.append(modules.LinkList(title="Quick Links",column=1,children=[("Django Docs","https://docs.djangoproject.com/",True,"official docs","_blank"),("Release Board","/admin/releases/"),],))self.children.append(modules.Group(title="Watchers",column=2,children=[modules.LinkList(title="Core Services",column=2,children=[("Nginx","/admin/nginx/")]),modules.LinkList(title="Task Queue",column=2,children=[("Celery","/admin/celery/")]),],))board=OpsDashboard()quick=board.children[0]quick.init_with_context({})group=board.children[1]print("dashboard title:",board.title)print("module count :",len(board.children))print("first link :",quick.children[0]["title"],quick.children[0]["target"])print("group css :",group.render_css_classes())

注意事项

  • 先确认 grappelli 在 INSTALLED_APPS 里的位置和 urls.py 的挂载顺序正确,再去改更细的 admin 行为。
  • 用少量 ModelAdmin 和 inline 先试跑排序、autocomplete、change list 模板,别一口气全后台替换。
  • 如果要做 dashboard,自定义模块时优先保证入口清晰和权限边界明确,不要把首页堆成信息垃圾场。

django-rules

权限规则编排方向开源(GitHub,2K Star,152 Fork)项目,把用户权限拆成可组合的谓词(predicate) ,可把谓词挂到Django的权限检查链路里。相比把判断逻辑散落在viewserializermodel各个层,更利于复用、审计、问题排查。

特点

  • 规则组合:基于谓词,可把每个判断拆成最小单元,再用&|~组合成真正的业务规则;
  • 可独立运行:底层是通用规则引擎,适用于各种Python项目;
  • 不依赖额外权限表:更偏向运行时判定,基于用户、对象和上下文信息直接判断,维护成本会轻不少。

适用场景

  • Django 项目已经有认证体系,但对象级权限逻辑开始变复杂
  • 想把权限判断从业务代码里抽出来统一管理
  • 需要一套既能独立测试、又能挂到has_perm上的规则表达方式
  • 后续再接Admin、CBV、DRF时还能延续同一套规则

实战

安装:pip install rules

接入 Django,别忘了把rules放进INSTALLED_APPS,并把rules.permissions.ObjectPermissionBackend加到AUTHENTICATION_BACKENDS里。
用 predicate 把这些条件收拢起来

importrulesfromdataclassesimportdataclass,field@dataclassclassTicket:owner:strreviewers:set[str]=field(default_factory=set)closed:bool=False@rules.predicatedefis_owner(user,ticket):returnticket.owner==user@rules.predicatedefis_reviewer(user,ticket):returnuserinticket.reviewers@rules.predicatedefnot_closed(user,ticket):returnnotticket.closed rules.set_rule("can_edit_ticket",(is_owner|is_reviewer)&not_closed)draft=Ticket(owner="mumu",reviewers={"ops","qa"})archived=Ticket(owner="mumu",reviewers={"ops"},closed=True)foractor,ticketin[("mumu",draft),("ops",draft),("guest",draft),("ops",archived),]:print(f"{actor:>5}->{rules.test_rule('can_edit_ticket',actor,ticket)}")

把同一套规则直接接进 Django 权限链路

importdjangoimportrulesimporttempfilefromdjango.confimportsettingsfromdjango.contrib.auth.modelsimportGroup,Userfromdjango.core.managementimportcall_commandfrompathlibimportPath DB=Path(tempfile.gettempdir())/"rules_demo2.sqlite3"ifDB.exists():DB.unlink()settings.configure(SECRET_KEY="demo",USE_TZ=True,DATABASES={"default":{"ENGINE":"django.db.backends.sqlite3","NAME":str(DB)}},INSTALLED_APPS=["django.contrib.auth","django.contrib.contenttypes","rules",],AUTHENTICATION_BACKENDS=("rules.permissions.ObjectPermissionBackend","django.contrib.auth.backends.ModelBackend",),)django.setup()call_command("migrate",run_syncdb=True,verbosity=0)classBook:def__init__(self,author,state):self.author=author self.state=state@rules.predicatedefis_book_author(user,book):returnbool(userandbookandbook.author==user)is_editor=rules.is_group_member("editors")rules.add_perm("library.change_book",is_book_author|is_editor)rules.add_perm("library.publish_book",is_book_author&rules.is_staff)alice=User.objects.create_user("alice")bob=User.objects.create_user("bob")carol=User.objects.create_user("carol")editors=Group.objects.create(name="editors")bob.groups.add(editors)book=Book(author=alice,state="draft")print("alice change :",alice.has_perm("library.change_book",book))print("bob change :",bob.has_perm("library.change_book",book))print("carol change :",carol.has_perm("library.change_book",book))print("alice publish:",alice.has_perm("library.publish_book",book))

invocation context,可以让同一轮规则求值过程共享上下文数据。很适合把一次昂贵计算缓存下来,后面的 predicate 直接复用。当你的权限判断依赖上下文信息时,不一定要在每个 predicate 里重复计算。先缓存,再复用,组合表达式会更清晰。

importrules@rules.predicate(bind=True)defcache_region(self,user,service):region=service["region"]self.context["region"]=regionprint("cache region :",region)returnTrue@rules.predicate(bind=True)defsame_region(self,user,service):print("reuse cached data :",self.context.get("region"))returnuser["region"]==self.context.get("region")can_deploy=cache_region&same_regionprint("cn user ->",can_deploy.test({"region":"cn"},{"region":"cn"}))print("us user ->",can_deploy.test({"region":"us"},{"region":"cn"}))

前一个 predicate 把 region 放进上下文,后一个 predicate 直接复用,不需要把相同数据再查一遍。

最佳实践

  1. 先把 predicate 命名成业务语言,再组合成 rule,避免后面只剩一坨布尔表达式。
  2. 接入 Django 时确认AUTHENTICATION_BACKENDS顺序和INSTALLED_APPS配置正确,再做一次真实的has_perm回归。
  3. 对复杂规则补充单元测试,尤其是 owner、group、上下文条件叠加时的边界分支。
http://www.jsqmd.com/news/1237505/

相关文章:

  • AI工具组合的“外科手术式精简”:从混沌到确定性的7步裁剪法(含兼容性矩阵与ROI测算表)
  • 红黑树:平衡二叉搜索树的工业级实现与优化
  • 504错误解析:从技术原理到人生隐喻
  • 真力时烟台官方客服热线及售后地址公告:2026年7月最新网点服务信息发布 - 亨得利钟表维修中心
  • PS 如何无痕去除图片水印?3 种不破坏原图的修图方法
  • 2026大连空调维修哪家效率高?3家平台响应速度实测 - 简单到家
  • Node.js安全扫描Web界面:从可视化结果到高效修复的实战指南
  • AI 电动家纺包装机智能功率 MOSFET 完整选型方案
  • 当散点图不够用时:用 t-SNE 可视化多维数据
  • 51单片机串口通信
  • 异构联盟链跨链互通的工程实现:从中继架构到接入连接器
  • 企业知识库RAG实战:用向量数据库构建精准语义检索系统
  • XXL-JOB分布式任务调度平台核心原理与实践指南
  • MCP Server:AI Agent安全高效调用业务API的标准化方案
  • Java面试突击:从八股文背诵到构建最小知识体系与问题拆解框架
  • 跨系统查数据要2天?企业AI落地的核心病根,藏在看不见的语义
  • 广州电商公司税务合规怎么做?2026电商行业合规管理与机构选型攻略 - 米諾
  • Qt的概述
  • YOLOv13涨点改进| CVPR 2026 | 独家Conv与频域改进篇| 引入SSFModule选择性空间频率模块,助力无人机航拍、遥感影像、小目标检测、语义分割、实例分割、目标跟踪任务,有效涨点
  • 郴州甲醛检测公司怎么选:只做检测不除醛的专业CMA资质实验室——国慷测研CMA甲醛检测及公共卫生检测 - CMA甲醛检测中心
  • 2026山西企业获客复盘:为什么传统SEO失效,GEO优化成主流? - 米諾
  • ElevenLabs语音合成API实战踩坑与优化指南
  • SpringBoot进阶实战:从原理到生产级应用与面试突破
  • SlowHTTPTest终极指南:如何用免费工具发现并防御慢速HTTP攻击漏洞
  • Claude Code与DeepSeek API一键安装配置指南
  • 石家庄平山县亨得利官方名表服务中心电话公示(2026年7月最新) - 亨得利官方
  • AI伦理三脚架:原则、流程与工具的落地断层解析
  • 双轴晶体的圆锥折射
  • 宁波GEO找哪家比较好?2026本地靠谱推荐与服务商分级选型指南 - 小随科技
  • Kimi K3大模型集成实战:从API接入到生产环境部署