Skip to content

Commit 3cfd47f

Browse files
authored
Feature 性能调优 (#263)
* [update] 核心模块性能改进 * [update] 改进产品API性能 * [update] 改进__str__方法性能 * [update] 改进__str__性能以及提高序列化器稳定性 * [update] 提高库存模块数据库查询效率 * [update] 更新单元测试代码
1 parent df906ed commit 3cfd47f

35 files changed

+320
-146
lines changed

Core/api/auth.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ class UserViewSet(viewsets.ModelViewSet):
1515
用户信息API
1616
"""
1717
pagination_class = SmallResultsSetPagination
18-
queryset = User.objects.exclude(is_staff=True).order_by('pk')
18+
queryset = (User.objects.exclude(is_staff=True)
19+
.prefetch_related('info').order_by('pk'))
1920
filter_class = filters.UserFilter
2021

2122
def destroy(self, request, pk=None):

Core/models/auth.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from django.db import models
2+
from django.conf import settings
23
from django.contrib.auth.models import User, Group
34

45
from Core import GENDER_CHOICES, GENDER_MALE
@@ -26,32 +27,43 @@ class Meta:
2627
verbose_name_plural = '个人信息'
2728

2829
def __str__(self):
29-
return '{1}(用户名:{0})'.format(
30-
self.user.username, self.user.first_name)
30+
return '{}(用户名:{})'.format(
31+
self.user.first_name, self.user.username)
3132

3233

3334
class DistributionDepartmentManager(models.Manager):
3435
def get_queryset(self):
3536
# TODO: To real
36-
return super().get_queryset().filter(id=1)
37+
return (super().get_queryset()
38+
.filter(
39+
id=settings.ERP_CONTEXT['DEPARTMENT']['DISTRIBUTION_ID'])
40+
.select_related('group'))
3741

3842

3943
class ProcessDepartmentManager(models.Manager):
4044
def get_queryset(self):
4145
# TODO: To real
42-
return super().get_queryset().filter(id=2)
46+
return (super().get_queryset()
47+
.filter(id=settings.ERP_CONTEXT['DEPARTMENT']['PROCESS_ID'])
48+
.select_related('group'))
4349

4450

4551
class ProcurementDepartmentManager(models.Manager):
4652
def get_queryset(self):
4753
# TODO: To real
48-
return super().get_queryset().filter(id=3)
54+
return (super().get_queryset()
55+
.filter(
56+
id=settings.ERP_CONTEXT['DEPARTMENT']['PROCUREMENT_ID'])
57+
.select_related('group'))
4958

5059

5160
class ProductionDepartmentManager(models.Manager):
5261
def get_queryset(self):
5362
# TODO: To real
54-
return super().get_queryset().filter(id=4)
63+
return (super().get_queryset()
64+
.filter(
65+
id=settings.ERP_CONTEXT['DEPARTMENT']['PRODUCTION_ID'])
66+
.select_related('group'))
5567

5668

5769
class Department(models.Model):
@@ -72,6 +84,7 @@ class Department(models.Model):
7284

7385
# Custom managers
7486
objects = models.Manager()
87+
# TODO: Optimization
7588
distribution = DistributionDepartmentManager()
7689
process = ProcessDepartmentManager()
7790
procurement = ProcurementDepartmentManager()
@@ -86,5 +99,7 @@ def __str__(self):
8699

87100
@classmethod
88101
def get_departments_dict(cls):
89-
departments = {d.group.name: d.id for d in cls.objects.all()}
102+
departments = {
103+
d.group.name: d.id
104+
for d in cls.objects.all().select_related('group')}
90105
return departments

Core/models/work_order.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ class Meta:
4646

4747
@property
4848
def uid(self):
49+
# TODO: Optimization
50+
# May be a bottleneck for all using this strategy
51+
# cascade sql calls
4952
return '{}-{}'.format(self.work_order, self.index)
5053

5154
def __str__(self):
52-
return self.uid
55+
return '{}-{}'.format(self.work_order_id, self.index)

Core/serializers/work_order.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ class WorkOrderSerializer(serializers.ModelSerializer):
99

1010
class Meta:
1111
model = WorkOrder
12-
fields = '__all__'
12+
fields = ('id', 'uid', 'sell_type', 'pretty_sell_type',
13+
'client', 'project', 'product', 'count', 'finished')
1314

1415

1516
class SubWorkOrderSerializer(serializers.ModelSerializer):
1617
class Meta:
1718
model = SubWorkOrder
18-
fields = '__all__'
19+
fields = ('id', 'work_order', 'index', 'finished')

Core/tests/tests_models.py

Lines changed: 34 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,36 @@
1-
from unittest.mock import Mock, patch
1+
from unittest.mock import patch
22

33
from django.test import TestCase
4-
from django.contrib.auth.models import User, Group
4+
from model_mommy import mommy
55

6-
from Core.models import (WorkOrder, SubWorkOrder,
7-
UserInfo, Department)
6+
from Core.models import Department
87

98

109
class UserInfoTest(TestCase):
1110
def test_str(self):
12-
user_info = UserInfo()
13-
user = Mock(spec=User)
14-
user._state = Mock()
15-
user.username = 'username'
16-
user.first_name = 'first_name'
17-
user_info.user = user
11+
username = 'username'
12+
name = 'name'
13+
user_info = mommy.prepare(
14+
'UserInfo', user__username=username, user__first_name=name)
1815
expected_str = '{}(用户名:{})'.format(
19-
user.first_name, user.username)
16+
name, username)
2017
self.assertEqual(str(user_info), expected_str)
2118

2219

23-
def return_fake_department():
24-
department = Department()
25-
group = Mock(spec=Group)
26-
group._state = Mock()
27-
group.name = 'group'
28-
group.id = 5
29-
department.group = group
30-
31-
def _wrapper():
32-
return [department]
33-
return _wrapper
34-
35-
3620
class DepartmentTest(TestCase):
3721
def test_str(self):
38-
department = Department()
39-
group = Mock(spec=Group)
40-
group._state = Mock()
41-
group.name = 'group'
42-
department.group = group
43-
expected_str = 'group'
44-
self.assertEqual(str(department), expected_str)
22+
name = 'group'
23+
department = mommy.prepare('Department', group__name=name)
24+
self.assertEqual(str(department), name)
4525

46-
@patch('Core.models.auth.Department.objects.all', return_fake_department())
47-
def test_get_departments_dict(self):
48-
departments = {d.group.name: d.id for d in Department.objects.all()}
26+
@patch('Core.models.auth.Department.objects')
27+
def test_get_departments_dict(self, mocked_objects):
28+
objects = mommy.prepare('Department', _quantity=4)
29+
mocked_objects.all.return_value = mocked_objects
30+
mocked_objects.select_related.return_value = objects
31+
departments = {
32+
d.group.name: d.id
33+
for d in Department.objects.all().select_related('group')}
4934
for key, val in Department.get_departments_dict().items():
5035
self.assertIn(key, departments)
5136
self.assertEqual(val, departments[key])
@@ -54,28 +39,29 @@ def test_get_departments_dict(self):
5439
def test_special_department(self, mocked_queryset):
5540
mocked_queryset.return_value = mocked_queryset
5641
mocked_queryset.filter.return_value = mocked_queryset
57-
mocked_queryset.get.return_value = None
58-
self.assertEqual(Department.distribution.get(), None)
59-
self.assertEqual(Department.process.get(), None)
60-
self.assertEqual(Department.procurement.get(), None)
61-
self.assertEqual(Department.production.get(), None)
42+
mocked_queryset.select_related.return_value = mocked_queryset
43+
dep = mommy.prepare('Department')
44+
mocked_queryset.get.return_value = dep
45+
self.assertEqual(Department.distribution.get(), dep)
46+
self.assertEqual(Department.process.get(), dep)
47+
self.assertEqual(Department.procurement.get(), dep)
48+
self.assertEqual(Department.production.get(), dep)
6249

6350

6451
class WorkOrderTest(TestCase):
6552
def test_str(self):
66-
uid = '123456'
67-
work_order = WorkOrder(uid=uid)
53+
uid = '1'
54+
work_order = mommy.prepare('WorkOrder', uid=uid)
6855
self.assertEqual(str(work_order), uid)
6956

7057

7158
class SubWorkOrderTest(TestCase):
7259
def test_str(self):
73-
sub_order = SubWorkOrder()
74-
work_order = Mock(spec=WorkOrder)
75-
work_order._state = Mock()
76-
work_order.__str__ = Mock()
77-
work_order.__str__.return_value = '1'
78-
sub_order.work_order = work_order
79-
sub_order.index = 1
60+
sub_order = mommy.prepare('SubWorkOrder', work_order_id='1', index=1)
8061
expected_str = '1-1'
8162
self.assertEqual(str(sub_order), expected_str)
63+
64+
def test_uid(self):
65+
sub_order = mommy.prepare('SubWorkOrder', work_order__uid='5', index=1)
66+
expected_str = '5-1'
67+
self.assertEqual(sub_order.uid, expected_str)

Core/utils/renderers.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from rest_framework import renderers
2+
3+
4+
class BrowsableAPIWithoutFormRenderer(renderers.BrowsableAPIRenderer):
5+
def get_rendered_html_form(self, data, view, method, request):
6+
if method in ('PUT', 'POST', 'DELETE'):
7+
return None
8+
return super().get_rendered_html_form(data, view, method, request)
9+
10+
def get_context(self, *args, **kwargs):
11+
context = super().get_context(*args, **kwargs)
12+
context['display_edit_forms'] = False
13+
return context

Distribution/api/product.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111

1212
class ProductViewSet(viewsets.ModelViewSet):
1313
pagination_class = SmallResultsSetPagination
14-
queryset = Product.objects.all().order_by('-pk')
14+
queryset = (Product.objects.all().order_by('-pk')
15+
.prefetch_related('documents'))
1516
filter_class = ProductFilter
1617

1718
def get_serializer_class(self):

Distribution/serializers/product.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from rest_framework import serializers
22

33
from django.db import transaction
4+
from django.core.cache import cache
45

56
from Core.models import Department
67
from Core.utils.fsm import TransitionSerializerMixin
@@ -54,31 +55,34 @@ class Meta:
5455

5556
@staticmethod
5657
def get_departments():
57-
dep_distribution = Department.distribution.get()
58-
dep_process = Department.process.get()
59-
dep_procurement = Department.procurement.get()
60-
dep_production = Department.production.get()
61-
return dep_distribution, dep_process, dep_procurement, dep_production
58+
departments = cache.get('ProductSerializer_get_departments')
59+
if departments is None:
60+
departments = (
61+
Department.distribution.get(), Department.process.get(),
62+
Department.procurement.get(), Department.production.get())
63+
cache.set('ProductSerializer_get_departments', departments)
64+
return departments
6265

6366
def _get_documents(self, product, deps=None, from_distribution=True):
6467
documents = {}
6568
dep_distribution, *_deps = self.get_departments()
6669
if deps is None:
6770
deps = _deps
68-
candidates = BiddingDocument.objects.filter(product=product)
6971
if from_distribution:
7072
src = dep_distribution
7173
else:
7274
dst = dep_distribution
75+
candidates = product.documents.all()
7376
for dep in deps:
7477
if from_distribution:
7578
dst = dep
7679
else:
7780
src = dep
78-
doc = candidates.filter(src=src, dst=dst)
81+
doc = [_doc for _doc in candidates
82+
if _doc.src_id == src.id and _doc.dst_id == dst.id]
7983
if doc:
8084
doc = BiddingDocumentSimpleSerializer(
81-
doc.get(),
85+
doc[0],
8286
context={'request': self.context['request']}).data
8387
else:
8488
doc = None

Distribution/tests/tests_serializers.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ def test_get_documents_from_distribution(self, mocked_filter,
3030
mocked_filter.filter.return_value = mommy.prepare('BiddingDocument')
3131
serializer = product_serializers.ProductSerializer()
3232
serializer.context['request'] = Mock()
33-
documents = serializer.get_documents_from_distribution(None)
33+
product = mommy.prepare('Product')
34+
documents = serializer.get_documents_from_distribution(product)
3435
self.assertEqual(len(documents), 3)
3536

3637
@patch('Distribution.serializers.product.BiddingDocumentSimpleSerializer')
@@ -42,7 +43,8 @@ def test_get_documents_to_distribution(self, mocked_filter,
4243
_mocked_filter.filter.return_value = []
4344
mocked_filter.return_value = _mocked_filter
4445
serializer = product_serializers.ProductSerializer()
45-
documents = serializer.get_documents_to_distribution(None)
46+
product = mommy.prepare('Product')
47+
documents = serializer.get_documents_to_distribution(product)
4648
self.assertEqual(len(documents), 3)
4749

4850
def test_product_serializer_fields(self):
@@ -68,9 +70,10 @@ def test_product_simple_serializer_get_documents(
6870
mocked_filter.return_value = _mocked_filter
6971
serializer = product_serializers.ProductSimpleSerializer()
7072
serializer.context['department'] = Mock()
71-
documents = serializer.get_documents_from_distribution(None)
73+
product = mommy.prepare('Product')
74+
documents = serializer.get_documents_from_distribution(product)
7275
self.assertEqual(len(documents), 1)
73-
documents = serializer.get_documents_to_distribution(None)
76+
documents = serializer.get_documents_to_distribution(product)
7477
self.assertEqual(len(documents), 1)
7578

7679
def test_product_update_serializer_fields(self):

ERP/settings.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
'django.contrib.messages',
3434
'django.contrib.staticfiles',
3535

36+
'debug_toolbar',
3637
'django_filters',
3738
'rest_framework',
3839

@@ -53,6 +54,8 @@
5354
'django.contrib.auth.middleware.AuthenticationMiddleware',
5455
'django.contrib.messages.middleware.MessageMiddleware',
5556
'django.middleware.clickjacking.XFrameOptionsMiddleware',
57+
58+
'debug_toolbar.middleware.DebugToolbarMiddleware'
5659
]
5760

5861
ROOT_URLCONF = 'ERP.urls'
@@ -121,3 +124,13 @@
121124
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
122125
'PAGE_SIZE': 100,
123126
}
127+
128+
129+
ERP_CONTEXT = {
130+
'DEPARTMENT': {
131+
'DISTRIBUTION_ID': 1,
132+
'PROCESS_ID': 2,
133+
'PROCUREMENT_ID': 3,
134+
'PRODUCTION_ID': 4,
135+
},
136+
}

0 commit comments

Comments
 (0)