Skip to content

Commit a98e43a

Browse files
committed
perf: 优化代码,支持多文件关联上传
1 parent 5a42e2c commit a98e43a

File tree

5 files changed

+428
-399
lines changed

5 files changed

+428
-399
lines changed

common/core/models.py

Lines changed: 130 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,109 +1,130 @@
1-
#!/usr/bin/env python
2-
# -*- coding:utf-8 -*-
3-
# project : xadmin-server
4-
# filename : models
5-
# author : ly_13
6-
# date : 12/20/2023
7-
import os
8-
import time
9-
import uuid
10-
11-
from django.conf import settings
12-
from django.db import models
13-
from django.utils.translation import gettext_lazy as _
14-
15-
from common.utils import get_logger
16-
17-
logger = get_logger(__name__)
18-
19-
20-
class DbUuidModel(models.Model):
21-
id = models.UUIDField(default=uuid.uuid4, primary_key=True, verbose_name=_("ID"))
22-
23-
class Meta:
24-
abstract = True
25-
26-
27-
class DbCharModel(models.Model):
28-
id = models.CharField(primary_key=True, max_length=128, verbose_name=_("ID"))
29-
30-
class Meta:
31-
abstract = True
32-
33-
34-
class AutoCleanFileMixin(object):
35-
"""
36-
当对象包含文件字段,更新或者删除的时候,自动删除底层文件
37-
"""
38-
39-
def save(self, *args, **kwargs):
40-
if kwargs.get('force_insert', None):
41-
filelist = []
42-
else:
43-
filelist = self.__get_filelist(self._meta.model.objects.filter(pk=self.pk).first())
44-
result = super().save(*args, **kwargs)
45-
self.__delete_file(filelist, True)
46-
return result
47-
48-
def delete(self, *args, **kwargs):
49-
filelist = self.__get_filelist()
50-
result = super().delete(*args, **kwargs)
51-
self.__delete_file(filelist)
52-
return result
53-
54-
def __delete_file(self, filelist, is_save=False):
55-
try:
56-
for item in filelist:
57-
if is_save:
58-
file = getattr(self, item[0], None)
59-
if file and file.name == item[1]:
60-
continue
61-
item[2].name = item[1]
62-
item[2].delete(save=False)
63-
except Exception as e:
64-
logger.warning(f"remove {self} old file {filelist} failed, {e}")
65-
66-
def __get_filelist(self, obj=None):
67-
filelist = []
68-
if obj is None:
69-
obj = self
70-
for field in obj._meta.fields:
71-
if isinstance(field, (models.ImageField, models.FileField)) and hasattr(obj, field.name):
72-
file_obj = getattr(obj, field.name, None)
73-
if file_obj:
74-
filelist.append((field.name, file_obj.name, file_obj))
75-
return filelist
76-
77-
78-
class DbBaseModel(models.Model):
79-
created_time = models.DateTimeField(auto_now_add=True, verbose_name=_("Created time"), null=True, blank=True)
80-
updated_time = models.DateTimeField(auto_now=True, verbose_name=_("Updated time"), null=True, blank=True)
81-
description = models.CharField(max_length=256, verbose_name=_("Description"), null=True, blank=True)
82-
83-
class Meta:
84-
abstract = True
85-
86-
87-
class DbAuditModel(DbBaseModel):
88-
creator = models.ForeignKey(to=settings.AUTH_USER_MODEL, related_query_name='creator_query', null=True, blank=True,
89-
verbose_name=_("Creator"), on_delete=models.SET_NULL, related_name='+')
90-
modifier = models.ForeignKey(to=settings.AUTH_USER_MODEL, related_query_name='modifier_query', null=True,
91-
blank=True, verbose_name=_("Modifier"), on_delete=models.SET_NULL, related_name='+')
92-
dept_belong = models.ForeignKey(to="system.DeptInfo", related_query_name='dept_belong_query', null=True, blank=True,
93-
verbose_name=_("Data ownership department"), on_delete=models.SET_NULL,
94-
related_name='+')
95-
96-
class Meta:
97-
abstract = True
98-
99-
100-
def upload_directory_path(instance, filename):
101-
prefix = filename.split('.')[-1]
102-
tmp_name = f"{filename}_{time.time()}"
103-
new_filename = f"{uuid.uuid5(uuid.NAMESPACE_DNS, tmp_name).__str__().replace('-', '')}.{prefix}"
104-
labels = instance._meta.label_lower.split('.')
105-
if creator := getattr(instance, "creator", None):
106-
creator_pk = creator.pk
107-
else:
108-
creator_pk = 0
109-
return os.path.join(labels[0], labels[1], str(creator_pk), new_filename)
1+
#!/usr/bin/env python
2+
# -*- coding:utf-8 -*-
3+
# project : xadmin-server
4+
# filename : models
5+
# author : ly_13
6+
# date : 12/20/2023
7+
import os
8+
import time
9+
import uuid
10+
11+
from django.conf import settings
12+
from django.db import models
13+
from django.db.models import QuerySet
14+
from django.utils.translation import gettext_lazy as _
15+
16+
from common.utils import get_logger
17+
18+
logger = get_logger(__name__)
19+
20+
21+
class DbUuidModel(models.Model):
22+
id = models.UUIDField(default=uuid.uuid4, primary_key=True, verbose_name=_("ID"))
23+
24+
class Meta:
25+
abstract = True
26+
27+
28+
class DbCharModel(models.Model):
29+
id = models.CharField(primary_key=True, max_length=128, verbose_name=_("ID"))
30+
31+
class Meta:
32+
abstract = True
33+
34+
35+
class AutoCleanFileMixin(object):
36+
"""
37+
当对象包含文件字段,更新或者删除的时候,自动删除底层文件
38+
"""
39+
40+
def save(self, *args, **kwargs):
41+
if kwargs.get('force_insert', None):
42+
filelist = []
43+
else:
44+
filelist = self.__get_filelist(self._meta.model.objects.filter(pk=self.pk).first())
45+
result = super().save(*args, **kwargs)
46+
self.__delete_file(filelist, True)
47+
return result
48+
49+
def delete(self, *args, **kwargs):
50+
filelist = self.__get_filelist()
51+
related_filelist = self.__get_related_filelist()
52+
result = super().delete(*args, **kwargs)
53+
self.__delete_file(filelist)
54+
self.__delete_related_files(related_filelist)
55+
return result
56+
57+
def __delete_file(self, filelist, is_save=False):
58+
try:
59+
for item in filelist:
60+
if is_save:
61+
file = getattr(self, item[0], None)
62+
if file and file.name == item[1]:
63+
continue
64+
item[2].name = item[1]
65+
item[2].delete(save=False)
66+
except Exception as e:
67+
logger.warning(f"remove {self} old file {filelist} failed, {e}")
68+
69+
def __get_filelist(self, obj=None):
70+
filelist = []
71+
if obj is None:
72+
obj = self
73+
for field in obj._meta.fields:
74+
if isinstance(field, (models.ImageField, models.FileField)) and hasattr(obj, field.name):
75+
file_obj = getattr(obj, field.name, None)
76+
if file_obj:
77+
filelist.append((field.name, file_obj.name, file_obj))
78+
return filelist
79+
80+
def __get_related_filelist(self, obj=None):
81+
filelist = []
82+
if obj is None:
83+
obj = self
84+
for field in obj._meta.get_fields():
85+
if field.is_relation and field.related_model._meta.label == "system.UploadFile":
86+
file_data = getattr(obj, field.name, None)
87+
if isinstance(field, models.ManyToManyField):
88+
file_data = file_data.all()
89+
if isinstance(file_data, (list, QuerySet)):
90+
filelist.extend(file_data)
91+
else:
92+
filelist.append(file_data)
93+
return filelist
94+
95+
def __delete_related_files(self, filelist):
96+
for file in filelist:
97+
file.delete()
98+
99+
class DbBaseModel(models.Model):
100+
created_time = models.DateTimeField(auto_now_add=True, verbose_name=_("Created time"), null=True, blank=True)
101+
updated_time = models.DateTimeField(auto_now=True, verbose_name=_("Updated time"), null=True, blank=True)
102+
description = models.CharField(max_length=256, verbose_name=_("Description"), null=True, blank=True)
103+
104+
class Meta:
105+
abstract = True
106+
107+
108+
class DbAuditModel(DbBaseModel):
109+
creator = models.ForeignKey(to=settings.AUTH_USER_MODEL, related_query_name='creator_query', null=True, blank=True,
110+
verbose_name=_("Creator"), on_delete=models.SET_NULL, related_name='+')
111+
modifier = models.ForeignKey(to=settings.AUTH_USER_MODEL, related_query_name='modifier_query', null=True,
112+
blank=True, verbose_name=_("Modifier"), on_delete=models.SET_NULL, related_name='+')
113+
dept_belong = models.ForeignKey(to="system.DeptInfo", related_query_name='dept_belong_query', null=True, blank=True,
114+
verbose_name=_("Data ownership department"), on_delete=models.SET_NULL,
115+
related_name='+')
116+
117+
class Meta:
118+
abstract = True
119+
120+
121+
def upload_directory_path(instance, filename):
122+
prefix = filename.split('.')[-1]
123+
tmp_name = f"{filename}_{time.time()}"
124+
new_filename = f"{uuid.uuid5(uuid.NAMESPACE_DNS, tmp_name).__str__().replace('-', '')}.{prefix}"
125+
labels = instance._meta.label_lower.split('.')
126+
if creator := getattr(instance, "creator", None):
127+
creator_pk = creator.pk
128+
else:
129+
creator_pk = 0
130+
return os.path.join(labels[0], labels[1], str(creator_pk), str(instance.pk if instance.pk else 0), new_filename)

common/utils/media.py

Lines changed: 73 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,73 @@
1-
#!/usr/bin/env python
2-
# -*- coding:utf-8 -*-
3-
# project : xadmin-server
4-
# filename : media
5-
# author : ly_13
6-
# date : 1/17/2024
7-
import mimetypes
8-
import os
9-
import posixpath
10-
from pathlib import Path
11-
12-
from django.apps import apps
13-
from django.http import FileResponse, Http404, HttpResponseNotModified
14-
from django.utils._os import safe_join
15-
from django.utils.http import http_date
16-
from django.utils.translation import gettext_lazy as _
17-
from django.views.static import directory_index, was_modified_since
18-
19-
from common.fields.image import ProcessedImageField, get_thumbnail
20-
21-
22-
def get_media_path(path):
23-
path_list = path.split('/')
24-
if len(path_list) == 4:
25-
pic_names = path_list[3].split('_')
26-
if len(pic_names) != 2:
27-
return
28-
model = apps.get_model(path_list[0], path_list[1])
29-
field = None
30-
for i in model._meta.fields:
31-
if isinstance(i, ProcessedImageField):
32-
field = i
33-
break
34-
if field:
35-
pk = path_list[2]
36-
fw = {"pk": pk}
37-
if pk == "None": # 通过form-data增加数据的时候,由于instance还未创建,pk不存在
38-
fw = {field.name: path.replace(f"_{pic_names[1]}", f".{field.format}")}
39-
obj = model.objects.filter(**fw).first()
40-
if obj:
41-
pic = getattr(obj, field.name)
42-
if os.path.isfile(pic.path):
43-
index = pic_names[1].split('.')
44-
if pic and len(index) > 0:
45-
return get_thumbnail(pic, int(index[0]))
46-
47-
48-
def media_serve(request, path, document_root=None, show_indexes=False):
49-
path = posixpath.normpath(path).lstrip("/")
50-
fullpath = Path(safe_join(document_root, path))
51-
if fullpath.is_dir():
52-
if show_indexes:
53-
return directory_index(path, fullpath)
54-
raise Http404(_("Directory indexes are not allowed here."))
55-
if not fullpath.exists():
56-
media_path = get_media_path(path)
57-
if media_path:
58-
fullpath = Path(safe_join(document_root, media_path))
59-
else:
60-
raise Http404(_("“%(path)s” does not exist") % {"path": fullpath})
61-
# Respect the If-Modified-Since header.
62-
statobj = fullpath.stat()
63-
if not was_modified_since(
64-
request.META.get("HTTP_IF_MODIFIED_SINCE"), statobj.st_mtime
65-
):
66-
return HttpResponseNotModified()
67-
content_type, encoding = mimetypes.guess_type(str(fullpath))
68-
content_type = content_type or "application/octet-stream"
69-
response = FileResponse(fullpath.open("rb"), content_type=content_type)
70-
response.headers["Last-Modified"] = http_date(statobj.st_mtime)
71-
if encoding:
72-
response.headers["Content-Encoding"] = encoding
73-
return response
1+
#!/usr/bin/env python
2+
# -*- coding:utf-8 -*-
3+
# project : xadmin-server
4+
# filename : media
5+
# author : ly_13
6+
# date : 1/17/2024
7+
import mimetypes
8+
import os
9+
import posixpath
10+
from pathlib import Path
11+
12+
from django.apps import apps
13+
from django.http import FileResponse, Http404, HttpResponseNotModified
14+
from django.utils._os import safe_join
15+
from django.utils.http import http_date
16+
from django.utils.translation import gettext_lazy as _
17+
from django.views.static import directory_index, was_modified_since
18+
19+
from common.fields.image import ProcessedImageField, get_thumbnail
20+
21+
22+
def get_media_path(path):
23+
path_list = path.split('/')
24+
if len(path_list) == 5:
25+
pic_names = path_list[4].split('_')
26+
if len(pic_names) != 2:
27+
return
28+
model = apps.get_model(path_list[0], path_list[1])
29+
field = None
30+
for i in model._meta.fields:
31+
if isinstance(i, ProcessedImageField):
32+
field = i
33+
break
34+
if field:
35+
pk = path_list[3]
36+
fw = {"pk": pk}
37+
if pk == "0": # 通过form-data增加数据的时候,由于instance还未创建,pk不存在,为默认0
38+
fw = {field.name: path.replace(f"_{pic_names[1]}", f".{field.format}")}
39+
obj = model.objects.filter(**fw).first()
40+
if obj:
41+
pic = getattr(obj, field.name)
42+
if os.path.isfile(pic.path):
43+
index = pic_names[1].split('.')
44+
if pic and len(index) > 0:
45+
return get_thumbnail(pic, int(index[0]))
46+
47+
48+
def media_serve(request, path, document_root=None, show_indexes=False):
49+
path = posixpath.normpath(path).lstrip("/")
50+
fullpath = Path(safe_join(document_root, path))
51+
if fullpath.is_dir():
52+
if show_indexes:
53+
return directory_index(path, fullpath)
54+
raise Http404(_("Directory indexes are not allowed here."))
55+
if not fullpath.exists():
56+
media_path = get_media_path(path)
57+
if media_path:
58+
fullpath = Path(safe_join(document_root, media_path))
59+
else:
60+
raise Http404(_("“%(path)s” does not exist") % {"path": fullpath})
61+
# Respect the If-Modified-Since header.
62+
statobj = fullpath.stat()
63+
if not was_modified_since(
64+
request.META.get("HTTP_IF_MODIFIED_SINCE"), statobj.st_mtime
65+
):
66+
return HttpResponseNotModified()
67+
content_type, encoding = mimetypes.guess_type(str(fullpath))
68+
content_type = content_type or "application/octet-stream"
69+
response = FileResponse(fullpath.open("rb"), content_type=content_type)
70+
response.headers["Last-Modified"] = http_date(statobj.st_mtime)
71+
if encoding:
72+
response.headers["Content-Encoding"] = encoding
73+
return response

0 commit comments

Comments
 (0)