Esempio n. 1
0
class PolyvVideoAdminView(AdminView):
    MODEL = PolyvVideo
    ORDER_BY = ['-id']
    USE_PAGINATION = True
    QUERYSET_SELECT_RELATED = [
        'owner',
        'thumbnail',
        'thumbnail__owner',
    ]

    SEARCH_FORM = Form([
        panels.TextPanel('title', search_op='icontains'),
        panels.TextPanel('vid'),
    ],
                       model=MODEL,
                       form_mode='search')

    EDIT_FORM = Form([
        panels.TextPanel('title'),
        panels.TextPanel('vid'),
        panels.TextPanel('duration'),
        panels.SwitchPanel('login_required'),
        panels.ImageUploaderPanel('thumbnail', bucket='thumbnails'),
    ],
                     model=MODEL,
                     form_mode='edit')

    def get_extra_create_kwargs(self, request):
        return dict(owner=request.user)
Esempio n. 2
0
 def get_edit_panels(cls):
     return super().get_edit_panels() + [
         panels.SelectPanel('video',
                            choices=choices.ApiChoices(
                                'api-media-videos', label_field='title')),
         panels.TextPanel('teacher_name'),
         panels.TextPanel('teacher_organization'),
         panels.RichTextPanel('introduction'),
         panels.RichTextPanel('teacher_introduction'),
     ]
Esempio n. 3
0
 def get_edit_panels(cls):
     return super().get_edit_panels() + [
         panels.TextPanel('title'),
         panels.TextPanel('author_name'),
         panels.ImageUploaderPanel('cover_picture', bucket='project'),
         panels.DateTimePickerPanel('publish_time'),
         ProjectPageAttachmentPanel('attachments',
                                    bucket='project-attachments'),
         panels.RichTextPanel('excerpt'),
         panels.RichTextPanel('content'),
     ]
Esempio n. 4
0
class ExaminationAdminView(AdminView):
    MODEL = ZhixiangExamination
    QUERYSET_SELECT_RELATED = [
        'course',
        'course__owner',
        'course__thumbnail',
        'course__thumbnail__owner',
    ]

    EDIT_FORM = Form([
        panels.TextPanel('title'),
        panels.TextPanel('wjx_url'),
        panels.SelectPanel('course',
                           choices=choices.ApiChoices('api-course-courses',
                                                      label_field='title')),
    ],
                     model=MODEL,
                     form_mode='edit')
Esempio n. 5
0
    def get_edit_panels(cls):
        edit_panels = [
            panels.SelectPanel('status'),
            panels.TextPanel('page_title',
                             default_value=cls.PAGE_TITLE_DEFAULT_VALUE),
            # panels.DocumentUploaderPanel(attachments')
        ]

        return edit_panels
Esempio n. 6
0
class ProjectCarouselItemAdminView(AdminView):
    MODEL = ProjectCarouselItem
    QUERYSET_PREFETCH_RELATED = [
        'image',
    ]

    SEARCH_FORM = Form([
        panels.SelectPanel('project_homepage', label='', help_text=''),
    ],
                       model=MODEL,
                       form_mode='search')

    EDIT_FORM = Form([
        panels.ImageUploaderPanel('image', bucket='carousel'),
        panels.TextPanel('title'),
        panels.TextPanel('link_url'),
    ],
                     model=MODEL,
                     form_mode='edit')
Esempio n. 7
0
class PresentationLessonAdminView(AdminView):
    MODEL = PresentationLesson
    ORDER_BY = ['-id']
    USE_PAGINATION = True
    QUERYSET_SELECT_RELATED = [
            'course',
            'course__owner',
            'course__thumbnail',
            'course__thumbnail__owner',
            'teacher_picture',
            'teacher_picture__owner',
            'presentation',
            'presentation__owner',
            'presentation__thumbnail',
            'presentation__thumbnail__owner',
            ]
    QUERYSET_PREFETCH_RELATED = [
            'attachments',
            'presentation__slides',
            ]

    SEARCH_FORM = Form([
        panels.TextPanel('title', search_op='icontains'),
        panels.SelectPanel('status'),
    ], model=MODEL, form_mode='search')

    EDIT_FORM = Form([
        panels.DividerPanel('基本信息'),
        panels.SelectPanel('course', choices=choices.ApiChoices('api-course-courses', label_field='title')),
        panels.TextPanel('title', validators=[validators.TextLengthValidator(max=30)]),
        panels.RichTextPanel('summary'),
        panels.SelectPanel('status'),
        panels.SelectPanel('presentation', choices=choices.ApiChoices('api-media-presentations', label_field='title')),
        # TODO implement attachments (ManyToManyField Chooser)
        panels.DividerPanel('讲者信息'),
        panels.TextPanel('teacher_name', required=False),
        panels.TextPanel('teacher_organization', required=False),
        panels.ImageUploaderPanel('teacher_picture', bucket='thumbnails', required=False),
        panels.RichTextPanel('teacher_introduction', required=False),
    ], model=MODEL, form_mode='edit')
Esempio n. 8
0
class CourseAdminView(AdminView):
    MODEL = Course
    ORDER_BY = ['-id']
    USE_PAGINATION = True
    QUERYSET_SELECT_RELATED = [
            'owner',
            'thumbnail',
            'thumbnail__owner',
            ]

    SEARCH_FORM = Form([
        panels.TextPanel('title', search_op='icontains'),
    ], model=MODEL, form_mode='search')

    EDIT_FORM = Form([
        panels.TextPanel('title'),
        panels.RichTextPanel('introduction'),
        panels.ImageUploaderPanel('thumbnail', bucket='thumbnails'),
    ], model=MODEL, form_mode='edit')

    def get_extra_create_kwargs(self, request):
        return dict(owner=request.user)
Esempio n. 9
0
class NewsAdminView(AdminView):
    MODEL = ZhixiangNews
    ORDER_BY = ['-id']
    USE_PAGINATION = True
    QUERYSET_SELECT_RELATED = ['thumbnail']

    SEARCH_FORM = Form([
        panels.TextPanel('title', search_op='icontains'),
        panels.TextPanel('author_name', search_op='icontains'),
        panels.DateRangePanel('publish_time', form_field_name='publish_range'),
    ],
                       model=MODEL,
                       form_mode='search')

    EDIT_FORM = Form([
        panels.TextPanel('title'),
        panels.TextPanel('author_name'),
        panels.ImageUploaderPanel('thumbnail', bucket='thumbnails'),
        panels.DateTimePickerPanel('publish_time'),
        panels.RichTextPanel('content'),
    ],
                     model=MODEL,
                     form_mode='edit')
Esempio n. 10
0
class ProjectNavMenuAdminView(AdminView):
    MODEL = ProjectNavMenu
    QUERYSET_SELECT_RELATED = [
        'link_page',
    ]
    QUERYSET_PREFETCH_RELATED = [
        'children',
        'children__link_page',
    ]
    """
    菜单管理比较特殊,我们将菜单分为两大类:根节点、非根节点。
    在管理API中,我们仅提供 GET、PATCH 操作,这两个操作都只能针对
    根节点进行。(创建操作是在创建项目时一起创建的,删除也是。)

    这里不提供 SEARCH_FORM,EDIT_FORM 实质上仅仅是为前端提供 panel
    的 schema,前端并不直接使用 <ns-form> 来构造表单,而是手动构造。
    """
    LIST_METHOD = None
    CREATE_METHOD = None
    UPDATE_METHOD = None
    DELETE_METHOD = None

    SEARCH_FORM = Form()
    EDIT_FORM = Form([
        panels.SelectPanel('link_type'),
        panels.TextPanel('link_url'),
        panels.SelectPanel('link_page',
                           choices=choices.ApiChoices(
                               'api-project-pages', label_field='page_title')),
        panels.TextPanel('text'),
    ],
                     model=MODEL,
                     form_mode='edit')

    def get_queryset(self, request):
        return super().get_queryset(request).filter(parent=None)

    @transaction.atomic
    def patch_model(self, request, pk):
        children = request.json.get('children', [])
        try:
            root = self.get_queryset(request).get(pk=pk)
        except self.MODEL.DoesNotExist:
            return api.not_found()

        old_children = list(root.children.all())
        old_ids = {child.id for child in old_children}
        new_ids = {child['id'] for child in children if child['id']}

        children_to_delete = [
            child for child in old_children if child.id not in new_ids
        ]
        children_to_update = [
            child for child in old_children if child.id in new_ids
        ]
        children_to_create = [
            ProjectNavMenu(
                id=None,
                parent_id=child['parent'],
                link_type=child['link_type'],
                link_page_id=child['link_page']
                if child['link_type'] == 'page' else None,
                link_url=child['link_url']
                if child['link_type'] == 'external' else '',
                text=child['text'],
                sort_order=child['sort_order'],
            ) for child in children if not child['id']
        ]
        children_to_update_data = {
            child['id']: child
            for child in children if child['id']
        }
        for child in children_to_update:
            data = children_to_update_data[child.id]
            child.link_type = data['link_type']
            child.link_page_id = data['link_page'] if data[
                'link_type'] == 'page' else None
            child.link_url = data['link_url'] if data[
                'link_url'] == 'external' else ''
            child.text = data['text']
            child.sort_order = data['sort_order']

        for child in children_to_delete:
            child.delete()
        for child in children_to_update:
            child.save()
        for child in children_to_create:
            child.save()

        root.refresh_from_db()
        return api.ok(data=root.serialize())
Esempio n. 11
0
class ProjectPageAdminView(AdminView):
    MODEL = ProjectPage

    # API: .../forms?page_type=xxx
    # API: .../pagetypes (should contain creatable info (e.g. single instance))

    PROJECT_SELECT_PANEL = panels.SelectPanel(
        'project',
        choices=choices.ApiChoices('api-project-projects',
                                   label_field='title'),
    )
    PAGETYPE_SELECT_PANEL = panels.SelectPanel(
        None,
        form_field_name='pagetype',
        form_field_property='type',
        label='页面类型',
        choices=choices.ApiChoices('api-project-pages-pagetypes',
                                   label_field='name',
                                   value_field='type'),
    )

    SEARCH_FORM = Form([
        PROJECT_SELECT_PANEL,
        PAGETYPE_SELECT_PANEL,
        panels.TextPanel('page_title'),
        panels.SelectPanel('status'),
    ],
                       model=MODEL,
                       form_mode='search')

    def get_serialize_kwargs(self, request):
        simple = get_boolean_query(request, 'simple', False)
        return dict(simple=simple)

    def get(self,
            request,
            menu=None,
            pagetypes=None,
            form_name=None,
            panel=None,
            **kwargs):
        if pagetypes:
            project = request.GET.get('project', None)
            return api.ok(data=ProjectPage.serialize_pagetypes(project))
        if form_name == 'edit':
            pagetype = request.GET.get('pagetype')
            Page = ProjectPage.PAGE_TYPES.get(pagetype)
            if not Page:
                return api.ok(data=Form().serialize())
            form = Form(Page.get_edit_panels(), model=Page, form_mode='edit')
            return api.ok(data=form.serialize())
        if form_name == 'empty':
            return api.ok(data=Form().serialize())
        if panel:
            if panel == 'project':
                return api.ok(data=self.PROJECT_SELECT_PANEL.serialize())
            elif panel == 'pagetype':
                return api.ok(data=self.PAGETYPE_SELECT_PANEL.serialize())
            else:
                return api.not_found()
        return super().get(request, form_name=form_name, **kwargs)

    def get_queryset(self, request):
        if request.method == 'GET':
            pagetype = request.GET.get('pagetype')
        else:
            pagetype = request.json.get('pagetype')

        if pagetype:
            Page = ProjectPage.PAGE_TYPES.get(pagetype)
            if not Page:
                return ProjectPage.objects.none()
            queryset = Page.objects.all()
        else:
            queryset = ProjectPage.objects.all().select_subclasses()

        if self.ORDER_BY:
            queryset = queryset.order_by(*self.ORDER_BY)

        if self.QUERYSET_SELECT_RELATED:
            queryset = queryset.select_related(*self.QUERYSET_SELECT_RELATED)

        if self.QUERYSET_PREFETCH_RELATED:
            queryset = queryset.prefetch_related(
                *self.QUERYSET_PREFETCH_RELATED)

        return queryset

    def get_edit_panels(self, request):
        pagetype = request.json.get('pagetype')
        Page = ProjectPage.PAGE_TYPES.get(pagetype)
        form = Form(Page.get_edit_panels(), model=Page, form_mode='edit')
        for panel in form.data_panels:
            yield panel

    def create_model(self, request):
        create_args = super().create_model(request, no_save=True)

        project_id = request.json.get('project')
        if not project_id:
            return api.bad_request(message='missing project id')
        create_args['project_id'] = project_id

        pagetype = request.json.get('pagetype')
        Page = ProjectPage.PAGE_TYPES.get(pagetype)
        if not Page:
            return api.bad_request(message=f'invalid pagetype: {pagetype}')

        page = Page.objects.create(**create_args)

        if pagetype == 'gallery':
            self.set_gallery_images(page, request.json.get('images', []))

        if pagetype == 'homepage':
            self.set_homepage_carousel_items(
                page, request.json.get('carousel_items', []))

        self.set_attachments(page, request.json.get('attachments', []))

        return api.ok(data=page)

    def patch_model(self, request, pk):
        model = super().patch_model(request, pk, no_save=True)

        if 'project' in request.json:
            model.project_id = request.json['project']

        if 'pagetype' in request.json:
            pagetype = request.json['pagetype']
            if pagetype != model.PAGE_TYPE:
                return api.bad_request(message=f'不能修改页面类型')

        model.save()

        if model.PAGE_TYPE == 'gallery':
            self.set_gallery_images(model, request.json.get('images', []))

        if pagetype == 'homepage':
            self.set_homepage_carousel_items(
                model, request.json.get('carousel_items', []))

        self.set_attachments(model, request.json.get('attachments', []))

        return api.ok(data=model)

    def set_attachments(self, model, attachment_ids):
        model.attachments.set(attachment_ids)

    def set_homepage_carousel_items(self, model, carousel_item_ids):
        items = ProjectCarouselItem.objects.filter(id__in=carousel_item_ids)
        items = {item.id: item for item in items}
        sort_order = 1
        for id in carousel_item_ids:
            items[id].sort_order = sort_order
            sort_order += 1
        ProjectCarouselItem.objects.bulk_update(items.values(), ['sort_order'])

        model.carousel_items.set(carousel_item_ids)

    def set_gallery_images(self, model, image_ids):
        images_to_update = list(model.images.all())
        images_to_update = {image.id: image for image in images_to_update}

        images = list(ProjectGalleryImage.objects.filter(id__in=image_ids))
        for image in images:
            images_to_update[image.id] = image

        for image in images_to_update.values():
            image.gallery = None
            image.sort_order = None

        sort_order = 1
        for image_id in image_ids:
            image = images_to_update[image_id]
            image.gallery = model
            image.sort_order = sort_order
            sort_order += 1

        ProjectGalleryImage.objects.bulk_update(images_to_update.values(),
                                                ['gallery', 'sort_order'])

    @classmethod
    def urls(cls, base, app):
        return super().urls(base, app) + [
            path(f'{base}/pagetypes',
                 cls.as_view(),
                 name=f'{app}-{base}-pagetypes',
                 kwargs=dict(pagetypes=True)),
            path(f'{base}/panels/<str:panel>',
                 cls.as_view(),
                 name=f'{app}-{base}-panels'),
        ]
Esempio n. 12
0
class ProjectDocumentAdminView(AdminView):
    MODEL = ProjectDocument
    QUERYSET_SELECT_RELATED = [
        'project',
        'document',
    ]

    SEARCH_FORM = Form([
        panels.SelectPanel('project',
                           choices=choices.ApiChoices('api-project-projects',
                                                      label_field='title')),
        panels.SelectPanel(
            'tag',
            choices=choices.ApiChoices('api-project-documents-tags',
                                       value_field=None,
                                       label_field=None),
        ),
        panels.TextPanel('subject', search_op='icontains'),
    ],
                       model=MODEL,
                       form_mode='search')

    EDIT_FORM = Form([
        panels.SelectPanel('project',
                           choices=choices.ApiChoices('api-project-projects',
                                                      label_field='title')),
        panels.TextPanel('subject'),
        panels.TextPanel('description'),
        panels.DateTimePickerPanel('publish_time'),
        panels.SelectPanel(
            'tag',
            choices=choices.ApiChoices('api-project-documents-tags',
                                       value_field=None,
                                       label_field=None),
            allow_create=True,
            placeholder='选择或创建新的标签',
        ),
        panels.DocumentUploaderPanel('document', bucket='project'),
    ],
                     model=MODEL,
                     form_mode='edit')

    def get(self, request, pk=None, form_name=None, tags=False):
        if not tags:
            return super().get(request, pk=pk, form_name=form_name)

        tags = [
            val['tag']
            for val in self.MODEL.objects.all().distinct('tag').values('tag')
        ]

        return api.ok(data=tags)

    @classmethod
    def urls(cls, base, app):
        return super().urls(base, app) + [
            path(f'{base}/tags',
                 cls.as_view(),
                 name=f'{app}-{base}-tags',
                 kwargs=dict(tags=True)),
        ]
Esempio n. 13
0
class EmailView(AdminView):
    """
    API Endpoints for managing emails.

    GET /api/admin/notification/emails
        description: list (search) emails
        return: array of email objects, with pagination
        status: 200, 400
        query string:
            * subject: filter subject with 'icontains'
            * from: filter from_email with 'icontains'
            * recipient: filter recipients with 'icontains'
            * status: filter status with '='
            * sent_range: filter email sent between date, format: 2019-03-21,2019-03-22
            * pagination params (page=1, page_size=10)

    GET /api/admin/notification/emails/{id}
        description: get email model
        return: email object
        status: 200, 404

    POST /api/admin/notification/emails
        description: send email, supposed to be used as testing purpose only.
        return: email object on success
        status: 200, 400
        body:
            {
                subject: 'plain text',
                content: '',
                from: '*****@*****.**',
                recipients: ['*****@*****.**', '*****@*****.**'],
            }
    """
    MODEL = Email
    ORDER_BY = ['-sent_at']
    USE_PAGINATION = True

    CREATE_METHOD = 'create_email'
    UPDATE_METHOD = None
    PATCH_METHOD = None
    DELETE_METHOD = None

    SEARCH_FORM = Form([
        panels.TextPanel('recipients',
                         form_field_name='recipient',
                         search_op='icontains',
                         label='收件人'),
        panels.TextPanel('from_email',
                         form_field_name='from',
                         search_op='icontains',
                         label='发件人'),
        panels.TextPanel('subject', search_op='icontains', label='标题'),
        panels.SelectPanel('status', label='状态'),
        panels.DateRangePanel(
            'sent_at', form_field_name='sent_range', label='发送日期'),
    ],
                       model=MODEL,
                       form_mode='search')

    def create_email(self, request):
        subject = request.json.get('subject')
        if not subject:
            return api.bad_request(form_errors=dict(subject=['标题不能为空']))

        content = request.json.get('content')
        if not content:
            return api.bad_request(form_errors=dict(content=['内容不能为空']))

        from_email = request.json.get('from_email')
        if not from_email:
            return api.bad_request(form_errors=dict(from_email=['发件人不能为空']))

        recipients = request.json.get('recipients')
        if not recipients:
            return api.bad_request(form_errors=dict(recipients=['收件人列表不能为空']))
        if not isinstance(recipients, list):
            return api.resopnse(400,
                                form_errors=dict(recipients=['收件人列表必须是数组']))

        email = send_email(recipients,
                           subject,
                           from_email=from_email,
                           content=content)

        return api.ok(data=email.serialize())
Esempio n. 14
0
class PresentationAdminView(AdminView):
    MODEL = Presentation
    ORDER_BY = ['-id']
    USE_PAGINATION = True
    QUERYSET_SELECT_RELATED = [
        'owner',
        'thumbnail',
        'thumbnail__owner',
    ]
    QUERYSET_PREFETCH_RELATED = ['slides']

    SEARCH_FORM = Form([panels.TextPanel('title', search_op='icontains')],
                       model=MODEL,
                       form_mode='search')

    EDIT_FORM = Form([
        panels.TextPanel('title'),
        TimeSliderPanel('min_watch_seconds'),
        panels.ImageUploaderPanel('thumbnail', bucket='thumbnails'),
        SlidesUploaderPanel('slides'),
    ],
                     model=MODEL,
                     form_mode='edit')

    def get_extra_create_kwargs(self, request):
        return dict(owner=request.user)

    def set_slides(self, presentation, slide_ids):
        # 对于PPT的图片,我们做如下处理:
        # * 获取当前所有的图片(对于 create 来说,应该是空的)
        # * 将所有图片的 presentation 置为空,sort_order 置为 null
        # * 获取用户提交的所有图片列表
        # * 将用户提交的所有图片设置正确的 presentation 以及 sort_order
        # * 保存所有涉及到的图片的修改
        # 我们维护一个 slides_to_update 字典,key 为 slide 的 id,方便记录

        slides_to_update = list(presentation.slides.all())
        slides_to_update = {slide.id: slide for slide in slides_to_update}

        slides = list(Slide.objects.filter(id__in=slide_ids))
        for slide in slides:
            slides_to_update[slide.id] = slide

        for slide in slides_to_update.values():
            slide.presentation = None
            slide.sort_order = None

        sort_order = 1
        for slide_id in slide_ids:
            # TODO if slide_id does not exist, it means the frontend passed in a non-exist slide id
            slide = slides_to_update[slide_id]
            slide.presentation = presentation
            slide.sort_order = sort_order
            sort_order += 1

        Slide.objects.bulk_update(slides_to_update.values(),
                                  ['presentation', 'sort_order'])

    def create_model(self, request):
        create_args = super().create_model(request, no_save=True)
        model = self.MODEL.objects.create(**create_args)
        self.set_slides(model, request.json.get('slides', []))
        return api.ok(data=model)

    def patch_model(self, request, pk):
        model = super().patch_model(request, pk, no_save=True)
        model.save()
        self.set_slides(model, request.json.get('slides', []))
        return api.ok(data=model)
Esempio n. 15
0
class ProjectAdminView(AdminView):
    MODEL = Project
    QUERYSET_SELECT_RELATED = [
        'banner',
        'menu',
        'menu__link_page',
    ]
    QUERYSET_PREFETCH_RELATED = [
        'menu__children',
        'menu__children__link_page',
    ]

    SEARCH_FORM = Form([
        panels.TextPanel('title'),
    ],
                       model=MODEL,
                       form_mode='search')

    EDIT_FORM = Form([
        panels.TextPanel('title'),
        panels.TextPanel('slug'),
        panels.RichTextPanel('introduction'),
        panels.ImageUploaderPanel('banner', bucket='banner'),
        panels.ImageUploaderPanel('banner_background', bucket='banner'),
        ProjectThemeColorPickerPanel('theme_colors'),
    ],
                     model=MODEL,
                     form_mode='edit')

    @transaction.atomic
    def create_model(self, request):
        create_args = super().create_model(request, no_save=True)

        # 创建菜单
        create_args['menu'] = ProjectNavMenu.objects.create()

        project = self.MODEL.objects.create(**create_args)

        # 创建首页
        ProjectHomepage.objects.create(
            project=project, status=ProjectHomepage.STATUSES.published)

        return api.ok(data=project)

    @transaction.atomic
    def delete_model(self, request, pk):
        try:
            model = self.get_queryset(request).get(pk=pk)
            data = model.serialize()
            model.delete()
            # 删除关联的菜单
            model.menu.delete()
            return api.ok(data=data)
        except self.MODEL.DoesNotExist:
            return api.not_found()

    def get(self, request, pk=None, menu=None, *args, **kwargs):
        if pk and menu:
            try:
                project = self.get_queryset(request).get(pk=pk)
                return api.ok(data=project.menu)
            except self.MODEL.DoesNotExist:
                return api.not_found()
        return super().get(request, pk=pk, **kwargs)

    @classmethod
    def urls(cls, base, app):
        return super().urls(base, app) + [
            path(f'{base}/<int:pk>/menu',
                 cls.as_view(),
                 name=f'{app}-{base}-menu',
                 kwargs=dict(menu=True)),
        ]
Esempio n. 16
0
 def get_edit_panels(cls):
     return super().get_edit_panels() + [
         panels.TextPanel('title'),
         panels.RichTextPanel('content'),
     ]
Esempio n. 17
0
class AliSmsView(AdminView):
    """
    API Endpoints for managing ali sms.

    GET /api/admin/notification/alisms
        description: list (search) sms
        return: array of sms objects, with pagination
        status: 200, 400
        query string:
            * phone: filter phone_numbers with 'icontains'
            * content: filter content with 'icontains'
            * status: filter status with '='
            * sent_before: filter sms sent before the date, date format: '2019-03-21'
            * sent_after: filter sms sent after the date, date format: '2019-03-21'
            * pagination params (page=1, page_size=10)

    GET /api/admin/notification/alisms/{id}
        description: get sms model
        return: sms object
        status: 200, 404

    POST /api/admin/notification/alisms
        description: send sms, supposed to be used as testing purpose only.
        return: email object on success
        status: 200, 400
        body:
            {
                phone_numbers: '13912345678, 13812345678',
                signature_name: '大鱼测试',
                template_code: 'SMS_134310520',
                template_param: { code: '123456' },
            }
    """
    MODEL = AliSms
    ORDER_BY = ['-sent_at']
    USE_PAGINATION = True

    CREATE_METHOD = 'create_sms'
    UPDATE_METHOD = None
    PATCH_METHOD = None
    DELETE_METHOD = None

    SEARCH_FORM = Form([
        panels.TextPanel('phone_numbers',
                         form_field_name='phone',
                         search_op='icontains',
                         labels='手机号'),
        panels.TextPanel('content', search_op='icontains', labels='内容'),
        panels.SelectPanel('status', labels='状态'),
        panels.DateRangePanel(
            'sent_at', form_field_name='sent_range', label='发送日期'),
    ],
                       model=MODEL,
                       form_mode='search')

    def create_sms(self, request):
        phone_numbers = request.json.get('phone_numbers')
        if not phone_numbers:
            return api.bad_request(form_errors=dict(
                phone_numbers=['接收方手机号不能为空']))

        signature_name = request.json.get('signature_name')
        if not signature_name:
            return api.bad_request(form_errors=dict(signature_name=['签名不能为空']))
        if signature_name not in AliSms.SIGNATURES:
            return api.bad_request(form_errors=dict(
                signature_name=['不允许使用的签名']))

        template_code = request.json.get('template_code')
        if not template_code:
            return api.bad_request(form_errors=dict(
                template_code=['模板编号不能为空']))
        if template_code not in AliSms.TEMPLATES:
            return api.bad_request(form_errors=dict(template_code=['模板编号不存在']))

        template_param = reuqest.json.get('template_param', {})

        sms = send_sms(phone_numbers, signature_name, template_code,
                       template_param)

        return api.ok(data=sms.serialize())