创建虚拟环境
python3 -m venv venv
source venv/bin/activate
安装 Django
pip install django mysqlclient
django-admin startproject ****
cd ******
python manage.py migrate # 创建初始数据库表
python manage.py createsuperuser # 创建管理员账号
python manage.py runserver 0.0.0.0:8000 # 启动开发服务器
pip install Pillow #用于显示图片
pip install djangorestframework #用于打开rest api接口
pip install django-import-export #用于导入导出excel
pip install openpyxl #用于导入导出excel
pip install django-extensions
pip install Werkzeug #用于https在dev模式下
当安装pip install mysqlclient失败的时候,你可以这样:
更新 apt
sudo apt update
安装编译工具和依赖
sudo apt install -y build-essential python3-dev default-libmysqlclient-dev libssl-dev pkg-config
然后再
pip install mysqlclient
默认左侧边栏缩进,使用js, 注意在***/admin.py里加入js位置
vi static/js/product_autocomplete.js
document.addEventListener('DOMContentLoaded', function() {
const sidebar = document.getElementById('nav-sidebar');
const toggleBtn = document.getElementById('toggle-nav-sidebar');
if (sidebar && toggleBtn && sidebar.getAttribute('aria-expanded') === 'true') {
// 触发折叠效果
toggleBtn.click();
}
});
vi ***/admin.py
...
class Media:
css = {
'all': ('css/custom_admin.css',) # 自定义 CSS 文件路径
#'all': ("admin/css/admin_override.css",)
}
js = ("js/product_autocomplete.js",) # 自定义js
....
导入excel,前端按钮
抽离 Excel 导入逻辑
# inventory/services/import_items.py
import pandas as pd
from inventory.models import Product
def parse_int(value, default=0):
try:
value_str = str(value).strip()
num = int(''.join(c for c in value_str if c.isdigit()))
return num
except Exception:
return default
def parse_decimal(value, default=0.0):
try:
value_str = str(value).strip()
cleaned = ''.join(c for c in value_str if c.isdigit() or c == '.')
return float(cleaned) if cleaned else default
except Exception:
return default
def import_products_from_excel(
file_path,
*,
dry_run=False
):
"""
从 Excel 导入 Product
:param file_path: excel 文件路径
:param dry_run: True 时不写入数据库
:return: dict 导入结果
"""
df = pd.read_excel(file_path)
created = 0
updated = 0
errors = []
for index, row in df.iterrows():
try:
defaults = {
'manufacturer': row.get('manufacturer', ''),
'lotno': row.get('lotno', ''),
'package_type': row.get('package_type', ''),
'stock_qty': parse_int(row.get('stock_qty', 0)),
'price_sale': parse_decimal(row.get('price_sale', 0.0)),
'remarks': row.get('remarks', ''),
'location': row.get('location', ''),
}
if dry_run:
continue
obj, is_created = Product.objects.update_or_create(
model=row['model'],
defaults=defaults,
)
if is_created:
created += 1
else:
updated += 1
except Exception as e:
errors.append({
"row": index + 2, # Excel 行号(含表头)
"error": str(e),
})
return {
"created": created,
"updated": updated,
"errors": errors,
"total": len(df),
}
vi inventory/management/commands/import_excel.py
# inventory/management/commands/import_excel.py
from django.core.management.base import BaseCommand
from inventory.services.import_items import import_products_from_excel
class Command(BaseCommand):
help = 'Import inventory from Excel'
def add_arguments(self, parser):
parser.add_argument('file', type=str, help='Path to Excel file')
parser.add_argument(
'--dry-run',
action='store_true',
help='Parse excel but do not write to database',
)
def handle(self, *args, **options):
result = import_products_from_excel(
options['file'],
dry_run=options['dry_run']
)
self.stdout.write(self.style.SUCCESS(
f"Import finished! "
f"Total={result['total']} "
f"Created={result['created']} "
f"Updated={result['updated']} "
f"Errors={len(result['errors'])}"
))
if result['errors']:
self.stdout.write(self.style.WARNING("Errors:"))
for err in result['errors'][:5]:
self.stdout.write(str(err))
表单(Form)vi inventory/forms.py
# inventory/forms.py
from django import forms
class ExcelImportForm(forms.Form):
file = forms.FileField(
label="上传 Excel",
help_text="仅支持 .xlsx"
)
View(上传 + 临时保存 + 导入)inventory/views.py
# inventory/views.py
import tempfile
from django.shortcuts import render, redirect
from inventory.forms import ExcelImportForm
from inventory.services.import_items import import_products_from_excel
def import_excel_view(request):
if request.method == "POST":
form = ExcelImportForm(request.POST, request.FILES)
if form.is_valid():
excel_file = form.cleaned_data["file"]
with tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx") as tmp:
for chunk in excel_file.chunks():
tmp.write(chunk)
import_products_from_excel(tmp.name)
return redirect("import_success")
else:
form = ExcelImportForm()
return render(request, "inventory/import_excel.html", {"form": form})
# Create your views here.
写一个上传excel的html: vi inventory/templates/admin/import_excel.html
# inventory/templates/admin/import_excel.html
{% extends "admin/base_site.html" %}
{% block content %}
<h1>导入前,请把excel表的第一行的列名改成相关键值,列名-键值对照表如下</h1>
<p style="color:#00FF00;">型号:model; 厂商/品牌:brand; 批号:lotno; 封装:package_type;库存数量:stock_qty;售价:price_sale;成本价:price_cost;仓库位置:location;说明/备注:remarks;</p>
<p style="color:#00FF00;">供应商:supplier_name;产品名:name;sku编号:sku_code</p>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="开始导入" class="default">
</form>
{% endblock %}
注册URL: vi
inventory/urls.py
from django.urls import path
from .views import import_excel_view
urlpatterns = [
path("import-excel/", import_excel_view, name="import_excel"),
]
修改
admin.py
# inventory/admin.py
from django.contrib import admin, messages
from django.urls import path
from django.shortcuts import render, redirect
import tempfile
from inventory.models import Product
from inventory.forms import ExcelImportForm
from inventory.services.import_items import import_products_from_excel
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ("model", "manufacturer", "stock_qty", "price_sale")
search_fields = ("model", "manufacturer")
def get_urls(self):
urls = super().get_urls()
custom_urls = [
path(
"import-excel/",
self.admin_site.admin_view(self.import_excel_view),
name="inventory_product_import_excel",
),
]
return custom_urls + urls
@permission_required("inventory.add_item")
def import_excel_view(self, request):
if request.method == "POST":
form = ExcelImportForm(request.POST, request.FILES)
if form.is_valid():
excel_file = form.cleaned_data["file"]
with tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx") as tmp:
for chunk in excel_file.chunks():
tmp.write(chunk)
result = import_products_from_excel(tmp.name)
messages.success(
request,
f"导入完成:"
f"总数 {result['total']},"
f"新增 {result['created']},"
f"更新 {result['updated']},"
f"错误 {len(result['errors'])}"
)
return redirect("..")
else:
form = ExcelImportForm()
context = {
"form": form,
"title": "导入 Excel",
}
return render(request, "admin/import_excel.html", context)
前端的按钮,结构如下:
templates/
└── admin/
└── inventory/
└── product/
└── change_list.html ✅ 新增
路径必需是:
admin / app_label / model_name / change_list.html
vi templates/admin/inventory/product/change_list.html
{% extends "admin/change_list.html" %}
{% block object-tools %}
<ul class="object-tools">
<li>
<a href="import-excel/" class="addlink">
导入 Excel
</a>
</li>
</ul>
{{ block.super }}
{% endblock %}
