From 95932c4a9a342d0575a348295739bbef0d174df0 Mon Sep 17 00:00:00 2001 From: Raghav Sharma Date: Sun, 17 Jul 2016 20:50:15 +0530 Subject: [PATCH 1/3] Add endpoint to export admin settings. Currently only committee structures. --- cleansweep/plugins/committees/models.py | 9 +++++++++ cleansweep/views/admin.py | 17 ++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/cleansweep/plugins/committees/models.py b/cleansweep/plugins/committees/models.py index 91586e0..8a06965 100644 --- a/cleansweep/plugins/committees/models.py +++ b/cleansweep/plugins/committees/models.py @@ -188,6 +188,15 @@ def get_all_members(self, place): q = self._get_members_query(place) return db.engine.execute(q).fetchall() + @staticmethod + def export(): + """ + Calls dict() on each committee type and stores it in a list. + :return: List of all committee types. + """ + query = CommitteeType.query.all() + committee_types = [committee_type.dict() for committee_type in query] + return committee_types class CommitteeRole(db.Model): """Role in a committee. diff --git a/cleansweep/views/admin.py b/cleansweep/views/admin.py index 1c84d7d..566afe6 100644 --- a/cleansweep/views/admin.py +++ b/cleansweep/views/admin.py @@ -1,6 +1,6 @@ """Views of the admin panel. """ - +from cleansweep.plugins.committees.models import CommitteeType from flask import (render_template, abort, url_for, redirect, request, make_response, session, flash, jsonify) from ..models import Member, db, PendingMember, Place @@ -283,3 +283,18 @@ def _load_contacts(place, data): contacts += p.add_contacts(prows) db.session.commit() return contacts + + +@app.route("/admin/settings/export", methods=['GET']) +@require_permission("siteadmin") +def export_admin_settings(): + data = {} + items_to_export = request.args.get('items', '').split(",") + + if "committees" in items_to_export: + data['committee_types'] = CommitteeType.export() + + response = jsonify(data) + response.headers['Content-Type'] = 'application/json' + response.headers['Content-Disposition'] = "attachment;filename=settings.json" + return response From 6bfa22ebae9695bb0c2de8b58254ae43a69d8953 Mon Sep 17 00:00:00 2001 From: Raghav Sharma Date: Sun, 17 Jul 2016 22:20:12 +0530 Subject: [PATCH 2/3] Moved endpoint to committees views. Added export button in template. --- .../templates/committee_structures.html | 6 +++++- cleansweep/plugins/committees/views.py | 11 ++++++++++- cleansweep/views/admin.py | 18 +----------------- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/cleansweep/plugins/committees/templates/committee_structures.html b/cleansweep/plugins/committees/templates/committee_structures.html index 9d7fd2e..0ce9cff 100644 --- a/cleansweep/plugins/committees/templates/committee_structures.html +++ b/cleansweep/plugins/committees/templates/committee_structures.html @@ -7,7 +7,11 @@
  • Admin Center
  • -

    Committee Structures

    +

    Committee Structures +
    + Export +
    +

    {% endblock %} diff --git a/cleansweep/plugins/committees/views.py b/cleansweep/plugins/committees/views.py index a5931a9..8f4af5a 100644 --- a/cleansweep/plugins/committees/views.py +++ b/cleansweep/plugins/committees/views.py @@ -2,7 +2,7 @@ from ...core import rbac from ...models import db, Member, PlaceType, Place from .models import CommitteeRole, CommitteeType -from flask import (flash, request, Response, make_response, render_template, redirect, url_for, abort) +from flask import (flash, request, Response, make_response, render_template, redirect, url_for, abort, jsonify) from . import forms from . import signals, notifications, audits from ...view_helpers import require_permission @@ -233,3 +233,12 @@ def download_members_of_committee_type(slug): response = Response(dataset.xls, content_type='application/vnd.ms-excel;charset=utf-8') response.headers['Content-Disposition'] = "attachment; filename='{0}'".format(filename) return response + + +@plugin.route("/admin/committee-structures/export", methods=['GET']) +@require_permission("admin.committee-structures.view") +def export_committee_structures(): + response = jsonify({'committee_types': CommitteeType.export()}) + response.headers['Content-Type'] = 'application/json' + response.headers['Content-Disposition'] = "attachment;filename=committee_structures.json" + return response diff --git a/cleansweep/views/admin.py b/cleansweep/views/admin.py index 566afe6..7c836d9 100644 --- a/cleansweep/views/admin.py +++ b/cleansweep/views/admin.py @@ -1,8 +1,7 @@ """Views of the admin panel. """ -from cleansweep.plugins.committees.models import CommitteeType from flask import (render_template, abort, url_for, redirect, request, - make_response, session, flash, jsonify) + make_response, session, flash) from ..models import Member, db, PendingMember, Place from .. import forms from ..app import app @@ -283,18 +282,3 @@ def _load_contacts(place, data): contacts += p.add_contacts(prows) db.session.commit() return contacts - - -@app.route("/admin/settings/export", methods=['GET']) -@require_permission("siteadmin") -def export_admin_settings(): - data = {} - items_to_export = request.args.get('items', '').split(",") - - if "committees" in items_to_export: - data['committee_types'] = CommitteeType.export() - - response = jsonify(data) - response.headers['Content-Type'] = 'application/json' - response.headers['Content-Disposition'] = "attachment;filename=settings.json" - return response From e6989de1b0d156907298a0a802a9d38df2675c25 Mon Sep 17 00:00:00 2001 From: Raghav Sharma Date: Mon, 18 Jul 2016 06:04:35 +0530 Subject: [PATCH 3/3] Added importing of committee structures. --- cleansweep/plugins/committees/models.py | 43 +++++++++++++++ .../templates/committee_structures.html | 52 +++++++++++-------- cleansweep/plugins/committees/views.py | 21 +++++++- 3 files changed, 94 insertions(+), 22 deletions(-) diff --git a/cleansweep/plugins/committees/models.py b/cleansweep/plugins/committees/models.py index 8a06965..44e43b9 100644 --- a/cleansweep/plugins/committees/models.py +++ b/cleansweep/plugins/committees/models.py @@ -42,6 +42,7 @@ def dict(self): "slug": self.slug, "description": self.description, "place_key": self.place.key, + "place_type_key": self.place_type.short_name, "roles": [role.dict() for role in self.roles] } @@ -198,6 +199,48 @@ def export(): committee_types = [committee_type.dict() for committee_type in query] return committee_types + @staticmethod + def import_committee_types(committee_types): + """ + Imports committee types from export data. + Validates if all required fields are present and slug does not already exist. + + :param committee_types: List of dictionary objects containing committee_type data. See dict() of CommitteeType. + :return: List of CommitteeType objects successfully created. + """ + created = [] + for c_type in committee_types: + place = Place.find(c_type.get('place_key')) + place_type = PlaceType.get(c_type.get('place_type_key')) + name = c_type.get('name', '').strip() + slug = c_type.get('slug', '').strip() + desc = c_type.get('description', '').strip() + + slug_already_exists = CommitteeType.find(place, slug) is not None + if not place or not place_type or not name or slug_already_exists: + continue + + committee_type = CommitteeType.new(place=place, place_type=place_type, name=name, slug=slug, desc=desc, + roles=c_type.get('roles', [])) + created.append(committee_type) + return created + + @staticmethod + def new(place, place_type, name, slug, desc="", roles=None): + """ + Creates a new CommitteeType from data provided and returns the object. + """ + committee_type = CommitteeType(place=place, place_type=place_type, name=name, slug=slug, description=desc) + db.session.add(committee_type) + + for role in roles or []: + name = role.get('role', '').strip() + if not name: + continue + committee_type.add_role(role_name=name, multiple=role.get('multiple'), permission=role.get('permission')) + return committee_type + + class CommitteeRole(db.Model): """Role in a committee. diff --git a/cleansweep/plugins/committees/templates/committee_structures.html b/cleansweep/plugins/committees/templates/committee_structures.html index 0ce9cff..6ecfa34 100644 --- a/cleansweep/plugins/committees/templates/committee_structures.html +++ b/cleansweep/plugins/committees/templates/committee_structures.html @@ -1,33 +1,43 @@ - {% extends "admin/base.html" %} {% block content_head %} -
    - -

    Committee Structures -
    - Export -
    -

    -
    +
    + +

    Committee Structures

    +
    {% endblock %} {% block content_body %}
    {% for p in place.type.all() %} {% set committees_count = place.committee_types.filter_by(place_type_id=p.id).count() %} - - {{ p.name }} - {{ committees_count }} - + + {{ p.name }} + {{ committees_count }} + {% endfor %}
    +
    + Export +
    + {% if has_permission("committees.import") %} +
    +
    + + + + +
    +
    + {% endif %} +
    +
    {% endblock %} diff --git a/cleansweep/plugins/committees/views.py b/cleansweep/plugins/committees/views.py index 8f4af5a..fc39b39 100644 --- a/cleansweep/plugins/committees/views.py +++ b/cleansweep/plugins/committees/views.py @@ -1,3 +1,5 @@ +import json + from ...plugin import Plugin from ...core import rbac from ...models import db, Member, PlaceType, Place @@ -238,7 +240,24 @@ def download_members_of_committee_type(slug): @plugin.route("/admin/committee-structures/export", methods=['GET']) @require_permission("admin.committee-structures.view") def export_committee_structures(): - response = jsonify({'committee_types': CommitteeType.export()}) + response = jsonify(committee_types=CommitteeType.export()) response.headers['Content-Type'] = 'application/json' response.headers['Content-Disposition'] = "attachment;filename=committee_structures.json" return response + + +@plugin.route("/admin/committee-structures/import", methods=['POST']) +@require_permission("admin.committee-structures.import") +def import_committee_structures(): + json_file = request.files['file'] + file_content = json_file.read() + try: + committee_types = json.loads(file_content).get('committee_types') or [] + created = CommitteeType.import_committee_types(committee_types) + db.session.commit() + flash("Successfully created {0} of {1} committee structures.".format(len(created), len(committee_types)), + category="success") + except ValueError: + flash("An error occurred. Maybe it was an invalid file. Make sure JSON is correct.", category="error") + + return redirect(url_for(".committee_structures"))