Export and import for MongoEngine model in Flask-Admin

Another tip for Flask-Admin. The task’s requirements are:

  • possibility to choose some model’s objects and download them in JSON
  • possibility to upload them back

There is a ModelView property can_export, it adds an action to export in CSV or another format supported by tablib, but it does not allow to select records and there is no import. So for my task it’s not a solution.

The export is easy to do with an action decorathor.

class MyModel(Document)
    name = StringField(max_lengt=255)
    data = DictField()
    date = DateTimeField(default=datetime.utcnow)


class MyView(ModelView):

    @action('export', 'Export')
    def action_export(self, ids):
        try:
            items = list(MyModel.objects(id__in=ids))

            if items:
                items = [
                    {'name': item.name,'data': item.data} 
                    for item in items
                ]

                return (
                    json.dumps(items, indent=4),
                    200,
                    {
                        'Content-Type': 'application/json',
                        'Pragma': 'no-cache',
                        'Cache-Control': 'no-cache, no-store, must-revalidate',
                        'Expires': '0',
                        'Content-Disposition': 'attachment; filename="mymodel.json"'
                    }
                )

            return ''
        except Exception as e:
            if not self.handle_view_exception(e):
                raise

            flash('Failed to export: {}'.format(str(e)), 'error')

The import is a little more complex, it requires to customize the template. In this example I put it at the bottom of the list page admin/mymodel_list.html.

{% extends 'admin/model/list.html' %}

{% block body %}
    {{ super() }}

    <form action="{{ url_for('admin_mymodel.import_json') }}"
          method="POST"
          enctype="multipart/form-data"
          class="admin-form form-inline">
        <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
        <div class="form-group">
            <input type="file" name="file" class="form-control"/>
            <button type="submit" class="btn btn-default">Import</button>
        </div>
    </form>
{% endblock %}

And added a view with @expose decorathor to proccess this form.

class MyView(ModelView):
    list_template = 'admin/mymodel_list.html'

    @expose('/import', methods=['POST'])
    def import_json(self):
        redirect_response = redirect(url_for('admin_mymodel.index_view'))

        if 'file' not in request.files:
            flash('No file part', 'error')
            return redirect_response

        uploaded_file = request.files['file']
        if uploaded_file.filename == '':
            flash('No selected file', 'error')
            return redirect_response

        if uploaded_file:
            try:
                items = json.loads(uploaded_file.read().decode('utf-8'))
            except (json.JSONDecodeError, TypeError, UnicodeDecodeError) as e:
                flash('Can not read json from the file. {}'.format(e), 'error')
                return redirect_response

            for item in items:
                MyModel(**item).save()

        return redirect_response

To add view to admin:

adm = Admin()
adm.add_view(MyView(MyModel, endpoint='admin_mymodel'))
comments powered by Disqus