Django开源CRM源码解析:模型、权限到生产部署
2026/9/13 13:11:30 网站建设 项目流程

简介:基于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或PHP

CRM这类系统对开发框架的挑剔程度远超普通博客或商城:客户字段今天要加一个“来源渠道”,明天要按区域划分数据权限,后天要把跟进记录导给销售大屏——如果框架本身不具备灵活的迁移机制、成熟的后台管理和细粒度的权限控制,二开成本会很快吞掉选型时的“轻量”优势。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.txt

config是Django推荐的最新目录组织方式,替代老项目里的同名子目录;apps下每个业务模块是一个独立app,app与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_length=128) phone = models.CharField('联系电话', max_length=20, blank=True) level = models.CharField('客户等级', max_length=10, choices=(('A', '重点'), ('B', '普通'), ('C', '观察')), default='B') source = models.CharField('来源渠道', max_length=32, blank=True) owner = models.ForeignKey('auth.User', verbose_name='负责人', on_delete=models.SET_NULL, null=True, blank=True) created_at = models.DateTimeField('创建时间', auto_now_add=True) class Contact(models.Model): customer = models.ForeignKey(Customer, verbose_name='所属客户', related_name='contacts', on_delete=models.CASCADE) name = models.CharField('联系人姓名', max_length=64) position = models.CharField('职位', max_length=64, blank=True) mobile = models.CharField('手机号', max_length=20)

这段代码的要点有三个:level字段用choices限定取值,方便后续在Admin下拉和报表分组;owner外键指向Django自带的auth.User,实现“每个客户归某个销售负责”的基础归属关系;Contactrelated_name='contacts'让反向查询语义更明确——你可以写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_name='followups', on_delete=models.CASCADE) user = models.ForeignKey('auth.User', on_delete=models.CASCADE) content = models.TextField('跟进内容') next_follow_date = models.DateField('下次跟进日期', blank=True, null=True) # apps/orders/models.py class Order(models.Model): customer = models.ForeignKey(Customer, related_name='orders', on_delete=models.PROTECT) amount = models.DecimalField(max_digits=12, decimal_places=2) status = models.CharField(max_length=12, choices=(('pending', '待签约'), ('signed', '已签约'), ('cancelled', '已取消')), default='pending')

on_delete=models.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(owner=user) class CustomerManager(models.Manager): def get_queryset(self): return CustomerQuerySet(self.model, using=self._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.txt

requirements.txt里至少要包含Django==4.2.*djangorestframeworkpython-dotenvmysqlclientpsycopg2-binary。如果源码里带了pyproject.tomlPipfile,优先用它们安装。虚拟环境隔离后,再查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 createsuperuser

makemigrations根据模型生成迁移文件,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. 二次开发三板斧:自定义字段、权限与 API

4.1 用迁移机制加自定义字段而不破坏源码

拿到开源CRM源码后,你最可能做的第一件事是加字段,比如客户表要加一个“行业类型”。正确做法是修改模型并生成迁移,而不是直接改数据库:

# apps/customers/models.py class Customer(models.Model): # ... 原有字段 industry = models.CharField('所属行业', max_length=32, blank=True)
python manage.py makemigrations customers python manage.py migrate

makemigrations 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, sender=FollowUp) 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 Framework(DRF)是开源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(r'customers', CustomerViewSet) urlpatterns += router.urls

viewsets.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.ini

processes=4表示4个worker进程,能同时处理4个并发请求。max-requests=5000是每个worker处理5000个请求后自动回收,防止内存泄漏。Django的runserver只能用于开发,生产必须用uWSGI或Gunicorn。

接着收集所有静态文件:

python manage.py collectstatic --noinput

这条命令把每个app下的 static 目录拷贝到STATIC_ROOT指定的位置。如果settings.py里没设置STATIC_ROOT,collectstatic会报错。常见配置是:

STATIC_URL = '/static/' STATIC_ROOT = BASE_DIR / 'staticfiles'

5.2 Nginx反向代理与静态文件路径

编辑Nginx站点配置(比如/etc/nginx/sites-available/crm):

server { 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.inihttp = 127.0.0.1:8001地址一致。如果改成socket = 127.0.0.1:8001,Nginx的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 = FalseALLOWED_HOSTS = ['your_domain.com'],否则Django会抛出DisallowedHost错误。用systemctl管理uWSGI时,还建议加上--die-on-term参数,否则systemctl stop时进程可能残留。

至此,一套基于Django的开源CRM源码就能从本地开发环境平稳迁移到生产服务器。二次开发时,记住优先在迁移文件和信号层动手,而不是直接改源码核心逻辑——这样后续拉取上游更新时才不会冲突遍地。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询