ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Django开源CRM源码解析:模型、权限到生产部署

Django开源CRM源码解析:模型、权限到生产部署 简介基于Django框架构建的开源CRM客户关系管理系统源码面向中小型企事业单位、Python开发学习者和二次开发工程师覆盖客户档案维护、商机跟进、数据统计等核心场景。压缩包内为完整前后端工程共466个文件约12.73MB核心包括71个Python业务逻辑文件、53个HTML模板页面、50个JavaScript交互脚本以及CSS、Less、SCSS等多层次样式与SQLite数据库文件同时内置151个GIF动图分步骤演示客户录入、编辑、删除、查询等操作流程降低阅读门槛。资源目录按Django工程规范组织鉴权、ORM模型、表单校验、通用视图、分页与搜索均有对应实现还附带supervisord与nginx配置便于本地部署和生产环境迁移。目前已有631人学习下载适合作为课程设计素材、企业系统原型或Django进阶实战参考。1. 为什么开源CRM大多选Django而不是SpringBoot或PHPCRM这类系统对开发框架的挑剔程度远超普通博客或商城客户字段今天要加一个“来源渠道”明天要按区域划分数据权限后天要把跟进记录导给销售大屏——如果框架本身不具备灵活的迁移机制、成熟的后台管理和细粒度的权限控制二开成本会很快吞掉选型时的“轻量”优势。Django在这三点上刚好是天生匹配自带ORM用于表结构变更自带Admin后台用于数据维护自带认证与权限框架用于角色控制。所以大量开源CRM源码会选择Django作为底座而不是让团队从零搭一套SpringBoot微服务或是靠PHP手工拼SQL。对于一个想直接拿来用的开发团队来说读一个“基于Django框架的开源CRM系统源码”并不需要先精通Django的所有组件而是抓三条主线模型怎么分表、权限怎么控制、部署怎么跑通。下面五章就按这个顺序推演每步都给出可复现的命令和代码最后落到生产环境部署的常见坑上。无论你是准备把这套源码作为内部客户管理系统还是计划二次开发后做成SaaS产品这条路径都适用。2. 读懂一套Django CRM源码项目结构与数据模型2.1 从项目目录看CRM的业务边界一个规范的Django CRM项目目录结构通常长这样crm_project/ ├── manage.py ├── config/ # 项目配置目录 │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── apps/ │ ├── customers/ # 客户管理 │ ├── contacts/ # 联系人 │ ├── followups/ # 跟进记录 │ ├── orders/ # 订单/合同 │ └── dashboard/ # 仪表盘 └── requirements.txtconfig是Django推荐的最新目录组织方式替代老项目里的同名子目录apps下每个业务模块是一个独立appapp与app之间通过外键关联而不是互相import模型类。看一套开源CRM源码第一件事不是读模型文件而是数一数有几个app——app的划分往往就是业务功能清单的映射。如果某个开源项目把客户、订单、跟近记录全部塞进一个models.py里后续扩展会非常痛苦也不建议选型。2.2 客户模型与联系人模型怎么建客户表是CRM的核心所有业务围绕它展开。一个典型模型如下# apps/customers/models.py from django.db import models class Customer(models.Model): name models.CharField(客户名称, max_length128) phone models.CharField(联系电话, max_length20, blankTrue) level models.CharField(客户等级, max_length10, choices((A, 重点), (B, 普通), (C, 观察)), defaultB) source models.CharField(来源渠道, max_length32, blankTrue) owner models.ForeignKey(auth.User, verbose_name负责人, on_deletemodels.SET_NULL, nullTrue, blankTrue) created_at models.DateTimeField(创建时间, auto_now_addTrue) class Contact(models.Model): customer models.ForeignKey(Customer, verbose_name所属客户, related_namecontacts, on_deletemodels.CASCADE) name models.CharField(联系人姓名, max_length64) position models.CharField(职位, max_length64, blankTrue) mobile models.CharField(手机号, max_length20)这段代码的要点有三个level字段用choices限定取值方便后续在Admin下拉和报表分组owner外键指向Django自带的auth.User实现“每个客户归某个销售负责”的基础归属关系Contact用related_namecontacts让反向查询语义更明确——你可以写customer.contacts.all()而不是customer.contact_set.all()。任何CRM源码的客户模型都绕不开这三个设计点哪怕字段名不同逻辑都是一样的。字段设计时要注意不要把所有维度都堆在Customer表上。开源CRM源码的常见做法是电话、地址等低频修改信息放Customer而高频交互记录如微信聊天摘要、上门拜访时间放独立的跟进表这样查询列表和导出Excel时不会因为大字段拖慢速度。2.3 跟进记录与订单的关联设计客户要跟进跟进要有结果结果可能转化为订单。三者关系如下# apps/followups/models.py from django.db import models from apps.customers.models import Customer class FollowUp(models.Model): customer models.ForeignKey(Customer, related_namefollowups, on_deletemodels.CASCADE) user models.ForeignKey(auth.User, on_deletemodels.CASCADE) content models.TextField(跟进内容) next_follow_date models.DateField(下次跟进日期, blankTrue, nullTrue) # apps/orders/models.py class Order(models.Model): customer models.ForeignKey(Customer, related_nameorders, on_deletemodels.PROTECT) amount models.DecimalField(max_digits12, decimal_places2) status models.CharField(max_length12, choices((pending, 待签约), (signed, 已签约), (cancelled, 已取消)), defaultpending)on_deletemodels.PROTECT是订单表的关键一个已经签约的订单不能因为客户被误删而连带消失数据库层会拒绝执行DELETE除非先处理掉关联订单。很多新手二开会把外键写成CASCADE结果清测试数据时把客户和订单一起清没了。这个细节在开源CRM源码里通常会谨慎处理如果你拿到手的项目不是这样写的建议在自己项目里改过来。跟进记录用next_follow_date字段驱动销售工作台——当天要跟进的客户列表就是一条FilterSet按日期查询的集合。这套模式几乎适用于所有B端销售场景比给客户表加一堆“最后跟进时间”冗余字段要干净得多。2.4 Django权限模型在CRM里的三层用法CRM最复杂的是权限不是数据。Django内置权限体系分三层可以从开源源码里学到它们的组合方式权限层级实现方式适用场景功能权限Django Group Permission销售能看客户列表销售经理能看报表数据行权限get_queryset()过滤 owner销售只能看自己名下的客户字段级/对象级权限django-guardian 或手动判断普通销售不能修改客户信用额度字段第一层直接用Django自带的django.contrib.auth即可。第二层的常见写法是class CustomerQuerySet(models.QuerySet): def visible_to(self, user): if user.groups.filter(name销售经理).exists(): return self return self.filter(owneruser) class CustomerManager(models.Manager): def get_queryset(self): return CustomerQuerySet(self.model, usingself._db)然后在视图里调用Customer.objects.visible_to(request.user)。这样业务逻辑不散落在各个视图函数中二开时新增角色只用扩展visible_to方法。如果开源项目需要第三层通常会引入django-guardian它在外键关联的permission表上做记录对于“某人只能编辑指定客户”这种需求比Django自带的全局权限更精确。注意对象级权限会带来查询复杂度非必要不推荐全表开启。3. 本地跑通源码环境准备、迁移与管理员初始化3.1 准备Python环境和依赖拿到源码第一步不是直接python manage.py runserver而是创建虚拟环境。Django 3.2以上版本推荐用官方venv我一般会用uv替代pip速度更快python -m venv .venv source .venv/bin/activate # Windows 用 .venv\Scripts\activate uv pip install -r requirements.txtrequirements.txt里至少要包含Django4.2.*、djangorestframework、python-dotenv、mysqlclient或psycopg2-binary。如果源码里带了pyproject.toml或Pipfile优先用它们安装。虚拟环境隔离后再查pip list确认Django版本——版本号决定后面很多写法比如path函数在Django 2.0以后才存在老项目如果还在用url需要按Django 3.2以上版本改写法。3.2 数据库配置与settings调整开源项目一般默认用SQLite方便快速起步生产切换到MySQL/PostgreSQL。看config/settings.py里的DATABASES配置# config/settings.py from pathlib import Path import os from dotenv import load_dotenv BASE_DIR Path(__file__).resolve().parent.parent load_dotenv(BASE_DIR / .env) DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: os.getenv(DB_NAME, crm_db), USER: os.getenv(DB_USER, root), PASSWORD: os.getenv(DB_PASSWORD, ), HOST: os.getenv(DB_HOST, 127.0.0.1), PORT: os.getenv(DB_PORT, 3306), } }用python-dotenv读取.env文件避免把数据库密码写进源码仓库。这是在开源项目里很常见的做法但很多二开者会忽略直接改settings.py导致后续git冲突。如果你拿到的源码没有这个配置建议自己补上。还需要确认INSTALLED_APPS里注册了所有业务app比如apps.customers、apps.orders漏掉任何一个都会在迁移时报No module named apps.customers。3.3 执行迁移并创建超级管理员数据库配置好后按顺序执行三个命令python manage.py makemigrations python manage.py migrate python manage.py createsuperusermakemigrations根据模型生成迁移文件migrate把迁移应用到数据库。对开源源码来说仓库里通常会携带已经生成的migrations/目录所以你只需要直接migrate不需要自己makemigrations。如果强行执行会扫描所有app可能产生“No changes detected”的输出这是正常的。只有当你改了模型后才需要再执行makemigrations生成新的迁移文件。createsuperuser交互式输入用户名、邮箱、密码。密码低于8位会提示太短但Django允许强制创建只是不推荐。创建完成后照样能登录后台。3.4 用Django admin快速验证运行python manage.py runserver 127.0.0.1:8000浏览器打开http://127.0.0.1:8000/admin/输入刚才创建的账号。如果后台页面能看到客户、联系人、跟进记录的菜单说明源码的基础数据模型和Admin注册都没有问题。注意Django Admin不会自动注册模型需要看apps/customers/admin.py里是否写了admin.site.register(Customer)。如果没注册你在Admin里看不到对应表但不是系统错误。有些开源项目会刻意不做Admin注册改用自定义前端界面这时就要用REST API或直接看数据库来验证数据是否写入。4. 二次开发三板斧自定义字段、权限与 API4.1 用迁移机制加自定义字段而不破坏源码拿到开源CRM源码后你最可能做的第一件事是加字段比如客户表要加一个“行业类型”。正确做法是修改模型并生成迁移而不是直接改数据库# apps/customers/models.py class Customer(models.Model): # ... 原有字段 industry models.CharField(所属行业, max_length32, blankTrue)python manage.py makemigrations customers python manage.py migratemakemigrations customers只处理customersapp避免其他app的未提交改动混进来。生成的迁移文件会记录在apps/customers/migrations/0002_customer_industry.py这个文件可以提交到git队友拉下来执行migrate就能同步表结构。这里的关键是永远不要手动执行ALTER TABLE否则Django的迁移历史会和数据库实际结构不一致后续部署时会出现 “relation does not exist” 之类的诡异错误。4.2 用信号量实现跟进天数自动提醒CRM里“超过3天没跟进客户”的提醒一般不用定时任务而是用Django的post_save信号判断下次跟进日期。看这个例子# apps/followups/signals.py from django.db.models.signals import post_save from django.dispatch import receiver from django.utils import timezone from .models import FollowUp receiver(post_save, senderFollowUp) def check_next_followup(sender, instance, **kwargs): if instance.next_follow_date: days (instance.next_follow_date - timezone.localdate()).days if days 3: # 这里可以发站内信或钉钉通知伪代码示意 notify_user(instance.user, f客户 {instance.customer.name} 将在 {days} 天内跟进)然后在apps/followups/apps.py里 import 信号class FollowupsConfig(AppConfig): default_auto_field django.db.models.BigAutoField name apps.followups def ready(self): from . import signals # noqa这套机制依赖每次保存FollowUp时触发信号。如果业务需要更复杂的周期任务比如每天上午10点统一发送提醒列表可以引入Celery Beat但复杂度会成倍上升。大多数中小团队用信号就够了——警告的目的不是实时推送而是让销售在保存时立刻看到“下次跟进时间太近了”。4.3 用DRF把CRM数据发布成API移动端打卡、公司内部系统对接、数据大屏展示都要求CRM有API。Django REST FrameworkDRF是开源CRM源码里最常用的方案# apps/customers/api.py from rest_framework import serializers, viewsets from .models import Customer class CustomerSerializer(serializers.ModelSerializer): class Meta: model Customer fields [id, name, phone, level, owner] class CustomerViewSet(viewsets.ModelViewSet): queryset Customer.objects.all() serializer_class CustomerSerializer# config/urls.py from rest_framework.routers import DefaultRouter from apps.customers.api import CustomerViewSet router DefaultRouter() router.register(rcustomers, CustomerViewSet) urlpatterns router.urlsviewsets.ModelViewSet自动生成GET/POST/PUT/DELETE五个接口。但要小心它默认不限制任何人只要请求里有session或token就能操作所有客户。所以必须结合第2.4节的权限控制class CustomerViewSet(viewsets.ModelViewSet): def get_queryset(self): return Customer.objects.visible_to(self.request.user)get_queryset在每次请求时都会执行所以用户只能看到有权限的数据。如果只给自己项目用用DRF自带的IsAuthenticated认证即可如果要对外提供开放API建议加djangorestframework-simplejwt用JWT token。这样既保留了源码的完整性又增加了接口鉴权能力。5. 生产环境部署Nginx uWSGI 与静态文件收集把CRM源码部署到公网服务器时最常出问题的是静态文件丢失和ALLOWED_HOSTS配置错误。下面按生产环境最小步骤操作。5.1 uWSGI配置与静态文件收集在项目根目录创建uwsgi.ini[uwsgi] http 127.0.0.1:8001 chdir /var/www/crm_project module config.wsgi:application master true processes 4 threads 2 vacuum true max-requests 5000然后执行pip install uwsgi uwsgi --ini uwsgi.iniprocesses4表示4个worker进程能同时处理4个并发请求。max-requests5000是每个worker处理5000个请求后自动回收防止内存泄漏。Django的runserver只能用于开发生产必须用uWSGI或Gunicorn。接着收集所有静态文件python manage.py collectstatic --noinput这条命令把每个app下的 static 目录拷贝到STATIC_ROOT指定的位置。如果settings.py里没设置STATIC_ROOTcollectstatic会报错。常见配置是STATIC_URL /static/ STATIC_ROOT BASE_DIR / staticfiles5.2 Nginx反向代理与静态文件路径编辑Nginx站点配置比如/etc/nginx/sites-available/crmserver { listen 80; server_name your_domain.com; location /static/ { alias /var/www/crm_project/staticfiles/; } location / { include uwsgi_params; uwsgi_pass 127.0.0.1:8001; } }这里uwsgi_pass必须和uwsgi.ini里http 127.0.0.1:8001地址一致。如果改成socket 127.0.0.1:8001Nginx的uwsgi_pass也要对应改成127.0.0.1:8001不加http://。很多部署失败都是因为协议不匹配。5.3 验证部署是否正常的快速检查启动顺序是先启动uWSGI再reload Nginx。验证用三条命令curl -I http://127.0.0.1:8001/admin/ curl -I http://your_domain.com/admin/ curl -I http://your_domain.com/static/admin/css/base.css第一条确认uWSGI进程没问题第二条确认Nginx反代生效第三条确认静态文件能访问。如果第一条返回200但第二条返回502说明Nginx和uWSGI的通信配置不对如果第三条返回404检查collectstatic是否执行以及alias路径是否正确。最后别忘了在settings.py里设置DEBUG False和ALLOWED_HOSTS [your_domain.com]否则Django会抛出DisallowedHost错误。用systemctl管理uWSGI时还建议加上--die-on-term参数否则systemctl stop时进程可能残留。至此一套基于Django的开源CRM源码就能从本地开发环境平稳迁移到生产服务器。二次开发时记住优先在迁移文件和信号层动手而不是直接改源码核心逻辑——这样后续拉取上游更新时才不会冲突遍地。本文还有配套的精品资源点击获取
返回列表