mirror of https://github.com/logos-co/open-law.git
work on permissions ui and base backend
This commit is contained in:
parent
5c21cc8d0f
commit
f60a9ad1c6
|
@ -26,6 +26,7 @@ def create_app(environment="development"):
|
|||
vote_blueprint,
|
||||
approve_blueprint,
|
||||
star_blueprint,
|
||||
permissions_blueprint,
|
||||
)
|
||||
from app.models import (
|
||||
User,
|
||||
|
@ -57,6 +58,7 @@ def create_app(environment="development"):
|
|||
app.register_blueprint(vote_blueprint)
|
||||
app.register_blueprint(approve_blueprint)
|
||||
app.register_blueprint(star_blueprint)
|
||||
app.register_blueprint(permissions_blueprint)
|
||||
|
||||
# Set up flask login.
|
||||
@login_manager.user_loader
|
||||
|
|
|
@ -13,3 +13,4 @@ from .interpretation import CreateInterpretationForm, EditInterpretationForm
|
|||
from .comment import CreateCommentForm
|
||||
from .vote import VoteForm
|
||||
from .comment import CreateCommentForm, DeleteCommentForm, EditCommentForm
|
||||
from .permission import EditPermissionForm
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, SubmitField, IntegerField
|
||||
from wtforms.validators import DataRequired
|
||||
|
||||
|
||||
class EditPermissionForm(FlaskForm):
|
||||
book_id = IntegerField("Book ID", [DataRequired()])
|
||||
user_id = IntegerField("User ID", [DataRequired()])
|
||||
permissions = StringField("Permissions JSON", [DataRequired()])
|
||||
submit = SubmitField("Edit")
|
File diff suppressed because one or more lines are too long
144007
app/static/js/main.js
144007
app/static/js/main.js
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,82 @@
|
|||
<!-- prettier-ignore-->
|
||||
<div id="access-level-modal" tabindex="-1" aria-hidden="true"
|
||||
class="fixed top-0 left-0 right-0 z-[150] hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full">
|
||||
<div class="relative w-full max-w-2xl max-h-full">
|
||||
<!-- Modal content -->
|
||||
<form action="{{ url_for('permission.set') }}" method="post" class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
||||
{{ form_hidden_tag() }}
|
||||
<input type="hidden" name="book_id" id="permission_modal_book_id"/>
|
||||
<input type="hidden" name="user_id" id="permission_modal_user_id"/>
|
||||
<input type="hidden" name="permissions" id="permissions_json"/>
|
||||
<!-- Modal header -->
|
||||
<div class="flex items-start justify-between p-4 border-b rounded-t dark:border-gray-600">
|
||||
<h3 class="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
Access Level
|
||||
</h3>
|
||||
<button id="modalAddCloseButton" data-modal-hide="access-level-modal" type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white">
|
||||
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"> </path> </svg>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Modal body -->
|
||||
<div class="p-6 space-y-6">
|
||||
<div class="checkbox-tree">
|
||||
<ul class="ml-4">
|
||||
<li>
|
||||
<div class="flex items-center space-x-2">
|
||||
<input type="checkbox" data-root="true" data-access-to="book" data-access-to-id="{{ book.id }}" class="w-4 h-4 text-blue-600 bg-gray-300 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-400 dark:border-gray-600" />
|
||||
<span class="text-center dark:text-gray-300">{{ book.label }}</span>
|
||||
</div>
|
||||
{% for collection in book.last_version.children_collections %}
|
||||
<ul class="ml-4">
|
||||
<li>
|
||||
<div class="flex items-center space-x-2">
|
||||
<input type="checkbox" data-access-to="collection" data-access-to-id="{{ collection.id }}" class="w-4 h-4 text-blue-600 bg-gray-300 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-400 dark:border-gray-600" />
|
||||
<span class="text-center dark:text-gray-300">{{ collection.label }}</span>
|
||||
</div>
|
||||
|
||||
{% for sub_collection in collection.children %}
|
||||
<ul class="ml-4">
|
||||
<li>
|
||||
<div class="flex items-center space-x-2">
|
||||
<input type="checkbox" data-access-to="sub_collection" data-access-to-id="{{ sub_collection.id }}" class="w-4 h-4 text-blue-600 bg-gray-300 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-400 dark:border-gray-600" />
|
||||
<span class="text-center dark:text-gray-300">{{ sub_collection.label }}</span>
|
||||
</div>
|
||||
{% for section in sub_collection.sections %}
|
||||
<ul class="ml-4">
|
||||
<li>
|
||||
<div class="flex items-center space-x-2">
|
||||
<input type="checkbox" data-access-to="section" data-access-to-id="{{ section.id }}" class="w-4 h-4 text-blue-600 bg-gray-300 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-400 dark:border-gray-600" />
|
||||
<span class="text-center dark:text-gray-300">{{ section.label }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
{% endfor %}
|
||||
</li>
|
||||
</ul>
|
||||
{% endfor %}
|
||||
|
||||
{% for section in collection.sections %}
|
||||
<ul class="ml-4">
|
||||
<li>
|
||||
<div class="flex items-center space-x-2">
|
||||
<input type="checkbox" data-access-to="section" data-access-to-id="{{ section.id }}" class="w-4 h-4 text-blue-600 bg-gray-300 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-400 dark:border-gray-600" />
|
||||
<span class="text-center dark:text-gray-300">{{ section.label }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
{% endfor %}
|
||||
</li>
|
||||
</ul>
|
||||
{% endfor %}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Modal footer -->
|
||||
<div class="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600">
|
||||
<button name="submit" type="submit"
|
||||
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
|
@ -2,6 +2,7 @@
|
|||
{% extends 'base.html' %}
|
||||
{% include 'book/add_contributor_modal.html' %}
|
||||
{% include 'book/delete_book_modal.html' %}
|
||||
{% include 'book/access_level_modal.html' %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Hide right_sidebar -->
|
||||
|
@ -108,7 +109,7 @@
|
|||
</form>
|
||||
</td>
|
||||
<td class="px-4 py-4 flex justify-center">
|
||||
<button type="button" class="text-gray-900 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-200 font-medium rounded-lg text-sm px-5 py-1 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700">
|
||||
<button type="button" data-book-id="{{book.id}}" data-user-id="{{contributor.user.id}}" data-modal-target="access-level-modal" data-modal-toggle="access-level-modal" class="edit-permissions-btn text-gray-900 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-200 font-medium rounded-lg text-sm px-5 py-1 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700">
|
||||
Edit
|
||||
</button>
|
||||
</td>
|
||||
|
|
|
@ -8,3 +8,4 @@ from .section import bp as section_blueprint
|
|||
from .vote import bp as vote_blueprint
|
||||
from .approve import bp as approve_blueprint
|
||||
from .star import bp as star_blueprint
|
||||
from .permission import bp as permissions_blueprint
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import json
|
||||
|
||||
from flask import redirect, url_for, Blueprint, request, flash
|
||||
from flask_login import current_user
|
||||
|
||||
from app import forms as f, models as m, db
|
||||
from app.logger import log
|
||||
|
||||
bp = Blueprint("permission", __name__, "/permission")
|
||||
|
||||
|
||||
@bp.route("/set", methods=["POST"])
|
||||
def set():
|
||||
form: f.EditPermissionForm = f.EditPermissionForm()
|
||||
|
||||
if form.validate_on_submit():
|
||||
book_id = form.book_id.data
|
||||
book: m.Book = db.session.get(m.Book, book_id)
|
||||
if not book or book.is_deleted or book.owner != current_user:
|
||||
log(log.INFO, "User: [%s] is not owner of book: [%s]", current_user, book)
|
||||
flash("You are not owner of this book!", "danger")
|
||||
return redirect(url_for("book.my_library"))
|
||||
|
||||
user_id = form.user_id.data
|
||||
contributor: m.BookContributor = m.BookContributor.query.filter_by(
|
||||
user_id=user_id, book_id=book_id
|
||||
).first()
|
||||
if not contributor:
|
||||
log(
|
||||
log.INFO,
|
||||
"User: [%s] is not contributor of book: [%s]",
|
||||
current_user,
|
||||
book,
|
||||
)
|
||||
flash("User are not contributor of this book!", "danger")
|
||||
return redirect(url_for("book.my_library"))
|
||||
|
||||
permissions = json.loads(form.permissions.data)
|
||||
|
||||
return {"status": "ok"}
|
|
@ -0,0 +1,81 @@
|
|||
interface Permissions {
|
||||
[key: string]: number[];
|
||||
}
|
||||
|
||||
const updatePermissionsJSON = (
|
||||
permissionsJSON: Permissions,
|
||||
checkBoxTrees: Element,
|
||||
) => {
|
||||
const inputs: NodeListOf<HTMLInputElement> = checkBoxTrees.querySelectorAll(
|
||||
'input[type=checkbox]',
|
||||
);
|
||||
inputs.forEach(element => {
|
||||
const accessTo: string = `${element.getAttribute('data-access-to')}`;
|
||||
const accessToId: number = parseInt(
|
||||
element.getAttribute('data-access-to-id'),
|
||||
);
|
||||
const checked = element.checked;
|
||||
if (checked && !permissionsJSON[accessTo].includes(accessToId)) {
|
||||
permissionsJSON[accessTo].push(accessToId);
|
||||
} else if (!checked && permissionsJSON[accessTo].includes(accessToId)) {
|
||||
permissionsJSON[accessTo] = permissionsJSON[accessTo].filter(
|
||||
el => el != accessToId,
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const uncheckParentInputs = (checkbox: HTMLElement) => {
|
||||
const parentLiElement: HTMLElement =
|
||||
checkbox.parentElement.parentElement.parentElement.parentElement;
|
||||
|
||||
const parentInputElement: HTMLInputElement = parentLiElement.querySelector(
|
||||
'input[type=checkbox]',
|
||||
);
|
||||
|
||||
parentInputElement.checked = false;
|
||||
|
||||
if (parentInputElement.getAttribute('data-root') != 'true') {
|
||||
uncheckParentInputs(parentInputElement);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCheckboxClick = (checkbox: HTMLInputElement) => {
|
||||
const parentLiElement: HTMLElement = checkbox.parentElement.parentElement;
|
||||
|
||||
const checked = checkbox.checked;
|
||||
|
||||
if (!checked) {
|
||||
uncheckParentInputs(checkbox);
|
||||
}
|
||||
|
||||
const checkboxes = parentLiElement.querySelectorAll('input[type=checkbox]');
|
||||
|
||||
checkboxes.forEach((checkbox: HTMLInputElement) => {
|
||||
checkbox.checked = checked;
|
||||
});
|
||||
};
|
||||
|
||||
export const initCheckBoxTree = () => {
|
||||
const permissionsJSON: Permissions = {
|
||||
book: [],
|
||||
sub_collection: [],
|
||||
collection: [],
|
||||
section: [],
|
||||
};
|
||||
const permissionsJsonInput: HTMLInputElement =
|
||||
document.querySelector('#permissions_json');
|
||||
const checkBoxTrees = document.querySelectorAll('.checkbox-tree');
|
||||
|
||||
checkBoxTrees.forEach((checkBoxTree: Element) => {
|
||||
const checkboxes = checkBoxTree.querySelectorAll('input[type=checkbox]');
|
||||
|
||||
checkboxes.forEach((checkbox: HTMLInputElement) => {
|
||||
checkbox.addEventListener('click', () => {
|
||||
handleCheckboxClick(checkbox);
|
||||
updatePermissionsJSON(permissionsJSON, checkBoxTree);
|
||||
permissionsJsonInput.value = JSON.stringify(permissionsJSON);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
|
@ -20,6 +20,8 @@ import {renameSubCollection} from './renameSubCollection';
|
|||
import {initQuillReadOnly} from './initQuillReadOnly';
|
||||
import {initGoBack} from './tabGoBackBtn';
|
||||
import {scroll} from './scroll';
|
||||
import {initCheckBoxTree} from './checkBoxTree';
|
||||
import {initPermissions} from './permissions';
|
||||
|
||||
initQuillReadOnly();
|
||||
initBooks();
|
||||
|
@ -43,3 +45,5 @@ deleteSubCollection();
|
|||
renameSubCollection();
|
||||
initGoBack();
|
||||
scroll();
|
||||
initCheckBoxTree();
|
||||
initPermissions();
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
export const initPermissions = () => {
|
||||
const editBtns = document.querySelectorAll('.edit-permissions-btn');
|
||||
|
||||
editBtns.forEach(element => {
|
||||
const bookIdInput: HTMLInputElement = document.querySelector(
|
||||
'#permission_modal_book_id',
|
||||
);
|
||||
const userIdInput: HTMLInputElement = document.querySelector(
|
||||
'#permission_modal_user_id',
|
||||
);
|
||||
element.addEventListener('click', () => {
|
||||
const book_id = element.getAttribute('data-book-id');
|
||||
const user_id = element.getAttribute('data-user-id');
|
||||
|
||||
bookIdInput.value = book_id;
|
||||
userIdInput.value = user_id;
|
||||
});
|
||||
});
|
||||
};
|
Loading…
Reference in New Issue