mirror of
https://github.com/logos-co/open-law.git
synced 2025-01-10 06:46:04 +00:00
Merge pull request #115 from Simple2B/svyat/feat/local_permission
Svyat/feat/local permissions
This commit is contained in:
commit
42137ca8d7
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -14,10 +14,11 @@
|
||||
"backref",
|
||||
"bookname",
|
||||
"Btns",
|
||||
"CUDA",
|
||||
"CLEANR",
|
||||
"CUDA",
|
||||
"Divs",
|
||||
"flowbite",
|
||||
"indeterminated",
|
||||
"jsonify",
|
||||
"pydantic",
|
||||
"pytest",
|
||||
|
@ -69,5 +69,4 @@ def create_editor_group(book_id: int):
|
||||
m.PermissionAccessGroups(
|
||||
permission_id=permission.id, access_group_id=group.id
|
||||
).save()
|
||||
|
||||
return group
|
||||
|
@ -20,11 +20,12 @@ def check_permissions(
|
||||
)
|
||||
entity = None
|
||||
for model in entities:
|
||||
entity_id_field = (model.__name__ + "_id").lower()
|
||||
entity_id = request_args.get(entity_id_field)
|
||||
entity: m.Book | m.Collection | m.Section | m.Interpretation | m.Comment = (
|
||||
db.session.get(model, entity_id)
|
||||
)
|
||||
if not entity:
|
||||
entity_id_field = (model.__name__ + "_id").lower()
|
||||
entity_id = request_args.get(entity_id_field)
|
||||
entity: m.Book | m.Collection | m.Section | m.Interpretation | m.Comment = (
|
||||
db.session.get(model, entity_id)
|
||||
)
|
||||
|
||||
if entity is None:
|
||||
log(log.INFO, "No entity [%s] found", entities)
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -60,8 +60,8 @@
|
||||
<button name="submit" type="submit"></button>
|
||||
</form>
|
||||
</button>
|
||||
</div>
|
||||
<svg id="dropdownCollectionContextButton{{collection.id}}" data-dropdown-toggle="dropdown" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 0 0" stroke-width="1.5" stroke="none" class="w-0 h-0"></svg>
|
||||
</div>
|
||||
<svg id="dropdownCollectionContextButton{{collection.id}}" data-dropdown-toggle="dropdown" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 0 0" stroke-width="1.5" stroke="none" class="w-0 h-0"></svg>
|
||||
</div>
|
||||
<div data="collection-context-menu-{{collection.id}}" id="dropdown" class="z-10 hidden bg-white divide-y divide-gray-800 border border-gray-800 dark:border-none dark:divide-gray-100 rounded-lg shadow w-44 dark:bg-gray-700">
|
||||
{% include 'book/components/collection_context_menu.html' %}
|
||||
|
@ -1,35 +1,36 @@
|
||||
{% if current_user.is_authenticated %}
|
||||
<!-- prettier-ignore -->
|
||||
{% set access_to_create_collections =has_permission(collection, Access.C) %}
|
||||
{% set access_to_update_collections =has_permission(collection, Access.U) %}
|
||||
{% set access_to_delete_collections =has_permission(collection, Access.D) %}
|
||||
{% set access_to_create_section =has_permission(collection, Access.C, EntityType.SECTION) %}
|
||||
{% set access_to_create_collections_in_root = has_permission(collection.parent, Access.C) %}
|
||||
{% set access_to_create_collections = has_permission(collection, Access.C) %}
|
||||
{% set access_to_update_collections = has_permission(collection, Access.U) %}
|
||||
{% set access_to_delete_collections = has_permission(collection, Access.D) %}
|
||||
{% set access_to_create_section = has_permission(collection, Access.C, EntityType.SECTION) %}
|
||||
{% if access_to_create_collections or access_to_update_collections %}
|
||||
<ul class="py-2 text-sm text-gray-700 dark:text-gray-200">
|
||||
{% if access_to_create_collections %}
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
data-modal-target="add-collection-modal"
|
||||
data-modal-toggle="add-collection-modal"
|
||||
class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
|
||||
New Collection
|
||||
</button>
|
||||
</li>
|
||||
{% if collection.active_children or not collection.active_sections%}
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
id="callAddSubCollectionModal"
|
||||
data-modal-target="add-sub-collection-modal"
|
||||
data-modal-toggle="add-sub-collection-modal"
|
||||
data-collection-id="{{collection.id}}"
|
||||
class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
|
||||
New Subcollection
|
||||
</button>
|
||||
</li>
|
||||
<!-- prettier-ignore -->
|
||||
{% if access_to_create_collections_in_root %}
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
data-modal-target="add-collection-modal"
|
||||
data-modal-toggle="add-collection-modal"
|
||||
class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
|
||||
New Collection
|
||||
</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if collection.active_children or not collection.active_sections%}
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
id="callAddSubCollectionModal"
|
||||
data-modal-target="add-sub-collection-modal"
|
||||
data-modal-toggle="add-sub-collection-modal"
|
||||
data-collection-id="{{collection.id}}"
|
||||
class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
|
||||
New Subcollection
|
||||
</button>
|
||||
</li>
|
||||
<!-- prettier-ignore -->
|
||||
{% endif %}
|
||||
{% if access_to_create_section %}
|
||||
{% if not collection.active_children or not collection.active_children and collection.active_sections %}
|
||||
|
@ -1,119 +1,125 @@
|
||||
<!-- prettier-ignore -->
|
||||
{% if current_user.is_authenticated %}
|
||||
{% set access_to_create_collections =has_permission(sub_collection, Access.C) %}
|
||||
{% set access_to_update_collections= has_permission(sub_collection, Access.U) %}
|
||||
{% set access_to_delete_collections = has_permission(sub_collection, Access.D) %}
|
||||
{% set access_to_create_section = has_permission(collection, Access.C,EntityType.SECTION) %}
|
||||
{% if access_to_create_collections or access_to_update_collections or access_to_create_section %}
|
||||
<ul class="py-2 text-sm text-gray-700 dark:text-gray-200">
|
||||
<!-- prettier-ignore -->
|
||||
{% if access_to_create_section and sub_collection.active_sections and not sub_collection.active_children %}
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
id="callAddSectionModal"
|
||||
data-modal-target="add-section-modal"
|
||||
data-modal-toggle="add-section-modal"
|
||||
data-collection-id="{{collection.id}}"
|
||||
data-sub-collection-id="{{sub_collection.id}}"
|
||||
class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
|
||||
New Section
|
||||
</button>
|
||||
</li>
|
||||
<!-- prettier-ignore -->
|
||||
{% elif not sub_collection.active_sections and not sub_collection.active_children %} {% if access_to_create_section %}
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
id="callAddSectionModal"
|
||||
data-modal-target="add-section-modal"
|
||||
data-modal-toggle="add-section-modal"
|
||||
data-collection-id="{{collection.id}}"
|
||||
data-sub-collection-id="{{sub_collection.id}}"
|
||||
class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
|
||||
New Section
|
||||
</button>
|
||||
</li>
|
||||
{% set access_to_create_collections =has_permission(sub_collection, Access.C) %}
|
||||
{% set access_to_update_collections= has_permission(sub_collection, Access.U) %}
|
||||
{% set access_to_delete_collections = has_permission(sub_collection, Access.D) %}
|
||||
{% set access_to_create_section = has_permission(sub_collection, Access.C,EntityType.SECTION) %}
|
||||
|
||||
{% if access_to_create_collections or access_to_update_collections or access_to_create_section %}
|
||||
<ul class="py-2 text-sm text-gray-700 dark:text-gray-200">
|
||||
<!-- prettier-ignore -->
|
||||
{% if access_to_create_section and sub_collection.active_sections and not sub_collection.active_children %}
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
id="callAddSectionModal"
|
||||
data-modal-target="add-section-modal"
|
||||
data-modal-toggle="add-section-modal"
|
||||
data-collection-id="{{collection.id}}"
|
||||
data-sub-collection-id="{{sub_collection.id}}"
|
||||
class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white"
|
||||
>
|
||||
New Section
|
||||
</button>
|
||||
</li>
|
||||
<!-- prettier-ignore -->
|
||||
{% elif not sub_collection.active_sections and not sub_collection.active_children %}
|
||||
{% if access_to_create_section %}
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
id="callAddSectionModal"
|
||||
data-modal-target="add-section-modal"
|
||||
data-modal-toggle="add-section-modal"
|
||||
data-collection-id="{{collection.id}}"
|
||||
data-sub-collection-id="{{sub_collection.id}}"
|
||||
class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white"
|
||||
>
|
||||
New Section
|
||||
</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
<!-- prettier-ignore -->
|
||||
{% if access_to_create_collections %}
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
id="callAddSubCollectionModal"
|
||||
data-modal-target="add-sub-collection-modal"
|
||||
data-modal-toggle="add-sub-collection-modal"
|
||||
data-collection-id="{{sub_collection.id}}"
|
||||
class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white"
|
||||
>
|
||||
New Subcollection
|
||||
</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<!-- prettier-ignore -->
|
||||
{% if access_to_create_collections %}
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
id="callAddSubCollectionModal"
|
||||
data-modal-target="add-sub-collection-modal"
|
||||
data-modal-toggle="add-sub-collection-modal"
|
||||
data-collection-id="{{sub_collection.id}}"
|
||||
class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
|
||||
New Subcollection
|
||||
</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
<!-- prettier-ignore -->
|
||||
{% if access_to_create_collections %}
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
id="callAddSubCollectionModal"
|
||||
data-modal-target="add-sub-collection-modal"
|
||||
data-modal-toggle="add-sub-collection-modal"
|
||||
data-collection-id="{{sub_collection.id}}"
|
||||
class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
|
||||
New Subcollection
|
||||
</button>
|
||||
</li>
|
||||
{% endif %} {% else %}
|
||||
<!-- prettier-ignore -->
|
||||
{% if access_to_create_collections %}
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
id="callAddSubCollectionModal"
|
||||
data-modal-target="add-sub-collection-modal"
|
||||
data-modal-toggle="add-sub-collection-modal"
|
||||
data-collection-id="{{sub_collection.id}}"
|
||||
class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
|
||||
New Subcollection
|
||||
</button>
|
||||
</li>
|
||||
<!-- prettier-ignore -->
|
||||
{% if access_to_update_collections or access_to_delete_collections%}
|
||||
<ul class="py-2 text-sm text-gray-700 dark:text-gray-200">
|
||||
{% if access_to_update_collections %}
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
id="rename-sub-collection-button-{{sub_collection.id}}"
|
||||
class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
|
||||
Rename Sub Collection
|
||||
</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
<!-- prettier-ignore -->
|
||||
{% if access_to_delete_collections %}
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
id="callDeleteSubCollectionModal"
|
||||
data-modal-target="delete-sub-collection-modal"
|
||||
data-modal-toggle="delete-sub-collection-modal"
|
||||
data-collection-id="{{collection.id}}"
|
||||
data-sub-collection-id="{{sub_collection.id}}"
|
||||
class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white"
|
||||
>
|
||||
Delete Sub Collection
|
||||
</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
<!-- prettier-ignore -->
|
||||
{% if access_to_update_collections or access_to_delete_collections%}
|
||||
<ul class="py-2 text-sm text-gray-700 dark:text-gray-200">
|
||||
{% if access_to_update_collections %}
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
id="rename-sub-collection-button-{{sub_collection.id}}"
|
||||
class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
|
||||
Rename Sub Collection
|
||||
</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
<!-- prettier-ignore -->
|
||||
{% if access_to_delete_collections %}
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
id="callDeleteSubCollectionModal"
|
||||
data-modal-target="delete-sub-collection-modal"
|
||||
data-modal-toggle="delete-sub-collection-modal"
|
||||
data-collection-id="{{collection.id}}"
|
||||
data-sub-collection-id="{{sub_collection.id}}"
|
||||
class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
|
||||
Delete Sub Collection
|
||||
</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
<ul class="py-2 text-sm text-gray-700 dark:text-gray-200">
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
|
||||
Export Sub Collection
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="py-2 text-sm text-gray-700 dark:text-gray-200">
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
|
||||
Export Sub Collection
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
{% else %}
|
||||
<ul class="py-2 text-sm text-gray-700 dark:text-gray-200">
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
|
||||
Connect your wallet to do this
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="py-2 text-sm text-gray-700 dark:text-gray-200">
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
|
||||
Connect your wallet to do this
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
@ -3,7 +3,7 @@
|
||||
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 action="{{ url_for('permission.set_permissions') }}" 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"/>
|
||||
@ -20,54 +20,74 @@
|
||||
<!-- Modal body -->
|
||||
<div class="p-6 space-y-6">
|
||||
<div class="checkbox-tree">
|
||||
<ul class="ml-4">
|
||||
<ul class="ml-6">
|
||||
<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" />
|
||||
<input
|
||||
type="checkbox"
|
||||
data-root="true"
|
||||
data-access-to="book"
|
||||
data-access-to-id="{{ book.id }}"
|
||||
class="w-4 h-4 text-purple-600 bg-purple-100 border-purple-400 rounded focus:ring-purple-500 dark:focus:ring-purple-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-purple-300 dark:border-purple-500"
|
||||
/>
|
||||
<span class="text-center dark:text-gray-300">{{ book.label }}</span>
|
||||
</div>
|
||||
{% for collection in book.last_version.children_collections %}
|
||||
<ul class="ml-4">
|
||||
{%- for collection in book.last_version.children_collections recursive %}
|
||||
<ul class="ml-5">
|
||||
<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" />
|
||||
<input type="checkbox" data-access-to="collection" data-access-to-id="{{ collection.id }}" class="w-4 h-4 text-purple-600 bg-purple-100 border-purple-400 rounded focus:ring-purple-500 dark:focus:ring-purple-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-purple-300 dark:border-purple-500" />
|
||||
<span class="text-center dark:text-gray-300">{{ collection.label }}</span>
|
||||
</div>
|
||||
|
||||
{% for sub_collection in collection.active_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 %}
|
||||
{% if collection.active_children %}
|
||||
|
||||
{% 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 %}
|
||||
{{ loop(collection.active_children)}}
|
||||
|
||||
{#
|
||||
{% for sub_collection in collection.active_children %}
|
||||
<ul class="ml-5">
|
||||
<li>
|
||||
<div class="flex items-center space-x-2">
|
||||
<input type="checkbox" data-access-to="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 %}
|
||||
#}
|
||||
|
||||
|
||||
{% else %}
|
||||
|
||||
|
||||
{% for section in collection.sections %}
|
||||
<ul class="ml-5">
|
||||
<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-purple-600 bg-purple-100 border-purple-400 rounded focus:ring-purple-500 dark:focus:ring-purple-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-purple-300 dark:border-purple-500" />
|
||||
|
||||
<span class="text-center dark:text-gray-300">{{ section.label }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
{% endfor %}
|
||||
{%- endfor %}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
170
app/templates/book/modals/access_level_modal_v2.html
Normal file
170
app/templates/book/modals/access_level_modal_v2.html
Normal file
@ -0,0 +1,170 @@
|
||||
|
||||
|
||||
<!-- Will be useful in next OpenLaw version -->
|
||||
|
||||
|
||||
<!-- 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_permissions') }}" 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-6">
|
||||
<li>
|
||||
<div class="flex items-center space-x-2">
|
||||
<div id="c-tooltip-book-{{book.id}}" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-600">
|
||||
Create
|
||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||
</div>
|
||||
<input data-tooltip-target="c-tooltip-book-{{book.id}}" type="checkbox" data-root="true" data-permission="C" data-access-to="book" data-access-to-id="{{ book.id }}" class="w-4 h-4 text-purple-600 bg-purple-100 border-purple-400 rounded focus:ring-purple-500 dark:focus:ring-purple-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-purple-300 dark:border-purple-500" />
|
||||
|
||||
<div id="u-tooltip-book-{{book.id}}" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-600">
|
||||
Update
|
||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||
</div>
|
||||
<input data-tooltip-target="u-tooltip-book-{{book.id}}" type="checkbox" data-root="true" data-permission="U" data-access-to="book" data-access-to-id="{{ book.id }}" class="w-4 h-4 text-teal-600 bg-teal-100 border-teal-400 rounded focus:ring-teal-500 dark:focus:ring-teal-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-teal-300 dark:border-teal-500" />
|
||||
|
||||
<div id="d-tooltip-book-{{book.id}}" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-600">
|
||||
Delete
|
||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||
</div>
|
||||
<input data-tooltip-target="d-tooltip-book-{{book.id}}" type="checkbox" data-root="true" data-permission="D" data-access-to="book" data-access-to-id="{{ book.id }}" class="w-4 h-4 text-red-600 bg-red-100 border-red-400 rounded focus:ring-red-500 dark:focus:ring-red-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-red-300 dark:border-red-500" />
|
||||
|
||||
<div id="a-tooltip-book-{{book.id}}" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-600">
|
||||
Approve
|
||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||
</div>
|
||||
<input data-tooltip-target="a-tooltip-book-{{book.id}}" type="checkbox" data-root="true" data-permission="A" data-access-to="book" data-access-to-id="{{ book.id }}" class="w-4 h-4 text-green-600 bg-green-100 border-green-400 rounded focus:ring-green-500 dark:focus:ring-green-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-green-300 dark:border-green-500" />
|
||||
|
||||
<span class="text-center dark:text-gray-300">{{ book.label }}</span>
|
||||
</div>
|
||||
{%- for collection in book.last_version.children_collections recursive %}
|
||||
<div class="flex">
|
||||
<ul class="ml-4">
|
||||
<li>
|
||||
<div class="flex items-center space-x-2">
|
||||
<div id="c-tooltip-collection-{{collection.id}}" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-600">
|
||||
Create
|
||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||
</div>
|
||||
<input data-tooltip-target="c-tooltip-collection-{{collection.id}}" type="checkbox" data-permission="C" data-access-to="collection" data-access-to-id="{{ collection.id }}" class="w-4 h-4 text-purple-600 bg-purple-100 border-purple-400 rounded focus:ring-purple-500 dark:focus:ring-purple-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-purple-300 dark:border-purple-500" />
|
||||
|
||||
<div id="u-tooltip-collection-{{collection.id}}" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-600">
|
||||
Update
|
||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||
</div>
|
||||
<input data-tooltip-target="u-tooltip-collection-{{collection.id}}" type="checkbox" data-permission="U" data-access-to="collection" data-access-to-id="{{ collection.id }}" class="w-4 h-4 text-teal-600 bg-teal-100 border-teal-400 rounded focus:ring-teal-500 dark:focus:ring-teal-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-teal-300 dark:border-teal-500" />
|
||||
|
||||
<div id="d-tooltip-collection-{{collection.id}}" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-600">
|
||||
Delete
|
||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||
</div>
|
||||
<input data-tooltip-target="d-tooltip-collection-{{collection.id}}" type="checkbox" data-permission="D" data-access-to="collection" data-access-to-id="{{ collection.id }}" class="w-4 h-4 text-red-600 bg-red-100 border-red-400 rounded focus:ring-red-500 dark:focus:ring-red-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-red-300 dark:border-red-500" />
|
||||
|
||||
<div id="a-tooltip-collection-{{collection.id}}" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-600">
|
||||
Approve
|
||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||
</div>
|
||||
<input data-tooltip-target="a-tooltip-collection-{{collection.id}}" type="checkbox" data-permission="A" data-access-to="collection" data-access-to-id="{{ collection.id }}" class="w-4 h-4 text-green-600 bg-green-100 border-green-400 rounded focus:ring-green-500 dark:focus:ring-green-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-green-300 dark:border-green-500" />
|
||||
|
||||
<span class="text-center dark:text-gray-300">{{ collection.label }}</span>
|
||||
</div>
|
||||
|
||||
{% if collection.active_children %}
|
||||
|
||||
{{ loop(collection.active_children)}}
|
||||
|
||||
{#
|
||||
{% for sub_collection in collection.active_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 %}
|
||||
#}
|
||||
|
||||
|
||||
{% else %}
|
||||
|
||||
|
||||
{% for section in collection.sections %}
|
||||
<ul class="ml-4">
|
||||
<li>
|
||||
<div class="flex items-center space-x-2">
|
||||
<div id="c-tooltip-section-{{section.id}}" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-600">
|
||||
Create
|
||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||
</div>
|
||||
<input data-tooltip-target="c-tooltip-section-{{section.id}}" disabled indeterminate="true" type="checkbox" data-permission="C" data-access-to="section" data-access-to-id="{{ section.id }}" class="w-4 h-4 dark:indeterminate:bg-purple-600 dark:indeterminate:border-purple-600 text-purple-600 bg-purple-100 border-purple-400 rounded focus:ring-purple-500 dark:focus:ring-purple-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-purple-300 dark:border-purple-500" />
|
||||
|
||||
<div id="u-tooltip-section-{{section.id}}" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-600">
|
||||
Update
|
||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||
</div>
|
||||
<input data-tooltip-target="u-tooltip-section-{{section.id}}" disabled indeterminate="true" type="checkbox" data-permission="U" data-access-to="section" data-access-to-id="{{ section.id }}" class="w-4 h-4 dark:indeterminate:bg-gray-600 dark:indeterminate:border-gray-600 text-teal-600 bg-teal-100 border-teal-400 rounded dark:bg-teal-300 dark:border-teal-500" />
|
||||
|
||||
<div id="d-tooltip-section-{{section.id}}" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-600">
|
||||
Delete
|
||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||
</div>
|
||||
<input data-tooltip-target="d-tooltip-section-{{section.id}}" type="checkbox" data-permission="D" data-access-to="section" data-access-to-id="{{ section.id }}" class="w-4 h-4 text-red-600 bg-red-100 border-red-400 rounded focus:ring-red-500 dark:focus:ring-red-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-red-300 dark:border-red-500" />
|
||||
|
||||
<div id="a-tooltip-section-{{section.id}}" role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-600">
|
||||
Approve
|
||||
<div class="tooltip-arrow" data-popper-arrow></div>
|
||||
</div>
|
||||
<input data-tooltip-target="a-tooltip-section-{{section.id}}" type="checkbox" data-permission="A" data-access-to="section" data-access-to-id="{{ section.id }}" class="w-4 h-4 text-green-600 bg-green-100 border-green-400 rounded focus:ring-green-500 dark:focus:ring-green-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-green-300 dark:border-green-500" />
|
||||
<span class="text-center dark:text-gray-300">{{ section.label }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
{% endfor %}
|
||||
|
||||
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{%- 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>
|
@ -1,18 +1,24 @@
|
||||
from flask import redirect, url_for, Blueprint, flash
|
||||
import json
|
||||
|
||||
from flask import redirect, url_for, Blueprint, flash, request
|
||||
from flask_login import current_user
|
||||
|
||||
from app import forms as f, models as m, db
|
||||
from app.logger import log
|
||||
from app.controllers.create_access_groups import (
|
||||
create_editor_group,
|
||||
create_moderator_group,
|
||||
)
|
||||
|
||||
bp = Blueprint("permission", __name__, "/permission")
|
||||
bp = Blueprint("permission", __name__, url_prefix="/permission")
|
||||
|
||||
|
||||
@bp.route("/set", methods=["POST"])
|
||||
def set():
|
||||
def set_permissions():
|
||||
form: f.EditPermissionForm = f.EditPermissionForm()
|
||||
|
||||
book_id = form.book_id.data
|
||||
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)
|
||||
@ -33,7 +39,128 @@ def set():
|
||||
flash("User are not contributor of this book!", "danger")
|
||||
return redirect(url_for("book.my_library"))
|
||||
|
||||
# TODO process data from checkbox tree
|
||||
# permissions = json.loads(form.permissions.data)
|
||||
user: m.User = contributor.user
|
||||
users_access_groups: list[m.AccessGroup] = list(
|
||||
set(book.list_access_groups).intersection(user.access_groups)
|
||||
)
|
||||
if len(users_access_groups) > 1:
|
||||
log(
|
||||
log.WARNING,
|
||||
"User: [%s] has more than 1 access group in book [%s]",
|
||||
user,
|
||||
book,
|
||||
)
|
||||
|
||||
return {"status": "ok"}
|
||||
for users_access in users_access_groups:
|
||||
users_access: m.AccessGroup
|
||||
users_access.users.remove(user)
|
||||
|
||||
permissions_json = json.loads(form.permissions.data)
|
||||
book_ids = permissions_json.get("book", [])
|
||||
for book_id in book_ids:
|
||||
entire_boot_access_group = m.AccessGroup.query.filter_by(
|
||||
book_id=book_id, name=contributor.role.name.lower()
|
||||
).first()
|
||||
m.UserAccessGroups(
|
||||
user_id=user.id, access_group_id=entire_boot_access_group.id
|
||||
).save(False)
|
||||
db.session.commit()
|
||||
flash("Success!", "success")
|
||||
return redirect(url_for("book.settings", book_id=book_id))
|
||||
|
||||
new_access_group = None
|
||||
match contributor.role:
|
||||
case m.BookContributor.Roles.EDITOR:
|
||||
new_access_group = create_editor_group(book.id)
|
||||
case m.BookContributor.Roles.MODERATOR:
|
||||
new_access_group = create_moderator_group(book.id)
|
||||
case _:
|
||||
log(
|
||||
log.CRITICAL,
|
||||
"Unknown contributor's [%s] role: [%s]",
|
||||
contributor,
|
||||
contributor.role,
|
||||
)
|
||||
flash("Unknown contributor's role", "danger")
|
||||
return redirect(url_for("book.settings", book_id=book_id))
|
||||
m.UserAccessGroups(user_id=user.id, access_group_id=new_access_group.id).save(
|
||||
False
|
||||
)
|
||||
|
||||
collection_ids = permissions_json.get("collection", [])
|
||||
for collection_id in collection_ids:
|
||||
m.CollectionAccessGroups(
|
||||
collection_id=collection_id, access_group_id=new_access_group.id
|
||||
).save(False)
|
||||
|
||||
section_ids = permissions_json.get("section", [])
|
||||
for section_id in section_ids:
|
||||
m.SectionAccessGroups(
|
||||
section_id=section_id, access_group_id=new_access_group.id
|
||||
).save(False)
|
||||
|
||||
db.session.commit()
|
||||
flash("Success!", "success")
|
||||
return redirect(url_for("book.settings", book_id=book_id))
|
||||
|
||||
log(log.ERROR, "Errors edit contributor access level: [%s]", form.errors)
|
||||
for field, errors in form.errors.items():
|
||||
field_label = form._fields[field].label.text
|
||||
for error in errors:
|
||||
flash(error.replace("Field", field_label), "danger")
|
||||
if book_id:
|
||||
return redirect(url_for("book.settings", book_id=book_id))
|
||||
return redirect(url_for("book.my_library"))
|
||||
|
||||
|
||||
@bp.route("/access_tree", methods=["GET"])
|
||||
def access_tree():
|
||||
user_id = request.args.get("user_id", type=int)
|
||||
book_id = request.args.get("book_id", type=int)
|
||||
if not user_id or not book_id:
|
||||
return {"message": "get parameters user_id and book_id are required"}, 404
|
||||
|
||||
user: m.User = db.session.get(m.User, user_id)
|
||||
if not user:
|
||||
return {"message": f"user with id {user_id} not found"}, 404
|
||||
book: m.Book = db.session.get(m.Book, book_id)
|
||||
if not book:
|
||||
return {"message": f"book with id {user_id} not found"}, 404
|
||||
|
||||
access_tree = {
|
||||
"book": [],
|
||||
"collection": [],
|
||||
"section": [],
|
||||
}
|
||||
|
||||
users_access_groups: list[m.AccessGroup] = list(
|
||||
set(book.list_access_groups).intersection(user.access_groups)
|
||||
)
|
||||
|
||||
if list(set(book.access_groups).intersection(users_access_groups)):
|
||||
access_tree["book"].append(book_id)
|
||||
|
||||
collections = (
|
||||
db.session.query(m.Collection).filter(
|
||||
m.Collection.version_id == book.last_version.id,
|
||||
m.Collection.is_root == False, # noqa: E712
|
||||
m.Collection.is_deleted == False, # noqa: E712
|
||||
)
|
||||
).all()
|
||||
|
||||
for collection in collections:
|
||||
if list(set(collection.access_groups).intersection(users_access_groups)):
|
||||
access_tree["collection"].append(collection.id)
|
||||
|
||||
sections = (
|
||||
db.session.query(m.Section).filter(
|
||||
m.Section.version_id == book.last_version.id,
|
||||
m.Section.is_deleted == False, # noqa: E712
|
||||
)
|
||||
).all()
|
||||
|
||||
for section in sections:
|
||||
if list(set(section.access_groups).intersection(users_access_groups)):
|
||||
access_tree["section"].append(section.id)
|
||||
|
||||
return {"access_tree": access_tree}
|
||||
|
@ -59,7 +59,6 @@ const handleCheckboxClick = (checkbox: HTMLInputElement) => {
|
||||
export const initCheckBoxTree = () => {
|
||||
const permissionsJSON: Permissions = {
|
||||
book: [],
|
||||
sub_collection: [],
|
||||
collection: [],
|
||||
section: [],
|
||||
};
|
||||
|
93
src/checkBoxTree_v2.ts
Normal file
93
src/checkBoxTree_v2.ts
Normal file
@ -0,0 +1,93 @@
|
||||
// Will be useful in next OpenLaw version
|
||||
|
||||
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 dataPermission = checkbox.getAttribute('data-permission');
|
||||
|
||||
const parentInputElement: HTMLInputElement = parentLiElement.querySelector(
|
||||
`input[type=checkbox][data-permission=${dataPermission}]`,
|
||||
);
|
||||
|
||||
if (!parentInputElement.disabled) {
|
||||
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 dataPermission = checkbox.getAttribute('data-permission');
|
||||
|
||||
const checkboxes = parentLiElement.querySelectorAll(
|
||||
`input[type=checkbox][data-permission=${dataPermission}]`,
|
||||
);
|
||||
|
||||
checkboxes.forEach((checkbox: HTMLInputElement) => {
|
||||
if (!checkbox.disabled) {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
@ -15,6 +15,7 @@ export function initDnD() {
|
||||
put: ['sections'],
|
||||
},
|
||||
animation: 100,
|
||||
filter: '.filter',
|
||||
onEnd: async function (/**Event*/ evt) {
|
||||
var itemEl = evt.item; // dragged HTMLElement
|
||||
const bookId = itemEl.getAttribute('data-book-id');
|
||||
@ -49,6 +50,7 @@ export function initDnD() {
|
||||
put: ['sub_collections'],
|
||||
},
|
||||
animation: 100,
|
||||
filter: '.filter',
|
||||
onEnd: async function (/**Event*/ evt) {
|
||||
var itemEl = evt.item; // dragged HTMLElement
|
||||
const bookId = itemEl.getAttribute('data-book-id');
|
||||
|
9
src/indeterminateInputs.ts
Normal file
9
src/indeterminateInputs.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export function indeterminateInputs() {
|
||||
const indeterminatedInputs = document.querySelectorAll(
|
||||
'input[indeterminate=true]',
|
||||
);
|
||||
|
||||
indeterminatedInputs.forEach((element: HTMLInputElement) => {
|
||||
element.indeterminate = true;
|
||||
});
|
||||
}
|
@ -30,6 +30,8 @@ import {slashSearch} from './slashSearch';
|
||||
import {initDnD} from './drag_and_drop';
|
||||
import {editInterpretations} from './editInterpretations';
|
||||
import {deleteInterpretation} from './deleteInterpretation';
|
||||
import {indeterminateInputs} from './indeterminateInputs';
|
||||
import {initRefreshAccessLevelTree} from './refreshAccessLevelTree';
|
||||
|
||||
initQuillReadOnly();
|
||||
initBooks();
|
||||
@ -63,3 +65,5 @@ slashSearch();
|
||||
initDnD();
|
||||
editInterpretations();
|
||||
deleteInterpretation();
|
||||
indeterminateInputs();
|
||||
initRefreshAccessLevelTree();
|
||||
|
35
src/refreshAccessLevelTree.ts
Normal file
35
src/refreshAccessLevelTree.ts
Normal file
@ -0,0 +1,35 @@
|
||||
const refreshAccessLevelTree = async (userId: string, bookId: string) => {
|
||||
const urlParams = new URLSearchParams({
|
||||
user_id: userId,
|
||||
book_id: bookId,
|
||||
});
|
||||
const res = await fetch('/permission/access_tree?' + urlParams);
|
||||
const json = await res.json();
|
||||
|
||||
Object.entries(json.access_tree).map(([key, ids]: [string, number[]]) => {
|
||||
const checkboxes = document.querySelectorAll(
|
||||
`input[type=checkbox][data-access-to=${key}]`,
|
||||
);
|
||||
|
||||
checkboxes.forEach((element: HTMLInputElement) => {
|
||||
const id = parseInt(element.getAttribute('data-access-to-id'));
|
||||
if (ids.includes(id)) {
|
||||
element.checked = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export function initRefreshAccessLevelTree() {
|
||||
const editPermissionsBtns = document.querySelectorAll(
|
||||
'.edit-permissions-btn',
|
||||
);
|
||||
|
||||
editPermissionsBtns.forEach(element => {
|
||||
const userId = element.getAttribute('data-user-id');
|
||||
const bookId = element.getAttribute('data-book-id');
|
||||
element.addEventListener('click', () => {
|
||||
refreshAccessLevelTree(userId, bookId);
|
||||
});
|
||||
});
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
import json
|
||||
|
||||
from flask import current_app as Response
|
||||
|
||||
from app import models as m
|
||||
@ -257,3 +259,74 @@ def test_moderator_access_to_entire_book(client):
|
||||
)
|
||||
assert b"You do not have permission" not in response.data
|
||||
assert b"Success!" in response.data
|
||||
|
||||
|
||||
def test_editor_access_tree_entire_book(client):
|
||||
login(client)
|
||||
book = create_book(client)
|
||||
collection_1, _ = create_collection(client, book.id)
|
||||
collection_2, _ = create_collection(client, book.id)
|
||||
|
||||
editor = m.User(username="editor", password="editor").save()
|
||||
response: Response = client.post(
|
||||
f"/book/{book.id}/add_contributor",
|
||||
data=dict(user_id=editor.id, role=m.BookContributor.Roles.EDITOR),
|
||||
follow_redirects=True,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert b"Contributor was added!" in response.data
|
||||
|
||||
response: Response = client.get(
|
||||
f"/permission/access_tree?user_id={editor.id}&book_id={book.id}",
|
||||
follow_redirects=True,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
json = response.json
|
||||
access_tree = json.get("access_tree")
|
||||
assert access_tree
|
||||
assert book.id in access_tree.get("book")
|
||||
collections_ids = access_tree.get("collection")
|
||||
assert collections_ids
|
||||
assert collection_1.id in collections_ids
|
||||
assert collection_2.id in collections_ids
|
||||
|
||||
|
||||
def test_set_access_level(client):
|
||||
login(client)
|
||||
book = create_book(client)
|
||||
collection_1, _ = create_collection(client, book.id)
|
||||
collection_2, _ = create_collection(client, book.id)
|
||||
|
||||
editor = m.User(username="editor", password="editor").save()
|
||||
response: Response = client.post(
|
||||
f"/book/{book.id}/add_contributor",
|
||||
data=dict(user_id=editor.id, role=m.BookContributor.Roles.EDITOR),
|
||||
follow_redirects=True,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert b"Contributor was added!" in response.data
|
||||
assert len(book.list_access_groups) == 2
|
||||
|
||||
json_string = json.dumps({"collection": [collection_1.id]})
|
||||
response: Response = client.post(
|
||||
"/permission/set",
|
||||
data=dict(
|
||||
book_id=book.id,
|
||||
user_id=editor.id,
|
||||
permissions=json_string,
|
||||
),
|
||||
follow_redirects=True,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert len(book.list_access_groups) == 3
|
||||
|
||||
response: Response = client.post(
|
||||
"/permission/set",
|
||||
data=dict(
|
||||
book_id=book.id,
|
||||
user_id=editor.id,
|
||||
),
|
||||
follow_redirects=True,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert b"Success!" not in response.data
|
||||
|
Loading…
x
Reference in New Issue
Block a user