feat: cms users institution department manager
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
"""CMS 导入 / 导出通用工具(基于 openpyxl)。"""
|
||||
import io
|
||||
|
||||
from django.http import HttpResponse
|
||||
from openpyxl import Workbook, load_workbook
|
||||
|
||||
XLSX_CONTENT_TYPE = (
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
)
|
||||
|
||||
|
||||
def xlsx_response(filename, headers, rows):
|
||||
"""生成 .xlsx 下载响应。
|
||||
|
||||
Args:
|
||||
filename: 下载文件名(含 .xlsx)
|
||||
headers: 表头列表
|
||||
rows: 二维数据(list[list]);空则只导出表头(可作模板)
|
||||
"""
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.append(list(headers))
|
||||
for row in rows:
|
||||
ws.append(list(row))
|
||||
buf = io.BytesIO()
|
||||
wb.save(buf)
|
||||
buf.seek(0)
|
||||
resp = HttpResponse(buf.getvalue(), content_type=XLSX_CONTENT_TYPE)
|
||||
resp['Content-Disposition'] = f'attachment; filename="{filename}"'
|
||||
return resp
|
||||
|
||||
|
||||
def rows_from_xlsx(file):
|
||||
"""解析上传的 .xlsx,首行为表头,返回 list[dict](表头→单元格字符串)。
|
||||
|
||||
空行跳过;单元格统一转为去空格字符串(None → '')。
|
||||
"""
|
||||
wb = load_workbook(file, read_only=True, data_only=True)
|
||||
ws = wb.active
|
||||
it = ws.iter_rows(values_only=True)
|
||||
try:
|
||||
header_cells = next(it)
|
||||
except StopIteration:
|
||||
return []
|
||||
headers = [str(h).strip() if h is not None else '' for h in header_cells]
|
||||
|
||||
result = []
|
||||
for raw in it:
|
||||
if raw is None or all(c is None or str(c).strip() == '' for c in raw):
|
||||
continue
|
||||
row = {}
|
||||
for i, h in enumerate(headers):
|
||||
if not h:
|
||||
continue
|
||||
val = raw[i] if i < len(raw) else None
|
||||
row[h] = '' if val is None else str(val).strip()
|
||||
result.append(row)
|
||||
return result
|
||||
+61
-1
@@ -1,10 +1,70 @@
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class BaseModel(models.Model):
|
||||
"""基础模型,包含通用字段"""
|
||||
"""基础模型,包含通用时间字段"""
|
||||
created_at = models.DateTimeField('创建时间', auto_now_add=True)
|
||||
updated_at = models.DateTimeField('更新时间', auto_now=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
# ─── 软删除(停用 = 逻辑删除)────────────────────────────────────────────────────
|
||||
|
||||
class SoftDeleteQuerySet(models.QuerySet):
|
||||
"""QuerySet.delete() 改为逻辑删除;hard_delete() 才物理删除。"""
|
||||
|
||||
def delete(self):
|
||||
return self.update(is_deleted=True, deleted_at=timezone.now())
|
||||
|
||||
def hard_delete(self):
|
||||
return super().delete()
|
||||
|
||||
def alive(self):
|
||||
return self.filter(is_deleted=False)
|
||||
|
||||
|
||||
class SoftDeleteManager(models.Manager):
|
||||
"""默认只返回未删除的数据。"""
|
||||
|
||||
def get_queryset(self):
|
||||
return SoftDeleteQuerySet(self.model, using=self._db).filter(is_deleted=False)
|
||||
|
||||
|
||||
class AllObjectsManager(models.Manager):
|
||||
"""包含已删除数据(管理/恢复用)。"""
|
||||
|
||||
def get_queryset(self):
|
||||
return SoftDeleteQuerySet(self.model, using=self._db)
|
||||
|
||||
|
||||
class SoftDeleteModel(BaseModel):
|
||||
"""软删除基类:停用/下架一律逻辑删除,绝不物理删除。
|
||||
|
||||
- `objects`:默认管理器,只含未删除数据。
|
||||
- `all_objects`:含已删除数据。
|
||||
- 实例 `.delete()` → 逻辑删除;`.hard_delete()` → 物理删除。
|
||||
"""
|
||||
is_deleted = models.BooleanField('是否删除', default=False, db_index=True)
|
||||
deleted_at = models.DateTimeField('删除时间', null=True, blank=True)
|
||||
|
||||
objects = SoftDeleteManager()
|
||||
all_objects = AllObjectsManager()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
self.is_deleted = True
|
||||
self.deleted_at = timezone.now()
|
||||
self.save(using=using, update_fields=['is_deleted', 'deleted_at', 'updated_at'])
|
||||
|
||||
def hard_delete(self, using=None, keep_parents=False):
|
||||
super().delete(using=using, keep_parents=keep_parents)
|
||||
|
||||
def restore(self, using=None):
|
||||
self.is_deleted = False
|
||||
self.deleted_at = None
|
||||
self.save(using=using, update_fields=['is_deleted', 'deleted_at', 'updated_at'])
|
||||
|
||||
Reference in New Issue
Block a user