提交 f8e248f0 编写于 作者: X xinwen 提交者: baltery

feat(ticket): 调整申请资产工单

上级 b3317304
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from ..mixins.api import SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin
from ..mixins.api import (
SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin, PaginatedResponseMixin
)
class JmsGenericViewSet(SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin, GenericViewSet):
class JmsGenericViewSet(SerializerMixin2,
QuerySetMixin,
ExtraFilterFieldsMixin,
PaginatedResponseMixin,
GenericViewSet):
pass
class JMSModelViewSet(SerializerMixin2, QuerySetMixin, ExtraFilterFieldsMixin, ModelViewSet):
class JMSModelViewSet(SerializerMixin2,
QuerySetMixin,
ExtraFilterFieldsMixin,
PaginatedResponseMixin,
ModelViewSet):
pass
......@@ -67,6 +67,17 @@ class ExtraFilterFieldsMixin:
return queryset
class PaginatedResponseMixin:
def get_paginated_response_with_query_set(self, queryset):
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin):
pass
......
......@@ -11,6 +11,8 @@ import time
import ipaddress
import psutil
from .timezone import dt_formater
UUID_PATTERN = re.compile(r'\w{8}(-\w{4}){3}-\w{12}')
ipip_db = None
......
import datetime
import pytz
from django.utils import timezone as dj_timezone
from rest_framework.fields import DateTimeField
max = datetime.datetime.max.replace(tzinfo=datetime.timezone.utc)
def astimezone(dt: datetime.datetime, tzinfo: pytz.tzinfo.DstTzInfo):
assert dj_timezone.is_aware(dt)
return tzinfo.normalize(dt.astimezone(tzinfo))
def as_china_cst(dt: datetime.datetime):
return astimezone(dt, pytz.timezone('Asia/Shanghai'))
def as_current_tz(dt: datetime.datetime):
return astimezone(dt, dj_timezone.get_current_timezone())
def utcnow():
return dj_timezone.now()
def now():
return as_current_tz(utcnow())
_rest_dt_field = DateTimeField()
dt_parser = _rest_dt_field.to_internal_value
dt_formater = _rest_dt_field.to_representation
......@@ -96,3 +96,5 @@ XPACK_LICENSE_IS_VALID = DYNAMIC.XPACK_LICENSE_IS_VALID
LOGO_URLS = DYNAMIC.LOGO_URLS
CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED = CONFIG.CHANGE_AUTH_PLAN_SECURE_MODE_ENABLED
DATETIME_DISPLAY_FORMAT = '%Y-%m-%d %H:%M:%S'
......@@ -62,7 +62,6 @@ class OrgModelMixin(models.Model):
org = get_current_org()
if org is None:
return super().save(*args, **kwargs)
if org.is_real() or org.is_system():
self.org_id = org.id
elif org.is_default():
......
from collections import namedtuple
from django.db.transaction import atomic
from django.db.models import F
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.request import Request
from orgs.models import Organization, ROLE as ORG_ROLE
from users.models.user import User
from common.const.http import POST, GET
from common.drf.api import JMSModelViewSet
from common.permissions import IsValidUser
from common.utils.django import get_object_or_none
from common.utils.timezone import dt_parser
from common.drf.serializers import EmptySerializer
from perms.models.asset_permission import AssetPermission, Asset
from assets.models.user import SystemUser
from ..exceptions import (
ConfirmedAssetsChanged, ConfirmedSystemUserChanged,
TicketClosed, TicketActionYet, NotHaveConfirmedAssets,
TicketClosed, TicketActionAlready, NotHaveConfirmedAssets,
NotHaveConfirmedSystemUser
)
from .. import serializers
......@@ -25,15 +26,15 @@ from ..permissions import IsAssignee
class RequestAssetPermTicketViewSet(JMSModelViewSet):
queryset = Ticket.objects.filter(type=Ticket.TYPE_REQUEST_ASSET_PERM)
queryset = Ticket.origin_objects.filter(type=Ticket.TYPE_REQUEST_ASSET_PERM)
serializer_classes = {
'default': serializers.RequestAssetPermTicketSerializer,
'approve': EmptySerializer,
'reject': EmptySerializer,
'assignees': serializers.OrgAssigneeSerializer,
'assignees': serializers.AssigneeSerializer,
}
permission_classes = (IsValidUser,)
filter_fields = ['status', 'title', 'action', 'user_display']
filter_fields = ['status', 'title', 'action', 'user_display', 'org_id']
search_fields = ['user_display', 'title']
def _check_can_set_action(self, instance, action):
......@@ -41,49 +42,39 @@ class RequestAssetPermTicketViewSet(JMSModelViewSet):
raise TicketClosed(detail=_('Ticket closed'))
if instance.action == action:
action_display = dict(instance.ACTION_CHOICES).get(action)
raise TicketActionYet(detail=_('Ticket has %s') % action_display)
raise TicketActionAlready(detail=_('Ticket has %s') % action_display)
@action(detail=False, methods=[GET], permission_classes=[IsValidUser])
def assignees(self, request, *args, **kwargs):
org_mapper = {}
UserTuple = namedtuple('UserTuple', ('id', 'name', 'username'))
def assignees(self, request: Request, *args, **kwargs):
user = request.user
superusers = User.objects.filter(role=User.ROLE.ADMIN)
admins_with_org = User.objects.filter(related_admin_orgs__users=user).annotate(
org_id=F('related_admin_orgs__id'), org_name=F('related_admin_orgs__name')
)
for user in admins_with_org:
org_id = user.org_id
if org_id not in org_mapper:
org_mapper[org_id] = {
'org_name': user.org_name,
'org_admins': set() # 去重
}
org_mapper[org_id]['org_admins'].add(UserTuple(user.id, user.name, user.username))
result = [
{
'org_name': _('Superuser'),
'org_admins': set(UserTuple(user.id, user.name, user.username)
for user in superusers)
}
]
for org in org_mapper.values():
result.append(org)
serializer_class = self.get_serializer_class()
serilizer = serializer_class(instance=result, many=True)
return Response(data=serilizer.data)
org_id = request.query_params.get('org_id', Organization.DEFAULT_ID)
q = Q(role=User.ROLE.ADMIN)
if org_id != Organization.DEFAULT_ID:
q |= Q(m2m_org_members__role=ORG_ROLE.ADMIN, orgs__id=org_id, orgs__members=user)
org_admins = User.objects.filter(q).distinct()
return self.get_paginated_response_with_query_set(org_admins)
def _get_extra_comment(self, instance):
meta = instance.meta
ips = ', '.join(meta.get('ips', []))
confirmed_assets = ', '.join(meta.get('confirmed_assets', []))
return f'''
{_('IP group')}: {ips}
{_('Hostname')}: {meta.get('hostname', '')}
{_('System user')}: {meta.get('system_user', '')}
{_('Confirmed assets')}: {confirmed_assets}
{_('Confirmed system user')}: {meta.get('confirmed_system_user', '')}
'''
@action(detail=True, methods=[POST], permission_classes=[IsAssignee, IsValidUser])
def reject(self, request, *args, **kwargs):
instance = self.get_object()
action = instance.ACTION_REJECT
self._check_can_set_action(instance, action)
instance.perform_action(action, request.user)
instance.perform_action(action, request.user, self._get_extra_comment(instance))
return Response()
@action(detail=True, methods=[POST], permission_classes=[IsAssignee, IsValidUser])
......@@ -109,29 +100,33 @@ class RequestAssetPermTicketViewSet(JMSModelViewSet):
if system_user is None:
raise ConfirmedSystemUserChanged(detail=_('Confirmed system-user changed'))
self._create_asset_permission(instance, assets, system_user)
self._create_asset_permission(instance, assets, system_user, request.user)
return Response({'detail': _('Succeed')})
def _create_asset_permission(self, instance: Ticket, assets, system_user):
def _create_asset_permission(self, instance: Ticket, assets, system_user, user):
meta = instance.meta
request = self.request
ap_kwargs = {
'name': meta.get('name', ''),
'name': _('From request ticket: {} {}').format(instance.user_display, instance.id),
'created_by': self.request.user.username,
'comment': _('{} request assets, approved by {}').format(instance.user_display,
instance.assignee_display)
instance.assignees_display)
}
date_start = meta.get('date_start')
date_expired = meta.get('date_expired')
date_start = dt_parser(meta.get('date_start'))
date_expired = dt_parser(meta.get('date_expired'))
if date_start:
ap_kwargs['date_start'] = date_start
if date_expired:
ap_kwargs['date_expired'] = date_expired
with atomic():
instance.perform_action(instance.ACTION_APPROVE, request.user)
instance.perform_action(instance.ACTION_APPROVE,
request.user,
self._get_extra_comment(instance))
ap = AssetPermission.objects.create(**ap_kwargs)
ap.system_users.add(system_user)
ap.assets.add(*assets)
ap.users.add(user)
return ap
......@@ -11,7 +11,7 @@ from .. import serializers, models, mixins
class TicketViewSet(mixins.TicketMixin, viewsets.ModelViewSet):
serializer_class = serializers.TicketSerializer
queryset = models.Ticket.objects.all()
queryset = models.Ticket.origin_objects.all()
permission_classes = (IsValidUser,)
filter_fields = ['status', 'title', 'action', 'user_display']
search_fields = ['user_display', 'title']
......
......@@ -21,5 +21,9 @@ class TicketClosed(JMSException):
pass
class TicketActionYet(JMSException):
class TicketActionAlready(JMSException):
pass
class OrgIdRequiredException(JMSException):
pass
# Generated by Django 2.2.10 on 2020-07-23 04:32
# Generated by Django 2.2.10 on 2020-07-28 03:46
from django.db import migrations, models
import django.db.models.manager
class Migration(migrations.Migration):
......@@ -10,6 +11,17 @@ class Migration(migrations.Migration):
]
operations = [
migrations.AlterModelManagers(
name='ticket',
managers=[
('origin_objects', django.db.models.manager.Manager()),
],
),
migrations.AddField(
model_name='ticket',
name='org_id',
field=models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization'),
),
migrations.AlterField(
model_name='ticket',
name='type',
......
......@@ -6,11 +6,12 @@ from .models import Ticket
class TicketMixin:
def get_queryset(self):
queryset = super().get_queryset()
assign = self.request.GET.get('assign', None)
if assign is None:
queryset = Ticket.get_related_tickets(self.request.user)
queryset = Ticket.get_related_tickets(self.request.user, queryset)
elif assign in ['1']:
queryset = Ticket.get_assigned_tickets(self.request.user)
queryset = Ticket.get_assigned_tickets(self.request.user, queryset)
else:
queryset = Ticket.get_my_tickets(self.request.user)
queryset = Ticket.get_my_tickets(self.request.user, queryset)
return queryset
......@@ -7,11 +7,12 @@ from django.utils.translation import ugettext_lazy as _
from common.mixins.models import CommonModelMixin
from common.fields.model import JsonDictTextField
from orgs.mixins.models import OrgModelMixin
__all__ = ['Ticket', 'Comment']
class Ticket(CommonModelMixin):
class Ticket(OrgModelMixin, CommonModelMixin):
STATUS_OPEN = 'open'
STATUS_CLOSED = 'closed'
STATUS_CHOICES = (
......@@ -46,6 +47,8 @@ class Ticket(CommonModelMixin):
status = models.CharField(choices=STATUS_CHOICES, max_length=16, default='open')
action = models.CharField(choices=ACTION_CHOICES, max_length=16, default='', blank=True)
origin_objects = models.Manager()
def __str__(self):
return '{}: {}'.format(self.user_display, self.title)
......@@ -79,13 +82,15 @@ class Ticket(CommonModelMixin):
self.status = status
self.save()
def create_action_comment(self, action, user):
def create_action_comment(self, action, user, extra_comment=None):
action_display = dict(self.ACTION_CHOICES).get(action)
body = '{} {} {}'.format(user, action_display, _("this ticket"))
if extra_comment is not None:
body += extra_comment
self.comments.create(body=body, user=user, user_display=str(user))
def perform_action(self, action, user):
self.create_action_comment(action, user)
def perform_action(self, action, user, extra_comment=None):
self.create_action_comment(action, user, extra_comment)
self.action = action
self.status = self.STATUS_CLOSED
self.assignee = user
......
from itertools import chain
from rest_framework import serializers
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.urls import reverse
from django.db.models import Q
from common.utils.timezone import dt_parser, dt_formater
from orgs.utils import tmp_to_root_org
from orgs.models import Organization, ROLE as ORG_ROLE
from assets.models.asset import Asset
from users.models.user import User
from ..models import Ticket
......@@ -22,9 +29,8 @@ class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
source='meta.confirmed_assets',
default=list, required=False,
label=_('Confirmed assets'))
confirmed_system_user = serializers.ListField(child=serializers.UUIDField(),
source='meta.confirmed_system_user',
default=list, required=False,
confirmed_system_user = serializers.UUIDField(source='meta.confirmed_system_user',
default='', required=False,
label=_('Confirmed system user'))
assets_waitlist_url = serializers.SerializerMethodField()
system_user_waitlist_url = serializers.SerializerMethodField()
......@@ -36,7 +42,7 @@ class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
'status', 'action', 'date_created', 'date_updated', 'system_user_waitlist_url',
'type', 'type_display', 'action_display', 'ips', 'confirmed_assets',
'date_start', 'date_expired', 'confirmed_system_user', 'hostname',
'assets_waitlist_url', 'system_user'
'assets_waitlist_url', 'system_user', 'org_id'
]
m2m_fields = [
'user', 'user_display', 'assignees', 'assignees_display',
......@@ -52,26 +58,44 @@ class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
extra_kwargs = {
'status': {'label': _('Status')},
'action': {'label': _('Action')},
'user_display': {'label': _('User')}
'user_display': {'label': _('User')},
'org_id': {'required': True}
}
def validate_assignees(self, assignees):
def validate(self, attrs):
org_id = attrs.get('org_id')
assignees = attrs.get('assignees')
instance = self.instance
if instance is not None:
if org_id and not assignees:
assignees = list(instance.assignees.all())
elif assignees and not org_id:
org_id = instance.org_id
elif assignees and org_id:
pass
else:
return attrs
user = self.context['request'].user
org = Organization.get_instance(org_id)
if org is None:
raise serializers.ValidationError(_('Invalid `org_id`'))
count = User.objects.filter(Q(related_admin_orgs__users=user) | Q(role=User.ROLE.ADMIN)).filter(
id__in=[assignee.id for assignee in assignees]).distinct().count()
q = Q(role=User.ROLE.ADMIN)
if not org.is_default():
q |= Q(m2m_org_members__role=ORG_ROLE.ADMIN, orgs__id=org_id, orgs__members=user)
q &= Q(id__in=[assignee.id for assignee in assignees])
count = User.objects.filter(q).distinct().count()
if count != len(assignees):
raise serializers.ValidationError(_('Must be organization admin or superuser'))
return assignees
raise serializers.ValidationError(_('Field `assignees` must be organization admin or superuser'))
return attrs
def get_system_user_waitlist_url(self, instance: Ticket):
if not self._is_assignee(instance):
return None
meta = instance.meta
url = reverse('api-assets:system-user-list')
query = meta.get('system_user', '')
return '{}?search={}'.format(url, query)
return reverse('api-assets:system-user-list')
def get_assets_waitlist_url(self, instance: Ticket):
if not self._is_assignee(instance):
......@@ -81,37 +105,106 @@ class RequestAssetPermTicketSerializer(serializers.ModelSerializer):
query = ''
meta = instance.meta
ips = meta.get('ips', [])
hostname = meta.get('hostname')
if ips:
query = '?ips=%s' % ','.join(ips)
elif hostname:
if hostname:
query = '?search=%s' % hostname
return asset_api + query
def _recommend_assets(self, data, instance):
confirmed_assets = data.get('confirmed_assets')
if not confirmed_assets and self._is_assignee(instance):
ips = data.get('ips')
hostname = data.get('hostname')
limit = 5
q = Q(id=None)
if ips:
limit = len(ips) + 2
q |= Q(ip__in=ips)
if hostname:
q |= Q(hostname__icontains=hostname)
data['confirmed_assets'] = list(
map(lambda x: str(x), chain(*Asset.objects.filter(q)[0: limit].values_list('id'))))
def to_representation(self, instance):
data = super().to_representation(instance)
self._recommend_assets(data, instance)
return data
def _create_body(self, validated_data):
meta = validated_data['meta']
type = dict(Ticket.TYPE_CHOICES).get(validated_data.get('type', ''))
date_start = dt_parser(meta.get('date_start')).strftime(settings.DATETIME_DISPLAY_FORMAT)
date_expired = dt_parser(meta.get('date_expired')).strftime(settings.DATETIME_DISPLAY_FORMAT)
validated_data['body'] = _('''
Type: {type}<br>
User: {username}<br>
Ip group: {ips}<br>
Hostname: {hostname}<br>
System user: {system_user}<br>
Date start: {date_start}<br>
Date expired: {date_expired}<br>
''').format(
type=type,
username=validated_data.get('user', ''),
ips=', '.join(meta.get('ips', [])),
hostname=meta.get('hostname', ''),
system_user=meta.get('system_user', ''),
date_start=date_start,
date_expired=date_expired
)
def create(self, validated_data):
# `type` 与 `user` 用户不可提交,
validated_data['type'] = self.Meta.model.TYPE_REQUEST_ASSET_PERM
validated_data['user'] = self.context['request'].user
# `confirmed` 相关字段只能审批人修改,所以创建时直接清理掉
self._pop_confirmed_fields()
self._create_body(validated_data)
return super().create(validated_data)
def save(self, **kwargs):
"""
做了一些数据转换
"""
meta = self.validated_data.get('meta', {})
org_id = self.validated_data.get('org_id')
if org_id is not None and org_id == Organization.DEFAULT_ID:
self.validated_data['org_id'] = ''
# 时间的转换,好烦😭,可能有更好的办法吧
date_start = meta.get('date_start')
if date_start:
meta['date_start'] = date_start.strftime('%Y-%m-%d %H:%M:%S%z')
meta['date_start'] = dt_formater(date_start)
date_expired = meta.get('date_expired')
if date_expired:
meta['date_expired'] = date_expired.strftime('%Y-%m-%d %H:%M:%S%z')
return super().save(**kwargs)
meta['date_expired'] = dt_formater(date_expired)
# UUID 的转换
confirmed_system_user = meta.get('confirmed_system_user')
if confirmed_system_user:
meta['confirmed_system_user'] = str(confirmed_system_user)
confirmed_assets = meta.get('confirmed_assets')
if confirmed_assets:
new_confirmed_assets = []
for asset in confirmed_assets:
new_confirmed_assets.append(str(asset))
meta['confirmed_assets'] = new_confirmed_assets
with tmp_to_root_org():
return super().save(**kwargs)
def update(self, instance, validated_data):
new_meta = validated_data['meta']
if not self._is_assignee(instance):
self._pop_confirmed_fields()
# Json 字段保存的坑😭
old_meta = instance.meta
meta = {}
meta.update(old_meta)
......@@ -134,8 +227,3 @@ class AssigneeSerializer(serializers.Serializer):
id = serializers.UUIDField()
name = serializers.CharField()
username = serializers.CharField()
class OrgAssigneeSerializer(serializers.Serializer):
org_name = serializers.CharField()
org_admins = AssigneeSerializer(many=True)
from django.test import TestCase
import datetime
# Create your tests here.
from common.utils.timezone import now
from django.urls import reverse
from rest_framework.test import APITestCase
from rest_framework import status
from orgs.models import Organization, OrganizationMember, ROLE as ORG_ROLE
from orgs.utils import set_current_org
from users.models.user import User
from assets.models import Asset, AdminUser, SystemUser
class TicketTest(APITestCase):
def setUp(self):
Organization.objects.bulk_create([
Organization(name='org-01'),
Organization(name='org-02'),
Organization(name='org-03'),
])
org_01, org_02, org_03 = Organization.objects.all()
self.org_01, self.org_02, self.org_03 = org_01, org_02, org_03
set_current_org(org_01)
AdminUser.objects.bulk_create([
AdminUser(name='au-01', username='au-01'),
AdminUser(name='au-02', username='au-02'),
AdminUser(name='au-03', username='au-03'),
])
SystemUser.objects.bulk_create([
SystemUser(name='su-01', username='su-01'),
SystemUser(name='su-02', username='su-02'),
SystemUser(name='su-03', username='su-03'),
])
admin_users = AdminUser.objects.all()
Asset.objects.bulk_create([
Asset(hostname='asset-01', ip='192.168.1.1', public_ip='192.168.1.1', admin_user=admin_users[0]),
Asset(hostname='asset-02', ip='192.168.1.2', public_ip='192.168.1.2', admin_user=admin_users[0]),
Asset(hostname='asset-03', ip='192.168.1.3', public_ip='192.168.1.3', admin_user=admin_users[0]),
])
new_user = User.objects.create
new_org_memeber = OrganizationMember.objects.create
u = new_user(name='user-01', username='user-01', email='user-01@jms.com')
new_org_memeber(org=org_01, user=u, role=ORG_ROLE.USER)
new_org_memeber(org=org_02, user=u, role=ORG_ROLE.USER)
self.user_01 = u
u = new_user(name='org-admin-01', username='org-admin-01', email='org-admin-01@jms.com')
new_org_memeber(org=org_01, user=u, role=ORG_ROLE.ADMIN)
self.org_admin_01 = u
u = new_user(name='org-admin-02', username='org-admin-02', email='org-admin-02@jms.com')
new_org_memeber(org=org_02, user=u, role=ORG_ROLE.ADMIN)
self.org_admin_02 = u
def test_create_request_asset_perm(self):
url = reverse('api-tickets:ticket-request-asset-perm')
ticket_url = reverse('api-tickets:ticket')
self.client.force_login(self.user_01)
date_start = now()
date_expired = date_start + datetime.timedelta(days=7)
data = {
"title": "request-01",
"ips": [
"192.168.1.1"
],
"date_start": date_start,
"date_expired": date_expired,
"hostname": "",
"system_user": "",
"org_id": self.org_01.id,
"assignees": [
str(self.org_admin_01.id),
str(self.org_admin_02.id),
]
}
self.client.post(data)
self.client.force_login(self.org_admin_01)
res = self.client.get(ticket_url, params={'assgin': 1})
......@@ -7,7 +7,7 @@ from .. import api
app_name = 'tickets'
router = BulkRouter()
# router.register('tickets/request-asset-perm', api.RequestAssetPermTicketViewSet, 'ticket-request-asset-perm')
router.register('tickets/request-asset-perm', api.RequestAssetPermTicketViewSet, 'ticket-request-asset-perm')
router.register('tickets', api.TicketViewSet, 'ticket')
router.register('tickets/(?P<ticket_id>[0-9a-zA-Z\-]{36})/comments', api.TicketCommentViewSet, 'ticket-comment')
......
# -*- coding: utf-8 -*-
#
from urllib.parse import urljoin
from django.conf import settings
from django.utils.translation import ugettext as _
from common.utils import get_logger, reverse
from common.utils import get_logger
from common.tasks import send_mail_async
logger = get_logger(__name__)
from tickets.models import Ticket
def send_new_ticket_mail_to_assignees(ticket, assignees):
def send_new_ticket_mail_to_assignees(ticket: Ticket, assignees):
recipient_list = [user.email for user in assignees]
user = ticket.user
if not recipient_list:
logger.error("Ticket not has assignees: {}".format(ticket.id))
return
subject = '{}: {}'.format(_("New ticket"), ticket.title)
detail_url = reverse('tickets:ticket-detail',
kwargs={'pk': ticket.id}, external=True)
# 这里要设置前端地址,因为要直接跳转到页面
if ticket.type == ticket.TYPE_REQUEST_ASSET_PERM:
detail_url = urljoin(settings.SITE_URL, f'/tickets/tickets/request-asset-perm/{ticket.id}')
else:
detail_url = urljoin(settings.SITE_URL, f'/tickets/tickets/{ticket.id}')
message = _("""
<div>
<p>Your has a new ticket</p>
......
......@@ -233,6 +233,11 @@ class RoleMixin:
def is_app(self):
return self.role == self.ROLE.APP
@lazyproperty
def user_all_orgs(self):
from orgs.models import Organization
return Organization.get_user_all_orgs(self)
@lazyproperty
def user_orgs(self):
from orgs.models import Organization
......
......@@ -27,6 +27,11 @@ class UserOrgSerializer(serializers.Serializer):
name = serializers.CharField()
class UserOrgLabelSerializer(serializers.Serializer):
value = serializers.CharField(source='id')
label = serializers.CharField(source='name')
class UserSerializer(CommonBulkSerializerMixin, serializers.ModelSerializer):
EMAIL_SET_PASSWORD = _('Reset link will be generated and sent to the user')
CUSTOM_PASSWORD = _('Set password')
......@@ -214,6 +219,7 @@ class UserRoleSerializer(serializers.Serializer):
class UserProfileSerializer(UserSerializer):
admin_or_audit_orgs = UserOrgSerializer(many=True, read_only=True)
user_all_orgs = UserOrgLabelSerializer(many=True, read_only=True)
current_org_roles = serializers.ListField(read_only=True)
public_key_comment = serializers.CharField(
source='get_public_key_comment', required=False, read_only=True, max_length=128
......@@ -231,7 +237,7 @@ class UserProfileSerializer(UserSerializer):
class Meta(UserSerializer.Meta):
fields = UserSerializer.Meta.fields + [
'public_key_comment', 'public_key_hash_md5', 'admin_or_audit_orgs', 'current_org_roles',
'guide_url'
'guide_url', 'user_all_orgs'
]
extra_kwargs = dict(UserSerializer.Meta.extra_kwargs)
extra_kwargs.update({
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册