Skip to content

Commit eb1507a

Browse files
committedAug 15, 2019
[add] 增加项目管理
1 parent ce6483c commit eb1507a

29 files changed

+2434
-894
lines changed
 

‎apps/assets/api/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
from .domain import *
77
from .cmd_filter import *
88
from .asset_user import *
9+
from .project import *

‎apps/assets/api/project.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
from rest_framework_bulk import BulkModelViewSet
5+
from common.mixins import IDInFilterMixin
6+
from common.utils import get_logger
7+
from ..models import Project, Asset
8+
from common.permissions import IsOrgAdmin, IsOrgAdminOrAppUser
9+
from .. import serializers
10+
from rest_framework import generics, viewsets
11+
from rest_framework.pagination import LimitOffsetPagination
12+
from django.shortcuts import get_object_or_404
13+
from rest_framework_extensions.cache.mixins import CacheResponseMixin
14+
from rest_framework.response import Response
15+
16+
logger = get_logger(__file__)
17+
18+
__all__ = [
19+
'ProjectViewSet', 'AssetUpdateProjectApi', 'ProjectUpdateApi',
20+
'ProjectAssetsListView', 'ProjectAssetsViewSet',
21+
]
22+
23+
24+
class ProjectViewSet(IDInFilterMixin, BulkModelViewSet):
25+
"""
26+
project api set, for add,delete,update,list,retrieve resource
27+
"""
28+
29+
filter_fields = ("name", "type")
30+
search_fields = filter_fields
31+
queryset = Project.objects.all()
32+
serializer_class = serializers.ProjectSerializer
33+
permission_classes = (IsOrgAdmin,)
34+
pagination_class = LimitOffsetPagination
35+
36+
def get_queryset(self):
37+
queryset = super().get_queryset().all()
38+
return queryset
39+
40+
41+
class ProjectAssetsViewSet(viewsets.ReadOnlyModelViewSet):
42+
"""
43+
project assets api set, for add,delete,update,list,retrieve resource
44+
"""
45+
filter_fields = ("id", "name")
46+
search_fields = filter_fields
47+
queryset = Project.objects.all()
48+
serializer_class = serializers.ProjectAssetsSerializer
49+
permission_classes = (IsOrgAdminOrAppUser,)
50+
pagination_class = LimitOffsetPagination
51+
52+
def get_queryset(self):
53+
queryset = super().get_queryset().all()
54+
return queryset
55+
56+
57+
class AssetUpdateProjectApi(generics.RetrieveUpdateAPIView):
58+
"""Asset update it's project api"""
59+
queryset = Asset.objects.all()
60+
serializer_class = serializers.AssetUpdateProjectSerializer
61+
permission_classes = (IsOrgAdmin,)
62+
63+
def perform_update(self, serializer):
64+
# 新增操作项目主机日志详情
65+
projects_old = [project.name for project in serializer.instance.projects.all()]
66+
instance = serializer.save()
67+
projects_update = [project.name for project in instance.projects.all()]
68+
add_projects = ','.join(set(projects_update).difference(set(projects_old)))
69+
del_projects = ','.join(set(projects_old).difference(set(projects_update)))
70+
changed_data = ''
71+
if add_projects:
72+
changed_data = "Add:{}".format(add_projects)
73+
if del_projects:
74+
changed_data = "Del:{}".format(del_projects)
75+
if add_projects and del_projects:
76+
changed_data = "Add:{}; Del:{}".format(add_projects, del_projects)
77+
# signal_resource_changed.send(sender=instance, action='update_projects', resource=str(instance), changed=changed_data)
78+
79+
80+
class ProjectUpdateApi(generics.RetrieveUpdateAPIView):
81+
"""Project, update it's asset member"""
82+
queryset = Project.objects.all()
83+
serializer_class = serializers.ProjectUpdateSerializer
84+
permission_classes = (IsOrgAdmin,)
85+
86+
def perform_update(self, serializer):
87+
# 新增操作项目主机日志详情
88+
assets_old = [asset.ip for asset in serializer.instance.assets.all()]
89+
instance = serializer.save()
90+
assets_update = [asset.ip for asset in instance.assets.all()]
91+
add_assets = ','.join(set(assets_update).difference(set(assets_old)))
92+
del_assets = ','.join(set(assets_old).difference(set(assets_update)))
93+
changed_data = ''
94+
if add_assets:
95+
changed_data = "Add:{}".format(add_assets)
96+
if del_assets:
97+
changed_data = "Del:{}".format(del_assets)
98+
if add_assets and del_assets:
99+
changed_data = "Add:{}; Del:{}".format(add_assets, del_assets)
100+
# signal_resource_changed.send(sender=instance, action='update_assets', resource=str(instance), changed=changed_data)
101+
102+
103+
class ProjectAssetsListView(generics.ListAPIView):
104+
permission_classes = (IsOrgAdmin,)
105+
serializer_class = serializers.AssetSimpleSerializer
106+
pagination_class = LimitOffsetPagination
107+
filter_fields = ("hostname", "ip", "environment")
108+
http_method_names = ['get']
109+
search_fields = filter_fields
110+
111+
def get_object(self):
112+
pk = self.kwargs.get('pk')
113+
return get_object_or_404(Project, pk=pk)
114+
115+
def get_queryset(self):
116+
project = self.get_object()
117+
return project.get_related_assets()

‎apps/assets/forms/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
from .user import *
66
from .domain import *
77
from .cmd_filter import *
8+
from .project import *

‎apps/assets/forms/project.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
from django import forms
5+
from django.utils.translation import gettext_lazy as _
6+
from ..models import Asset, Project
7+
from orgs.mixins import OrgModelForm
8+
from orgs.utils import current_org
9+
10+
11+
class ProjectCreateForm(OrgModelForm):
12+
def __init__(self, *args, **kwargs):
13+
super().__init__(*args, **kwargs)
14+
sa_users_field = self.fields.get('sa_users')
15+
dev_users_field = self.fields.get('dev_users')
16+
scrum_master_field = self.fields.get('scrum_master')
17+
if hasattr(dev_users_field, 'queryset'):
18+
dev_users_field.queryset = current_org.get_org_users()
19+
if hasattr(sa_users_field, 'queryset'):
20+
sa_users_field.queryset = current_org.get_org_users()
21+
if hasattr(scrum_master_field, 'queryset'):
22+
scrum_master_field.queryset = current_org.get_org_users()
23+
24+
def clean(self):
25+
cleaned_data = super().clean()
26+
level = cleaned_data.get("level")
27+
28+
if int(level) > 4 or int(level) < 0:
29+
raise forms.ValidationError('level must in 0-4')
30+
31+
class Meta:
32+
model = Project
33+
fields = [
34+
"name", "type", "domain_name", "git_address", "port", "dev_users", "sa_users", "scrum_master", "comment", "parent", "level"
35+
]
36+
widgets = {
37+
'sa_users': forms.SelectMultiple(attrs={
38+
'class': 'select2', 'data-placeholder': _('Sa Users')
39+
}),
40+
'dev_users': forms.SelectMultiple(attrs={
41+
'class': 'select2', 'data-placeholder': _('Dev Users')
42+
}),
43+
'type': forms.Select(attrs={
44+
'class': 'select2', 'data-placeholder': _('Project Type')
45+
}),
46+
'parent': forms.Select(attrs={
47+
'class': 'select2', 'data-placeholder': _('Parent')
48+
}),
49+
'scrum_master': forms.Select(attrs={
50+
'class': 'select2', 'data-placeholder': _('Scrum Master')
51+
}),
52+
'port': forms.TextInput(),
53+
}
54+
55+
help_texts = {
56+
'level': '0-4, 0 top level'
57+
}
58+
59+
60+
class ProjectForm(forms.ModelForm):
61+
# See AdminUserForm comment same it
62+
assets = forms.ModelMultipleChoiceField(
63+
queryset=Asset.objects.all(),
64+
label=_('Asset'),
65+
required=False,
66+
widget=forms.SelectMultiple(
67+
attrs={'class': 'select2', 'data-placeholder': _('Select assets')})
68+
)
69+
70+
def __init__(self, *args, **kwargs):
71+
if kwargs.get('instance', None):
72+
initial = kwargs.get('initial', {})
73+
initial['assets'] = kwargs['instance'].assets.all()
74+
super(ProjectForm, self).__init__(*args, **kwargs)
75+
76+
def _save_m2m(self):
77+
super(ProjectForm, self)._save_m2m()
78+
assets = self.cleaned_data['assets']
79+
self.instance.assets.clear()
80+
self.instance.assets.add(*tuple(assets))
81+
82+
class Meta:
83+
model = Project
84+
fields = [
85+
"name", "type", "domain_name", "git_address", "port", "dev_users", "sa_users", "scrum_master", "comment", "level"
86+
]
87+
help_texts = {
88+
'name': '* required',
89+
'type': '* required',
90+
'level': '0-4, 0 lower level'
91+
}

‎apps/assets/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
from .cmd_filter import *
99
from .utils import *
1010
from .authbook import *
11+
from .project import *

‎apps/assets/models/asset.py

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,29 @@
1515

1616
from .user import AdminUser, SystemUser
1717
from orgs.mixins import OrgModelMixin, OrgManager
18+
from .project import Project
1819

1920
__all__ = ['Asset']
2021
logger = logging.getLogger(__name__)
2122

23+
# Important
24+
PLATFORM_CHOICES = (
25+
('Linux', 'Linux'),
26+
('Unix', 'Unix'),
27+
('MacOS', 'MacOS'),
28+
('BSD', 'BSD'),
29+
('Windows', 'Windows'),
30+
('Windows2016', 'Windows(2016)'),
31+
('Other', 'Other'),
32+
)
33+
ENV_CHOICES = (
34+
('DEV', 'dev'),
35+
('TEST', 'test'),
36+
('DEMO', 'demo'),
37+
('TDEMO', 'tdemo'),
38+
('PROD', 'prod'),
39+
('Other', 'other'),
40+
)
2241

2342
def default_cluster():
2443
from .cluster import Cluster
@@ -49,15 +68,8 @@ def valid(self):
4968

5069
class Asset(OrgModelMixin):
5170
# Important
52-
PLATFORM_CHOICES = (
53-
('Linux', 'Linux'),
54-
('Unix', 'Unix'),
55-
('MacOS', 'MacOS'),
56-
('BSD', 'BSD'),
57-
('Windows', 'Windows'),
58-
('Windows2016', 'Windows(2016)'),
59-
('Other', 'Other'),
60-
)
71+
PLATFORM_CHOICES = PLATFORM_CHOICES
72+
ENV_CHOICES = ENV_CHOICES
6173

6274
PROTOCOL_SSH = 'ssh'
6375
PROTOCOL_RDP = 'rdp'
@@ -118,6 +130,13 @@ class Asset(OrgModelMixin):
118130
(REACHABLE, _('Reachable')),
119131
(UNKNOWN, _("Unknown")),
120132
)
133+
environment = models.CharField(max_length=32, choices=ENV_CHOICES, default='DEV', verbose_name=_('Environment'))
134+
# Project
135+
projects = models.ManyToManyField(Project, blank=True, verbose_name=_('Projects'), related_name='assets', )
136+
137+
@property
138+
def projects_amount(self):
139+
return len(self.projects.all())
121140

122141
def __str__(self):
123142
return '{0.hostname}({0.ip})'.format(self)

‎apps/assets/models/project.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
from __future__ import unicode_literals
5+
6+
from django.db import models
7+
import logging
8+
from django.utils.translation import ugettext_lazy as _
9+
import uuid
10+
from django.core.cache import cache
11+
12+
__all__ = ['Project', 'TYPE_CHOICES']
13+
logger = logging.getLogger(__name__)
14+
15+
TYPE_CHOICES = (
16+
('Java', 'java'),
17+
('React', 'react'),
18+
('Cpp', 'c++'),
19+
('Python', 'python'),
20+
('Middleware', 'Middleware'),
21+
('Other', 'Other'),
22+
)
23+
24+
25+
class Project(models.Model):
26+
TYPE_CHOICES =TYPE_CHOICES
27+
28+
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
29+
name = models.CharField(max_length=64, unique=True, verbose_name=_('Name'))
30+
31+
parent = models.ForeignKey('self', null=True, blank=True, related_name="children", on_delete=models.SET_NULL)
32+
33+
domain_name = models.CharField(max_length=64, blank=True, verbose_name=_('Domain Name'))
34+
git_address = models.CharField(max_length=128, blank=True, verbose_name=_('Git Address'))
35+
port = models.IntegerField(blank=True, null=True, verbose_name=_('Open port'))
36+
type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True,
37+
default='Java', verbose_name=_('Project type'), )
38+
dev_users = models.ManyToManyField(
39+
'users.User', related_name='users1',
40+
blank=True, verbose_name=_('Dev Users')
41+
)
42+
sa_users = models.ManyToManyField(
43+
'users.User', related_name='users2',
44+
blank=True, verbose_name=_('Sa Users')
45+
)
46+
scrum_master = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, verbose_name=_('Scrum Master'))
47+
created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
48+
updated_by = models.CharField(max_length=32, blank=True, verbose_name=_('Updated by'))
49+
date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created'))
50+
date_updated = models.DateTimeField(auto_now=True, null=True, verbose_name=_('Date updated'))
51+
comment = models.TextField(blank=True, verbose_name=_('Comment'))
52+
level = models.IntegerField(default=0, verbose_name=_('Level'))
53+
54+
def __unicode__(self):
55+
return self.name
56+
__str__ = __unicode__
57+
58+
@property
59+
def all_sa_users(self):
60+
return [user.name for user in self.sa_users.all()]
61+
62+
@property
63+
def all_dev_users(self):
64+
return [user.name for user in self.dev_users.all()]
65+
66+
def get_related_assets(self):
67+
assets = self.assets.all()
68+
return assets
69+
70+
def get_related_assets_ip(self):
71+
assets = self.assets.all()
72+
return [{'ip': asset.ip, 'environment': asset.environment} for asset in assets]
73+
74+
@property
75+
def all_assets_ip(self):
76+
return self.get_related_assets_ip()
77+
78+
class Meta:
79+
ordering = ['name']
80+

‎apps/assets/serializers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
from .domain import *
1010
from .cmd_filter import *
1111
from .asset_user import *
12+
from .project import *

‎apps/assets/serializers/asset.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,4 @@ class Meta:
8383
class AssetSimpleSerializer(serializers.ModelSerializer):
8484
class Meta:
8585
model = Asset
86-
fields = ['id', 'hostname', 'port', 'ip', 'connectivity']
86+
fields = ['id', 'hostname', 'port', 'ip', 'connectivity', 'environment', 'hardware_info']

0 commit comments

Comments
 (0)