class CredentialsDataView(ModelView): column_list = ['cert', 'key'] list_template = 'admin/credentials_list.html' details_template = 'admin/credentials_details.html' column_formatters = { 'cert': macro('render_cert'), 'key': macro('render_key'), } @expose('/cert.pem') def cert_view(self): creds = self.get_one(request.args.get('id')) return Response(creds.cert, mimetype='text/plain') @expose('/key.pem') def key_view(self): creds = self.get_one(request.args.get('id')) return Response(creds.key, mimetype='text/plain') @expose('/cert.txt') def cert_text_view(self): creds = self.get_one(request.args.get('id')) info = pki.get_certificate_text(creds.cert) return Response(info, mimetype='text/plain') def is_visible(self): return False
class VideoModelView(BaseModelView): form = VideoForm edit_template = 'admin/edit/_video_edit.html' can_create = False list_template = 'admin/list/_video_list.html' def _list_thumbnail_cover(view, context, model, name): if not os.path.exists(current_app.config['IMG_THUMB_DEST']): os.makedirs(current_app.config['IMG_THUMB_DEST']) os.chmod(current_app.config['IMG_THUMB_DEST'], stat.S_IRWXU|stat.S_IRGRP|stat.S_IWGRP|stat.S_IROTH) # 设置缩略图 size = 200, 200 im = Image.open(os.path.join(current_app.config['UPLOADS_DEFAULT_DEST'], 'images/', model.cover)) im.thumbnail(size) extension = os.path.splitext(model.cover) thumbnail_name = extension[0] + '_thumbnail' + extension[-1] im.save(os.path.join(current_app.config['IMG_THUMB_DEST'], thumbnail_name)) model.thumbnail_cover = url_for('static', filename='uploads/thumbnails/' + thumbnail_name) return Markup('<img src="%s" alt="视频封面"' % model.thumbnail_cover) def _list_url(view, context, model, name): return Markup('<a href="%s" target="_blank">视频链接</a>' % url_for('home.video', id=model.id)) def _list_uploader(view, context, model, name): return Markup('<a href="%s" target="_blank">%s</a>' % (url_for('home.user', username=model.uploader.username), model.uploader.username)) column_formatters = dict(intro=macro('render_intro'), thumbnail_cover=_list_thumbnail_cover, url=_list_url, video_tag=macro('render_video_tag'), uploader=_list_uploader) column_exclude_list = ['cover',] column_labels = { 'title': '视频标题', 'intro': '视频简介', 'thumbnail_cover': '封面缩略图', 'playnum': '播放数', 'add_time': '上传时间', 'video_tag': '视频分类', 'uploader': '上传者' } column_sortable_list = ('title', 'intro', 'thumbnail_cover', 'url', 'playnum', 'add_time', ('video_tag', 'video_tag.name'), ('uploader', 'uploader.username')) column_searchable_list = ('title', 'intro', 'video_tag.name', 'uploader.username') column_default_sort = ('add_time', True) def on_model_change(self, form, model, is_created): if not is_created: if Video.query.filter_by(title=form.title.data).first() != model: raise ValidationError('视频标题已经被使用') if form.tag.data != model.video_tag.name: tag = VideoTag.query.filter_by(name=form.tag.data).first() model.video_tag = tag
class ImageView(BasicAuthModelView): list_template = 'admin/model/object_list.html' form_excluded_columns = ['created_at', 'verification_url', 'user'] page_size = 50 column_exclude_list = ('user') column_filters = ('gender', 'status') column_formatters = dict(url=macro('render_url'), verification_url=macro('render_verification_url'))
class FilesView(MyBaseView): column_list = [ 'id', 'name', 'sha1', 'date_b', 'status_f', 'score', 'message', 'evals_count' ] column_sortable_list = [ 'id', 'name', 'sha1', 'date_b', 'status_f', 'score', 'message', 'evals_count' ] column_exclude_list = [ 'uuid_f', 'evals_len', 'results', 'md5', 'mtype', 'exec_time', 'expect_sandbox', 'hash' ] column_details_list = [ 'id', 'name', 'mtype', 'md5', 'sha1', 'hash', 'date_b', 'exec_time', 'status_f', 'score', 'message', 'results', 'evals_count', 'evals' ] column_export_list = [ 'id', 'name', 'mtype', 'md5', 'sha1', 'hash', 'date_b', 'exec_time', 'status_f', 'score', 'message', 'evals_count' ] column_export_exclude_list = ['evals', 'results'] column_formatters_export = {} column_searchable_list = [] column_default_sort = ('date_b', True) column_filters = ['name', 'mtype', 'sha1', 'score', 'evals_count'] column_formatters_export = dict( score=fmt_render_score, date_b=lambda v, c, m, p: str(getattr(m, p)), exec_time=lambda v, c, m, p: str(tdelta(seconds=getattr(m, p)))) column_formatters = dict(evals_count=fmt_counts, evals=macro('render_evals_cname'), exec_time=fmt_elapsed_time_secs, name=fmt_file_details, score=macro('render_score'), status_f=fmt_text_bold, message=macro('render_message'), results=fmt_file_results) column_labels = dict(id='Id', name='Filename', mtype='MIME Type', md5='MD5 Hash', sha1='SHA1 Hash', hash='SHA256 Hash', uuid_f='UUID', status_f='Status', date_b='Status Date', exec_time='Elapsed Time', score='Is Malicious?', expect_sandbox='Expect Sandbox?', message='Message', evals_count='# of Evaluations', evals='List of Evaluations', results='Results')
class EvalsView(MyBaseView): column_list = [ 'id', 'uuid_f', 'client', 'corrid', 'date_f', 'date_b', 'status_f', 'score', 'files_count' ] column_sortable_list = [ 'id', 'uuid_f', 'client', 'corrid', 'date_f', 'date_b', 'status_f', 'score', 'files_count' ] column_details_list = [ 'id', 'client', 'uuid_f', 'corrid', 'date_f', 'date_b', 'score', 'status_f', 'files_count', 'files' ] column_exclude_list = [ '', ] column_export_list = [ 'id', 'client', 'uuid_f', 'corrid', 'date_f', 'date_b', 'score', 'status_f', 'files_count' ] column_export_exclude_list = ['files'] column_formatters_export = dict( score=fmt_render_score, client=lambda v, c, m, p: str(getattr(m, p)), date_f=lambda v, c, m, p: str(getattr(m, p)), date_b=lambda v, c, m, p: str( dtparser(str(m.date_b)) - dtparser(str(m.date_f)))) column_searchable_list = [] column_default_sort = ('date_f', True) column_filters = [ 'uuid_f', 'corrid', 'status_f', 'score', 'client', 'files_count' ] column_formatters = dict(score=macro('render_score'), client=fmt_eclient_details, uuid_f=fmt_eval_details, corrid=fmt_text_boldital, date_b=fmt_elapsed_time, status_f=fmt_text_bold, files_count=fmt_counts, files=macro('render_files_name')) column_labels = dict(id='Id', client_id='Client Id', client='[X.509] Common Name', uuid_f='Evaluation UUID', corrid='Correlation ID', status_f='Status', date_f='Submit Date', date_b='Elapsed Time', score='Is Malicious?', files_count='# of Files', files='List of Files')
class LocationSetAdminView(SetViewMixin, BaseAdminView): column_list = ('name', 'administrative_divisions', 'locations') column_labels = { 'name': _('Name'), 'administrative_divisions': _('Administrative Divisions'), 'locations': _('Location Data') } column_formatters = { 'administrative_divisions': macro('locations_builder'), 'locations': macro('locations_list'), } form_columns = ('name', ) inline_models = (ExtraDataInlineFormAdmin(models.LocationDataField), ) inline_model_form_converter = LocationExtraDataModelConverter def on_model_delete(self, model): # delete dependent objects first models.LocationPath.query.filter_by(location_set=model).delete() models.Location.query.filter_by(location_set=model).delete() models.LocationTypePath.query.filter_by(location_set=model).delete() models.LocationType.query.filter_by(location_set=model).delete() return super().on_model_delete(model) @expose('/builder/<int:location_set_id>', methods=['GET', 'POST']) def builder(self, location_set_id): return locations_builder(self, location_set_id) @expose('/builder/<int:location_set_id>/import', methods=['POST']) def import_divisions(self, location_set_id): return import_divisions(location_set_id) @expose('/builder/<int:location_set_id>/export') def export_divisions(self, location_set_id): return export_divisions(location_set_id) @expose('/locations/<int:location_set_id>') def locations_list(self, location_set_id): return locations_list(self, location_set_id) @expose('/locations/<int:location_set_id>/import', methods=['POST']) def locations_import(self, location_set_id): return locations_import(location_set_id) @expose('/locations/<int:location_set_id>/headers/<int:upload_id>', methods=['GET', 'POST']) def locations_headers(self, location_set_id, upload_id): return locations_headers(self, location_set_id, upload_id) @expose('/location/<int:id>', methods=['GET', 'POST']) def location_edit(self, id): return location_edit(self, id)
class IssueView(DeveloperModelView): can_create = False can_delete = False can_edit = False can_view_details = True column_default_sort = ('total', True) column_display_actions = False column_filters = ('product', 'platform', 'reason') column_formatters = dict( avg_uptime=lambda v, c, m, n: '{} s'.format(m.avg_uptime), actions=macro('render_actions'), ) column_list = [ 'platform', 'version', 'reason', 'location', 'avg_uptime', 'last_seen', 'total', 'actions' ] form_args = dict(total={'label': 'Total Crash Reports'}) list_template = 'admin/issue_list.html' named_filter_urls = True page_size = 10 @expose('/details/') def details_view(self): issue = models.Issue.objects.get(id=request.args.get('id')) minidumps = models.Minidump.objects(product=issue.product, version=issue.version, platform=issue.platform, crash_reason=issue.reason) page_num = int(request.args.get('page') or 1) per_page = 10 return self.render('admin/issue_details.html', issue=issue, column_details_list=self.column_details_list, minidumps=minidumps.paginate(page=page_num, per_page=per_page), per_page=per_page) @expose('/resolve', methods=['POST']) def resolve(self): issue_id = request.args.get('id') if not issue_id and not ObjectId.is_valid(issue_id): flash('Invalid request.') return redirect(self.get_url('.index_view')) issue = models.Issue.objects(id=issue_id).first() if not issue: flash('Issue not found.') return redirect(self.get_url('.index_view')) issue.resolve_issue() flash('Issue has been resolved.') return redirect(self.get_url('.index_view')) @action('resolve_issues', 'Resolve selected issues', 'Are you sure you want to resolve selected issues?') def action_resolve_issues(self, ids): issues = models.Issue.objects(id__in=ids) for issue in issues: issue.resolve_issue() flash('Selected issues have been resolved.')
class CommentModelView(BaseModelView): can_create = False can_edit = False list_template = 'admin/list/_comment_list.html' def _list_video(view, context, model, name): return Markup('<a href="%s" target="_blank">%s</a>' % (url_for('home.video', id=model.video.id), model.video.title)) def _list_author(view, context, model, name): return Markup('<a href="%s" target="_blank">%s</a>' % (url_for('home.user', username=model.author.username), model.author.username)) column_formatters = dict(content=macro('render_content'), video=_list_video, author=_list_author) column_labels = { 'content': '评论内容', 'video': '评论于', 'author': '评论者', 'add_time': '评论时间', 'disabled': '屏蔽评论' } column_list = ('content', 'author', 'video', 'add_time', 'disabled') column_sortable_list = ('content', ('author', 'author.username'), ('video', 'video.title'), 'add_time', 'disabled') column_searchable_list = ('content', 'author.username', 'video.title') column_default_sort = ('add_time', True)
class ReportView(CustomView): """ Report view. """ can_create = False can_edit = False column_searchable_list = ('version', 'filename') column_filters = ('version', 'filename', 'latest') column_formatters = dict(changes=macro('render_changes')) @action('rollback', 'Rollback', 'Are you sure you want to rollback the configuration ?') def rollback(self): """ This is not used. :return: """ if session.get('ipaddr') is None: flash('APIC Credentials have not been entered', 'error') return redirect(url_for('snapshotsadmin.index_view')) return redirect(url_for('snapshotsadmin.index_view')) @action('view', 'View') def view(*args, **kwargs): """ :param args: :param kwargs: :return: """ return redirect(url_for('fileview.index'))
class BankAccountModelView(TimeTrackedModelView, AuthorizationRequiredView): list_template = 'bank_account/list.html' column_default_sort = 'name' page_size = 100 can_delete = True can_view_details = True def get_accessible_roles(self): return [RolesEnum.ADMIN.value] form_args = _form_args column_descriptions = CMC.get_column_descriptions() column_list = _column_list column_labels = CMC.get_column_labels() # NOTE: Don't touch encode at all and delegate to Markup column_formatters = { 'vendors': macro('render_vendors'), }
class CategoryView(BaseBlogView): def _list_thumbnail(view, context, model, name): if not model.picture or not model.picture.path: return '' return Markup( '<img src="%s">' % url_for('static', filename='blog/picture/' + 'thumb-' + model.picture.path)) column_formatters = dict(abstract=macro('render_abstract'), picture=_list_thumbnail) column_list = [ 'id', 'name', 'abstract', 'hidden', 'create_time', 'update_time', 'articles', 'user', 'picture' ] column_editable_list = ['hidden'] column_searchable_list = ['name', 'abstract'] column_filters = ['name', 'create_time', 'hidden'] column_labels = { 'id': u'序号', 'name': u'类别', 'abstract': u'介绍', 'hidden': u'状态', 'create_time': u'创建时间', 'update_time': u'更新时间', 'articles': u'文章', 'user': u'作者', 'picture': u'配图缩略图' } def __init__(self, session, **kwargs): super(CategoryView, self).__init__(Category, session, **kwargs)
class WorkflowView(ModelView): """View for managing Compliance results.""" can_edit = False can_delete = False can_create = False can_view_details = True column_default_sort = ('created', True) column_list = ('created', 'modified', 'status', 'name', 'info') column_labels = {'workflow.name': 'Workflow name'} column_sortable_list = () column_filters = ( 'created', 'modified', 'name', FilterStatus(column=Workflow.status, name='Status') ) column_formatters = { 'info': macro('render_info'), 'data': data_formatter, 'error_msg': error_msg_formatter, 'message': msg_formatter, } column_auto_select_related = True column_details_list = column_list + ('message', 'error_msg', 'data', ) column_details_exclude_list = ('info', ) @action('resume', 'Resume', 'Are you sure?') def action_resume(self, ids): objects = Workflow.query.filter(Workflow.uuid.in_(ids)).all() if len(objects) != len(ids): raise ValueError("Invalid id for workflow(s).") try: for workflow in objects: for workflow_object in workflow.objects: resume.apply_async((workflow_object.id,)) flash("Selected workflow(s) resumed.", "success") except Exception as e: flash("Failed to resume all selected workflows. Reason: %s" % e.message, "error") @action('restart', 'Restart', 'Are you sure?') def action_restart(self, ids): try: for id in ids: restart.apply_async((id,)) flash("Selected workflow(s) restarted.", "success") except Exception as e: flash("Failed to restart all selected workflows. Reason: %s" % e.message, "error") list_template = 'scoap3_workflows/admin/list.html'
class ProjectView(AdminModelView): can_delete = False can_view_details = True column_display_actions = False column_editable_list = ['name'] column_formatters = dict(actions=macro('render_actions')) column_labels = dict(min_version='Minimum Version') column_list = ['name', 'min_version', 'allowed_platforms', 'actions'] create_modal = True create_modal_template = 'admin/add_project_modal.html' create_template = 'admin/add_project.html' edit_template = 'admin/edit_project.html' form_args = dict( min_version={'label': 'Minimum required version of crashed app'}, allowed_platforms={'label': 'Allowed platforms'}) form_create_rules = ('name', ) form_edit_rules = ('min_version', 'allowed_platforms') form_overrides = dict(min_version=StringField) list_template = 'admin/project_list.html' @expose('/details/') def details_view(self): project = models.Project.objects.get(id=request.args.get('id')) minidump_versions = models.Minidump.get_versions_per_product( product=project.name) last_10_minidumps = models.Minidump.get_last_n_project_minidumps( n=10, project_name=project.name) issues = models.Issue.get_top_n_project_issues( n=10, project_name=project.name) return self.render('admin/project_overview.html', project=project, versions=minidump_versions, latest_crash_reports=last_10_minidumps, top_issues=issues) @expose('/_crash_reports') def crash_reports_chart(self): version = request.args.get('version') project = models.Project.objects.get(id=request.args.get('id')) platforms = project.get_allowed_platforms() if version and 'All' not in version: project_minidumps = models.Minidump.objects(product=project.name, version=version) else: project_minidumps = models.Minidump.objects(product=project.name) data = {} for platform in platforms: platform_minidumps = project_minidumps(platform=platform) data[platform] = \ models.Minidump.get_last_12_months_minidumps_counts( platform_minidumps) labels = get_last_12_months_labels() return jsonify(result=get_decorated_data( labels=labels, data=data.values(), data_labels=list(data.keys())))
class AdminAudioModelView(ModelView): edit_template = "test.html" can_export = True can_view_details = True export_types = ['xls'] column_display_pk = True # column_display_all_relations = True column_formatters = dict(url=macro('render_audio')) def is_accessible(self): return True return current_user.is_authenticated and ( current_user.superuser or current_user.email in U.superuser_set)
class QAListView(sqla.ModelView): column_searchable_list = ('name', ) # columns list Data source link to admin page, has data, source_url, run log with cleaned and source, date injested def is_accessible(self): return require.perms.is_admin() can_delete = False can_create = False can_edit = False column_formatters = dict(name=macro('render_qalist'), source_url=macro('render_sourceurl'), report_url=macro('render_report'), number_errors=macro('num_log_records')) #column_formatters = dict(dataset_admin_url=macro('render_price')) column_list = ( 'name', 'source_url', 'report_url', 'number_errors', ) list_template = 'adminsection/qalist.html'
class ClientsView(MyBaseView): column_list = [ 'id', 'x509_cname', 'x509_orgname', 'x509_orgstate', 'date_fseen', 'date_lseen', 'last_ip', 'evals_count' ] column_sortable_list = [ 'id', 'x509_serial', 'x509_cname', 'x509_orgdept', 'x509_orgname', 'x509_orgstate', 'evals_count' ] column_details_list = [ 'id', 'fingerprint', 'x509_serial', 'x509_cname', 'x509_email', 'x509_orgdept', 'x509_orgname', 'x509_orgstate', 'date_fseen', 'date_lseen', 'last_ip', 'evals_count', 'evals' ] column_exclude_list = ['x509_data'] column_export_list = [ 'id', 'fingerprint', 'x509_serial', 'x509_cname', 'x509_email', 'x509_orgdept', 'x509_orgname', 'x509_orgstate', 'date_fseen', 'date_lseen', 'last_ip', 'evals_count' ] column_export_exclude_list = ['x509_data', 'evals'] column_formatters_export = {} column_searchable_list = [] column_default_sort = ('id', True) column_filters = [ 'fingerprint', 'x509_serial', 'x509_cname', 'x509_orgname', 'x509_email', 'x509_orgstate', 'x509_orgdept', 'evals_count' ] column_formatters = dict(x509_cname=fmt_cclient_details, x509_orgname=fmt_text_bold, evals_count=fmt_counts, evals=macro('render_evals')) column_formatters_export = dict( x509_serial=lambda v, c, m, p: str(getattr(m, p)), date_fseen=lambda v, c, m, p: str(getattr(m, p)), date_lseen=lambda v, c, m, p: str(getattr(m, p))) column_labels = dict(id='Id', fingerprint='[X.509] Thumbprint', last_ip='Last seen IP', date_fseen='First seen Date', date_lseen='Last seen Date', evals_count='# of Evaluations', evals='List of Evaluations', x509_serial='[X.509] Serial', x509_cname='[X.509] Common Name', x509_email='[X.509] Email Address', x509_orgdept='[X.509] Department', x509_orgname='[X.509] Organization', x509_orgstate='[X.509] BULSTAT')
class AdminModelView(BaseModelView): can_edit = False form = AdminForm def on_model_change(self, form, model, is_created): if is_created: model.password = form.password.data column_exclude_list = ['password_hash'] column_searchable_list = ('name', 'email') column_labels = { 'name': '账户名', 'email': '账户邮箱', 'confirmed': '确认邮箱' } list_template = 'admin/list/_admin_list.html' column_formatters = dict(confirmed=macro('render_confirmed'))
class HomeSlideShowImagesView(ModelView): list_template = "admin/home_slide_show_settings.html" form_overrides = { "order": fields.IntegerField, "image_name": fields.FileField } column_formatters = { "image_name":macro("render_image") } def create_model(self, form): picture = save_picture( form.image_name.data, "static/vendor_product_pictures", 800,800 ) db.HomeSlideShowImages(form.order.data,picture,form.caption.data) flash("Record was successfully created", "success") def update_model(self, form, model): picture = save_picture( form.image_name.data, "static/vendor_product_pictures", 800,800 ) update = Session.query( db.HomeSlideShowImages ).filter( db.HomeSlideShowImages.image_id == model.id ).update( { "order":int(form.order.data), "image_name":picture, "caption": form.caption.data }) if update: Session.commit() return True else: return False
class UserModelView(BaseModelView): can_create = False form = UserForm list_template = 'admin/list/_user_list.html' edit_template = 'admin/edit/_user_edit.html' def _list_thumb_head_img(view, context, model, name): if not model.thumb_head_img: return Markup('<img src="%s" alt="头像缩略图">' % model.gravatar(size=50)) return Markup('<img src="%s" alt="头像缩略图">' % model.thumb_head_img) def _list_username(view, context, model, name): return Markup('<a href="%s" target="_blank">%s</a>' % (url_for('home.user', username=model.username), model.username)) column_formatters = dict(info=macro('render_info'), head_img=macro('render_head_img'), thumb_head_img=_list_thumb_head_img, phone=macro('render_phone'), location=macro('render_location'), get_like_num=macro('render_get_like_num'), username=_list_username, confirmed=macro('render_confirmed')) column_labels = { 'username': '******', 'email': '邮箱', 'phone': '手机号码', 'location': '所在地', 'info': '简介', 'head_img': '头像', 'thumb_head_img': '头像缩略图', 'confirmed': '是否确认', 'member_since': '注册时间', 'last_visit': '最后访问', 'get_like_num': '获得赞数' } column_exclude_list = ['password_hash', 'avatar_hash'] column_searchable_list = ('email', 'username', 'info', 'location', 'phone') column_default_sort = ('member_since', True) def on_model_change(self, form, model, is_created): if not is_created and form.dis_haed_img.data == 'True': model.head_img = None model.thumb_head_img = None
class ProvisionView(ModelView): column_list = ['applied_at', 'config_version', 'data'] column_details_list = ['applied_at', 'config_version', 'data'] column_default_sort = ('applied_at', True) list_template = 'admin/provision_list.html' details_template = 'admin/provision_details.html' column_formatters = { 'data': macro('render_data'), } def get_query(self): query = super().get_query() node_id = request.args.get('node_id') if node_id: query = query.filter_by(node_id=node_id) return query @expose('/ipxe') def raw_ipxe_view(self): provision = self.get_one(request.args.get('id')) return Response(provision.ipxe_config, mimetype='text/plain') @expose('/ignition.json') def raw_ignition_view(self): provision = self.get_one(request.args.get('id')) data = json.loads(provision.ignition_config) data = json.dumps(data, indent=2) return Response(data, mimetype='application/json') @expose('/ignition.tar.gz') def ignition_filesystem_view(self): provision = self.get_one(request.args.get('id')) tgz_data = ignition_parser.render_ignition_tgz( json.loads(provision.ignition_config)) return Response(tgz_data, mimetype='application/tar+gzip') def is_visible(self): return False
class ExternalObjectView(DefaultView): def __init__(self, *args, **kwargs): kwargs["category"] = "External Objects" kwargs["endpoint"] = kwargs["name"].lower().replace(" ", "") super(ExternalObjectView, self).__init__(ExternalObject, *args, **kwargs) can_view_details = True can_export = True # TODO: Export formatters export_types = ["csv", "xls"] column_details_list = ("id", "type", "values_list", "links_list") column_formatters = { "name": attribute_formatter(partial(eq, ValueType.NAME)), "title": attribute_formatter(partial(eq, ValueType.TITLE)), "date": attribute_formatter(partial(eq, ValueType.DATE), filter=lambda t: len(t) == 4), "genres": attribute_formatter(partial(eq, ValueType.GENRES), limit=3), "country": attribute_formatter(partial(eq, ValueType.COUNTRY), filter=lambda t: len(t) == 2), "duration": attribute_formatter( partial(eq, ValueType.DURATION), filter=lambda t: t.replace(".", "").isdigit(), limit=1, ), "series": series_formatter, "season": meta_formatter("season"), "episode": meta_formatter("episode"), "episode_details": macro("episode_details"), "episodes": episodes_formatter, "episodes_list": macro("episodes_list"), "values": attribute_formatter(show_score=True), "values_list": macro("values_list"), "links_list": macro("links_list"), "links": count_formatter, } column_extra_row_actions = [ EndpointLinkRowAction("glyphicon icon-search", "allobjects.index_view", id_arg="flt0_0") ] inline_models = ( (Value, dict(form_columns=("id", "type", "text"))), (ObjectLink, dict(form_columns=("id", "platform", "external_id"))), ) column_filters = [ ExternalObjectSimilarFilter(name="Similar"), ExternalObjectPlatformFilter( column=Platform.country, name="Platform", options=[(c[0], str(c[0]).upper()) for c in db.session.query(Platform.country).distinct()], ), ExternalObjectPlatformFilter( column=Platform.type, name="Platform", options=[(t.name, t.name) for t in PlatformType], ), ExternalObjectPlatformFilter( column=Platform.slug, name="Platform", options=[(p.slug, p.name) for p in db.session.query(Platform.slug, Platform.name)], ), ExternalObjectPlatformFilter( column=Platform.slug, invert=True, name="Platform", options=[(p.slug, p.name) for p in db.session.query(Platform.slug, Platform.name)], ), ] def get_query(self): q = (super(ExternalObjectView, self).get_query().options( joinedload(ExternalObject.values).joinedload(Value.sources))) if hasattr(self, "external_object_type"): q = q.filter(ExternalObject.type == self.external_object_type) return q def get_count_query(self): q = super(ExternalObjectView, self).get_count_query() if hasattr(self, "external_object_type"): q = q.filter(ExternalObject.type == self.external_object_type) return q
class DataModelView(ModelView, ViewAuthMixin): column_hide_backrefs = False column_exclude_list = ('cover_art', 'notes', 'lyrics', 'info') column_formatters = { 'catalog': macro('format_filters'), 'composer': macro('format_filters'), 'disc': macro('format_filters'), 'length': format_length } column_labels = { 'catalog': 'Catalog number', 'vgmdb_id': 'VGMdb id', 'id': 'Unique ID' } form_overrides = {'lyrics': TallTextAreaField, 'notes': TallTextAreaField} list_template = 'admin/list_filtered.html' def __init__(self, model, session): table = model.metadata.tables[model.__tablename__] self.column_list = [] self.form_columns = [] for column in table.c: if model is Track and column.name == 'catalog': self.column_list.append(column.name) self.column_list.append('album') self.form_columns.append('album') elif model is Track and column.name == 'composer_name': self.column_list.append('composer_name') self.form_columns.append('composer') elif column.name != 'id': self.column_list.append(column.name) self.form_columns.append(column.name) if model is Track: self.form_columns.append('vocalists') self.form_columns.append('lyricists') super(DataModelView, self).__init__(model, session) self.superuser = False def get_request_filters(self): return { key: value for key, value in request.args.items() if key in self.column_list } def get_filtered_query(self, query): filters = self.get_request_filters() if filters: return query.filter_by(**filters) else: return query def get_query(self): return self.get_filtered_query(super(DataModelView, self).get_query()) def get_count_query(self): return self.get_filtered_query( super(DataModelView, self).get_count_query()) def render(self, template, **kw): filters = self.get_request_filters() return super(DataModelView, self).render(template, column_labels=self.column_labels, request_filters=filters, **kw)
class ExternalObjectView(DefaultView): def __init__(self, *args, **kwargs): kwargs['category'] = 'External Objects' kwargs['endpoint'] = kwargs['name'].lower() + 'object' super(ExternalObjectView, self).__init__(ExternalObject, *args, **kwargs) can_view_details = True can_export = True # TODO: Export formatters export_types = ['csv', 'xls'] column_details_list = ('id', 'type', 'attributes_list', 'links_list') column_formatters = { 'name': attribute_formatter(partial(eq, ValueType.NAME)), 'title': attribute_formatter(partial(eq, ValueType.TITLE)), 'date': attribute_formatter(partial(eq, ValueType.DATE), filter=lambda t: len(t) == 4), 'genres': attribute_formatter(partial(eq, ValueType.GENRES), limit=3), 'country': attribute_formatter(partial(eq, ValueType.COUNTRY), filter=lambda t: len(t) == 2), 'duration': attribute_formatter(partial(eq, ValueType.DURATION), filter=lambda t: t.replace('.', '').isdigit(), limit=1), 'attributes': attribute_formatter(show_score=True), 'attributes_list': macro('attributes_list'), 'links_list': macro('links_list'), 'links': count_formatter } column_extra_row_actions = [ EndpointLinkRowAction('glyphicon icon-search', 'allobject.index_view', id_arg='flt0_0') ] inline_models = ((Value, dict(form_columns=('id', 'type', 'text'))), (ObjectLink, dict(form_columns=('id', 'platform', 'external_id')))) column_filters = [ ExternalObjectSimilarFilter(name='Similar'), ExternalObjectPlatformFilter( column=Platform.country, name='Platform', options=[(c[0], str(c[0]).upper()) for c in db.session.query(Platform.country).distinct()]), ExternalObjectPlatformFilter(column=Platform.type, name='Platform', options=[(t.name, t.name) for t in PlatformType]), ExternalObjectPlatformFilter( column=Platform.slug, name='Platform', options=[(p.slug, p.name) for p in db.session.query(Platform.slug, Platform.name)]), ExternalObjectPlatformFilter( column=Platform.slug, invert=True, name='Platform', options=[(p.slug, p.name) for p in db.session.query(Platform.slug, Platform.name)]), ] def get_query(self): q = super(ExternalObjectView, self).get_query() if hasattr(self, 'external_object_type'): q = q.filter(ExternalObject.type == self.external_object_type) return q def get_count_query(self): q = super(ExternalObjectView, self).get_count_query() if hasattr(self, 'external_object_type'): q = q.filter(ExternalObject.type == self.external_object_type) return q
def test_export_csv(): app, admin = setup() client = app.test_client() # test redirect when csv export is disabled view = MockModelView(Model, column_list=['col1', 'col2'], endpoint="test") admin.add_view(view) rv = client.get('/admin/test/export/csv/') eq_(rv.status_code, 302) # basic test of csv export with a few records view_data = { 1: Model(1, "col1_1", "col2_1"), 2: Model(2, "col1_2", "col2_2"), 3: Model(3, "col1_3", "col2_3"), } view = MockModelView(Model, view_data, can_export=True, column_list=['col1', 'col2']) admin.add_view(view) rv = client.get('/admin/model/export/csv/') data = rv.data.decode('utf-8') eq_(rv.mimetype, 'text/csv') eq_(rv.status_code, 200) ok_("Col1,Col2\r\n" "col1_1,col2_1\r\n" "col1_2,col2_2\r\n" "col1_3,col2_3\r\n" == data) # test utf8 characters in csv export view_data[4] = Model(1, u'\u2013ut8_1\u2013', u'\u2013utf8_2\u2013') view = MockModelView(Model, view_data, can_export=True, column_list=['col1', 'col2'], endpoint="utf8") admin.add_view(view) rv = client.get('/admin/utf8/export/csv/') data = rv.data.decode('utf-8') eq_(rv.status_code, 200) ok_(u'\u2013ut8_1\u2013,\u2013utf8_2\u2013\r\n' in data) # test row limit view_data = { 1: Model(1, "col1_1", "col2_1"), 2: Model(2, "col1_2", "col2_2"), 3: Model(3, "col1_3", "col2_3"), } view = MockModelView(Model, view_data, can_export=True, column_list=['col1', 'col2'], export_max_rows=2, endpoint='row_limit_2') admin.add_view(view) rv = client.get('/admin/row_limit_2/export/csv/') data = rv.data.decode('utf-8') eq_(rv.status_code, 200) ok_("Col1,Col2\r\n" "col1_1,col2_1\r\n" "col1_2,col2_2\r\n" == data) # test None type, integer type, column_labels, and column_formatters view_data = { 1: Model(1, "col1_1", 1), 2: Model(2, "col1_2", 2), 3: Model(3, None, 3), } view = MockModelView( Model, view_data, can_export=True, column_list=['col1', 'col2'], column_labels={'col1': 'Str Field', 'col2': 'Int Field'}, column_formatters=dict(col2=lambda v, c, m, p: m.col2*2), endpoint="types_and_formatters" ) admin.add_view(view) rv = client.get('/admin/types_and_formatters/export/csv/') data = rv.data.decode('utf-8') eq_(rv.status_code, 200) ok_("Str Field,Int Field\r\n" "col1_1,2\r\n" "col1_2,4\r\n" ",6\r\n" == data) # test column_formatters_export and column_formatters_export type_formatters = {type(None): lambda view, value: "null"} view = MockModelView( Model, view_data, can_export=True, column_list=['col1', 'col2'], column_formatters_export=dict(col2=lambda v, c, m, p: m.col2*3), column_formatters=dict(col2=lambda v, c, m, p: m.col2*2), # overridden column_type_formatters_export=type_formatters, endpoint="export_types_and_formatters" ) admin.add_view(view) rv = client.get('/admin/export_types_and_formatters/export/csv/') data = rv.data.decode('utf-8') eq_(rv.status_code, 200) ok_("Col1,Col2\r\n" "col1_1,3\r\n" "col1_2,6\r\n" "null,9\r\n" == data) # Macros are not implemented for csv export yet and will throw an error view = MockModelView( Model, can_export=True, column_list=['col1', 'col2'], column_formatters=dict(col1=macro('render_macro')), endpoint="macro_exception" ) admin.add_view(view) rv = client.get('/admin/macro_exception/export/csv/') data = rv.data.decode('utf-8') eq_(rv.status_code, 500)
def test_export_csv(): app, admin = setup() client = app.test_client() # test redirect when csv export is disabled view = MockModelView(Model, column_list=['col1', 'col2'], endpoint="test") admin.add_view(view) rv = client.get('/admin/test/export/csv/') eq_(rv.status_code, 302) # basic test of csv export with a few records view_data = { 1: Model(1, "col1_1", "col2_1"), 2: Model(2, "col1_2", "col2_2"), 3: Model(3, "col1_3", "col2_3"), } view = MockModelView(Model, view_data, can_export=True, column_list=['col1', 'col2']) admin.add_view(view) rv = client.get('/admin/model/export/csv/') data = rv.data.decode('utf-8') eq_(rv.mimetype, 'text/csv') eq_(rv.status_code, 200) ok_("Col1,Col2\r\n" "col1_1,col2_1\r\n" "col1_2,col2_2\r\n" "col1_3,col2_3\r\n" == data) # test explicit use of column_export_list view = MockModelView(Model, view_data, can_export=True, column_list=['col1', 'col2'], column_export_list=['id', 'col1', 'col2'], endpoint='exportinclusion') admin.add_view(view) rv = client.get('/admin/exportinclusion/export/csv/') data = rv.data.decode('utf-8') eq_(rv.mimetype, 'text/csv') eq_(rv.status_code, 200) ok_("Id,Col1,Col2\r\n" "1,col1_1,col2_1\r\n" "2,col1_2,col2_2\r\n" "3,col1_3,col2_3\r\n" == data) # test explicit use of column_export_exclude_list view = MockModelView(Model, view_data, can_export=True, column_list=['col1', 'col2'], column_export_exclude_list=['col2'], endpoint='exportexclusion') admin.add_view(view) rv = client.get('/admin/exportexclusion/export/csv/') data = rv.data.decode('utf-8') eq_(rv.mimetype, 'text/csv') eq_(rv.status_code, 200) ok_("Col1\r\n" "col1_1\r\n" "col1_2\r\n" "col1_3\r\n" == data) # test utf8 characters in csv export view_data[4] = Model(1, u'\u2013ut8_1\u2013', u'\u2013utf8_2\u2013') view = MockModelView(Model, view_data, can_export=True, column_list=['col1', 'col2'], endpoint="utf8") admin.add_view(view) rv = client.get('/admin/utf8/export/csv/') data = rv.data.decode('utf-8') eq_(rv.status_code, 200) ok_(u'\u2013ut8_1\u2013,\u2013utf8_2\u2013\r\n' in data) # test None type, integer type, column_labels, and column_formatters view_data = { 1: Model(1, "col1_1", 1), 2: Model(2, "col1_2", 2), 3: Model(3, None, 3), } view = MockModelView( Model, view_data, can_export=True, column_list=['col1', 'col2'], column_labels={'col1': 'Str Field', 'col2': 'Int Field'}, column_formatters=dict(col2=lambda v, c, m, p: m.col2 * 2), endpoint="types_and_formatters" ) admin.add_view(view) rv = client.get('/admin/types_and_formatters/export/csv/') data = rv.data.decode('utf-8') eq_(rv.status_code, 200) ok_("Str Field,Int Field\r\n" "col1_1,2\r\n" "col1_2,4\r\n" ",6\r\n" == data) # test column_formatters_export and column_formatters_export type_formatters = {type(None): lambda view, value: "null"} view = MockModelView( Model, view_data, can_export=True, column_list=['col1', 'col2'], column_formatters_export=dict(col2=lambda v, c, m, p: m.col2 * 3), column_formatters=dict(col2=lambda v, c, m, p: m.col2 * 2), # overridden column_type_formatters_export=type_formatters, endpoint="export_types_and_formatters" ) admin.add_view(view) rv = client.get('/admin/export_types_and_formatters/export/csv/') data = rv.data.decode('utf-8') eq_(rv.status_code, 200) ok_("Col1,Col2\r\n" "col1_1,3\r\n" "col1_2,6\r\n" "null,9\r\n" == data) # Macros are not implemented for csv export yet and will throw an error view = MockModelView( Model, can_export=True, column_list=['col1', 'col2'], column_formatters=dict(col1=macro('render_macro')), endpoint="macro_exception" ) admin.add_view(view) rv = client.get('/admin/macro_exception/export/csv/') data = rv.data.decode('utf-8') eq_(rv.status_code, 500) # We should be able to specify column_formatters_export # and not get an exception if a column_formatter is using a macro def export_formatter(v, c, m, p): return m.col1 if m else '' view = MockModelView( Model, view_data, can_export=True, column_list=['col1', 'col2'], column_formatters=dict(col1=macro('render_macro')), column_formatters_export=dict(col1=export_formatter), endpoint="macro_exception_formatter_override" ) admin.add_view(view) rv = client.get('/admin/macro_exception_formatter_override/export/csv/') data = rv.data.decode('utf-8') eq_(rv.status_code, 200) ok_("Col1,Col2\r\n" "col1_1,1\r\n" "col1_2,2\r\n" ",3\r\n" == data) # We should not get an exception if a column_formatter is # using a macro but it is on the column_export_exclude_list view = MockModelView( Model, view_data, can_export=True, column_list=['col1', 'col2'], column_formatters=dict(col1=macro('render_macro')), column_export_exclude_list=['col1'], endpoint="macro_exception_exclude_override" ) admin.add_view(view) rv = client.get('/admin/macro_exception_exclude_override/export/csv/') data = rv.data.decode('utf-8') eq_(rv.status_code, 200) ok_("Col2\r\n" "1\r\n" "2\r\n" "3\r\n" == data) # When we use column_export_list to hide the macro field # we should not get an exception view = MockModelView( Model, view_data, can_export=True, column_list=['col1', 'col2'], column_formatters=dict(col1=macro('render_macro')), column_export_list=['col2'], endpoint="macro_exception_list_override" ) admin.add_view(view) rv = client.get('/admin/macro_exception_list_override/export/csv/') data = rv.data.decode('utf-8') eq_(rv.status_code, 200) ok_("Col2\r\n" "1\r\n" "2\r\n" "3\r\n" == data) # If they define a macro on the column_formatters_export list # then raise an exception view = MockModelView( Model, view_data, can_export=True, column_list=['col1', 'col2'], column_formatters=dict(col1=macro('render_macro')), endpoint="macro_exception_macro_override" ) admin.add_view(view) rv = client.get('/admin/macro_exception_macro_override/export/csv/') data = rv.data.decode('utf-8') eq_(rv.status_code, 500)
class ApiRegistrationsView(ModelView): """View for managing access to actions by users.""" can_view_details = True can_edit = False column_list = ('id', 'creation_date', 'name', 'partner', 'organization', 'email', 'role', 'country', 'description', 'accepted') column_default_sort = ('id', True) column_labels = { 'creation_date': "Registration date", 'name': "Name", 'email': "E-mail", } list_template = 'scoap3_api/custom_list.html' details_template = 'scoap3_api/details.html' column_filters = ('id', 'creation_date', 'name', 'partner', 'organization', 'country', 'accepted') column_formatters = {'accepted': macro('render_tri_state_boolean_to_icon')} def _create_user_for_api_registration(self, api_user_id): api_registration = ApiRegistrations.query.filter_by( id=api_user_id).one() password = os.urandom(5).encode('hex') kwargs = dict(email=api_registration.email, password=password, active='y') form = ConfirmRegisterForm(MultiDict(kwargs), csrf_enabled=False) u = None # Role with id 4 is an API user r = Role.query.filter_by(id=4).one() if form.validate(): kwargs['password'] = hash_password(kwargs['password']) kwargs['active'] = True u = _datastore.create_user(**kwargs) if _datastore.add_role_to_user(u, r): msg = TemplatedMessage( template_html='scoap3_api/email/confirmed.html', subject='SCOAP3 - Partner registration confirmation', sender=current_app.config.get('MAIL_DEFAULT_SENDER'), recipients=[api_registration.email], ctx={ 'email': api_registration.email, 'password': password, 'recipient': api_registration.name }) current_app.extensions['mail'].send(msg) else: flash('Error creating user. %s' % form.errors, 'error') @action('accept', 'Accept', 'Are you sure you want to accept selected Partner registrations?') def action_accept(self, ids): """Accept users.""" try: count = 0 for api_user_id in ids: user = ApiRegistrations.query.filter_by(id=api_user_id).one() if user.accepted == 1: flash('API user %s was already accepted.' % (user.email, ), 'warning') continue if not ApiRegistrations.accept(api_user_id): raise ValueError("Cannot find API user registration.") else: count += 1 self._create_user_for_api_registration(api_user_id) db.session.commit() if count > 0: flash('API user(s) were successfully accepted.', 'success') except Exception as exc: if not self.handle_view_exception(exc): raise current_app.logger.exception(str(exc)) # pragma: no cover flash('Failed to accept API users.', 'error') # pragma: no cover @action('reject', 'Reject', 'Are you sure you want to reject selected Partner registrations?') def action_reject(self, ids): """Accept users.""" try: count = 0 for api_user_id in ids: if not ApiRegistrations.reject(api_user_id): raise ValueError("Cannot find API user registration.") else: count += 1 db.session.commit() if count > 0: flash('API user(s) were successfully rejected.', 'success') except Exception as exc: if not self.handle_view_exception(exc): raise current_app.logger.exception(str(exc)) # pragma: no cover flash('Failed to reject API users.', 'error') # pragma: no cover
class IdentityView(ModelView): column_list = [ 'issuer', 'name', 'status', ] list_template = 'admin/identity_list.html' column_formatters = { 'status': macro('render_status'), } form_excluded_columns = [ 'name', 'issues', 'pairs', ] form_extra_fields = { 'subj_cn': fields.StringField('CN', description='Common Name', validators=required), 'subj_c': fields.StringField('C', description='Country'), 'subj_o': fields.StringField('O', description='Organization'), 'subj_ou': fields.StringField('OU', description='Organizational Unit'), 'subj_dnq': fields.StringField('', description='Distinguished name qualifier'), 'subj_st': fields.StringField('ST', description='State or province name'), 'subj_sn': fields.StringField('', description='Serial number'), 'cert_validate_since': fields.DateTimeField('Valid since', description='Not valid before', validators=required, default=default_since), 'cert_validate_till': fields.DateTimeField('Valid till', description='Not valid after', validators=required, default=default_till), 'cert_ca_path_length': fields.IntegerField('CA path length', default=0), 'san_ips': InlineFieldList(fields.StringField('IP', [validators.IPAddress()]), 'IP', description='IP address'), 'san_dns_names': InlineFieldList(fields.StringField('DNS'), 'DNS', description='DNS names'), 'ku_web_server_auth': fields.BooleanField('Web server auth', description='TLS Web Server Authentication'), 'ku_web_client_auth': fields.BooleanField('Web client auth', description='TLS Web Client Authentication'), 'key_size': fields.IntegerField('Size', default=2048), 'key_public_exponent': fields.IntegerField('Public exponent', default=65537), } form_rules = [ rules.Field('issuer'), rules.FieldSet([ 'cert_validate_since', 'cert_validate_till', 'cert_ca_path_length', ], 'Certificate settings'), rules.FieldSet([ 'subj_cn', 'subj_c', 'subj_o', 'subj_ou', 'subj_dnq', 'subj_st', 'subj_sn', ], 'Subject'), rules.FieldSet([ 'san_ips', 'san_dns_names', ], 'Subject Alternative Names'), rules.FieldSet([ 'ku_web_server_auth', 'ku_web_client_auth', ], 'Key Usage'), rules.FieldSet([ 'key_size', 'key_public_exponent', ], 'Key Settings'), ] def create_model(self, *args, **kwargs): with self.session.no_autoflush: return super().create_model(*args, **kwargs) def on_model_change(self, form, model, is_created): data = x509.CertInfo(form.data) if data.issuer: pair = models.Pair( *x509.issue_certificate(data, data.issuer.pair.as_tuple)) else: pair = models.Pair(*x509.issue_certificate(data)) model.pair = pair model.name = data.subj_cn @expose('/reissue/', methods=['POST']) def reissue_view(self): model = self.get_one(request.values.get('id')) info = x509.load_certificate_info(model.pair.as_tuple, reissue=True) if model.issuer: pair = models.Pair( *x509.issue_certificate(info, model.issuer.pair.as_tuple)) else: pair = models.Pair(*x509.issue_certificate(info)) model.pair = pair self.session.commit() return_url = get_redirect_target() or self.get_url('.index_view') flash('The identity certificate was successfully reissued', 'success') return redirect(return_url) def edit_form(self, obj=None): if obj: info = x509.load_certificate_info(obj.pair.as_tuple, reissue=True) for k, v in info.as_dict().items(): if not hasattr(obj, k): setattr(obj, k, v) return super().edit_form(obj) @expose('/details/') def details_view(self): model = self.get_one(request.values.get('id')) return_url = get_redirect_target() or self.get_url('.index_view') return redirect( self.get_url('pair.details_view', id=model.pair.id, url=return_url)) @expose('/import/', methods=['GET', 'POST']) def import_view(self): return_url = get_redirect_target() or self.get_url('.index_view') form = ImportForm(get_form_data()) if self.validate_form(form): pair_tuple = form.data['cert'].encode( 'ascii'), form.data['key'].encode('ascii') info = x509.load_certificate_info(pair_tuple) if not x509.does_keys_match(pair_tuple): flash('Failed to import identity: keys does not match.', 'error') return redirect(return_url) identity = models.Identity() identity.name = info.subj_cn if not info.self_signed: def find_issuer(): for issuer in models.Identity.query.filter_by( name=info.issuer_cn): cert_chain = issuer.get_cert_chain() try: x509.verify_certificate_chain( pair_tuple[0], cert_chain) except x509.InvalidCertificate: pass else: return issuer identity.issuer = find_issuer() if not identity.issuer: flash( 'Failed to import identity: issuer identity not found.', 'error') return redirect(return_url) self.session.add(identity) pair = models.Pair(*pair_tuple) pair.identity = identity self.session.add(pair) try: self.session.commit() except IntegrityError: flash( 'Failed to import identity: identity with same name already exists.', 'error') return redirect(return_url) flash('Identity was successfully imported.', 'success') return redirect(self.get_save_return_url(identity, is_created=True)) return self.render('admin/identity_import.html', form=form, return_url=return_url)
class ArticleView(BaseBlogView): list_template = '_article_list.html' edit_template = '_edit_get_form.html' # column_exclude_list = ['abstract', 'text', 'comments'] column_list = [ 'id', 'title', 'abstract', 'text', 'create_time', 'update_time', 'state', 'visit_num', 'category', 'tags', 'comments', 'user', 'picture' ] column_searchable_list = ['title'] column_filters = ['title', 'create_time', 'state'] column_editable_list = ['state', 'visit_num', 'tags', 'category'] form_excluded_columns = ['title', 'text'] column_default_sort = ('id', True) # 覆盖path默认显示 def _list_thumbnail(view, context, model, name): try: if not model.picture or not model.picture.path: return '' return Markup('<img src="%s">' % url_for( 'static', filename='blog/picture/' + 'thumb-' + model.picture.path)) except Exception as e: current_app.logger.error(e) column_formatters = dict(text=macro('render_text'), abstract=macro('render_abstract'), picture=_list_thumbnail) column_labels = { 'id': u'序号', 'title': u'题目', 'abstract': u'摘要', 'text': u'正文', 'create_time': u'创建时间', 'update_time': u'更新时间', 'state': u'状态', 'category': u'类型', 'tags': u'标签', 'comments': u'评论', 'user': u'作者', 'visit_num': u'浏览次数', 'picture': u'配图' } @expose('/editor_pic', methods=["POST"]) def editor_pic(self): image_file = request.files['editormd-image-file'] if image_file and allowed_photo(image_file.filename): try: filename = secure_filename(image_file.filename) filename = str( date.today()) + '-' + random_str() + '-' + filename file_path = os.path.join(bpdir, 'static/editor.md/photoupdate/', filename) qiniu_path = os.path.join(bpdir, 'static/blog/qiniu_pic/', filename) image_file.save(file_path) ting_pic(file_path, qiniu_path) qiniu_link = get_link(qiniu_path, filename) data = { 'success': 1, 'message': 'image of editor.md', 'url': qiniu_link } return json.dumps(data) except Exception as e: current_app.logger.error(e) else: return u"没有获得图片或图片类型不支持" @expose('/change_do_article', methods=["GET", "POST"]) def change_do_article(self): return self.render('_change_doarticle.html') @expose('/create_article', methods=["GET", "POST"]) def create_article(self): article_form = ArticleForm() article_form.picture.choices = [ (picture, picture.name) for picture in Picture.query.order_by('name').filter_by(state=True) ] article_form.tags.choices = [(tag, tag.name) for tag in Tag.query.order_by('name')] article_form.category.choices = [ (category, category.name) for category in Category.query.order_by('name') ] article_form.create_time.default = datetime.utcnow() article_form.update_time.default = datetime.utcnow() return self.render('_create_article.html', article_form=article_form) @expose('/save_article', methods=["GET", "POST"]) def save_article(self): article_form = ArticleForm() article_form.tags.choices = [(tag, tag.name) for tag in Tag.query.order_by('name')] article_form.category.choices = [ (category, category.name) for category in Category.query.order_by('name') ] article_form.picture.choices = [ (picture, picture.name) for picture in Picture.query.order_by('name').filter_by(state=True) ] new_article = Article(title=article_form.title.data, text=article_form.text.data, html_text=article_form.html.data) new_article.category = article_form.category.data new_article.abstract = article_form.abstract.data new_article.tags = article_form.tags.data new_article.picture = article_form.picture.data new_article.create_time = article_form.create_time.data new_article.update_time = article_form.update_time.data if article_form.print_submit.data: new_article.state = True if article_form.picture.data: the_picture = Picture.query.filter_by( name=article_form.picture.data.name).first() if the_picture.name != u'暂不选择配图': the_picture.state = False db.session.add(the_picture) db.session.add(new_article) db.session.commit() try: filename = ' '.join(article_form.title.data.split()) + '.md' with codecs.open(bpdir + '/static/blog/mdfile/' + filename, 'w', encoding='utf-8') as f: f.write(article_form.text.data) except Exception as e: current_app.logger.info(e) if not article_form.print_submit.data: return redirect( url_for('.edit_get_form', article_id=new_article.id)) return redirect('/huangzp/article') @expose('/edit_get_form/<article_id>', methods=["GET", "POST"]) def edit_get_form(self, article_id): article_form = ArticleForm() article_form.tags.choices = [(tag, tag.name) for tag in Tag.query.order_by('name')] article_form.category.choices = [ (category, category.name) for category in Category.query.order_by('name') ] article_form.picture.choices = [ (picture, picture.name) for picture in Picture.query.order_by('name').filter_by(state=True) ] the_article = Article.query.filter_by(id=article_id).first() # 修改文章默认配图 if the_article.picture and the_article.picture.name != u'暂不选择配图': article_form.picture.choices.append( (the_article.picture, the_article.picture.name)) article_form.title.default = the_article.title article_form.text.default = the_article.text article_form.abstract.default = the_article.abstract article_form.category.default = the_article.category article_form.tags.default = the_article.tags article_form.picture.default = the_article.picture article_form.create_time.default = the_article.create_time article_form.update_time.default = the_article.update_time article_form.process() return self.render('_edit_article.html', article_form=article_form, article_id=article_id) @expose('/edit_to_save/<article_id>', methods=["GET", "POST"]) def edit_to_save(self, article_id): article_form = ArticleForm() article_form.tags.choices = [(tag, tag.name) for tag in Tag.query.order_by('name')] article_form.category.choices = [ (category, category.name) for category in Category.query.order_by('name') ] article_form.picture.choices = [ (picture, picture.name) for picture in Picture.query.order_by('name').filter_by(state=True) ] the_article = Article.query.filter_by(id=article_id).first() # 删除旧备份 old_filename = ' '.join(the_article.title.split()) + '.md' try: os.remove(bpdir + '/static/blog/mdfile/' + old_filename) except: pass # 有时候直接删除已经关联文章的照片,会造成文章的照片属性为空 if not the_article.picture: new_picture = Picture.query.filter_by( name=article_form.picture.data.name).first() if new_picture.name != u'暂不选择配图': new_picture.state = False db.session.add(new_picture) else: new_picture = Picture.query.filter_by( name=article_form.picture.data.name).first() old_picture = Picture.query.filter_by( name=the_article.picture.name).first() if new_picture == old_picture: pass elif new_picture.name == u'暂不选择配图': new_picture.state = True old_picture.state = True db.session.add(new_picture, old_picture) else: new_picture.state = False old_picture.state = True db.session.add(new_picture, old_picture) the_article.title = article_form.title.data the_article.tags = article_form.tags.data the_article.category = article_form.category.data the_article.picture = article_form.picture.data the_article.text = article_form.text.data the_article.html_text = article_form.html.data the_article.abstract = article_form.abstract.data the_article.create_time = article_form.create_time.data the_article.update_time = article_form.update_time.data # else确保修改的文章状态是True然后点的保存 if article_form.print_submit.data: the_article.state = True else: the_article.state = False db.session.add(the_article) db.session.commit() try: new_filename = ' '.join(article_form.title.data.split()) + '.md' with codecs.open(bpdir + '/static/blog/mdfile/' + new_filename, 'w', encoding='utf-8') as f: f.write(article_form.text.data) except Exception as e: current_app.logger.info(e) if not article_form.print_submit.data: return redirect( url_for('.edit_get_form', article_id=the_article.id)) return redirect('/huangzp/article') @expose('/do_file', methods=["POST"]) def do_file(self): md_file = request.files['file'] if md_file and allowed_file(md_file.filename): try: fname = secure_filename(md_file.filename) ext = fname.rsplit('.', 1)[1] unix_time = int(time.time()) new_filename = str(unix_time) + '.' + ext filepath = os.path.join(bpdir, 'static/blog/read_mdfile/', new_filename) md_file.save(filepath) except Exception as e: current_app.logger.info(e) with open(filepath, 'r') as f: file_context = f.read() # if file_context[:3] == codecs.BOM_UTF8: # data = file_context[3:] # print data.decode("utf-8") article_form = ArticleForm() article_form.tags.choices = [ (tag, tag.name) for tag in Tag.query.order_by('name') ] article_form.category.choices = [ (category, category.name) for category in Category.query.order_by('name') ] article_form.picture.choices = [ (picture, picture.name) for picture in Picture.query.order_by('name').filter_by( state=True) ] article_form.text.default = file_context.decode('utf-8') article_form.process() return self.render('_create_article.html', article_form=article_form) else: return u"没有获得文件或文件类型错误" def __init__(self, session, **kwargs): super(ArticleView, self).__init__(Article, session, **kwargs)
class ParticipantSetAdminView(SetViewMixin, BaseAdminView): column_list = ('name', 'location_set', 'participants') column_labels = { 'name': _('Name'), 'location_set': _('Location Set'), 'participants': _('Participants'), 'gender_hidden': _('Hide Gender'), 'role_hidden': _('Hide Role'), 'partner_hidden': _('Hide Organization'), } column_descriptions = { 'gender_hidden': _('If enabled, will hide the gender in the participant list.'), # noqa 'role_hidden': _('If enabled, will hide the role in the participant list.'), # noqa 'partner_hidden': _('If enabled, will hide the organization in the participant list.' ), # noqa } form_columns = ('name', 'location_set', 'gender_hidden', 'role_hidden', 'partner_hidden') column_formatters = {'participants': macro('participants_list')} inline_models = (ExtraDataInlineFormAdmin(models.ParticipantDataField), ) inline_model_form_converter = ParticipantExtraDataModelConverter def create_form(self, obj=None): deployment = g.deployment form = super().create_form(obj) form.location_set.choices = models.LocationSet.query.filter_by( deployment=deployment).with_entities( models.LocationSet.id, models.LocationSet.name).all() return form def edit_form(self, obj=None): deployment = g.deployment form = super().edit_form(obj) form.location_set.choices = models.LocationSet.query.filter_by( deployment=deployment).with_entities( models.LocationSet.id, models.LocationSet.name).all() return form def after_model_change(self, form, model, is_created): _role = models.ParticipantRole.query.filter( models.ParticipantRole.name == '$FC', models.ParticipantRole.participant_set == model).first() if not _role: _role = models.ParticipantRole.create(name='$FC', participant_set=model) @expose('/participants/<int:participant_set_id>', methods=['GET', 'POST']) def participants_list(self, participant_set_id): return participant_list(participant_set_id, self) @expose('/participants/<int:participant_set_id>/import', methods=['POST']) def participants_import(self, participant_set_id): return participant_list_import(participant_set_id) @expose('/participants/<int:participant_set_id>/headers/<int:upload_id>', methods=['GET', 'POST']) def participants_headers(self, participant_set_id, upload_id): return participant_headers(upload_id, participant_set_id, self) @expose('/participant/<int:id>', methods=['GET', 'POST']) def participant_edit(self, id): return participant_edit(id, self)
class NodeView(ModelView): column_list = [ 'cluster', 'fqdn', 'ip', 'maintenance_mode', 'credentials', 'config', ] list_template = 'admin/node_list.html' details_template = 'admin/node_details.html' form_excluded_columns = [ 'credentials', 'target_config_version', 'active_config_version', 'provisions', 'disks', ] column_formatters = { 'credentials': macro('render_credentials'), 'config': macro('render_config'), } column_labels = { 'ip': "Public IP", 'fqdn': "Fully Qualified Domain Name", 'maintenance_mode': "Maintenance mode", 'debug_boot': "Debug boot", 'coreos_autologin': "******", 'linux_consoles': "Linux console devices", 'disable_ipv6': "Disable IPv6 in Linux kernel", 'is_etcd_server': "etcd server", 'is_k8s_schedulable': "Kubernetes schedulable", 'is_k8s_master': "Kubernetes master", 'mountpoints': 'Additional mountpoints', 'addresses': 'Additional IP addresses', } column_descriptions = { 'maintenance_mode': "If this is enabled, node will be booted in minimal CoreOS environment without " "touching root partition.", 'debug_boot': "Forward all system journal messages to kmsg for troubleshooting.", 'coreos_autologin': "******" "for debugging. Don't enable in production.", 'linux_consoles': "Passed to kernel as `console` arguments. (Separate by comma.)", 'disable_ipv6': "Passed to kernel as `ipv6.disable=1` argument.", 'is_etcd_server': "Run etcd server on this node and connect other nodes to it.", 'is_k8s_schedulable': "Run kubelet on this node and register it as schedulable.", 'is_k8s_master': "Run kubelet on this node and add persistent kube-apiserver, kube-controller-manager, " "kube-scheduler pods to it.", } inline_models = [ (models.Mountpoint, { 'column_descriptions': { 'what': 'Device to mount.', 'where': 'Mount path.', 'wanted_by': 'WantedBy systemd unit.', 'is_persistent': 'Use this partition to store critical data that should survive reboots.', } }), (models.Address, { 'column_descriptions': { 'interface': 'Network interface.', 'ip': 'IP address.', } }), ] form_rules = [ rules.Field('cluster'), rules.Field('ip'), rules.Field('fqdn'), rules.FieldSet([ 'maintenance_mode', 'debug_boot', 'coreos_autologin', 'linux_consoles', 'disable_ipv6', 'mountpoints', 'addresses', 'additional_kernel_cmdline', ], 'Boot'), rules.FieldSet([ 'is_etcd_server', 'is_k8s_schedulable', 'is_k8s_master', ], 'Components'), ] # without this, Node is saved to database before on_model_change() gets called # and this happens only when there is inline_models def create_model(self, *args, **kwargs): with self.session.no_autoflush: return super().create_model(*args, **kwargs) def _issue_creds(self, model): with self.session.no_autoflush: ca_creds = model.cluster.ca_credentials creds = models.CredentialsData() creds.cert, creds.key = pki.issue_certificate('system:node:' + model.fqdn, ca_cert=ca_creds.cert, ca_key=ca_creds.key, organizations=['system:nodes'], san_dns=model.certificate_alternative_dns_names, san_ips=model.certificate_alternative_ips, certify_days=10000, is_web_server=True, is_web_client=True) self.session.add(creds) model.credentials = creds def on_model_change(self, form, model, is_created): if is_created: self._issue_creds(model) else: model.target_config_version += 1 def on_model_delete(self, model): model.mountpoints.delete() model.addresses.delete() def after_model_delete(self, model): models.CredentialsData.query.filter_by(id=model.credentials_id).delete() @expose('/reissue-credentials', methods=['POST']) def reissue_creds_view(self): model = self.get_one(request.args.get('id')) model.target_config_version += 1 self._issue_creds(model) self.session.add(model) self.session.commit() return_url = get_redirect_target() or self.get_url('.index_view') flash('The credentials successfully reissued', 'success') return redirect(return_url) @expose('/target-ignition.json') def target_ignition_config_view(self): node = self.get_one(request.args.get('id')) response = config_renderer.ignition.render(node, indent=True) return Response(response, mimetype='application/json') @expose('/target.ipxe') def target_ipxe_config_view(self): node = self.get_one(request.args.get('id')) response = config_renderer.ipxe.render(node, request.url_root) return Response(response, mimetype='text/plain')
class AircraftInformationView(MongoCustomView): "飞行器的通用视图" create_template = 'aircraft/create.html' details_modal_template = 'modal/details.html' edit_modal_template = 'modal/edit.html' create_modal_template = 'modal/create.html' # 为了使用datepicker相关插件 extra_js = [ '/static/js/list_light.js', '/static/js/bootstrap-datetimepicker.min.js', '/static/js/datetimepicker.zh-cn.js', '/static/js/jquery.magnific-popup.min.js', '/static/js/custom_action.js', '/static/js/jquery.json-2.2.js', '/static/js/datetostr.js', # 利用率的弹出框 '/static/js/jquery.uniform.min.js', '/static/js/jquery.slimscroll.min.js', '/static/js/jquery.mockjax.js', '/static/js/bootstrap-editable.js', # select插件 '/static/js/jquery-migrate.min.js', '/static/js/jquery.blockUI.min.js', '/static/js/bootstrap-selectsplitter.min.js', '/static/js/components-form-tools2.js', '/static/js/bluebird.js', ] extra_css = [ '/static/css/aircraft.css', # '/static/css/bootstrap-switch.min.css', '/static/css/bootstrap-editable.css', # '/static/css/fonts.css', '/static/css/datepicker.css', '/static/css/bootstrap-datetimepicker.min.css', ] column_labels = column_labels support_popup = True column_list = ( 'id', 'planeType', 'totalHours', 'totalTimes', 'boundedMxp', ) details_modal = True create_modal = True edit_modal = True form = AircraftInformationForm one_line_columns = ['imageUrl', 'etag'] column_filters = [ FilterEqual(column='aircraftId', name='aircraftId'), FilterEqual(column='aircraftType', name='aircraftType'), ] def basic_operation(view, ctx, model, name): # 操作按钮 html = [ '<div class="clearfix">' '<div class="btn-group btn-group-xs btn-group-solid">' ] for op in _buttons_map: html.append(_buttons_map[op].render_ctx(ctx, view.get_pk_value(model), model)) html.append('</div></div>') return Markup(''.join(html)) def render_aircraft_id(view, ctx, model, name): # 由于分为多个页面,如果存在sub指示为非机队列表首页 sub = request.args.get('sub', None) # 如果用户无法查看飞机详情,只显示对应的名称即可 if view.can_view_details and sub is None: return Markup( '<a class="btn grey-steel btn-xs green-stripe" href="%s"><i class="fa fa-plane"></i>%s</a>' % (view.get_url('.aircraft_details_view', id=str(model['_id']), sub='basic'), model['id'])) return Markup('<span class="label label-info">%s</span>' % (model['id'], )) _column_formatters = { 'id': render_aircraft_id, 'planeType': aircrafttype_formatter, 'operation': basic_operation, 'departureTime': macro('timetostr'), 'landingTime': macro('timetostr'), 'importedDate': macro('timetostr'), 'manufactureDate': macro('timetodate'), 'acnDeadline': macro('timetodate'), 'slnDeadline': macro('timetodate'), 'nrnDeadline': macro('timetodate'), 'totalHours': hour_formater, } # 绑定状态缓存,无需频繁查询 # WUJG: 这里缓存还是去掉吧,正常情况下,原来担心的API访问速度慢的问题一定是 # 部署导致的 # @cache.memoize(timeout=3600*24) def get_bindable_status(self, model): resp = self._api_proxy.get('/v1/mxp-binding/status?id=%s' % (model['id'], )) bounded_status = [] if resp.status_code == 200: bounded_status = resp.json() for item in bounded_status: if 'status' in item and item['status']: return bounded_status, True return bounded_status, False @expose('/bind-mxp', methods=['POST']) def bind_mxp(self): mxp_id = request.args.get('id', '') plane_id = request.args.get('plane', '') return_url = request.args.get('return_url') or get_redirect_target( ) or self.get_url('.index_view') # 默认为解除绑定 is_bind = request.args.get('bind', '') dest_url = '/v1/mxp-binding/unbind' if is_bind: dest_url = '/v1/mxp-binding/bind' if not mxp_id or not plane_id: return redirect(return_url) resp = self._api_proxy.create( { 'force': True, 'mxpId': mxp_id, 'planeId': plane_id, }, dest_url) if resp.status_code != 200: return abort(503) cache.delete_memoized(self.get_bindable_status) return redirect(return_url) @expose('/update-util/', methods=['POST']) def update_util(self): return_url = get_redirect_target() or self.get_url('.index_view') # 正常应该跳转到对应飞机的详情页 id = request.args.get('id', '') if request.form.get('name') == 'hours': times = None hours = request.form.get('value', None) if hours is not None: hours = float(hours) elif request.form.get('name') == 'times': hours = None times = request.form.get('value', None) if times is not None: times = int(float(times)) if id: return_url = self.get_url('.aircraft_details_view', id=id, sub='basic') try: self._api_proxy.create( { 'id': id, 'hours': hours, 'times': times, }, '/v1/utilization/') except Exception as ex: # TODO: 客户端应该根据返回的JSON数据确定是否保存成功 return jsonify(status='400', message=unicode(ex)) # TODO: 根据前端所需要的JSON内容进行返回 return jsonify(status='200', msg='ok', times=times, hours=hours) @expose('/aircraft-details/') def aircraft_details_view(self): sub = request.args.get('sub') return_url = get_redirect_target() or self.get_url('.index_view') if not self.can_view_details: return redirect(return_url) id = get_mdict_item_or_list(request.args, 'id') if id is None: return redirect(return_url) model = self.get_aircraft(id) if model is None: flash(gettext('Record does not exist.'), 'error') return redirect(return_url) # 获得绑定状态 bind_status, bounded = self.get_bindable_status(model) # 获得当前飞机实例支持的绑定状态 # 无需显示的绑定状态会在构建列表时剔除 boundable_categories = [{ 'id': item[0], 'name': item[1] } for item in support_due_list[model['planeType']] if item[2]] self._template_args.update({ 'sub': sub, 'bind_status': bind_status, 'bounded': bounded, 'support_bounded_categories': boundable_categories, }) find_method = self._delegate_to_sub('find_method') or self.get_list extra_args = self._delegate_to_sub('extra') or {} if callable(extra_args): extra_args = extra_args(model=model) self._template_args.update(**extra_args) self._template_args.update({ 'sub': sub, 'time_formatter': datetime.time, }) return self.render_aircraft_or_list(id, model, sub, return_url, find_method, **extra_args) def get_aircraft(self, id): return self._mongo[self.view_list['basic']['coll_name']].find_one( {'_id': self._get_valid_id(id)}, { 'predictTime': False, 'boundedItems': False }) @property def view_list(self): # 每一个具体的子方案在界面视图处理时应该提供下面的信息 # 1. 子方案的名称 # 2. 子方案对应的主键 # 3. 子方案的集合名称(mongo) # 4. 一些与flask-admin相关的视图配置信息 return { 'basic': dict(**Basic()), 'flightlog': dict(**FlightLog()), 'due_list': dict(**DuelistLogic(self)()), 'maintenancelog': dict(**MaintenanceLog()), } def render_aircraft_or_list(self, id, model, sub, return_url, find_method, **kwargs): """ :param model: 通常就是飞机实例 :param sub: 当前的子视图 :param find_method: 根据需要可能替换为子文档的查询 """ template = self._delegate_to_sub('template') for x in model: if x != 'imageUrl': if model[x] is None: model[x] = '--' ret = get_aircraft_afterrepaired_flytime_enginetime(model['id']) total_ellapse_hour = '00:00' total_engine_time = '00:00' total_propeller_time = '00:00' if ret: total_ellapse_hour = ret.flyTime total_engine_time = ret.engineTime total_propeller_time = ret.propellerTime if not sub or sub == 'basic': return self.render(template, model=model, total_ellapse_hour=total_ellapse_hour, total_engine_time=total_engine_time, total_propeller_time=total_propeller_time, id=id, return_url=return_url) # WUJG: 下面的实现,类似于显示列表页,绝大多数重复使用了原Index view的实现 if self.can_delete: delete_form = self.delete_form() else: delete_form = None # Grab parameters from URL view_args = self._get_list_extra_args() # Map column index to column name sort_column = self._get_column_by_idx(view_args.sort) if sort_column is not None: sort_column = sort_column[0] # Get page size page_size = view_args.page_size or self.page_size # Get count and data count, data = find_method(view_args.page, sort_column, view_args.sort_desc, view_args.search, view_args.filters, page_size=page_size) list_forms = {} if self.column_editable_list: for row in data: list_forms[self.get_pk_value(row)] = self.list_form(obj=row) # Calculate number of pages if count is not None and page_size: num_pages = int(ceil(count / float(page_size))) elif not page_size: num_pages = 0 # hide pager for unlimited page_size else: num_pages = None # use simple pager # Various URL generation helpers def pager_url(p): # Do not add page number if it is first page if p == 0: p = None return self._get_list_url(view_args.clone(page=p)) def sort_url(column, invert=False, desc=None): if not desc and invert and not view_args.sort_desc: desc = 1 return self._get_list_url( view_args.clone(sort=column, sort_desc=desc)) def page_size_url(s): if not s: s = self.page_size return self._get_list_url(view_args.clone(page_size=s)) # Actions actions, actions_confirmation = self.get_actions_list() if actions: action_form = self.action_form() else: action_form = None clear_search_url = self._get_list_url( view_args.clone(page=0, sort=view_args.sort, sort_desc=view_args.sort_desc, search=None, filters=None)) return self.render( template, model=model, data=data, list_forms=list_forms, delete_form=delete_form, action_form=action_form, # List list_columns=self.get_column_names( only_columns=self.scaffold_list_columns(), excluded_columns=self.column_exclude_list, ), sortable_columns=self._sortable_columns, editable_columns=self.column_editable_list, list_row_actions=self.get_list_row_actions(), # Pagination count=count, pager_url=pager_url, num_pages=num_pages, can_set_page_size=self.can_set_page_size, page_size_url=page_size_url, page=view_args.page, page_size=page_size, default_page_size=self.page_size, # Sorting sort_column=view_args.sort, sort_desc=view_args.sort_desc, sort_url=sort_url, # Search search_supported=self._search_supported, clear_search_url=clear_search_url, search=view_args.search, # Filters filters=self._filters, filter_groups=self._get_filter_groups(), active_filters=view_args.filters, filter_args=self._get_filters(view_args.filters), # Actions actions=actions, actions_confirmation=actions_confirmation, # Misc enumerate=enumerate, get_pk_value=self.get_pk_value, get_value=self.get_list_value, return_url=self._get_list_url(view_args), **kwargs) @property def default_subordinate_view(self): return 'basic' @property def _details_columns(self): return self.get_column_names( only_columns=self._delegate_to_sub('details_columns'), excluded_columns=self.column_details_exclude_list) def get_real_url(self, url, model): requestPath = request.path.split('/') if 'delete' in requestPath: url = url % {'plane_type': ''} else: sub = request.args.get('sub', self.default_subordinate_view) plane_type_dict = dict(basic='planeType', flightlog='aircraftType') if sub not in plane_type_dict: raise ValueError('Not correct subordinate view type.') url = url % {'plane_type': model[plane_type_dict[sub]] + '/'} return url @expose('/new/', methods=('GET', 'POST')) def create_view(self): # if request.method == 'POST': # engineTime = request.form['engineTime'] # if engineTime is not None: # 在该视图下,当前支持飞机实例和飞行日志实例的创建 # 这里的主要逻辑是判断使用哪个窗体来创建对应的实例 # 注意, super使用的就是MongoCustomView而非AircraftInformationView sub = request.args.get('sub', self.default_subordinate_view) aircraft_type = request.args.get('type', '').lower() if not aircraft_type: self._create_form_class = self._delegate_to_sub('form') return super(MongoCustomView, self).create_view() allowed_forms = self._delegate_to_sub('form') if aircraft_type not in allowed_forms: return abort(400) self._create_form_class = allowed_forms.get(aircraft_type) self._template_args.update({ 'sub': sub, 'type': aircraft_type, }) return super(MongoCustomView, self).create_view() @expose('/edit/', methods=('GET', 'POST')) def edit_view(self): # 类似于create_view的逻辑,但要复杂一些,因为编辑时,即使飞机实例本身 # 也会存在机型信息 return_url = request.url or self.get_url('.index_view') aircraft_type = request.args.get('type', '').lower() if not aircraft_type: return redirect(return_url) sub = request.args.get('sub', self.default_subordinate_view) self._template_args.update({ 'sub': sub, 'type': aircraft_type, }) if sub == self.default_subordinate_view: self._edit_form_class = self._delegate_to_sub('form') return super(MongoCustomView, self).edit_view() if sub == 'flightlog': allowed_forms = self._delegate_to_sub('form') if aircraft_type not in allowed_forms: return abort(400) self._edit_form_class = allowed_forms.get(aircraft_type) return super(MongoCustomView, self).edit_view() def get_save_return_url(self, model, is_created): sub = request.args.get('sub') type = request.args.get('type') if sub == 'basic': id = request.args.get('id') if 'edit' in request.url: return self.get_url('.aircraft_details_view', id=id, type=type, sub=sub) elif 'delete' in request.url: return self.get_url('.aircraft_details_view') elif sub == 'flightlog': id = request.args.get('basicId') return self.get_url('.aircraft_details_view', flt_0=model['aircraftId'], id=id, flt_1=type, sub=sub) elif sub is None: return self.get_url('.aircraft_details_view') def get_one(self, id): # WUJG: 重写该方法实现,避免包含不必要的数据 return self.coll.find_one({'_id': self._get_valid_id(id)}, projection={ 'predictTime': False, 'boundedItems': False }) def get_list(self, page, sort_column, sort_desc, search, filters, execute=True, page_size=None): # WUJG: 这里的实现,几乎与flask-admin的框架实现一样,唯一不同的就是查询时做了 # 投影的设置,避免数据过大,导致加载很慢的问题 query = {} # Filters if self._filters: data = [] for flt, flt_name, value in filters: f = self._filters[flt] data = f.apply(data, value) if data: if len(data) == 1: query = data[0] else: query['$and'] = data # Search if self._search_supported and search: query = self._search(query, search) # Get count count = self.coll.find(query, projection={ 'id': True, }).count() if not self.simple_list_pager else None # Sorting sort_by = None if sort_column: sort_by = [(sort_column, pymongo.DESCENDING if sort_desc else pymongo.ASCENDING) ] else: order = self._get_default_order() if order: sort_by = [ (order[0], pymongo.DESCENDING if order[1] else pymongo.ASCENDING) ] # Pagination if page_size is None: page_size = self.page_size skip = 0 if page and page_size: skip = page * page_size results = self.coll.find(query, sort=sort_by, skip=skip, limit=page_size, projection={ 'boundedItems': False, 'predictTime': False, }) if execute: results = list(results) return count, results @property def column_formatters(self): formatters = self._delegate_to_sub('column_formatters') if formatters is not None: return formatters return self._column_formatters @column_formatters.setter def column_formatters(self, val): pass @property def can_edit_status(self): perm = ActionNeedPermission(self._action_name, EditBoundStatus) return perm.can() @property def can_remove_status(self): perm = ActionNeedPermission(self._action_name, RemoveBoundStatus) return perm.can() @expose('/mx-bounded-status/', methods=['POST', 'GET']) def get_bounded_status(self): # TODO: 如果是POST则需要执行更新操作 if request.method == 'GET': resp = self._api_proxy.get( '/v1/mxp-binding/details?id=%s&mxtype=%s' % (request.args.get('id'), request.args.get('mxtype'))) else: model = request.get_json(force=True) resp = self._api_proxy.update(model, None, '/v1/mxp-binding/update?batch=1') if resp.status_code == 200: return jsonify(code=200, data=resp.json()) return jsonify(code=resp.status_code, message=resp.json()['message']) @expose('/mx-duplicate/', methods=['GET']) def get_duplicate(self): resp = self._api_proxy.create( { 'mxId': request.args.get('mxId'), 'mxType': request.args.get('mxType'), 'planeId': request.args.get('planeId'), }, '/v1/mxp-binding/duplicate') if resp.status_code == 200: return jsonify(code=200, data=resp.json()) return jsonify(code=resp.status_code, message=resp.json()['message']) @expose('/get-subsidiary-work/') def get_subsidiary_work(self): # if not self.can_routine_work: # return jsonify(code=403, message='You are not allowed to do so.') plane_id = request.args.get('plane', '') mx_id = request.args.get('mxid', '') if not plane_id or not mx_id: return jsonify(code=400, message='You should provide plane and mx id.') ret = get_subsidiary_materials_related_available_work(plane_id, mx_id) if ret is None: return jsonify(code=404, message='Nothing of the related work.') return jsonify(code=200, **ret) # 下面内容为打印相关实现 @expose('/export/<export_type>/') def export(self, export_type): mx_type = request.args.get('mxtype', 'scheduled') self._export_columns = self.get_export_columns(mx_type) return_url = get_redirect_target() or self.get_url('.index_view') if export_type == 'csv': return self._export_csv(return_url) else: return self._export_tablib(export_type, return_url) def get_export_columns(self, mx_type=None): # 定检打印字段 col = [ ('mxId', '维修方案编号'), ('description', '描述信息'), ('leftHour', '剩余小时'), ('leftTimes', '剩余次数'), ('leftDay', '剩余天'), ('leftEngineTime', '剩余发动机时间'), # ('best', '预计检查日期'), # ('warningLevel', '预警等级'), # ('error', '错误提示'), # ('completeDate', '上次完成时间') ] if mx_type != 'scheduled': # 时空/时寿 col = [ # ('mxId', '维修方案编号'), # ('description', '描述信息'), ('pn', '件号'), ('serialNumber', '序号'), ('name', '名称'), ('completeDate', '装机日期'), ('leftHour', '剩余小时'), ('leftTimes', '剩余次数'), ('leftDay', '剩余天'), ('leftEngineTime', '剩余发动机时间'), ('best', '预计检查日期'), # ('warningLevel', '预警等级'), # ('error', '错误提示'), ] return col def get_export_value(self, model, name): if name in model: return model[name] return '' def _export_data(self): # 导出数据 export_page = request.args.get('export_page', 0, type=int) export_size = request.args.get('export_size', 0, type=int) search = request.args.get('search', '') air_id = request.args.get('id', '') mx_type = request.args.get('mxtype', 'scheduled') view_args = self._get_list_extra_args() due_list = DuelistLogic(self) count, export_data = due_list.get_list(export_page, None, view_args.sort_desc, search, view_args.filters, page_size=export_size) export_data = self.get_export_data(export_data, air_id, mx_type) return count, export_data def get_bangding(self): # 绑定相关信息 com = {} air = self.coll.find({"_id": bson.ObjectId(request.args.get('id'))}) if not air.count(): return com bounds = air[0]['boundedItems'] for item in bounds: if item['refId'].collection not in \ ['time_control_unit_y5b', 'life_control_unit_y5b']: continue com[item['boundedId']] = [ self.unix_to_string(item['completeDate']), item['serialNumber'] ] return com def get_export_data(self, data, id, mx_type): mxp = self.get_mx_name(mx_type) export_datas = [] for item in data: export_data = { 'mxId': mxp[item['predictTime']['mxRefId'].id][0], 'description': mxp[item['predictTime']['mxRefId'].id][1], 'warningLevel': item['level'], 'error': item['predictTime']['err'], 'best': self.unix_to_string(item['predictTime']['earliest']), 'leftHour': item['intervaltype'][0] if 0 in item['intervaltype'].keys() else '', 'leftTimes': item['intervaltype'][1] if 1 in item['intervaltype'].keys() else '', 'leftDay': item['intervaltype'][2] if 2 in item['intervaltype'].keys() else '', 'leftEngineTime': item['intervaltype'][9] if 9 in item['intervaltype'].keys() else '', } if export_data['leftHour']: export_data['leftHour'] = convert_float_to_hh_mm( export_data['leftHour']) if export_data['leftEngineTime']: export_data['leftEngineTime'] = convert_float_to_hh_mm( export_data['leftEngineTime']) if mx_type != 'scheduled': com = self.get_bangding() export_data["completeDate"] = com[item['predictTime'] ['itemId']][0] export_data["serialNumber"] = com[item['predictTime'] ['itemId']][1] export_data['pn'] = mxp[item['predictTime']['mxRefId'].id][2] export_data['name'] = mxp[item['predictTime']['mxRefId'].id][3] export_datas.append(export_data) return export_datas def unix_to_string(self, data): # unix 时间戳格式化 if not data: return '' data = datetime.datetime.fromtimestamp(data) return data.strftime("%y-%m-%d") def get_mx_name(self, mx_type): # 获取维修方案相关信息 if mx_type != 'scheduled': coll = 'time_control_unit_y5b' if mx_type == 'timecontrol' else 'life_control_unit_y5b' datas = list(self._mongo[coll].find({}, { "id": 1, "description": 1, "pn": 1, "name": 1 })) data = {} for item in datas: data[item['_id']] = [ item['id'], item['description'], item['pn'], item['name'] ] return data datas = list(self._mongo['scheduled_mx_check_y5b'].find( {}, { "id": 1, "description": 1 })) data = {} for item in datas: data[item['_id']] = [item['id'], item['description']] return data # 下面的内容为通用的飞机实例与飞行日志实例同处于相同页面的实现 # 如果日志需要分开实现,无需提供下面的操作 @property def can_create_flightlog(self): return ActionNeedPermission('flightlog', Create).can() @property def can_edit_flightlog(self): return ActionNeedPermission('flightlog', Edit).can() @property def can_delete_flightlog(self): return ActionNeedPermission('flightlog', Delete).can() @property def can_view_details_flightlog(self): return ActionNeedPermission('flightlog', View).can() @property def can_routine_work(self): perm = ActionNeedPermission('routinework', 'create') return perm.can()
class EventAdminView(BaseAdminView): column_filters = ('name', 'start', 'end') column_list = ('name', 'start', 'end', 'location_set', 'participant_set', 'archive') column_labels = { 'name': _('Name'), 'start': _('Start'), 'end': _('End'), 'location_set': _('Location Set'), 'participant_set': _('Participant Set'), 'archive': _('Archive') } column_descriptions = { 'start': _('What time the event is to start in the local time.'), 'end': _('What time the event is to end in the local time.'), 'forms': _('What forms should be enabled for this event.') } form_columns = ('name', 'start', 'end', 'forms', 'participant_set') form_rules = [ rules.FieldSet(('name', 'start', 'end', 'forms', 'participant_set'), _('Event')) ] column_formatters = { 'archive': macro('event_archive'), } @expose('/download/<int:event_id>') def download(self, event_id): event = services.events.find(id=event_id).first_or_404() eas = EventArchiveSerializer() fp = BytesIO() with ZipFile(fp, 'w', ZIP_DEFLATED) as zf: eas.serialize(event, zf) fp.seek(0) fname = slugify( f'event archive {event.name.lower()} {datetime.utcnow().strftime("%Y %m %d %H%M%S")}' ) # noqa return send_file(fp, attachment_filename=f'{fname}.zip', as_attachment=True) def get_one(self, pk): event = super(EventAdminView, self).get_one(pk) # convert start and end dates to app time zone event.start = event.start.astimezone(app_time_zone) event.end = event.end.astimezone(app_time_zone) return event @contextfunction def get_list_value(self, context, model, name): if name in ['start', 'end']: attribute = getattr(model, name, None) if attribute: return attribute.astimezone(app_time_zone).strftime( DATETIME_FORMAT_SPEC) return attribute return super(EventAdminView, self).get_list_value(context, model, name) def get_query(self): '''Returns the queryset of the objects to list.''' user = current_user._get_current_object() return models.Event.query.filter_by(deployment_id=user.deployment.id) def on_model_change(self, form, model, is_created): # if we're creating a new event, make sure to set the # deployment, since it won't appear in the form if is_created: model.deployment = current_user.deployment # add role permissions for this event roles = models.Role.query.filter_by( deployment=model.deployment).all() model.roles = roles if form.participant_set.data: model.location_set = form.participant_set.data.location_set else: model.location_set = None # convert to the app time zone model.start = app_time_zone.localize( model.start).astimezone(utc_time_zone) model.end = app_time_zone.localize(model.end).astimezone(utc_time_zone)
def test_export_csv(): app, admin = setup() client = app.test_client() # test redirect when csv export is disabled view = MockModelView(Model, column_list=["col1", "col2"], endpoint="test") admin.add_view(view) rv = client.get("/admin/test/export/csv/") eq_(rv.status_code, 302) # basic test of csv export with a few records view_data = {1: Model(1, "col1_1", "col2_1"), 2: Model(2, "col1_2", "col2_2"), 3: Model(3, "col1_3", "col2_3")} view = MockModelView(Model, view_data, can_export=True, column_list=["col1", "col2"]) admin.add_view(view) rv = client.get("/admin/model/export/csv/") data = rv.data.decode("utf-8") eq_(rv.mimetype, "text/csv") eq_(rv.status_code, 200) ok_("Col1,Col2\r\n" "col1_1,col2_1\r\n" "col1_2,col2_2\r\n" "col1_3,col2_3\r\n" == data) # test explicit use of column_export_list view = MockModelView( Model, view_data, can_export=True, column_list=["col1", "col2"], column_export_list=["id", "col1", "col2"], endpoint="exportinclusion", ) admin.add_view(view) rv = client.get("/admin/exportinclusion/export/csv/") data = rv.data.decode("utf-8") eq_(rv.mimetype, "text/csv") eq_(rv.status_code, 200) ok_("Id,Col1,Col2\r\n" "1,col1_1,col2_1\r\n" "2,col1_2,col2_2\r\n" "3,col1_3,col2_3\r\n" == data) # test explicit use of column_export_exclude_list view = MockModelView( Model, view_data, can_export=True, column_list=["col1", "col2"], column_export_exclude_list=["col2"], endpoint="exportexclusion", ) admin.add_view(view) rv = client.get("/admin/exportexclusion/export/csv/") data = rv.data.decode("utf-8") eq_(rv.mimetype, "text/csv") eq_(rv.status_code, 200) ok_("Col1\r\n" "col1_1\r\n" "col1_2\r\n" "col1_3\r\n" == data) # test utf8 characters in csv export view_data[4] = Model(1, u"\u2013ut8_1\u2013", u"\u2013utf8_2\u2013") view = MockModelView(Model, view_data, can_export=True, column_list=["col1", "col2"], endpoint="utf8") admin.add_view(view) rv = client.get("/admin/utf8/export/csv/") data = rv.data.decode("utf-8") eq_(rv.status_code, 200) ok_(u"\u2013ut8_1\u2013,\u2013utf8_2\u2013\r\n" in data) # test None type, integer type, column_labels, and column_formatters view_data = {1: Model(1, "col1_1", 1), 2: Model(2, "col1_2", 2), 3: Model(3, None, 3)} view = MockModelView( Model, view_data, can_export=True, column_list=["col1", "col2"], column_labels={"col1": "Str Field", "col2": "Int Field"}, column_formatters=dict(col2=lambda v, c, m, p: m.col2 * 2), endpoint="types_and_formatters", ) admin.add_view(view) rv = client.get("/admin/types_and_formatters/export/csv/") data = rv.data.decode("utf-8") eq_(rv.status_code, 200) ok_("Str Field,Int Field\r\n" "col1_1,2\r\n" "col1_2,4\r\n" ",6\r\n" == data) # test column_formatters_export and column_formatters_export type_formatters = {type(None): lambda view, value: "null"} view = MockModelView( Model, view_data, can_export=True, column_list=["col1", "col2"], column_formatters_export=dict(col2=lambda v, c, m, p: m.col2 * 3), column_formatters=dict(col2=lambda v, c, m, p: m.col2 * 2), # overridden column_type_formatters_export=type_formatters, endpoint="export_types_and_formatters", ) admin.add_view(view) rv = client.get("/admin/export_types_and_formatters/export/csv/") data = rv.data.decode("utf-8") eq_(rv.status_code, 200) ok_("Col1,Col2\r\n" "col1_1,3\r\n" "col1_2,6\r\n" "null,9\r\n" == data) # Macros are not implemented for csv export yet and will throw an error view = MockModelView( Model, can_export=True, column_list=["col1", "col2"], column_formatters=dict(col1=macro("render_macro")), endpoint="macro_exception", ) admin.add_view(view) rv = client.get("/admin/macro_exception/export/csv/") data = rv.data.decode("utf-8") eq_(rv.status_code, 500)