diff --git a/cleansweep/plugins/committees/models.py b/cleansweep/plugins/committees/models.py index 91586e0..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] } @@ -188,6 +189,57 @@ 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 + + @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 9d7fd2e..6ecfa34 100644 --- a/cleansweep/plugins/committees/templates/committee_structures.html +++ b/cleansweep/plugins/committees/templates/committee_structures.html @@ -1,29 +1,43 @@ - {% extends "admin/base.html" %} {% block content_head %} -
- -

Committee Structures

-
+
+ +

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 a5931a9..fc39b39 100644 --- a/cleansweep/plugins/committees/views.py +++ b/cleansweep/plugins/committees/views.py @@ -1,8 +1,10 @@ +import json + from ...plugin import Plugin 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 +235,29 @@ 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 + + +@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")) diff --git a/cleansweep/views/admin.py b/cleansweep/views/admin.py index 1c84d7d..7c836d9 100644 --- a/cleansweep/views/admin.py +++ b/cleansweep/views/admin.py @@ -1,8 +1,7 @@ """Views of the admin panel. """ - 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