第一次提交

master
laoxiao 2 years ago
commit 2463587f19

8
.idea/.gitignore vendored

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/../../../../:\py_workspace\ERP_5\.idea/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="FacetManager">
<facet type="django" name="Django">
<configuration>
<option name="rootFolder" value="$MODULE_DIR$" />
<option name="settingsModule" value="ERP_5/settings.py" />
<option name="manageScript" value="$MODULE_DIR$/manage.py" />
<option name="environment" value="&lt;map/&gt;" />
<option name="doNotUseTestRunner" value="false" />
<option name="trackFilePattern" value="migrations" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/ERP_5/apps" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Django" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/../ERP_5\templates" />
</list>
</option>
</component>
</module>

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="erp_5" uuid="4bc53945-f65a-487e-8aae-a32e60fe31b7">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://localhost:3306/erp_5</jdbc-url>
</data-source>
</component>
</project>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,2 @@
#n:information_schema
!<md> [null, 0, null, null, -2147483648, -2147483648]

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PublishConfigData" remoteFilesAllowedToDisappearOnAutoupload="false">
<serverData>
<paths name="root@192.168.23.3:22 agent">
<serverdata>
<mappings>
<mapping local="$PROJECT_DIR$" web="/" />
</mappings>
</serverdata>
</paths>
<paths name="root@192.168.23.3:22 password">
<serverdata>
<mappings>
<mapping local="$PROJECT_DIR$" web="/" />
</mappings>
</serverdata>
</paths>
</serverData>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>

@ -0,0 +1,33 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="6">
<item index="0" class="java.lang.String" itemvalue="mysqlclient" />
<item index="1" class="java.lang.String" itemvalue="install" />
<item index="2" class="java.lang.String" itemvalue="Django" />
<item index="3" class="java.lang.String" itemvalue="urllib3" />
<item index="4" class="java.lang.String" itemvalue="redis" />
<item index="5" class="java.lang.String" itemvalue="idna" />
</list>
</value>
</option>
</inspection_tool>
<inspection_tool class="PyPep8Inspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="E501" />
</list>
</option>
</inspection_tool>
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="N802" />
</list>
</option>
</inspection_tool>
</profile>
</component>

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9 (django-3.2.16_env) (3)" project-jdk-type="Python SDK" />
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/ERP_5.iml" filepath="$PROJECT_DIR$/.idea/ERP_5.iml" />
</modules>
</component>
</project>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/datas/nation_data.sql" dialect="GenericSQL" />
<file url="PROJECT" dialect="MySQL" />
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -0,0 +1,4 @@
from .celery import celery_app
__all__ = ('celery_app',)

@ -0,0 +1 @@
# 存放所有项目中APP

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

@ -0,0 +1,9 @@
from django.apps import AppConfig
class BasicInfoConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'basic_info'
def ready(self):
import basic_info.signals

@ -0,0 +1,56 @@
# Generated by Django 3.2.16 on 2023-03-26 20:15
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Nation',
fields=[
('id', models.IntegerField(primary_key=True, serialize=False)),
('n_name', models.CharField(max_length=30, verbose_name='国家名称')),
],
options={
'verbose_name': '国家表',
'verbose_name_plural': '国家表',
'db_table': 'nation',
'ordering': ['id'],
},
),
migrations.CreateModel(
name='Province',
fields=[
('id', models.IntegerField(primary_key=True, serialize=False)),
('p_name', models.CharField(max_length=30, verbose_name='省份名称')),
('nation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='province_list', to='basic_info.nation', verbose_name='省份所在的国家')),
],
options={
'verbose_name': '省份表',
'verbose_name_plural': '省份表',
'db_table': 'province',
'ordering': ['id'],
},
),
migrations.CreateModel(
name='City',
fields=[
('id', models.IntegerField(primary_key=True, serialize=False)),
('c_name', models.CharField(max_length=30, verbose_name='城市名称')),
('province', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='city_list', to='basic_info.province', verbose_name='城市所在的省份')),
],
options={
'verbose_name': '城市表',
'verbose_name_plural': '城市表',
'db_table': 'city',
'ordering': ['id'],
},
),
]

@ -0,0 +1,44 @@
# Generated by Django 3.2.16 on 2023-03-26 20:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('basic_info', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='SupplierModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('delete_flag', models.CharField(default='0', help_text='是否启用', max_length=1, verbose_name='是否启用')),
('create_time', models.DateTimeField(auto_now_add=True, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='更新时间', verbose_name='更新时间')),
('name', models.CharField(max_length=100, unique=True, verbose_name='供应商名称')),
('mobile', models.CharField(blank=True, max_length=11, null=True, verbose_name='手机号码')),
('phone', models.CharField(blank=True, max_length=22, null=True, verbose_name='联系电话')),
('contacts_name', models.CharField(blank=True, max_length=22, null=True, verbose_name='联系人名')),
('email', models.CharField(blank=True, max_length=50, null=True, verbose_name='电子邮箱')),
('ratepayer_number', models.CharField(blank=True, max_length=50, null=True, verbose_name='纳税人识别号码')),
('bank', models.CharField(blank=True, max_length=50, null=True, verbose_name='开户银行')),
('account_number', models.CharField(blank=True, max_length=50, null=True, verbose_name='银行账号')),
('nation', models.CharField(blank=True, max_length=50, null=True, verbose_name='国家')),
('province', models.CharField(blank=True, max_length=50, null=True, verbose_name='省份')),
('city', models.CharField(blank=True, max_length=50, null=True, verbose_name='城市')),
('address', models.CharField(blank=True, max_length=50, null=True, verbose_name='详细地址')),
('remark', models.CharField(blank=True, max_length=512, null=True, verbose_name='备注')),
('init_pay', models.DecimalField(blank=True, decimal_places=2, default=0, max_digits=20, verbose_name='初期应付')),
('current_pay', models.DecimalField(blank=True, decimal_places=2, default=0, max_digits=20, verbose_name='末期应付')),
('order_number', models.IntegerField(default=100, verbose_name='排序号码')),
],
options={
'verbose_name': '供应商',
'verbose_name_plural': '供应商',
'db_table': 't_supplier',
'ordering': ['order_number', 'id'],
},
),
]

@ -0,0 +1,44 @@
# Generated by Django 3.2.16 on 2023-03-26 21:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('basic_info', '0002_suppliermodel'),
]
operations = [
migrations.CreateModel(
name='CustomerModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('delete_flag', models.CharField(default='0', help_text='是否启用', max_length=1, verbose_name='是否启用')),
('create_time', models.DateTimeField(auto_now_add=True, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='更新时间', verbose_name='更新时间')),
('name', models.CharField(max_length=100, unique=True, verbose_name='客户名称')),
('mobile', models.CharField(blank=True, max_length=11, null=True, verbose_name='手机号码')),
('phone', models.CharField(blank=True, max_length=22, null=True, verbose_name='联系电话')),
('contacts_name', models.CharField(blank=True, max_length=22, null=True, verbose_name='联系人名')),
('email', models.CharField(blank=True, max_length=50, null=True, verbose_name='电子邮箱')),
('ratepayer_number', models.CharField(blank=True, max_length=50, null=True, verbose_name='纳税人识别号码')),
('bank', models.CharField(blank=True, max_length=50, null=True, verbose_name='开户银行')),
('account_number', models.CharField(blank=True, max_length=50, null=True, verbose_name='银行账号')),
('nation', models.CharField(blank=True, max_length=50, null=True, verbose_name='国家')),
('province', models.CharField(blank=True, max_length=50, null=True, verbose_name='省份')),
('city', models.CharField(blank=True, max_length=50, null=True, verbose_name='城市')),
('address', models.CharField(blank=True, max_length=50, null=True, verbose_name='详细地址')),
('remark', models.CharField(blank=True, max_length=512, null=True, verbose_name='备注')),
('init_receivable', models.DecimalField(blank=True, decimal_places=2, max_digits=20, null=True, verbose_name='初期应收')),
('current_receivable', models.DecimalField(blank=True, decimal_places=2, max_digits=20, null=True, verbose_name='末期应收')),
('order_number', models.IntegerField(default=100, verbose_name='排序号码')),
],
options={
'verbose_name': '客户',
'verbose_name_plural': '客户',
'db_table': 't_customer',
'ordering': ['order_number', 'id'],
},
),
]

@ -0,0 +1,64 @@
# Generated by Django 3.2.16 on 2023-03-28 20:49
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('basic_info', '0003_customermodel'),
]
operations = [
migrations.CreateModel(
name='SettlementAccountModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('delete_flag', models.CharField(default='0', help_text='是否启用', max_length=1, verbose_name='是否启用')),
('create_time', models.DateTimeField(auto_now_add=True, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='更新时间', verbose_name='更新时间')),
('name', models.CharField(max_length=100, unique=True, verbose_name='仓库名称')),
('number_code', models.CharField(max_length=28, unique=True, verbose_name='编号')),
('remark', models.CharField(blank=True, max_length=512, null=True, verbose_name='备注')),
('init_amount', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='初期金额')),
('balance', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='余额')),
('order_number', models.IntegerField(default=100, verbose_name='排序号码')),
('is_default', models.BooleanField(default=False, verbose_name='是否默认')),
],
options={
'verbose_name': '结算账户',
'verbose_name_plural': '结算账户',
'db_table': 't_settlement_account',
'ordering': ['order_number', 'id'],
},
),
migrations.CreateModel(
name='WarehouseModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('delete_flag', models.CharField(default='0', help_text='是否启用', max_length=1, verbose_name='是否启用')),
('create_time', models.DateTimeField(auto_now_add=True, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='更新时间', verbose_name='更新时间')),
('name', models.CharField(max_length=100, unique=True, verbose_name='仓库名称')),
('nation', models.CharField(blank=True, max_length=50, null=True, verbose_name='国家')),
('province', models.CharField(blank=True, max_length=50, null=True, verbose_name='省份')),
('city', models.CharField(blank=True, max_length=50, null=True, verbose_name='城市')),
('address', models.CharField(blank=True, max_length=50, null=True, verbose_name='详细地址')),
('remark', models.CharField(blank=True, max_length=512, null=True, verbose_name='备注')),
('warehouse_fee', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='仓储费用(元/天/KG)')),
('truckage', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='搬运费用')),
('order_number', models.IntegerField(default=100, verbose_name='排序号码')),
('is_default', models.BooleanField(default=False, verbose_name='是否默认的仓库')),
('leader_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='仓库负责人')),
],
options={
'verbose_name': '仓库',
'verbose_name_plural': '仓库',
'db_table': 't_warehouse',
'ordering': ['order_number', 'id'],
},
),
]

@ -0,0 +1,153 @@
from django.db import models
# 区域的模型类
from ERP_5.utils.base_model import BaseModel
class Nation(models.Model):
id = models.IntegerField(primary_key=True)
n_name = models.CharField('国家名称', max_length=30)
class Meta:
db_table = 'nation'
verbose_name = '国家表'
verbose_name_plural = verbose_name
ordering = ['id']
def __str__(self):
return self.n_name
# 省份的模型类
class Province(models.Model):
id = models.IntegerField(primary_key=True)
p_name = models.CharField('省份名称', max_length=30)
nation = models.ForeignKey('Nation', related_name='province_list', on_delete=models.CASCADE, verbose_name='省份所在的国家')
class Meta:
db_table = 'province'
verbose_name = '省份表'
verbose_name_plural = verbose_name
ordering = ['id']
def __str__(self):
return self.p_name
# 城市的模型类
class City(models.Model):
id = models.IntegerField(primary_key=True)
c_name = models.CharField('城市名称', max_length=30)
province = models.ForeignKey('Province', related_name='city_list', on_delete=models.CASCADE, verbose_name='城市所在的省份')
class Meta:
db_table = 'city'
verbose_name = '城市表'
verbose_name_plural = verbose_name
ordering = ['id']
def __str__(self):
return self.c_name
# 供应商模型类
class SupplierModel(BaseModel):
name = models.CharField(max_length=100, verbose_name='供应商名称', unique=True)
mobile = models.CharField('手机号码', max_length=11, blank=True, null=True)
phone = models.CharField('联系电话', max_length=22, blank=True, null=True)
contacts_name = models.CharField('联系人名', max_length=22, blank=True, null=True)
email = models.CharField('电子邮箱', max_length=50, blank=True, null=True)
ratepayer_number = models.CharField('纳税人识别号码', max_length=50, blank=True, null=True)
bank = models.CharField('开户银行', max_length=50, blank=True, null=True)
account_number = models.CharField('银行账号', max_length=50, blank=True, null=True)
nation = models.CharField('国家', max_length=50, blank=True, null=True)
province = models.CharField('省份', max_length=50, blank=True, null=True)
city = models.CharField('城市', max_length=50, blank=True, null=True)
address = models.CharField('详细地址', max_length=50, blank=True, null=True)
remark = models.CharField('备注', max_length=512, blank=True, null=True)
init_pay = models.DecimalField('初期应付', max_digits=20, decimal_places=2, blank=True, default=0) # 精确到小数点后两位
current_pay = models.DecimalField('末期应付', max_digits=20, decimal_places=2, default=0, blank=True) # 精确到小数点后两位
order_number = models.IntegerField('排序号码', default=100)
class Meta:
db_table = 't_supplier'
verbose_name = '供应商'
verbose_name_plural = verbose_name
ordering = ['order_number', 'id']
def __str__(self):
return self.name
# 客户模型类
class CustomerModel(BaseModel):
name = models.CharField(max_length=100, verbose_name='客户名称', unique=True)
mobile = models.CharField('手机号码', max_length=11, blank=True, null=True)
phone = models.CharField('联系电话', max_length=22, blank=True, null=True)
contacts_name = models.CharField('联系人名', max_length=22, blank=True, null=True)
email = models.CharField('电子邮箱', max_length=50, blank=True, null=True)
ratepayer_number = models.CharField('纳税人识别号码', max_length=50, blank=True, null=True)
bank = models.CharField('开户银行', max_length=50, blank=True, null=True)
account_number = models.CharField('银行账号', max_length=50, blank=True, null=True)
nation = models.CharField('国家', max_length=50, blank=True, null=True)
province = models.CharField('省份', max_length=50, blank=True, null=True)
city = models.CharField('城市', max_length=50, blank=True, null=True)
address = models.CharField('详细地址', max_length=50, blank=True, null=True)
remark = models.CharField('备注', max_length=512, blank=True, null=True)
init_receivable = models.DecimalField('初期应收', max_digits=20, decimal_places=2, blank=True, null=True) # 精确到小数点后两位
current_receivable = models.DecimalField('末期应收', max_digits=20, decimal_places=2, blank=True, null=True) # 精确到小数点后两位
order_number = models.IntegerField('排序号码', default=100)
class Meta:
db_table = 't_customer'
verbose_name = '客户'
verbose_name_plural = verbose_name
ordering = ['order_number', 'id']
def __str__(self):
return self.name
# 仓库模型类
class WarehouseModel(BaseModel):
name = models.CharField(max_length=100, verbose_name='仓库名称', unique=True)
nation = models.CharField('国家', max_length=50, blank=True, null=True)
province = models.CharField('省份', max_length=50, blank=True, null=True)
city = models.CharField('城市', max_length=50, blank=True, null=True)
address = models.CharField('详细地址', max_length=50, blank=True, null=True)
remark = models.CharField('备注', max_length=512, blank=True, null=True)
warehouse_fee = models.DecimalField('仓储费用(元/天/KG)', max_digits=10, decimal_places=2, blank=True, null=True) # 精确到小数点后两位
truckage = models.DecimalField('搬运费用', max_digits=10, decimal_places=2, blank=True, null=True) # 精确到小数点后两位
order_number = models.IntegerField('排序号码', default=100)
is_default = models.BooleanField('是否默认的仓库', default=False)
# 用户模型来自于erp_system的APP中必须要加前缀
leader_user = models.ForeignKey('erp_system.UserModel', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='仓库负责人')
class Meta:
db_table = 't_warehouse'
verbose_name = '仓库'
verbose_name_plural = verbose_name
ordering = ['order_number', 'id']
def __str__(self):
return self.name
# 结算账户模型类
class SettlementAccountModel(BaseModel):
name = models.CharField(max_length=100, verbose_name='仓库名称', unique=True)
number_code = models.CharField('编号', max_length=28, unique=True)
remark = models.CharField('备注', max_length=512, blank=True, null=True)
init_amount = models.DecimalField('初期金额', max_digits=10, decimal_places=2, blank=True, null=True) # 精确到小数点后两位
balance = models.DecimalField('余额', max_digits=10, decimal_places=2, blank=True, null=True) # 精确到小数点后两位
order_number = models.IntegerField('排序号码', default=100)
is_default = models.BooleanField('是否默认', default=False)
class Meta:
db_table = 't_settlement_account'
verbose_name = '结算账户'
verbose_name_plural = verbose_name
ordering = ['order_number', 'id']
def __str__(self):
return self.name

@ -0,0 +1,29 @@
from rest_framework import serializers
from basic_info.models import Nation, Province, City
class NationSerializer(serializers.ModelSerializer):
"""
国家列化器
"""
class Meta:
model = Nation
fields = '__all__'
class ProvinceSerializer(serializers.ModelSerializer):
"""
省份列化器
"""
class Meta:
model = Province
fields = '__all__'
class CitySerializer(serializers.ModelSerializer):
"""
城市列化器
"""
class Meta:
model = City
fields = '__all__'

@ -0,0 +1,24 @@
from rest_framework import serializers
from basic_info.models import CustomerModel
class CustomerSerializer(serializers.ModelSerializer):
"""
客户信息的序列化器
"""
class Meta:
model = CustomerModel
fields = '__all__'
class CustomerFindSerializer(serializers.ModelSerializer):
"""
搜索过滤的序列化器
"""
name = serializers.CharField(required=False, write_only=True, help_text='客户名称关键字')
class Meta:
model = CustomerModel
fields = ['name', 'mobile', 'phone']

@ -0,0 +1,12 @@
from basic_info.models import SettlementAccountModel
from rest_framework import serializers
class AccountSerializer(serializers.ModelSerializer):
"""
结算账户信息的序列化器
"""
class Meta:
model = SettlementAccountModel
fields = '__all__'

@ -0,0 +1,24 @@
from rest_framework import serializers
from basic_info.models import SupplierModel
class SupplierSerializer(serializers.ModelSerializer):
"""
供应商信息的序列化器
"""
class Meta:
model = SupplierModel
fields = '__all__'
class SupplierFindSerializer(serializers.ModelSerializer):
"""
搜索过滤的序列化器
"""
name = serializers.CharField(required=False, write_only=True)
class Meta:
model = SupplierModel
fields = ['name', 'mobile', 'phone']

@ -0,0 +1,24 @@
from basic_info.models import WarehouseModel
from rest_framework import serializers
class WarehouseSerializer(serializers.ModelSerializer):
"""
仓库信息的序列化器
"""
class Meta:
model = WarehouseModel
fields = '__all__'
# 用于查询结果的序列化器,因为需要显示负责人的真实名字
class WarehouseSearchSerializer(serializers.ModelSerializer):
"""
查询结果的序列化, SlugRelatedField,返回主表的某一个字段值:slug_field可以指定一个需要展示的字段
"""
leader_user = serializers.SlugRelatedField(slug_field='real_name', read_only=True)
class Meta:
model = WarehouseModel
fields = '__all__'

@ -0,0 +1,31 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
from basic_info.models import WarehouseModel
from goods_info.models import GoodsModel, GoodsInventoryModel
import logging
logger = logging.getLogger('my')
@receiver(post_save, sender=WarehouseModel)
def create_goods_inventory(sender, instance, created, **kwargs):
"""
创建信号监控函数
创建新仓库之后 给所有的货品在当前的新仓库 新增库存数据
"""
if created:
logger.info('创建当前的新仓库——新增库存数据')
if isinstance(instance, WarehouseModel):
# 首先查询所有的货品id
ids = GoodsModel.objects.values_list('id', flat=True).all()
inventory_list = []
for gid in ids:
inventory_list.append(GoodsInventoryModel(
goods_id=gid,
warehouse_name=instance.name,
warehouse=instance
))
# 一定要采用批量新增函数bulk_create
GoodsInventoryModel.objects.bulk_create(inventory_list)
else:
logger.info('不是WarehouseModel类型所以不需要创建库存数据')

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

@ -0,0 +1,39 @@
"""ERP_5 URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, re_path
from rest_framework.routers import DefaultRouter
from .views import supplier_views, customer_views, area_viewsets, settlement_account_views, warehouse_views
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
# path('admin/', admin.site.urls),
# path('users/login/', obtain_jwt_token), # 登录并且由JWT签发token
# re_path(r'^users/reset/(?P<pk>\d+)/$', user_views.ResetPasswordView.as_view()),
]
router = DefaultRouter()
router.register('suppliers', supplier_views.SupplierViewSet)
router.register('customers', customer_views.CustomerViewSet)
router.register(r'nation', area_viewsets.NationView) # 国家的视图接口
router.register(r'province', area_viewsets.ProvinceView) # 省份的视图接口
router.register(r'city', area_viewsets.CityView) # 城市的视图接口
router.register(r'warehouses', warehouse_views.WarehouseView) #
router.register(r'accounts', settlement_account_views.AccountView) #
urlpatterns += router.urls

@ -0,0 +1,154 @@
import logging
from rest_framework import viewsets
from ERP_5.utils.base_views import MultipleDestroyMixin
from basic_info.models import Nation, Province, City
from basic_info.serializer.area_serializer import NationSerializer, ProvinceSerializer, CitySerializer
logger = logging.getLogger('my')
class NationView(viewsets.ModelViewSet, MultipleDestroyMixin):
"""
create:
国家--新增
国家新增, status: 201(成功), return: 新增国家信息
destroy:
国家--删除
国家删除, status: 204(成功), return: None
multiple_delete:
国家--批量删除,必传参数ids=[1,2,3,4...]
国家批量删除, status: 204(成功), return: None
update:
国家--修改,只是修改国家名字
国家修改, status: 200(成功), return: 修改后的国家信息
partial_update:
国家--局部修改,只是修改国家名字
国家局部修改, status: 200(成功), return: 修改后的国家信息
list:
国家--获取列表
国家列表信息, status: 200(成功), return: 国家信息列表
retrieve:
查询某一个国家
查询指定ID的国家, status: 200(成功), return: 用户国家
"""
queryset = Nation.objects.all()
serializer_class = NationSerializer
class ProvinceView(viewsets.ModelViewSet, MultipleDestroyMixin):
"""
create:
省份--新增
省份新增, status: 201(成功), return: 新增省份信息
destroy:
省份--删除
省份删除, status: 204(成功), return: None
multiple_delete:
省份--批量删除,必传参数ids=[1,2,3,4...]
省份批量删除, status: 204(成功), return: None
update:
省份--修改,只是修改省份名字
省份修改, status: 200(成功), return: 修改后的省份信息
partial_update:
省份--局部修改,只是修改省份名字
省份局部修改, status: 200(成功), return: 修改后的省份信息
list:
省份--获取列表请求参数为nid(国家ID), 没有传参数表示所有省份列表nid=x 表示查询该国家的省份列表
省份列表信息, status: 200(成功), return: 省份信息列表
retrieve:
查询某一个省份
查询指定ID的省份, status: 200(成功), return: 用户省份
"""
queryset = Province.objects.all()
serializer_class = ProvinceSerializer
def get_queryset(self):
# 请求参数为nid(国家ID), 没有传参数表示所有省份列表。nid=x 表示查询该国家的省份列表
nid = self.request.query_params.get('nid', None)
if nid:
nid = int(nid)
return Province.objects.filter(nation__id=nid).all()
else:
return Province.objects.all()
class CityView(viewsets.ModelViewSet, MultipleDestroyMixin):
"""
create:
城市--新增
城市新增, status: 201(成功), return: 新增城市信息
destroy:
城市--删除
城市删除, status: 204(成功), return: None
multiple_delete:
城市--批量删除,必传参数ids=[1,2,3,4...]
城市批量删除, status: 204(成功), return: None
update:
城市--修改,只是修改城市名字
城市修改, status: 200(成功), return: 修改后的城市信息
partial_update:
城市--局部修改,只是修改城市名字
城市局部修改, status: 200(成功), return: 修改后的城市信息
list:
城市--获取列表请求参数为pid(省份ID), 没有传参数表示所有城市列表pid=x 表示查询该省份下的城市列表
城市列表信息, status: 200(成功), return: 城市信息列表
retrieve:
查询某一个城市
查询指定ID的城市, status: 200(成功), return: 用户城市
"""
queryset = City.objects.all()
serializer_class = CitySerializer
def get_queryset(self):
# 请求参数为pid(省份ID), 没有传参数表示所有城市列表。pid=x 表示查询该省份下的城市列表
pid = self.request.query_params.get('pid', None)
if pid:
pid = int(pid)
return City.objects.filter(province__id=pid).all()
else:
return City.objects.all()

@ -0,0 +1,88 @@
from django.db.models import Q
from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.viewsets import ModelViewSet
from ERP_5.utils.base_views import MultipleDestroyMixin, MultipleOpenMixin
from ERP_5.utils.paginations import GlobalPagination
from basic_info.models import CustomerModel
from basic_info.serializer.customer_serializer import CustomerSerializer, CustomerFindSerializer
class CustomerViewSet(ModelViewSet, MultipleDestroyMixin, MultipleOpenMixin):
"""
create:
客户信息--新增
客户信息新增, status: 201(成功), return: 新增客户信息信息
destroy:
客户信息--删除
客户信息删除, status: 204(成功), return: None
multiple_delete:
客户信息--批量删除,必传参数ids=[1,2,3,4...]
客户信息批量删除, status: 204(成功), return: None
multiple_open:
客户信息--批量启用或者禁用,必传(json)参数ids=[1,2,3,4...](列表中可以只有一个)is_open=1/0
{
"ids":[1,2],
"is_open":"0"
}
is_open=1表示禁用is_open=0表示启用
客户信息批量启用或者禁用, status: 204(成功), return: None
update:
客户信息--修改,
客户信息修改, status: 200(成功), return: 修改后的客户信息信息
partial_update:
客户信息--局部修改,可以传参任意属性的值服务器会修改指定的属性值
客户信息局部修改, status: 200(成功), return: 修改后的客户信息信息
find:
客户信息--获取分页列表可选json参数:name(名称)mobile(手机号码)phone(联系电话)
{
"name":"长沙",
"mobile":"186","phone":xxx
}
客户信息列表信息, status: 200(成功), return: 客户信息信息列表
retrieve:
查询某一个客户信息
查询指定ID的客户信息, status: 200(成功), return: 用户客户信息
"""
queryset = CustomerModel.objects.all()
serializer_class = CustomerSerializer
pagination_class = GlobalPagination
def get_queryset(self):
if self.action == 'find': # 过滤查询
# 获取请求中的数据
name = self.request.data.get('name', None)
phone = self.request.data.get('phone', None)
mobile = self.request.data.get('mobile', None)
query = Q()
if name:
query.add(Q(name__contains=name), 'AND') # 多条件组合
if phone:
query.add(Q(phone__contains=phone), 'AND')
if mobile:
query.add(Q(mobile__contains=mobile), 'AND')
return CustomerModel.objects.filter(query).all()
else:
return CustomerModel.objects.all()
@swagger_auto_schema(method='POST', request_body=CustomerFindSerializer, operation_description="查询过滤")
@action(methods=['POST'], detail=False)
def find(self, request, *args, **kwargs):
return super(CustomerViewSet, self).list(request, args, kwargs)

@ -0,0 +1,96 @@
import logging
from django.db.models import Q
from django.utils.decorators import method_decorator
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import viewsets
from basic_info.models import SettlementAccountModel
from ERP_5.apps.basic_info.serializer.settlement_account_serializer import AccountSerializer
from ERP_5.utils.paginations import GlobalPagination
from ERP_5.utils.base_views import MultipleDestroyMixin, MultipleOpenMixin
logger = logging.getLogger('my')
param1 = openapi.Parameter(name='name', type=openapi.TYPE_STRING, description="名称的关键字", in_=openapi.IN_QUERY)
param2 = openapi.Parameter(name='remark', type=openapi.TYPE_STRING, description="备注的关键字", in_=openapi.IN_QUERY)
param3 = openapi.Parameter(name='number_code', type=openapi.TYPE_STRING, description="编号关键字", in_=openapi.IN_QUERY)
@method_decorator(name='list', decorator=swagger_auto_schema(
manual_parameters=[param1, param2, param3],
operation_description="账户的搜索过滤")
)
class AccountView(viewsets.ModelViewSet, MultipleDestroyMixin, MultipleOpenMixin):
"""
create:
结算账户信息--新增
结算账户信息新增, status: 201(成功), return: 新增结算账户信息信息
destroy:
结算账户信息--删除
结算账户信息删除, status: 204(成功), return: None
multiple_delete:
结算账户信息--批量删除,必传json参数ids=[1,2,3,4...]
结算账户信息批量删除, status: 204(成功), return: None
multiple_open:
结算账户信息--批量启用或者禁用,必传(json)参数ids=[1,2,3,4...](列表中可以只有一个)is_open=1/0
{
"ids":[1,2],
"is_open":"0"
}
is_open=1表示禁用is_open=0表示启用
结算账户信息批量启用或者禁用, status: 204(成功), return: None
update:
结算账户信息--修改,一般用于修改多个属性
结算账户信息修改, status: 200(成功), return: 修改后的结算账户信息信息
partial_update:
结算账户信息--局部修改(一般修改一个属性),可以传参任意属性的值服务器会修改指定的属性值
结算账户信息局部修改, status: 200(成功), return: 修改后的结算账户信息信息
list:
结算账户信息--获取分页列表可选json参数:name(名称)remark(描述关键字),number_code(编号)
{
"name":"长沙",
"remark":"xxx", "number_code":"xxxx"
}
结算账户信息列表信息, status: 200(成功), return: 结算账户信息信息列表
retrieve:
查询某一个结算账户信息
查询指定ID的结算账户信息, status: 200(成功), return: 用户结算账户信息
"""
queryset = SettlementAccountModel.objects.all()
serializer_class = AccountSerializer
pagination_class = GlobalPagination
def get_queryset(self):
if self.action == 'list': # 过滤查询
# 获取请求参数(在json中)namephone, mobile
name = self.request.data.get('name', None)
remark = self.request.data.get('remark', None)
number_code = self.request.data.get('number_code', None)
query = Q()
if name:
query.add(Q(name__contains=name), 'AND') # 多条件组合
if remark:
query.add(Q(remark__contains=remark), 'AND')
if remark:
query.add(Q(number_code__contains=number_code), 'AND')
return SettlementAccountModel.objects.filter(query).all()
else:
return SettlementAccountModel.objects.all()

@ -0,0 +1,91 @@
from django.db.models import Q
from django.utils.decorators import method_decorator
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework.viewsets import ModelViewSet
from ERP_5.utils.base_views import MultipleDestroyMixin, MultipleOpenMixin
from ERP_5.utils.paginations import GlobalPagination
from basic_info.models import SupplierModel
from basic_info.serializer.supplier_serializer import SupplierSerializer, SupplierFindSerializer
param1 = openapi.Parameter(name='name', type=openapi.TYPE_STRING, description="供应商名称的关键字", in_=openapi.IN_QUERY)
param2 = openapi.Parameter(name='mobile', type=openapi.TYPE_STRING, description="供应商的手机号码", in_=openapi.IN_QUERY)
param3 = openapi.Parameter(name='phone', type=openapi.TYPE_STRING, description="供应商名称的联系电话", in_=openapi.IN_QUERY)
@method_decorator(name='list', decorator=swagger_auto_schema(
manual_parameters=[param1, param2, param3],
operation_description="供应商的搜索过滤")
)
class SupplierViewSet(ModelViewSet, MultipleDestroyMixin, MultipleOpenMixin):
"""
create:
供应商信息--新增
供应商信息新增, status: 201(成功), return: 新增供应商信息信息
destroy:
供应商信息--删除
供应商信息删除, status: 204(成功), return: None
multiple_delete:
供应商信息--批量删除,必传参数ids=[1,2,3,4...]
供应商信息批量删除, status: 204(成功), return: None
multiple_open:
供应商信息--批量启用或者禁用,必传(json)参数ids=[1,2,3,4...](列表中可以只有一个)is_open=1/0
{
"ids":[1,2],
"is_open":"0"
}
is_open=1表示禁用is_open=0表示启用
供应商信息批量启用或者禁用, status: 204(成功), return: None
update:
供应商信息--修改,
供应商信息修改, status: 200(成功), return: 修改后的供应商信息信息
partial_update:
供应商信息--局部修改,可以传参任意属性的值服务器会修改指定的属性值
供应商信息局部修改, status: 200(成功), return: 修改后的供应商信息信息
list:
供应商信息--搜索过滤的分页列表可选json参数:name(名称)mobile(手机号码)phone(联系电话)
{
"name":"长沙",
"mobile":"186","phone":xxx
}
供应商信息列表信息, status: 200(成功), return: 供应商信息信息列表
retrieve:
查询某一个供应商信息
查询指定ID的供应商信息, status: 200(成功), return: 用户供应商信息
"""
pagination_class = GlobalPagination
queryset = SupplierModel.objects.all()
serializer_class = SupplierSerializer
def get_queryset(self):
if self.action == 'list': # 过滤查询
# 获取请求中的数据
name = self.request.query_params.get('name', None)
phone = self.request.query_params.get('phone', None)
mobile = self.request.query_params.get('mobile', None)
query = Q()
if name:
query.add(Q(name__contains=name), 'AND') # 多条件组合
if phone:
query.add(Q(phone__contains=phone), 'AND')
if mobile:
query.add(Q(mobile__contains=mobile), 'AND')
return SupplierModel.objects.filter(query).all()
else:
return SupplierModel.objects.all()

@ -0,0 +1,98 @@
import logging
from django.db.models import Q
from django.utils.decorators import method_decorator
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import viewsets
from basic_info.models import WarehouseModel
from ERP_5.apps.basic_info.serializer.warehouse_serializer import WarehouseSerializer, WarehouseSearchSerializer
from ERP_5.utils.paginations import GlobalPagination
from ERP_5.utils.base_views import MultipleDestroyMixin, MultipleOpenMixin
logger = logging.getLogger('my')
param1 = openapi.Parameter(name='name', type=openapi.TYPE_STRING, description="名称的关键字", in_=openapi.IN_QUERY)
param2 = openapi.Parameter(name='remark', type=openapi.TYPE_STRING, description="备注关键字", in_=openapi.IN_QUERY)
@method_decorator(name='list', decorator=swagger_auto_schema(
manual_parameters=[param1, param2],
operation_description="仓库的搜索过滤")
)
class WarehouseView(viewsets.ModelViewSet, MultipleDestroyMixin, MultipleOpenMixin):
"""
create:
仓库信息--新增
仓库信息新增, status: 201(成功), return: 新增仓库信息信息
destroy:
仓库信息--删除
仓库信息删除, status: 204(成功), return: None
multiple_delete:
仓库信息--批量删除,必传json参数ids=[1,2,3,4...]
仓库信息批量删除, status: 204(成功), return: None
multiple_open:
仓库信息--批量启用或者禁用,必传(json)参数ids=[1,2,3,4...](列表中可以只有一个)is_open=1/0
{
"ids":[1,2],
"is_open":"0"
}
is_open=1表示禁用is_open=0表示启用
仓库信息批量启用或者禁用, status: 204(成功), return: None
update:
仓库信息--修改,一般用于修改多个属性
仓库信息修改, status: 200(成功), return: 修改后的仓库信息信息
partial_update:
仓库信息--局部修改(一般修改一个属性),可以传参任意属性的值服务器会修改指定的属性值
仓库信息局部修改, status: 200(成功), return: 修改后的仓库信息信息
list:
仓库信息--获取分页列表可选json参数:name(名称)remark(描述关键字)
{
"name":"长沙",
"remark":"xxxx"
}
仓库信息列表信息, status: 200(成功), return: 仓库信息信息列表
retrieve:
查询某一个仓库信息
查询指定ID的仓库信息, status: 200(成功), return: 用户仓库信息
"""
queryset = WarehouseModel.objects.all()
serializer_class = WarehouseSerializer
pagination_class = GlobalPagination
def get_queryset(self):
if self.action == 'list': # 过滤查询
# 获取请求参数(在json中)namephone, mobile
name = self.request.data.get('name', None)
remark = self.request.data.get('remark', None)
query = Q()
if name:
query.add(Q(name__contains=name), 'AND') # 多条件组合
if remark:
query.add(Q(remark__contains=remark), 'AND')
return WarehouseModel.objects.filter(query).all()
else:
return WarehouseModel.objects.all()
def get_serializer_class(self):
if self.action == 'list': # 过滤查询 ,设置新的序列化器
return WarehouseSearchSerializer
else:
return WarehouseSerializer

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

@ -0,0 +1,9 @@
from django.apps import AppConfig
class ErpSystemConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'erp_system'
#
# def ready(self):
# import erp_system.signals

@ -0,0 +1,28 @@
import logging
from django.contrib.auth.backends import ModelBackend
from ERP_5.utils.cache_permissions import cache_permissions_by_user
from erp_system.models import UserModel
logger = logging.getLogger('my')
class UserLoginAuth(ModelBackend):
"""
重新用户登录认证
"""
def authenticate(self, request, username=None, password=None, **kwargs):
try:
user = UserModel.objects.get(username=username)
except Exception as ex:
logger.error(ex)
return None
# 判断密码
if user and user.check_password(password):
# 缓存用户的权限
cache_permissions_by_user(user)
return user
return None

@ -0,0 +1,67 @@
# Generated by Django 3.2.16 on 2023-03-14 20:25
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
migrations.CreateModel(
name='Menu',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('delete_flag', models.CharField(default='0', help_text='是否启用', max_length=1, verbose_name='是否启用')),
('create_time', models.DateTimeField(auto_now_add=True, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='更新时间', verbose_name='更新时间')),
('number', models.IntegerField(blank=True, null=True, verbose_name='排序数字')),
('url', models.CharField(blank=True, max_length=256, null=True, verbose_name='菜单访问的URL')),
('name', models.CharField(max_length=50, verbose_name='菜单名字')),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='erp_system.menu')),
],
options={
'verbose_name': '功能菜单',
'verbose_name_plural': '功能菜单',
'db_table': 't_menu',
'ordering': ['number'],
},
),
migrations.CreateModel(
name='UserModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('phone', models.CharField(blank=True, max_length=11, null=True, unique=True, verbose_name='手机号码')),
('real_name', models.CharField(blank=True, max_length=128, null=True, verbose_name='真实姓名')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
],
options={
'verbose_name': '功能菜单',
'verbose_name_plural': '功能菜单',
'db_table': 't_user',
'ordering': ['id'],
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
]

@ -0,0 +1,56 @@
# Generated by Django 3.2.16 on 2023-03-14 21:42
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('erp_system', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='PermissionsModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('delete_flag', models.CharField(default='0', help_text='是否启用', max_length=1, verbose_name='是否启用')),
('create_time', models.DateTimeField(auto_now_add=True, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='更新时间', verbose_name='更新时间')),
('name', models.CharField(max_length=50, verbose_name='权限名')),
('is_menu', models.BooleanField(default=True, verbose_name='是否为菜单')),
('method', models.CharField(blank=True, choices=[('POST', ''), ('DELETE', ''), ('PUT', ''), ('PATCH', '改局部'), ('GET', '')], default='', max_length=8, verbose_name='请求方法')),
('path', models.CharField(blank=True, default='', max_length=200, verbose_name='请求路径')),
('desc', models.CharField(blank=True, default='', max_length=512, verbose_name='权限描述')),
('menu', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='permissions_list', to='erp_system.menu')),
],
options={
'verbose_name': '权限',
'verbose_name_plural': '权限',
'db_table': 't_permissions',
'ordering': ['id'],
},
),
migrations.CreateModel(
name='RolesModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('delete_flag', models.CharField(default='0', help_text='是否启用', max_length=1, verbose_name='是否启用')),
('create_time', models.DateTimeField(auto_now_add=True, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='更新时间', verbose_name='更新时间')),
('name', models.CharField(max_length=32, unique=True, verbose_name='角色名字')),
('permissions', models.ManyToManyField(blank=True, db_table='t_roles_permissions', to='erp_system.PermissionsModel', verbose_name='权限')),
],
options={
'verbose_name': '角色',
'verbose_name_plural': '角色',
'db_table': 't_roles',
},
),
migrations.AddField(
model_name='usermodel',
name='roles',
field=models.ManyToManyField(blank=True, db_table='t_users_roles', to='erp_system.RolesModel', verbose_name='用户所拥有的角色列表'),
),
]

@ -0,0 +1,36 @@
# Generated by Django 3.2.16 on 2023-03-21 20:24
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('erp_system', '0002_auto_20230314_2142'),
]
operations = [
migrations.CreateModel(
name='DeptModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('delete_flag', models.CharField(default='0', help_text='是否启用', max_length=1, verbose_name='是否启用')),
('create_time', models.DateTimeField(auto_now_add=True, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='更新时间', verbose_name='更新时间')),
('name', models.CharField(max_length=32, unique=True, verbose_name='部门')),
('address', models.CharField(blank=True, default='', max_length=128, verbose_name='部门地址')),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='erp_system.deptmodel', verbose_name='父部门')),
],
options={
'verbose_name': '部门',
'verbose_name_plural': '部门',
'db_table': 't_dept',
},
),
migrations.AddField(
model_name='usermodel',
name='dept',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='erp_system.deptmodel', verbose_name='用户所在的部门'),
),
]

@ -0,0 +1,111 @@
from django.contrib.auth.models import AbstractUser
from django.db import models
from ERP_5.utils.base_model import BaseModel
class Menu(BaseModel):
"""菜单功能的模型类"""
number = models.IntegerField('排序数字', blank=True, null=True)
url = models.CharField('菜单访问的URL', max_length=256, blank=True, null=True)
name = models.CharField('菜单名字', max_length=50)
# 父菜单对象parent
parent = models.ForeignKey('self', blank=True, null=True, related_name='children', on_delete=models.CASCADE)
class Meta:
db_table = 't_menu'
verbose_name = '功能菜单'
verbose_name_plural = verbose_name
ordering = ['number']
def __str__(self):
return self.name
# ERP系统的操作用户模型类默认是采用django自带的User需要配置settings
class UserModel(AbstractUser):
phone = models.CharField('手机号码', max_length=11, unique=True, blank=True, null=True)
real_name = models.CharField('真实姓名', max_length=128, blank=True, null=True)
roles = models.ManyToManyField('RolesModel', db_table='t_users_roles', blank=True, verbose_name='用户所拥有的角色列表')
dept = models.ForeignKey('DeptModel', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='用户所在的部门')
class Meta:
db_table = 't_user'
verbose_name = '功能菜单'
verbose_name_plural = verbose_name
ordering = ['id']
def __str__(self):
return self.username
class PermissionsModel(BaseModel):
"""
权限模型
"""
method_choices = (
('POST', ''),
('DELETE', ''),
('PUT', ''),
('PATCH', '改局部'),
('GET', '')
)
"""
只要是choices参数的字段 如果你想要获取对应信息 固定写法 get_字段名_display()
print(permission.method)
print(permission.get_method_display())
"""
name = models.CharField(max_length=50, verbose_name='权限名')
is_menu = models.BooleanField(verbose_name='是否为菜单', default=True) # True为菜单,False为接口
# 操作 , 如果是菜单(非接口),method
method = models.CharField(max_length=8, blank=True, default='', choices=method_choices, verbose_name='请求方法')
path = models.CharField(max_length=200, blank=True, default='', verbose_name='请求路径')
# 资源
menu = models.ForeignKey('Menu', null=True, blank=True, related_name='permissions_list', on_delete=models.CASCADE)
desc = models.CharField(max_length=512, blank=True, default='', verbose_name='权限描述')
def __str__(self):
return self.name
class Meta:
db_table = 't_permissions'
verbose_name = '权限'
verbose_name_plural = verbose_name
ordering = ['id']
class RolesModel(BaseModel):
"""
角色
"""
name = models.CharField(max_length=32, unique=True, verbose_name='角色名字')
permissions = models.ManyToManyField('PermissionsModel', db_table='t_roles_permissions',
blank=True, verbose_name='权限')
# 注意在用户模型中最好也要加ManyToManyField
def __str__(self):
return self.name
class Meta:
db_table = 't_roles'
verbose_name = '角色'
verbose_name_plural = verbose_name
class DeptModel(BaseModel):
"""
组织架构 部门
"""
name = models.CharField(max_length=32, unique=True, verbose_name='部门')
parent = models.ForeignKey('self', null=True, blank=True, related_name='children', on_delete=models.CASCADE, verbose_name='父部门')
address = models.CharField(max_length=128, blank=True, default='', verbose_name='部门地址')
def __str__(self):
return self.name
class Meta:
db_table = 't_dept'
verbose_name = '部门'
verbose_name_plural = verbose_name

@ -0,0 +1,27 @@
from rest_framework import serializers
from erp_system.models import DeptModel
class ChildDeptSerializer(serializers.ModelSerializer):
children = serializers.SerializerMethodField(read_only=True)
class Meta:
model = DeptModel
fields = ['id', 'name', 'parent', 'children']
def get_children(self, obj):
if obj.children:
return ChildDeptSerializer(instance=obj.children, many=True).data
return None
class DeptSerializer(serializers.ModelSerializer):
"""
部门的序列化器
"""
children = ChildDeptSerializer(read_only=True, many=True)
class Meta:
model = DeptModel
fields = ['id', 'name', 'parent', 'children']

@ -0,0 +1,10 @@
from rest_framework.serializers import ModelSerializer
from erp_system.models import Menu
class MenuSerializer(ModelSerializer):
class Meta:
model = Menu
fields = '__all__'

@ -0,0 +1,9 @@
from rest_framework.serializers import ModelSerializer, ValidationError
from erp_system.models import PermissionsModel
# 权限基本的序列化类
class PermissionsSerializer(ModelSerializer):
class Meta:
model = PermissionsModel
fields = '__all__'

@ -0,0 +1,53 @@
from rest_framework.serializers import ModelSerializer, Serializer, IntegerField, BooleanField
from .permission_serializer import PermissionsSerializer
from erp_system.models import RolesModel, PermissionsModel
from rest_framework import serializers
class RolesSerializer(ModelSerializer):
"""
普通的序列化
"""
permissions = PermissionsSerializer(many=True, read_only=True)
delete_flag = serializers.CharField(read_only=True)
class Meta:
model = RolesModel
fields = '__all__'
class RolesPartialSerializer(ModelSerializer):
"""
给一个角色批量授权的序列化器
"""
class Meta:
model = RolesModel
fields = ['id', 'permissions']
def save(self, **kwargs):
# 针对某一个角色批量授权,之前存在的不变,之前没有的会增加权限
if self.instance:
permission_list = self.validated_data.get('permissions')
current_list = self.instance.permissions.all()
if permission_list:
for p in permission_list:
if p not in current_list:
# 多对多的关联 如果数据库中已经存在对象则使用add否则使用
# 变量名=变量名.B类名小写_set.create(字段='值')
self.instance.permissions.add(p.id)
self.instance.save()
return self.instance
class RoleSetPermissionSerializer(Serializer):
"""
角色单一授权或者取消单一授权
"""
# 角色ID
role_id = IntegerField(write_only=True, required=True, help_text='选择的角色ID')
permission_id = IntegerField(write_only=True, required=True, help_text='选择的权限ID')
# 是否是:取消授权还是授予权限
is_create = BooleanField(write_only=True, required=True, help_text='是否授予还是删除权限')

@ -0,0 +1,101 @@
from rest_framework.serializers import ModelSerializer, ValidationError
from rest_framework import serializers
from erp_system.models import UserModel
import re
# 用户注册基本的序列化类
from erp_system.serializers.roles_serialzier import RolesSerializer
class UserAddSerializer(ModelSerializer):
"""
仅仅用于用户注册
"""
class Meta:
model = UserModel
fields = ('id', 'username', 'password', 'phone', 'real_name')
extra_kwargs = {
'username': {
'max_length': 12,
'min_length': 2
},
'password': {
'max_length': 8,
'min_length': 3,
'write_only': True
},
}
def validate_phone(self, phone):
"""
自定义验证手机号码规则函数名=validate_<field_name>
"""
if not re.match(r'^1[3589]\d{9}$', phone):
raise ValidationError("请输入正确的手机号码")
return phone
def create(self, validated_data):
"""重写用户注册,否则直接把明文密码保存到数据库"""
user = UserModel.objects.create_user(**validated_data)
return user
from rest_framework.serializers import ModelSerializer, ValidationError, CharField, ValidationError
class UserUpdateDeleteSerializer(ModelSerializer):
"""
只用于修改用户修改用户的角色修改用户的部门和删除用户
"""
class Meta:
model = UserModel
fields = ('id', 'roles', 'dept')
class ResetPasswordSerializer(ModelSerializer):
"""
重置密码序列化器,只用与修改密码
"""
confirm_password = CharField(write_only=True)
class Meta:
model = UserModel
fields = ['id', 'password', 'confirm_password']
extra_kwargs = {
'password': {
'write_only': True
}
}
def validate(self, attrs):
# partial_update, 局部更新required验证无效, 手动验证数据
password = attrs.get('password')
confirm_password = attrs.get('confirm_password')
if not password:
raise ValidationError('字段password为必填项')
if not confirm_password:
raise ValidationError('字段confirm_password为必填项')
if password != confirm_password:
raise ValidationError('两次密码不一致')
return attrs
def save(self, *args):
# 重写save方法, 保存密码
self.instance.set_password(self.validated_data.get('password'))
self.instance.save()
return self.instance
class UserGetSerializer(ModelSerializer):
"""
只用于查询用户
"""
roles = RolesSerializer(many=True, read_only=True)
dept_name = serializers.CharField(source='dept.name')
class Meta:
model = UserModel
fields = ('id', 'username', 'phone', 'real_name', 'roles', 'dept_name')

@ -0,0 +1,45 @@
# import logging
#
# from django.dispatch import receiver
# from django.db.models.signals import post_save
#
# from erp_system.models import Menu, PermissionsModel
#
# # 默认情况下,每一个功能菜单的接口的请求方法数量
# methods = {'POST': '新增', 'GET': '查询', 'PUT': '修改', 'DELETE': '删除', 'PATCH': '局部修改'}
#
# logger = logging.getLogger('my')
#
#
# @receiver(post_save, sender=Menu)
# def create_menus_permissions(sender, instance, created, **kwargs):
# """
# 当有人新增一个功能菜单Menu)之后, 需要把该功能菜单对应的权限也插入到数据库
# :param sender:
# :param instance:
# :param created:
# :param kwargs:
# :return:
# """
#
# if created:
# logger.info('Menu对象已经新增完成了')
# if isinstance(instance, Menu):
# if not instance.parent: # 表示是一个顶级菜单没有请求URL地址不是一个接口
# permission = PermissionsModel.objects.create(
# name=instance.name + "的权限",
# is_menu=True,
# menu=instance
# )
# else: # 表示是接口有父菜单有请求的url地址
# for method in methods:
# permission = PermissionsModel.objects.create(
# name=methods.get(method) + "的权限",
# is_menu=False,
# menu=instance,
# method=method,
# path=instance.url,
# desc=f'{instance.name}的{methods.get(method)}的权限'
# )
# else:
# logger.info('instance不是Menu类型所以不需要任何处理')

@ -0,0 +1,35 @@
import logging
from celery import shared_task
from ERP_5.utils.tasks_hook import HookTask
from erp_system.models import PermissionsModel, Menu
# 默认情况下,每一个功能菜单的接口的请求方法数量
methods = {'POST': '新增', 'GET': '查询', 'PUT': '修改', 'DELETE': '删除', 'PATCH': '局部修改'}
logger = logging.getLogger('my')
# 在装饰器中,指定一个任务钩子
@shared_task(base=HookTask)
def create_menus_permissions(menu_id): # 定义了一个任务
logger.info('Menu对象已经新增完成了')
instance = Menu.objects.get(pk=menu_id)
if not instance.parent: # 表示是一个顶级菜单没有请求URL地址不是一个接口
permission = PermissionsModel.objects.create(
name=instance.name + "的权限",
is_menu=True,
menu=instance
)
else: # 表示是接口有父菜单有请求的url地址
for method in methods:
permission = PermissionsModel.objects.create(
name=methods.get(method) + "的权限",
is_menu=False,
menu=instance,
method=method,
path=instance.url,
desc=f'{instance.name}{methods.get(method)}的权限'
)

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

@ -0,0 +1,39 @@
"""ERP_5 URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, re_path
from rest_framework.routers import DefaultRouter
from .views import menu_views, user_views, dept_views, role_views, permission_views
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
# path('admin/', admin.site.urls),
path('users/login/', obtain_jwt_token), # 登录并且由JWT签发token
re_path(r'^users/reset/(?P<pk>\d+)/$', user_views.ResetPasswordView.as_view()),
]
router = DefaultRouter()
router.register('menus', menu_views.MenuViewSet)
router.register('users/register', user_views.RegisterUserView)
router.register('roles', role_views.RolesViewSet)
router.register('permissions', permission_views.PermissionViewSet)
router.register('dept', dept_views.DeptViewSet)
router.register('users', user_views.UserView)
urlpatterns += router.urls

@ -0,0 +1,83 @@
from django.utils.decorators import method_decorator
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ModelViewSet
from ERP_5.utils.base_views import MultipleDestroyMixin
from ERP_5.utils.paginations import GlobalPagination
from erp_system.models import DeptModel
from erp_system.serializers.dept_serializer import DeptSerializer
# 定义好了一个swagger的参数
pid_param = openapi.Parameter(name='pid', in_=openapi.IN_QUERY, description='父部门的ID', required=False,
type=openapi.TYPE_INTEGER)
@method_decorator(name='list', decorator=swagger_auto_schema(
manual_parameters=[pid_param],
operation_description="查询部门的树形结构数据, pid=0或者不传pid表示查询顶级机构的树。")
)
class DeptViewSet(ModelViewSet, MultipleDestroyMixin):
"""
create:
部门机构--新增
部门机构新增, status: 201(成功), return: 新增部门机构信息
destroy:
部门机构--删除
部门机构删除, status: 204(成功), return: None
multiple_delete:
部门机构--批量删除,必传参数ids=[1,2,3,4...]
部门机构批量删除, status: 204(成功), return: None
update:
部门机构--修改,不能修改部门机构只是修改部门机构名字
部门机构修改, status: 200(成功), return: 修改后的部门机构信息
partial_update:
部门机构--局部修改(部门机构批量授权),只能真的某一个部门机构一次性授权之前的的授权会被覆盖
部门机构批量授权, status: 200(成功), return: 修改后的部门机构信息
list:
部门机构--获取列表, 如果传参pid=0或者pid不传则查询所有顶级部门列表 如果传参pid=具体的值则查询某个父部门下的所有子部门
部门机构列表信息, status: 200(成功), return: 部门机构信息列表
retrieve:
查询某一个部门机构
查询指定ID的部门机构, status: 200(成功), return: 用户部门机构
"""
def get_queryset(self):
if self.action == 'list':
q = self.request.query_params.get('pid', None)
if q: # 参数中有pid
pid = int(q)
if pid == 0:
return DeptModel.objects.filter(parent__isnull=True).all()
else:
return DeptModel.objects.filter(parent__id=pid).all()
else:
return DeptModel.objects.filter(parent__isnull=True).all()
else:
return DeptModel.objects.all()
queryset = DeptModel.objects.all()
serializer_class = DeptSerializer
pagination_class = GlobalPagination
# permission_classes = [IsAuthenticated]
# @swagger_auto_schema(method='GET', manual_parameters=[pid_param])
# @action(methods=['GET'], detail=False)
# def find(self, request, *args, **kwargs):
# return super(DeptViewSet, self).list(request, *args, **kwargs)

@ -0,0 +1,103 @@
from django.db.models import Q
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from erp_system.tasks import create_menus_permissions
from erp_system.models import Menu, PermissionsModel
from erp_system.serializers.menus_serializer import MenuSerializer
class MenuViewSet(ModelViewSet):
"""
create:
功能菜单--新增
功能菜单新增, status: 201(成功), return: 新增功能菜单信息
destroy:
功能菜单--删除
功能菜单删除, status: 204(成功), return: None
update:
功能菜单--修改,全局修改
功能菜单修改, status: 200(成功), return: 修改后的功能菜单信息
partial_update:
功能菜单--局部修改
功能菜单批修改 status: 200(成功), return: 修改后的功能菜单信息
list:
功能菜单--获取列表
功能菜单列表信息, status: 200(成功), return: 功能菜单信息列表
retrieve:
查询某一个功能菜单
查询指定ID的功能菜单, status: 200(成功), return: 用户功能菜单
find_parent:
查询所有的顶级功能菜单 不需要传参
get_menus_by_permission:
查询当前登录的用户所有的拥有GET权限的菜单列表而且这些菜单列表必须是树形结构
返回树形菜单列表, status: 200(成功)
"""
serializer_class = MenuSerializer
queryset = Menu.objects.all()
# 必须是登录成功之后才能访问所有功能菜单的接口
# permission_classes = [IsAuthenticated]
@action(methods=['get'], detail=False)
def find_parent(self, request, *args, **kwargs):
lst = self.get_queryset().filter(parent__isnull=True)
ser = self.get_serializer(instance=lst, many=True)
return Response(ser.data)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
menu = serializer.save()
# 发布异步的任务
if menu and menu.id:
create_menus_permissions.delay(menu.id)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
@action(methods=['get'], detail=False)
def get_menus_by_permission(self, request, *args, **kwargs):
# 得到当前登录用户
user = request.user
# 查询该用户所拥有的权限ID列表
permission_id = user.roles.values_list('permissions', flat=True).distinct()
# 过滤那些GET的或者是顶级菜单权限
menu_ids = PermissionsModel.objects.filter(Q(method='GET') | Q(is_menu=True)).filter(id__in=permission_id)\
.values_list('menu', flat=True)
# 根据menu_id得到菜单接口对象
menu_list = Menu.objects.filter(id__in=menu_ids, delete_flag=0).all()
serializer = MenuSerializer(instance=menu_list, many=True)
# 定义 Element
tree_dict = {} # 代表一个父菜单
tree_data = []
try:
for item in serializer.data:
tree_dict[item['id']] = item
for i in tree_dict:
if tree_dict[i]['parent']:
pid = tree_dict[i]['parent']
parent = tree_dict[pid]
parent.setdefault('children', []).append(tree_dict[i])
else:
tree_data.append(tree_dict[i])
results = tree_data
except KeyError:
results = serializer.data
return Response(results)

@ -0,0 +1,125 @@
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from erp_system.models import PermissionsModel, RolesModel
from erp_system.serializers.permission_serializer import PermissionsSerializer
import logging
logger = logging.getLogger('my')
class PermissionViewSet(ModelViewSet):
"""
create:
权限--新增
权限新增, status: 201(成功), return: 新增权限信息
destroy:
权限--删除
权限删除, status: 204(成功), return: None
multiple_delete:
权限--批量删除,必传参数ids=[1,2,3,4...]
权限批量删除, status: 204(成功), return: None
update:
权限--修改
权限修改, status: 200(成功), return: 修改增权限信息
partial_update:
权限--局部修改
权限局部修改, status: 200(成功), return: 修改增权限信息
list:
权限--获取所有的权限
find_permissions_by_menu:
获取当前菜单的所有权限设置 列表,必传参数menu_id
例如http://127.0.0.1:8000/permissions/find_permissions_by_menu/?menu_id=2
权限列表信息, status: 200(成功), return: 权限信息列表
find_permissions:
打开授权页面的时候首先请求的接口获取当前角色已经授予的权限ID列表和整个项目的所有权限(树形)列表,必传参数rid(角色ID)
例如http://127.0.0.1:8000/permissions/find_permissions/?rid=2
树形权限列表信息, status: 200(成功), return: 权限信息列表
retrieve:
查询某一个指定的权限信息
查询指定ID的权限, status: 200(成功), return: 权限信息
"""
queryset = PermissionsModel.objects.all()
serializer_class = PermissionsSerializer
# 定义好了一个swagger的参数
menu_param = openapi.Parameter(name='menu_id', in_=openapi.IN_QUERY, description='所选的菜单ID',
type=openapi.TYPE_INTEGER)
@swagger_auto_schema(method='GET', manual_parameters=[menu_param], operation_description='单一授权或者取消单一权限')
@action(methods=['GET'], detail=False)
def find_permissions_by_menu(self, request, *args, **kwargs):
menu_id = request.query_params.get('menu_id')
permission_list = PermissionsModel.objects.filter(menu__id=menu_id).all()
return Response(data=self.get_serializer(instance=permission_list, many=True).data)
# 定义好了一个swagger的参数
menu_param2 = openapi.Parameter(name='rid', in_=openapi.IN_QUERY, description='所选的角色ID',
type=openapi.TYPE_INTEGER)
@swagger_auto_schema(method='GET', manual_parameters=[menu_param2])
@action(methods=['GET'], detail=False)
def find_permissions(self, request, *args, **kwargs):
# 获取当前角色已经授予的权限ID列表和整个项目的所有权限(树形)列表
result = {} # 返回的整个字典数据,
rid = request.query_params.get('rid', None)
if rid: # 查询当前角色的所有已经授权的ID
ids = RolesModel.objects.filter(id=rid).values_list('permissions', flat=True).distinct()
result['ids'] = ids # 存入返回字典中
# 整个ERP系统中所有权限列表而且要树形展示
permissions_list = PermissionsModel.objects.values('id', 'name', 'menu__parent__id', 'menu__name', 'menu__id')
tree_dict = {} # 一个父节点权限(里面是树形嵌套)
tree_data = [] # 所有权限
"""
[
{父节点1的内容children=[{子节点内容}{子节点内容}...]},
{父节点2的内容children=[{子节点内容}{子节点内容}...]},
...
]
"""
# 第一步构建一颗二级树
for item in permissions_list:
tree_dict[item['menu__id']] = item
for i in tree_dict:
if tree_dict[i]['menu__parent__id']: # 表示子权限
pid = tree_dict[i]['menu__parent__id']
parent = tree_dict[pid]
child = dict() # 创建一个空字典(二级)第二级只要menu__id, menu__name, permissions(权限列表)
child['menu__id'] = tree_dict[i]['menu__id']
child['menu__name'] = tree_dict[i]['menu__name']
child.setdefault('permissions', [])
parent.setdefault('children', []).append(child)
else:
tree_data.append(tree_dict[i])
# 第二步:在前面二级树形结构的基础上,添加子权限。得到三级树
for parent in tree_data:
if 'children' in parent:
for child in parent['children']:
for node in permissions_list:
if node['menu__parent__id'] and node['menu__id'] == child['menu__id']:
child['permissions'].append(node)
logger.info(tree_data)
result['tree_data'] = tree_data # 把三级树结构,加入到结果中
return Response(result)

@ -0,0 +1,92 @@
from drf_yasg.utils import swagger_auto_schema
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from erp_system.models import RolesModel, PermissionsModel, Menu
from erp_system.serializers.roles_serialzier import RolesPartialSerializer, RoleSetPermissionSerializer, RolesSerializer
class RolesViewSet(ModelViewSet):
# 整个ERP系统有一个默认存在的角色特殊 admin角色。这个角色不允许删除
"""
create:
角色--新增
角色新增, status: 201(成功), return: 新增角色信息
destroy:
角色--删除
角色删除, status: 204(成功), return: None
multiple_delete:
角色--批量删除,必传参数ids=[1,2,3,4...]
角色批量删除, status: 204(成功), return: None
update:
角色--修改,不能修改角色只是修改角色名字
角色修改, status: 200(成功), return: 修改后的角色信息
partial_update:
角色--(不需要)局部修改(角色批量授权),针对某一个角色批量授权之前存在的不变之前没有的会增加权限
角色批量授权, status: 200(成功), return: 修改后的角色信息
set_permission_to_role:
角色--单个授权一次只能授予角色一个permission也可以取消一个permission
status: 200(成功), return: 修改后的角色信息
list:
角色--获取列表
角色列表信息, status: 200(成功), return: 角色信息列表
retrieve:
查询某一个角色
查询指定ID的角色, status: 200(成功), return: 用户角色
"""
queryset = RolesModel.objects.all()
def get_serializer_class(self):
"""
不同的视图函数采用的序列化器是不一样的
:return:
"""
if self.action == 'partial_update':
return RolesPartialSerializer
elif self.action == 'set_permission_to_role':
return RoleSetPermissionSerializer
else:
return RolesSerializer
@swagger_auto_schema(method='POST', request_body=RoleSetPermissionSerializer, operation_description='单一授权或者取消单一权限')
@action(methods=['POST'], detail=False)
def set_permission_to_role(self, request, *args, **kwargs):
ser: RoleSetPermissionSerializer = self.get_serializer(data=request.data)
if ser.is_valid():
role = RolesModel.objects.get(id=ser.validated_data.get('role_id'))
permission = PermissionsModel.objects.get(id=ser.validated_data.get('permission_id'))
if ser.validated_data.get('is_create'):
# 判断给当前角色新增一个权限, 首先判断该权限所对应的父菜单权限是否已经被授予了
parent = Menu.objects.filter(id=permission.menu.id).values_list('parent', flat=True).all()
if parent:
p_permission = PermissionsModel.objects.get(menu_id=parent[0])
role.permissions.add(p_permission)
role.permissions.add(permission)
else: # 取消权限
role.permissions.remove(permission)
return Response(data=RolesSerializer(instance=role).data)
def destroy(self, request, *args, **kwargs):
if self.get_object().name == 'admin':
return Response(data={'detail': 'admin角色不可删除'}, status=status.HTTP_400_BAD_REQUEST)
else:
pass

@ -0,0 +1,53 @@
from rest_framework import mixins
from rest_framework.generics import GenericAPIView
from rest_framework.viewsets import GenericViewSet
from ERP_5.utils.base_views import MultipleDestroyMixin
from ERP_5.utils.paginations import GlobalPagination
from erp_system.models import UserModel
from erp_system.serializers.user_serializer import UserAddSerializer, UserUpdateDeleteSerializer, UserGetSerializer, \
ResetPasswordSerializer
class RegisterUserView(mixins.CreateModelMixin, GenericViewSet):
"""
create:
用户注册
并且把用户注册之后的用户信息返回
"""
queryset = UserModel.objects.all()
serializer_class = UserAddSerializer
class UserView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
MultipleDestroyMixin, # 批量删除用户
GenericViewSet):
"""
没有用户注册和修改用户密码的模型类
"""
queryset = UserModel.objects.all()
pagination_class = GlobalPagination
def get_serializer_class(self):
if self.action == 'partial_update' or self.action == 'update' or self.action == 'destroy':
return UserUpdateDeleteSerializer
else:
return UserGetSerializer
class ResetPasswordView(mixins.UpdateModelMixin, GenericAPIView):
"""
patch:
用户--重置密码
用户重置密码, status: 200(成功), return: None
"""
queryset = UserModel.objects.all()
serializer_class = ResetPasswordSerializer
def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

@ -0,0 +1,6 @@
from django.apps import AppConfig
class GoodsInfoConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'goods_info'

@ -0,0 +1,121 @@
# Generated by Django 3.2.16 on 2023-03-28 20:51
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('basic_info', '0004_settlementaccountmodel_warehousemodel'),
]
operations = [
migrations.CreateModel(
name='AttachmentModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('delete_flag', models.CharField(default='0', help_text='是否启用', max_length=1, verbose_name='是否启用')),
('create_time', models.DateTimeField(auto_now_add=True, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='更新时间', verbose_name='更新时间')),
('a_file', models.FileField(upload_to='', verbose_name='附件或者图片')),
('a_type', models.CharField(blank=True, choices=[('image', '图片'), ('doc', 'Word文档'), ('excel', 'Excel文档'), ('zip', '压缩文件'), ('other', '其他文件')], max_length=20, null=True, verbose_name='附件的类型')),
],
options={
'verbose_name': '附件表',
'verbose_name_plural': '附件表',
'db_table': 't_attachment',
'ordering': ['id'],
},
),
migrations.CreateModel(
name='GoodsCategoryModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('delete_flag', models.CharField(default='0', help_text='是否启用', max_length=1, verbose_name='是否启用')),
('create_time', models.DateTimeField(auto_now_add=True, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='更新时间', verbose_name='更新时间')),
('name', models.CharField(max_length=100, verbose_name='类别名称')),
('number_code', models.CharField(max_length=28, unique=True, verbose_name='编号')),
('remark', models.CharField(blank=True, max_length=512, null=True, verbose_name='备注')),
('order_number', models.IntegerField(default=100, verbose_name='排序号码')),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='goods_info.goodscategorymodel')),
],
options={
'verbose_name': '商品类别',
'verbose_name_plural': '商品类别',
'db_table': 't_category',
'ordering': ['order_number', 'id'],
},
),
migrations.CreateModel(
name='UnitsModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('delete_flag', models.CharField(default='0', help_text='是否启用', max_length=1, verbose_name='是否启用')),
('create_time', models.DateTimeField(auto_now_add=True, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='更新时间', verbose_name='更新时间')),
('basic_name', models.CharField(max_length=20, unique=True, verbose_name='基本单位')),
('backup_name', models.CharField(blank=True, max_length=20, null=True, verbose_name='副单位')),
('remark', models.CharField(blank=True, max_length=512, null=True, verbose_name='备注')),
],
options={
'verbose_name': '计量单位',
'verbose_name_plural': '计量单位',
'db_table': 't_units',
'ordering': ['id'],
},
),
migrations.CreateModel(
name='GoodsModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('delete_flag', models.CharField(default='0', help_text='是否启用', max_length=1, verbose_name='是否启用')),
('create_time', models.DateTimeField(auto_now_add=True, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='更新时间', verbose_name='更新时间')),
('name', models.CharField(max_length=20, unique=True, verbose_name='货品名称')),
('specification', models.CharField(blank=True, max_length=50, null=True, verbose_name='规格')),
('model_number', models.CharField(blank=True, max_length=50, null=True, verbose_name='型号')),
('color', models.CharField(blank=True, max_length=50, null=True, verbose_name='颜色')),
('basic_weight', models.CharField(blank=True, max_length=50, null=True, verbose_name='基础重量')),
('expiration_day', models.IntegerField(blank=True, null=True, verbose_name='保质期')),
('remark', models.CharField(blank=True, max_length=512, null=True, verbose_name='备注')),
('number_code', models.CharField(max_length=28, unique=True, verbose_name='编号或者批号')),
('purchase_price', models.DecimalField(blank=True, decimal_places=2, default=0, max_digits=10, verbose_name='采购价')),
('retail_price', models.DecimalField(blank=True, decimal_places=2, default=0, max_digits=10, verbose_name='零售价')),
('sales_price', models.DecimalField(blank=True, decimal_places=2, default=0, max_digits=10, verbose_name='销售价')),
('lowest_price', models.DecimalField(blank=True, decimal_places=2, default=0, max_digits=10, verbose_name='最低售价')),
('order_number', models.IntegerField(default=100, verbose_name='排序号码')),
('images_list', models.CharField(blank=True, max_length=20, null=True, verbose_name='商品图片所对应的id列表')),
('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='goods_info.goodscategorymodel')),
('units', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='goods_info.unitsmodel')),
],
options={
'verbose_name': '货品表',
'verbose_name_plural': '货品表',
'db_table': 't_goods',
'ordering': ['order_number', 'id'],
},
),
migrations.CreateModel(
name='GoodsInventoryModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('init_inventory', models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='期初库存数量')),
('cur_inventory', models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='现在库存数量')),
('lowest_inventory', models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='最低安全库存, 0表示不设置')),
('highest_inventory', models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='最高安全库存,0表示不设置')),
('warehouse_name', models.CharField(max_length=50, verbose_name='仓库的名称')),
('goods', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='inventory_list', to='goods_info.goodsmodel')),
('warehouse', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='basic_info.warehousemodel')),
],
options={
'verbose_name': '货品库存表',
'verbose_name_plural': '货品库存表',
'db_table': 't_goods_inventory',
'ordering': ['id'],
},
),
]

@ -0,0 +1,18 @@
# Generated by Django 3.2.16 on 2023-03-30 20:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('goods_info', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='goodsmodel',
name='images_list',
field=models.CharField(blank=True, max_length=20, null=True, verbose_name='商品附件所对应的id列表'),
),
]

@ -0,0 +1,120 @@
from django.db import models
from django.db import models
from ERP_5.utils.base_model import BaseModel
# 货品(商品)类别模型类
class GoodsCategoryModel(BaseModel):
name = models.CharField(max_length=100, verbose_name='类别名称')
number_code = models.CharField('编号', max_length=28, unique=True)
remark = models.CharField('备注', max_length=512, blank=True, null=True)
order_number = models.IntegerField('排序号码', default=100)
parent = models.ForeignKey('self', blank=True, null=True, related_name='children', on_delete=models.SET_NULL)
class Meta:
db_table = 't_category'
verbose_name = '商品类别'
verbose_name_plural = verbose_name
ordering = ['order_number', 'id']
def __str__(self):
return self.name
# 计量单位模型类
class UnitsModel(BaseModel):
basic_name = models.CharField(max_length=20, verbose_name='基本单位', unique=True)
backup_name = models.CharField('副单位', max_length=20, null=True, blank=True)
remark = models.CharField('备注', max_length=512, blank=True, null=True)
class Meta:
db_table = 't_units'
verbose_name = '计量单位'
verbose_name_plural = verbose_name
ordering = ['id']
def __str__(self):
return f'{self.basic_name}({self.backup_name})'
# 图片或者附件的模型类
class AttachmentModel(BaseModel):
# 附件文件的类型
type_choices = (
('image', '图片'),
('doc', 'Word文档'),
('excel', 'Excel文档'),
('zip', '压缩文件'),
('other', '其他文件')
)
"""
只要是choices参数的字段 如果你想要获取对应信息 固定写法 get_字段名_display()
print(Attachment.a_type)
print(Attachment.get_a_type_display())
"""
a_file = models.FileField('附件或者图片')
a_type = models.CharField('附件的类型', max_length=20, blank=True, null=True, choices=type_choices)
class Meta:
db_table = 't_attachment'
verbose_name = '附件表'
verbose_name_plural = verbose_name
ordering = ['id']
def __str__(self):
return self.a_file.name
# 货品库存 模型类
class GoodsInventoryModel(models.Model):
# 期初库存数量在创建好之后不能修改
init_inventory = models.DecimalField('期初库存数量', max_digits=10, decimal_places=2, default=0)
cur_inventory = models.DecimalField('现在库存数量', max_digits=10, decimal_places=2, default=0)
lowest_inventory = models.DecimalField('最低安全库存, 0表示不设置', max_digits=10, decimal_places=2, default=0)
highest_inventory = models.DecimalField('最高安全库存,0表示不设置', max_digits=10, decimal_places=2, default=0)
goods = models.ForeignKey('GoodsModel', related_name='inventory_list', on_delete=models.CASCADE, blank=True,
null=True)
warehouse = models.ForeignKey('basic_info.WarehouseModel', on_delete=models.CASCADE)
# 冗余字段,主要目的:减少联表查询的次数
warehouse_name = models.CharField('仓库的名称', max_length=50)
class Meta:
db_table = 't_goods_inventory'
verbose_name = '货品库存表'
verbose_name_plural = verbose_name
ordering = ['id']
def __str__(self):
return self.warehouse_name + str(self.id)
# 货品(商品)模型类
class GoodsModel(BaseModel):
name = models.CharField(max_length=20, verbose_name='货品名称', unique=True)
specification = models.CharField('规格', max_length=50, null=True, blank=True)
model_number = models.CharField('型号', max_length=50, null=True, blank=True)
color = models.CharField('颜色', max_length=50, null=True, blank=True)
basic_weight = models.CharField('基础重量', max_length=50, null=True, blank=True)
expiration_day = models.IntegerField('保质期', null=True, blank=True)
remark = models.CharField('备注', max_length=512, blank=True, null=True)
number_code = models.CharField('编号或者批号', max_length=28, unique=True)
purchase_price = models.DecimalField('采购价', max_digits=10, decimal_places=2, blank=True, default=0) # 精确到小数点后两位
retail_price = models.DecimalField('零售价', max_digits=10, decimal_places=2, blank=True, default=0) # 精确到小数点后两位
sales_price = models.DecimalField('销售价', max_digits=10, decimal_places=2, blank=True, default=0) # 精确到小数点后两位
lowest_price = models.DecimalField('最低售价', max_digits=10, decimal_places=2, blank=True, default=0) # 精确到小数点后两位
order_number = models.IntegerField('排序号码', default=100)
units = models.ForeignKey('UnitsModel', on_delete=models.SET_NULL, null=True, blank=True)
category = models.ForeignKey('GoodsCategoryModel', on_delete=models.SET_NULL, null=True, blank=True)
images_list = models.CharField('商品附件所对应的id列表', max_length=20, null=True, blank=True) # 字段的值为: 1,2,3,4
class Meta:
db_table = 't_goods'
verbose_name = '货品表'
verbose_name_plural = verbose_name
ordering = ['order_number', 'id']
def __str__(self):
return self.name

@ -0,0 +1,21 @@
from goods_info.models import AttachmentModel
from rest_framework import serializers
class AttachmentsSerializer(serializers.ModelSerializer):
"""
附件和图片的序列化器
"""
# source 如果是字段,会显示字段,如果是方法,会执行方法,不用加括号
# 附件或者图片的文件名
file_name = serializers.CharField(source='a_file.name', read_only=True)
# 附件或者图片的访问地址
file_url = serializers.CharField(source='a_file.url', read_only=True)
# 附件的文件类型(中文)
type_display = serializers.CharField(source='get_a_type_display', read_only=True)
class Meta:
model = AttachmentModel
fields = ['id', 'a_file', 'a_type', 'file_url', 'file_name', 'type_display']

@ -0,0 +1,19 @@
from goods_info.models import GoodsCategoryModel
from rest_framework import serializers
class CategorySerializer(serializers.ModelSerializer):
"""
商品类别的序列化器
"""
# 树形展示
children = serializers.SerializerMethodField(read_only=True)
class Meta:
model = GoodsCategoryModel
fields = ['id', 'number_code', 'remark', 'name', 'order_number', 'parent', 'children']
def get_children(self, obj):
if obj.children:
return CategorySerializer(obj.children, many=True).data
return None

@ -0,0 +1,88 @@
from django.db import transaction
from rest_framework import serializers
from ERP_5.utils.get_inventory import get_inventory_by_goods
from goods_info.models import GoodsModel, GoodsInventoryModel, AttachmentModel
from goods_info.serializer.attachment_serializer import AttachmentsSerializer
from goods_info.serializer.category_serializer import CategorySerializer
from goods_info.serializer.units_serializer import UnitsSerializer
class GoodsInventorySerializer(serializers.ModelSerializer):
"""
货品库存信息的序列化器
"""
class Meta:
model = GoodsInventoryModel
fields = '__all__'
class GoodsBaseSerializer(serializers.ModelSerializer):
"""
商品货品的序列化器
"""
# 商品对应的多个库存记录
inventory_list = GoodsInventorySerializer(many=True, required=True)
class Meta:
model = GoodsModel
fields = '__all__'
def create(self, validated_data):
# 添加货品, 把库存信息先拿出来
inventory_list = validated_data.pop('inventory_list')
with transaction.atomic():
goods = GoodsModel.objects.create(**validated_data)
for item in inventory_list: # item是字典
# item代表一条库存信息, # 新建时,当前库存=初始库存
item['cur_inventory'] = item.get('init_inventory', 0)
GoodsInventoryModel.objects.create(goods=goods, **item)
return goods
def update(self, instance, validated_data):
item_list = validated_data.pop('inventory_list')
for item in item_list: # 遍历每条库存信息,并修改最高库存和最低库存
GoodsInventoryModel.objects.filter(warehouse_name=item['warehouse_name'], goods_id=instance.id).update(
lowest_inventory=item.get('lowest_inventory', 0), highest_inventory=item.get('highest_inventory', 0))
goods = super(GoodsBaseSerializer, self).update(instance=instance, validated_data=validated_data)
return goods
class GoodsGetSerializer(serializers.ModelSerializer):
"""
商品查询的序列化器
目标
1把商品的单位所有信息展示出来
2展示商品信息所属的商品类别所有信息展示出来
3展示商品的所有附件图片所有图片信息展示出来
4展示商品的所有仓库中的库存信息总库存
"""
units = UnitsSerializer(read_only=True)
category = CategorySerializer(read_only=True)
images_list = serializers.SerializerMethodField(read_only=True)
# 当前货品的总库存
cur_inventory = serializers.SerializerMethodField(read_only=True)
class Meta:
model = GoodsModel
fields = '__all__'
def get_images_list(self, obj):
result = [] # 返回的列表, 列表里面是字典(每个字典就是一个附件)
if obj.images_list:
ids = obj.images_list.split(',') # 按照逗号分割
if ids:
for _id in ids:
item = AttachmentModel.objects.get(id=_id)
ser = AttachmentsSerializer(instance=item)
result.append(ser.data)
return result
def get_cur_inventory(self, obj):
# 获取一个商品的总库存
return get_inventory_by_goods(obj.id)

@ -0,0 +1,17 @@
from goods_info.models import UnitsModel
from rest_framework import serializers
class UnitsSerializer(serializers.ModelSerializer):
"""
计量单位的序列化器
"""
# 指定新增的属性,是从一个函数中得到值
units_name = serializers.SerializerMethodField(read_only=True)
class Meta:
model = UnitsModel
fields = ['id', 'basic_name', 'backup_name', 'delete_flag', 'units_name']
def get_units_name(self, obj):
return str(obj) # 就是调用该对象的__str__函数

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

@ -0,0 +1,34 @@
"""ERP_5 URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, re_path
from rest_framework.routers import DefaultRouter
from .views import units_views, attachments_views, goods_category_views, goods_views
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
# path('admin/', admin.site.urls),
]
router = DefaultRouter()
router.register('category', goods_category_views.CategoryView)
router.register('units', units_views.UnitsView)
router.register('attachments', attachments_views.AttachmentView)
router.register('goods', goods_views.GoodsInfoViewSet)
urlpatterns += router.urls

@ -0,0 +1,37 @@
import logging
from rest_framework import mixins
from rest_framework import viewsets
from goods_info.models import AttachmentModel
from goods_info.serializer.attachment_serializer import AttachmentsSerializer
logger = logging.getLogger('my')
class AttachmentView(mixins.CreateModelMixin, mixins.DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
"""
create: a_file必须是选择的一个文件 a_type:是一个字符串参考模型类代码
附件或者图片--新增
附件或者图片新增, status: 201(成功), return: 新增附件或者图片信息
destroy:
附件或者图片--删除
附件或者图片删除, status: 204(成功), return: None
list:
附件或者图片--获取分页列表
附件或者图片列表信息, status: 200(成功), return: 附件或者图片信息列表
retrieve:
查询某一个附件或者图片
查询指定ID的附件或者图片, status: 200(成功), return: 用户附件或者图片
"""
queryset = AttachmentModel.objects.all()
serializer_class = AttachmentsSerializer

@ -0,0 +1,73 @@
import logging
from django.db.models import Q
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import viewsets
from goods_info.models import GoodsCategoryModel
from goods_info.serializer.category_serializer import CategorySerializer
from ERP_5.utils.base_views import MultipleDestroyMixin
logger = logging.getLogger('my')
class CategoryView(viewsets.ModelViewSet, MultipleDestroyMixin):
"""
create:
货品类别--新增
货品类别新增, status: 201(成功), return: 新增货品类别信息
destroy:
货品类别--删除
货品类别删除, status: 204(成功), return: None
multiple_delete:
货品类别--批量删除,必传参数ids=[1,2,3,4...]
货品类别批量删除, status: 204(成功), return: None
update:
货品类别--修改,
货品类别修改, status: 200(成功), return: 修改后的货品类别信息
partial_update:
货品类别--局部修改,可以传参任意属性的值服务器会修改指定的属性值
货品类别局部修改, status: 200(成功), return: 修改后的货品类别信息
list:
货品类别--获取类别列表可选get请求参数:pid如果没有传则查询顶级类别否则查询指定pid下的子类别
货品类别列表信息, status: 200(成功), return: 货品类别信息列表
retrieve:
查询某一个货品类别
查询指定ID的货品类别, status: 200(成功), return: 用户货品类别
"""
queryset = GoodsCategoryModel.objects.all()
serializer_class = CategorySerializer
def get_queryset(self):
if self.action == 'list': # 判断是否查询顶级列表还是某个子列表
# 获取请求参数(在get中)pid
pid = self.request.query_params.get('pid', None)
if pid:
return GoodsCategoryModel.objects.filter(parent__id=pid).all()
return GoodsCategoryModel.objects.filter(parent__isnull=True).all()
else:
return GoodsCategoryModel.objects.all()
# 分页参数必须是query_param(看源码)
pid_param = openapi.Parameter(name='pid', in_=openapi.IN_QUERY, description="父级的ID可以不传", type=openapi.TYPE_INTEGER)
@swagger_auto_schema(manual_parameters=[pid_param],
operation_description="商品类别的列表")
def list(self, request, *args, **kwargs):
return super(CategoryView, self).list(request=request, *args, **kwargs)

@ -0,0 +1,121 @@
from django.db.models import Q
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.viewsets import ModelViewSet
from ERP_5.utils.base_views import MultipleDestroyMixin, MultipleOpenMixin
from ERP_5.utils.paginations import GlobalPagination
from goods_info.models import GoodsModel
from goods_info.serializer.goods_serializer import GoodsBaseSerializer, GoodsGetSerializer
class GoodsInfoViewSet(ModelViewSet, MultipleDestroyMixin, MultipleOpenMixin):
"""
create:
货品(商品)信息--新增,注意其中images_list="1,2,3,4";里面是附件的ID
货品(商品)信息新增, status: 201(成功), return: 新增货品(商品)信息信息
destroy:
货品(商品)信息--删除
货品(商品)信息删除, status: 204(成功), return: None
multiple_delete:
货品(商品)信息--批量删除,必传参数ids=[1,2,3,4...]
货品(商品)信息批量删除, status: 204(成功), return: None
multiple_open:
货品(商品)信息--批量启用或者禁用,必传(json)参数ids=[1,2,3,4...](列表中可以只有一个)is_open=1/0
{
"ids":[1,2],
"is_open":"0"
}
is_open=1表示禁用is_open=0表示启用
货品(商品)信息批量启用或者禁用, status: 204(成功), return: None
update:
货品(商品)信息--修改,注意其中images_list="1,2,3,4";里面是附件的ID
货品(商品)信息修改, status: 200(成功), return: 修改后的货品(商品)信息信息
partial_update:
货品(商品)信息--局部修改,可以传参任意属性的值服务器会修改指定的属性值
货品(商品)信息局部修改, status: 200(成功), return: 修改后的货品(商品)信息信息
list:
货品(商品)信息--该接口可以弃用
货品(商品)信息列表信息, status: 200(成功), return: 货品(商品)信息信息列表
retrieve:
查询某一个货品(商品)信息
查询指定ID的货品(商品)信息, status: 200(成功), return: 用户货品(商品)信息
"""
queryset = GoodsModel.objects.all()
serializer_class = GoodsBaseSerializer
pagination_class = GlobalPagination
def get_queryset(self):
if self.action == 'find': # 过滤查询
# 获取请求参数(在json中)
keyword = self.request.data.get('keyword', None)
color = self.request.data.get('color', None)
category = self.request.data.get('category', 0)
number_code = self.request.data.get('number_code', None)
basic_weight = self.request.data.get('basic_weight', None)
expiration_day = self.request.data.get('expiration_day', 0)
delete_flag = self.request.data.get('delete_flag', None)
query = Q() # 就是为了完成条件的组合
if keyword:
child_query = Q()
child_query.add(Q(name__contains=keyword), 'OR')
child_query.add(Q(specification=keyword), 'OR')
child_query.add(Q(model_number=keyword), 'OR')
query.add(child_query, 'AND')
if color:
query.add(Q(color=color), 'AND')
if category:
query.add(Q(category__id=category), 'AND')
if number_code:
query.add(Q(number_code__contains=number_code), 'AND')
if basic_weight:
query.add(Q(basic_weight=basic_weight), 'AND')
if expiration_day:
query.add(Q(expiration_day=expiration_day), 'AND')
if delete_flag:
query.add(Q(delete_flag=delete_flag), 'AND')
return GoodsModel.objects.filter(query).all()
else:
return GoodsModel.objects.all()
def get_serializer_class(self):
if self.action == 'find' or self.action == 'retrieve':
return GoodsGetSerializer
else:
return GoodsBaseSerializer
params = openapi.Schema(type=openapi.TYPE_OBJECT, properties={
'keyword': openapi.Schema(type=openapi.TYPE_STRING, description="名称的关键字或者规格和型号"),
'color': openapi.Schema(type=openapi.TYPE_STRING, description="颜色"),
'number_code': openapi.Schema(type=openapi.TYPE_STRING, description="批号(序列号)"),
'basic_weight': openapi.Schema(type=openapi.TYPE_STRING, description="基础质量"),
'delete_flag': openapi.Schema(type=openapi.TYPE_STRING, description="状态0或者1"),
'category': openapi.Schema(type=openapi.TYPE_INTEGER, description="类别的ID"),
'expiration_day': openapi.Schema(type=openapi.TYPE_INTEGER, description="保质期天数"),
})
# 分页参数必须是query_param(看源码)
page_param = openapi.Parameter(name='page', in_=openapi.IN_QUERY, description="页号", type=openapi.TYPE_INTEGER)
size_param = openapi.Parameter(name='size', in_=openapi.IN_QUERY, description="每页显示数量", type=openapi.TYPE_INTEGER)
@swagger_auto_schema(method='POST', request_body=params, manual_parameters=[page_param, size_param],
operation_description="商品搜索过滤")
@action(methods=['POST'], detail=False)
def find(self, request, *args, **kwargs):
return super(GoodsInfoViewSet, self).list(request, *args, **kwargs)

@ -0,0 +1,89 @@
import logging
from django.db.models import Q
from django.utils.decorators import method_decorator
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import viewsets
from goods_info.models import UnitsModel
from goods_info.serializer.units_serializer import UnitsSerializer
from ERP_5.utils.paginations import GlobalPagination
from ERP_5.utils.base_views import MultipleDestroyMixin, MultipleOpenMixin
logger = logging.getLogger('my')
param3 = openapi.Parameter(name='name', type=openapi.TYPE_STRING, description="名称关键字", in_=openapi.IN_QUERY)
@method_decorator(name='list', decorator=swagger_auto_schema(
manual_parameters=[param3],
operation_description="计量单位的搜索过滤")
)
class UnitsView(viewsets.ModelViewSet, MultipleDestroyMixin, MultipleOpenMixin):
"""
create:
计量单位--新增
计量单位新增, status: 201(成功), return: 新增计量单位信息
destroy:
计量单位--删除
计量单位删除, status: 204(成功), return: None
multiple_delete:
计量单位--批量删除,必传参数ids=[1,2,3,4...]
计量单位批量删除, status: 204(成功), return: None
multiple_open:
计量单位--批量启用或者禁用,必传(json)参数ids=[1,2,3,4...](列表中可以只有一个)is_open=1/0
{
"ids":[1,2],
"is_open":"0"
}
is_open=1表示禁用is_open=0表示启用
计量单位批量启用或者禁用, status: 204(成功), return: None
update:
计量单位--修改,
计量单位修改, status: 200(成功), return: 修改后的计量单位信息
partial_update:
计量单位--局部修改,可以传参任意属性的值服务器会修改指定的属性值
计量单位局部修改, status: 200(成功), return: 修改后的计量单位信息
list:
计量单位--获取分页列表可选json参数:name(名称)
{
"name":"长沙",
}
计量单位列表信息, status: 200(成功), return: 计量单位信息列表
retrieve:
查询某一个计量单位
查询指定ID的计量单位, status: 200(成功), return: 用户计量单位
"""
queryset = UnitsModel.objects.all()
serializer_class = UnitsSerializer
pagination_class = GlobalPagination
def get_queryset(self):
if self.action == 'list': # 过滤查询
# 获取请求参数(在json中)namephone, mobile
name = self.request.data.get('name', None)
query = Q()
if name:
# 关键字查询两个字段
query.add(Q(basic_name__contains=name), 'OR') # 多条件组合
query.add(Q(backup_name__contains=name), 'OR')
return UnitsModel.objects.filter(query).all()
else:
return UnitsModel.objects.all()

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

@ -0,0 +1,6 @@
from django.apps import AppConfig
class PurchaseInfoConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'purchase_info'

@ -0,0 +1,71 @@
from django.db import models
from ERP_5.utils.base_model import BaseModel
# 采购单 模型类
class PurchaseModel(BaseModel):
invoices_date = models.DateTimeField('单据日期')
number_code = models.CharField('单据编号,不让用户填写', max_length=28)
discount = models.DecimalField('优惠率,最多精确到小数点后两位', max_digits=5, decimal_places=2, blank=True, default=0)
discount_money = models.DecimalField('优惠金额(付款优惠),最多精确到小数点后两位', max_digits=10, decimal_places=2, blank=True,
default=0)
remark = models.CharField('备注', max_length=512, blank=True, null=True)
last_amount = models.DecimalField('优惠后总金额,最多精确到小数点后两位', max_digits=13, decimal_places=2, blank=True, default=0)
deposit = models.DecimalField('支付定金,最多精确到小数点后两位', max_digits=10, decimal_places=2, blank=True, default=0)
number_count = models.DecimalField('采购数量,最多精确到小数点后两位', max_digits=10, decimal_places=2, blank=True, default=0)
status = models.CharField('状态,0:未审核,1:已审核,2:部分入库,3:全部入库,4:完成采购,5:已付定金', max_length=1, default='0')
operator_user = models.ForeignKey('erp_system.UserModel', related_name='operator_purchase_list', null=True,
blank=True, on_delete=models.SET_NULL,
verbose_name='采购操作人员,不能修改')
# 增加一个冗余字段
operator_user_name = models.CharField('操作人员的真实姓名', max_length=20, null=True, blank=True)
check_user = models.ForeignKey('erp_system.UserModel', null=True, blank=True, on_delete=models.SET_NULL,
verbose_name='审核人员,不能修改')
# 增加一个冗余字段
check_user_name = models.CharField('操作人员的真实姓名', max_length=20, null=True, blank=True)
account = models.ForeignKey('basic_info.SettlementAccountModel', null=True, blank=True, on_delete=models.SET_NULL,
verbose_name='结算账户,审核之后不能改')
supplier = models.ForeignKey('basic_info.SupplierModel', null=True, blank=True, on_delete=models.SET_NULL,
verbose_name='供应商,审核之后不能改')
# 增加一个冗余字段
supplier_name = models.CharField('供应商名称', max_length=30, null=True, blank=True)
attachment_list = models.CharField('附件的id列表字段的值为: 1,2,3,4', max_length=20, null=True, blank=True)
class Meta:
db_table = 't_purchase'
verbose_name = '采购表'
verbose_name_plural = verbose_name
ordering = ['id']
# 采购单中的货品(采购项) 模型类
class PurchaseItemModel(BaseModel):
# 个个也都是冗余字段
name = models.CharField(max_length=20, verbose_name='货品名称')
specification = models.CharField('货品规格', max_length=50, null=True, blank=True)
number_code = models.CharField('货品的编号或者批号', max_length=28, null=True, blank=True)
remark = models.CharField('备注', max_length=512, blank=True, null=True)
purchase_count = models.DecimalField('采购数量,最多精确到小数点后两位', max_digits=10, decimal_places=2, blank=True, default=0)
purchase_price = models.DecimalField('采购单价,最多精确到小数点后两位', max_digits=10, decimal_places=2, blank=True, default=0)
purchase_money = models.DecimalField('采购金额,最多精确到小数点后两位', max_digits=10, decimal_places=2, blank=True, default=0)
purchase = models.ForeignKey('PurchaseModel', related_name='item_list', null=True, blank=True,
on_delete=models.CASCADE,
verbose_name='采购单')
goods = models.ForeignKey('goods_info.GoodsModel', null=True, blank=True, on_delete=models.SET_NULL,
verbose_name='货品')
class Meta:
db_table = 't_purchase_items'
verbose_name = '采购项表'
verbose_name_plural = verbose_name
ordering = ['id']
def __str__(self):
return self.name + ' ' + self.specification

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

@ -0,0 +1,31 @@
"""ERP_5 URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, re_path
from rest_framework.routers import DefaultRouter
# from .views import units_views, attachments_views, goods_category_views, goods_views
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
# path('admin/', admin.site.urls),
]
router = DefaultRouter()
# router.register('category', goods_category_views.CategoryView)
urlpatterns += router.urls

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

@ -0,0 +1,16 @@
"""
ASGI config for ERP_5 project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ERP_5.settings')
application = get_asgi_application()

@ -0,0 +1,15 @@
import os
from celery import Celery
from django.conf import settings
import django
# 设置环境变量
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ERP_5.settings.dev')
# 注册Celery的APP, ERP_5必须是项目名字
celery_app = Celery('ERP_5')
# 绑定配置文件
celery_app.config_from_object('django.conf:settings', namespace='CELERY')
# 自动从settings.INSTALLED_APPS注册的应用中加载tasks.py
celery_app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 MiB

@ -0,0 +1,279 @@
"""
Django settings for ERP_5 project.
Generated by 'django-admin startproject' using Django 3.2.16.
For more information on this file, see
https://docs.djangoproject.com/en/3.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.2/ref/settings/
"""
import sys
import os
import datetime
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-o!nl319=c4gr)@c)n#@*kq)zil-i(4puha&59&1o*wd0cbrj!5'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'corsheaders',
'erp_system',
'basic_info',
'goods_info',
'purchase_info',
# 'django.contrib.staticfiles', # required for serving swagger ui's css/js files
'drf_yasg',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
CORS_ORIGIN_WHITELIST = (
'http://127.0.0.1:8080',
'http://localhost:8080',
)
CORS_ALLOW_CREDENTIALS = True
ROOT_URLCONF = 'ERP_5.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates']
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'ERP_5.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'erp_5',
'USER': 'root',
'PASSWORD': '123123',
'HOST': '127.0.0.1',
'PORT': '3306',
}
}
# 配置Redis数据库
CACHES = {
"default": { # 默认
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/0",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/
LANGUAGE_CODE = 'zh-Hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = False
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/
STATIC_URL = '/static/'
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': { #
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s %(filename)s: %(lineno)d %(message)s'
},
'simple': {
'format': '%(levelname)s %(module)s %(lineno)d %(message)s'
},
},
'filters': { #
'require_debug_true': { #
'()': 'django.utils.log.RequireDebugTrue',
},
},
'handlers': { #
'console': { #
'level': 'INFO',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
'formatter': 'verbose'
},
'my_con': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
},
'file': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(BASE_DIR, 'logs/mangguo.log'),
'maxBytes': 300 * 1024 * 1024,
'backupCount': 10,
'formatter': 'verbose'
},
},
'loggers': {
'my': {
# 'handlers': ['console', 'file'],
'handlers': ['console'],
'level': 'DEBUG',
'propagate': True,
},
'django.db.backends': {
# 'handlers': ['console', 'file'],
'handlers': ['my_con'],
'propagate': True,
'level': 'DEBUG',
},
}
}
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
),
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
'DEFAULT_PERMISSION_CLASSES':
(
# 'rest_framework.permissions.IsAuthenticated',
# 'ERP_5.utils.rbac_permissions.RbacPermission', # 自定义的权限验证
)
}
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
'JWT_RESPONSE_PAYLOAD_HANDLER': 'ERP_5.utils.jwt_handler.my_jwt_response_msg',
}
# 添加自定义用户模型类(应用名.模型类名)
AUTH_USER_MODEL = 'erp_system.UserModel'
# 指定自定义认证类路径
AUTHENTICATION_BACKENDS = ['erp_system.erp_auth.UserLoginAuth']
# SWAGGER 接口文档支持JWT的自动认证自动在请求头中加入token
SWAGGER_SETTINGS = {
# 'PERSIST_AUTH': True,
'REFETCH_SCHEMA_WITH_AUTH': True,
'REFETCH_SCHEMA_ON_LOGOUT': True,
'SECURITY_DEFINITIONS': {
'JWT': {
'type': 'apiKey',
'name': 'Authorization',
'in': 'header'
},
}
}
# celery
# Broker配置使用Redis作为消息中间件
CELERY_BROKER_URL = 'redis://127.0.0.1:6379/4'
# BACKEND配置这里使用redis
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/5'
# 结果序列化方案
CELERY_RESULT_SERIALIZER = 'json'
# 任务结果过期时间,秒
CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24
# 时区配置
CELERY_TIMEZONE = 'Asia/Shanghai'
BASE_API = 'api/' # 项目BASE API, 如设置时必须以/结尾
# 权限认证白名单
WHITE_LIST = [f'/{BASE_API}users/login/', f'/{BASE_API}users/register/', f'/docs/.*', f'/swagger/.*']
REGEX_URL = '^{url}$' # 权限匹配时,严格正则url
# 设置媒体路由地址信息
MEDIA_URL = '/media/'
# # 配置上传件存放的目录获取media文件夹的完整路径信息
MEDIA_ROOT = BASE_DIR / 'media'

@ -0,0 +1,126 @@
"""
Django settings for ERP_5 project.
Generated by 'django-admin startproject' using Django 3.2.16.
For more information on this file, see
https://docs.djangoproject.com/en/3.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.2/ref/settings/
"""
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-o!nl319=c4gr)@c)n#@*kq)zil-i(4puha&59&1o*wd0cbrj!5'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'ERP_5.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates']
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'ERP_5.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/
STATIC_URL = '/static/'
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

@ -0,0 +1,51 @@
"""ERP_5 URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.conf import settings
from django.contrib import admin
from django.urls import path, include, re_path
from django.views.static import serve
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
from rest_framework import permissions
from rest_framework.documentation import include_docs_urls
from ERP_5.utils.base_views import GenerateCode
schema_view = get_schema_view(
openapi.Info(
title="接口 API文档",
default_version='v1',
description="马士兵ERP系统项目接口文档",
),
public=True,
permission_classes=(permissions.AllowAny,),
)
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('erp_system.urls')),
path('api/', include('basic_info.urls')),
path('api/', include('goods_info.urls')),
path('api/', include('purchase_info.urls')),
re_path(r'^api/generate_code/$', GenerateCode.as_view()),
# 媒体资源文件的访问路由
re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
path('docs/', include_docs_urls(title='ERP系统的接口文档')),
# swagger接口文档的路由
path('swagger<format>/', schema_view.without_ui(cache_timeout=0), name='schema-json'),
path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
]

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save