mirror of
https://github.com/lucaspalomodevelop/eventcally.git
synced 2026-03-13 00:07:22 +00:00
parent
70b05e49fe
commit
2c2b95918d
@ -225,7 +225,7 @@ def can_verify_admin_unit():
|
||||
if not current_user.is_authenticated: # pragma: no cover
|
||||
return False
|
||||
|
||||
if has_current_user_role("admin"):
|
||||
if has_current_user_role("admin"): # pragma: no cover
|
||||
return True
|
||||
|
||||
admin_units = get_admin_units_for_manage()
|
||||
|
||||
@ -61,6 +61,7 @@ from project.api.organization_relation.schemas import (
|
||||
OrganizationRelationIdSchema,
|
||||
OrganizationRelationListRequestSchema,
|
||||
OrganizationRelationListResponseSchema,
|
||||
OrganizationRelationSchema,
|
||||
)
|
||||
from project.api.organizer.schemas import (
|
||||
OrganizerIdSchema,
|
||||
@ -76,6 +77,7 @@ from project.api.place.schemas import (
|
||||
)
|
||||
from project.api.resources import BaseResource, require_api_access
|
||||
from project.models import AdminUnit, Event, PublicStatus
|
||||
from project.models.admin_unit import AdminUnitRelation
|
||||
from project.services.admin_unit import (
|
||||
get_admin_unit_invitation_query,
|
||||
get_admin_unit_query,
|
||||
@ -395,6 +397,25 @@ class OrganizationOutgoingRelationListResource(BaseResource):
|
||||
return relation, 201
|
||||
|
||||
|
||||
class OrganizationOutgoingRelationResource(BaseResource):
|
||||
@doc(
|
||||
summary="Get outgoing relation to given target organization",
|
||||
tags=["Organizations", "Organization Relations"],
|
||||
)
|
||||
@marshal_with(OrganizationRelationSchema)
|
||||
@require_api_access("organization:read")
|
||||
def get(self, id, target_id):
|
||||
login_api_user_or_401()
|
||||
admin_unit = get_admin_unit_for_manage_or_404(id)
|
||||
access_or_401(admin_unit, "admin_unit:update")
|
||||
|
||||
relation = AdminUnitRelation.query.filter(
|
||||
AdminUnitRelation.source_admin_unit_id == id,
|
||||
AdminUnitRelation.target_admin_unit_id == target_id,
|
||||
).first_or_404(id)
|
||||
return relation
|
||||
|
||||
|
||||
class OrganizationOrganizationInvitationListResource(BaseResource):
|
||||
@doc(
|
||||
summary="List organization invitations of organization",
|
||||
@ -586,6 +607,11 @@ add_api_resource(
|
||||
"/organizations/<int:id>/relations/outgoing",
|
||||
"api_v1_organization_outgoing_relation_list",
|
||||
)
|
||||
add_api_resource(
|
||||
OrganizationOutgoingRelationResource,
|
||||
"/organizations/<int:id>/relations/outgoing/<int:target_id>",
|
||||
"api_v1_organization_outgoing_relation",
|
||||
)
|
||||
add_api_resource(
|
||||
OrganizationOrganizationInvitationListResource,
|
||||
"/organizations/<int:id>/organization-invitations",
|
||||
|
||||
@ -68,6 +68,7 @@ class OrganizationRefSchema(OrganizationIdSchema):
|
||||
|
||||
class OrganizationListRefSchema(OrganizationRefSchema):
|
||||
short_name = marshmallow.auto_field()
|
||||
is_verified = fields.Boolean()
|
||||
|
||||
|
||||
class OrganizationListRequestSchema(PaginationRequestSchema):
|
||||
|
||||
@ -109,9 +109,59 @@ def get_context_processors():
|
||||
|
||||
return has_tos()
|
||||
|
||||
def get_current_user_roles():
|
||||
from flask_security import current_user
|
||||
|
||||
if not current_user.is_authenticated: # pragma: no cover
|
||||
return []
|
||||
|
||||
return [r.name for r in current_user.roles]
|
||||
|
||||
def get_current_user_permissions():
|
||||
from flask_security import current_user
|
||||
|
||||
if not current_user.is_authenticated: # pragma: no cover
|
||||
return []
|
||||
|
||||
return sum([r.permissions for r in current_user.roles], [])
|
||||
|
||||
def get_current_admin_unit_roles():
|
||||
from project.access import get_current_user_member_for_admin_unit
|
||||
|
||||
current_admin_unit = get_current_admin_unit()
|
||||
|
||||
if not current_admin_unit: # pragma: no cover
|
||||
return []
|
||||
|
||||
member = get_current_user_member_for_admin_unit(current_admin_unit.id)
|
||||
|
||||
if not member: # pragma: no cover
|
||||
return []
|
||||
|
||||
return [r.name for r in member.roles]
|
||||
|
||||
def get_current_admin_unit_permissions():
|
||||
from project.access import get_current_user_member_for_admin_unit
|
||||
|
||||
current_admin_unit = get_current_admin_unit()
|
||||
|
||||
if not current_admin_unit: # pragma: no cover
|
||||
return []
|
||||
|
||||
member = get_current_user_member_for_admin_unit(current_admin_unit.id)
|
||||
|
||||
if not member: # pragma: no cover
|
||||
return []
|
||||
|
||||
return sum([r.permissions for r in member.roles], [])
|
||||
|
||||
return dict(
|
||||
current_admin_unit=get_current_admin_unit(),
|
||||
get_current_admin_unit_roles=get_current_admin_unit_roles,
|
||||
get_current_admin_unit_permissions=get_current_admin_unit_permissions,
|
||||
get_manage_menu_options=get_manage_menu_options,
|
||||
has_access=has_access,
|
||||
has_tos=has_tos,
|
||||
get_current_user_roles=get_current_user_roles,
|
||||
get_current_user_permissions=get_current_user_permissions,
|
||||
)
|
||||
|
||||
@ -102,10 +102,17 @@ const CustomTypeahead = {
|
||||
);
|
||||
},
|
||||
watch: {
|
||||
// Handles internal model changes.
|
||||
selected(newVal) {
|
||||
this.$emit("input", newVal)
|
||||
this.$refs.validationProvider.syncValue(newVal)
|
||||
this.$refs.validationProvider.validate()
|
||||
this.$emit("input", newVal);
|
||||
this.$refs.validationProvider.syncValue(newVal);
|
||||
this.$refs.validationProvider.validate();
|
||||
},
|
||||
// Handles external model changes.
|
||||
value(newVal) {
|
||||
this.selected = newVal;
|
||||
this.$refs.typeahead.inputValue =
|
||||
newVal != null ? this.$refs.typeahead.serializer(newVal) : null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -73,7 +73,7 @@ const OrganizationRelationCreate = {
|
||||
this.form = {
|
||||
targetOrganization: null,
|
||||
auto_verify_event_reference_requests: false,
|
||||
verify: false,
|
||||
verify: this.$route.query.verify == "1",
|
||||
}
|
||||
this.loadAdminUnit();
|
||||
},
|
||||
@ -86,6 +86,22 @@ const OrganizationRelationCreate = {
|
||||
})
|
||||
.then((response) => {
|
||||
this.adminUnit = response.data;
|
||||
this.loadTarget();
|
||||
});
|
||||
},
|
||||
loadTarget() {
|
||||
if (this.$route.query.target == undefined) {
|
||||
return;
|
||||
}
|
||||
axios
|
||||
.get(`/api/v1/organizations/${this.$route.query.target}`, {
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response) => {
|
||||
this.form.targetOrganization = {
|
||||
id: response.data.id,
|
||||
name: response.data.name
|
||||
};
|
||||
});
|
||||
},
|
||||
handleLoadingAdminUnit(isLoading) {
|
||||
|
||||
@ -37,7 +37,7 @@ const OrganizationList = {
|
||||
>
|
||||
<template #cell(name)="data">
|
||||
<b-link :to="{ name: 'OrganizationById', params: { organization_id: data.item.id } }">
|
||||
<div>{{ data.item.name }}</div>
|
||||
<div>{{ data.item.name }} <template v-if="data.item.is_verified"><i class="fa fa-check-circle text-primary"></i></template></div>
|
||||
<div class="text-muted">@{{ data.item.short_name }}</div>
|
||||
</b-link>
|
||||
</template>
|
||||
|
||||
@ -40,7 +40,47 @@ const OrganizationRead = {
|
||||
</div>
|
||||
|
||||
<div v-if="organization.description" class="mt-3">
|
||||
<span style="white-space: pre;">{{ organization.description }}</span>
|
||||
<span style="white-space: pre-wrap;">{{ organization.description }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="canRelation" class="mt-3">
|
||||
<b-overlay :show="isLoadingRelation">
|
||||
<div>
|
||||
<b-card v-if="relation">
|
||||
<b-card-text>
|
||||
<div v-if="relation.verify">
|
||||
<i class="fa fa-fw fa-check-circle"></i> {{ $t('comp.relationVerify', { source: $root.currentAdminUnit.name, target: organization.name }) }}
|
||||
</div>
|
||||
<div v-if="relation.auto_verify_event_reference_requests">
|
||||
<i class="fa fa-fw fa-check-circle"></i> {{ $t('comp.relationAutoVerifyEventReferenceRequests', { source: $root.currentAdminUnit.name, target: organization.name }) }}
|
||||
</div>
|
||||
</b-card-text>
|
||||
<b-link :href="relationEditUrl">
|
||||
{{ $t("comp.relationEdit") }}
|
||||
</b-link>
|
||||
</b-card>
|
||||
|
||||
<template v-if="relationDoesNotExist">
|
||||
<b-card v-if="organization.is_verified">
|
||||
<b-card-text>
|
||||
{{ $t('comp.relationDoesNotExist', { source: $root.currentAdminUnit.name, target: organization.name }) }}
|
||||
</b-card-text>
|
||||
<b-link :href="relationCreateUrl">
|
||||
{{ $t("comp.relationCreate") }}
|
||||
</b-link>
|
||||
</b-card>
|
||||
|
||||
<b-card v-else border-variant="warning">
|
||||
<b-card-text>
|
||||
{{ $t("comp.organizationNotVerified", { organization: organization.name }) }}
|
||||
</b-card-text>
|
||||
<b-link :href="relationCreateUrl">
|
||||
{{ $t("comp.relationCreateToVerify", { organization: organization.name }) }}
|
||||
</b-link>
|
||||
</b-card>
|
||||
</template>
|
||||
</div>
|
||||
</b-overlay>
|
||||
</div>
|
||||
|
||||
<b-list-group class="mt-4">
|
||||
@ -67,6 +107,7 @@ const OrganizationRead = {
|
||||
<b-button variant="outline-secondary" @click="hide()">{{ $t("shared.close") }}</b-button>
|
||||
</template>
|
||||
</b-modal>
|
||||
|
||||
</b-col>
|
||||
</b-row>
|
||||
</div>
|
||||
@ -81,6 +122,13 @@ const OrganizationRead = {
|
||||
download: "Download",
|
||||
icalCopied: "Link copied",
|
||||
icalExport: "iCal calendar",
|
||||
organizationNotVerified: "{organization} is not verified",
|
||||
relationVerify: "{source} verifies {target}",
|
||||
relationAutoVerifyEventReferenceRequests: "{source} verifies reference requests from {target} automatically",
|
||||
relationDoesNotExist: "There is no relation from {source} to {target}",
|
||||
relationEdit: "Edit relation",
|
||||
relationCreate: "Create relation",
|
||||
relationCreateToVerify: "Verify {organization}",
|
||||
},
|
||||
},
|
||||
de: {
|
||||
@ -89,13 +137,24 @@ const OrganizationRead = {
|
||||
download: "Runterladen",
|
||||
icalCopied: "Link kopiert",
|
||||
icalExport: "iCal Kalender",
|
||||
organizationNotVerified: "{organization} ist nicht verifiziert",
|
||||
relationVerify: "{source} verifiziert {target}",
|
||||
relationAutoVerifyEventReferenceRequests: "{source} verifiziert Empfehlungsanfragen von {target} automatisch",
|
||||
relationDoesNotExist: "Es besteht keine Beziehung von {source} zu {target}",
|
||||
relationEdit: "Beziehung bearbeiten",
|
||||
relationCreate: "Beziehung erstellen",
|
||||
relationCreateToVerify: "Verifiziere {organization}",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
data: () => ({
|
||||
isLoading: false,
|
||||
isLoadingRelation: false,
|
||||
organization: null,
|
||||
relation: null,
|
||||
canRelation: false,
|
||||
relationDoesNotExist: false,
|
||||
}),
|
||||
computed: {
|
||||
organizationId() {
|
||||
@ -107,11 +166,22 @@ const OrganizationRead = {
|
||||
icalDocsUrl() {
|
||||
return this.$root.docsUrl ? `${this.$root.docsUrl}/goto/ical-calendar` : null;
|
||||
},
|
||||
relationEditUrl() {
|
||||
return `/manage/admin_unit/${this.$root.currentAdminUnit.id}/relations/${this.relation.id}/update`;
|
||||
},
|
||||
relationCreateUrl() {
|
||||
return `/manage/admin_unit/${this.$root.currentAdminUnit.id}/relations/create?target=${this.organizationId}&verify=1`;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.isLoading = false;
|
||||
this.isLoadingRelation = false;
|
||||
this.organization = null;
|
||||
this.relation = null;
|
||||
this.canRelation = this.$root.has_access("admin_unit:update");
|
||||
this.relationDoesNotExist = false;
|
||||
this.loadData();
|
||||
this.loadRelationData();
|
||||
},
|
||||
methods: {
|
||||
loadData() {
|
||||
@ -124,6 +194,33 @@ const OrganizationRead = {
|
||||
this.organization = response.data;
|
||||
});
|
||||
},
|
||||
loadRelationData() {
|
||||
if (!this.$root.hasOwnProperty("currentAdminUnit")) {
|
||||
return;
|
||||
}
|
||||
const vm = this;
|
||||
axios
|
||||
.get(`/api/v1/organizations/${this.$root.currentAdminUnit.id}/relations/outgoing/${this.organizationId}`, {
|
||||
withCredentials: true,
|
||||
handleLoading: this.handleLoadingRelation,
|
||||
handler: {
|
||||
handleLoading: function(isLoading) {
|
||||
vm.isLoadingRelation = isLoading;
|
||||
},
|
||||
handleRequestError: function(error, message) {
|
||||
const status = error && error.response && error.response.status;
|
||||
if (status == 404) {
|
||||
vm.relationDoesNotExist = true;
|
||||
return;
|
||||
}
|
||||
this.$root.makeErrorToast(message);
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((response) => {
|
||||
this.relation = response.data;
|
||||
});
|
||||
},
|
||||
handleLoading(isLoading) {
|
||||
this.isLoading = isLoading;
|
||||
},
|
||||
|
||||
@ -348,6 +348,11 @@
|
||||
|
||||
axios.defaults.baseURL = "{{ get_base_url() }}";
|
||||
axios.defaults.headers.common["X-CSRFToken"] = "{{ csrf_token() }}";
|
||||
|
||||
{% if current_admin_unit %}
|
||||
axios.defaults.headers.common["X-OrganizationId"] = "{{ current_admin_unit.id }}";
|
||||
{% endif %}
|
||||
|
||||
axios.interceptors.request.use(
|
||||
function (config) {
|
||||
if (config) {
|
||||
@ -381,6 +386,29 @@
|
||||
vue_app_data["docsUrl"] = "{{ config["DOCS_URL"] }}";
|
||||
{% endif %}
|
||||
|
||||
{% if current_user %}
|
||||
vue_app_data["currentUser"] = {
|
||||
{% if current_user.is_authenticated %}
|
||||
isAuthenticated: true,
|
||||
id: "{{ current_user.id }}",
|
||||
email: "{{ current_user.email }}",
|
||||
roles: [{% for role in get_current_user_roles() %}"{{ role }}"{% if not loop.last %},{% endif %}{% endfor %}],
|
||||
permissions: [{% for permission in get_current_user_permissions() %}"{{ permission }}"{% if not loop.last %},{% endif %}{% endfor %}]
|
||||
{% else %}
|
||||
isAuthenticated: false
|
||||
{% endif %}
|
||||
};
|
||||
{% endif %}
|
||||
|
||||
{% if current_admin_unit %}
|
||||
vue_app_data["currentAdminUnit"] = {
|
||||
id: "{{ current_admin_unit.id }}",
|
||||
name: "{{ current_admin_unit.name }}",
|
||||
roles: [{% for role in get_current_admin_unit_roles() %}"{{ role }}"{% if not loop.last %},{% endif %}{% endfor %}],
|
||||
permissions: [{% for permission in get_current_admin_unit_permissions() %}"{{ permission }}"{% if not loop.last %},{% endif %}{% endfor %}]
|
||||
};
|
||||
{% endif %}
|
||||
|
||||
{% block vue_init_data %}
|
||||
var vue_init_data = {
|
||||
el: "#vue-container",
|
||||
@ -397,6 +425,14 @@
|
||||
config.handler.handleRequestStart();
|
||||
}
|
||||
|
||||
if (
|
||||
config &&
|
||||
config.handler &&
|
||||
config.handler.hasOwnProperty("handleLoading")
|
||||
) {
|
||||
config.handler.handleLoading(true);
|
||||
}
|
||||
|
||||
if (
|
||||
config &&
|
||||
config.hasOwnProperty("handleLoading")
|
||||
@ -413,6 +449,14 @@
|
||||
config.handler.handleRequestFinish();
|
||||
}
|
||||
|
||||
if (
|
||||
config &&
|
||||
config.handler &&
|
||||
config.handler.hasOwnProperty("handleLoading")
|
||||
) {
|
||||
config.handler.handleLoading(false);
|
||||
}
|
||||
|
||||
if (
|
||||
config &&
|
||||
config.hasOwnProperty("handleLoading")
|
||||
@ -479,6 +523,11 @@
|
||||
});
|
||||
},
|
||||
goBack(fallbackPath) {
|
||||
if ('referrer' in document) {
|
||||
window.location = document.referrer;
|
||||
return;
|
||||
}
|
||||
|
||||
window.history.length > 1 ? this.$router.go(-1) : this.$router.push({ path: fallbackPath })
|
||||
},
|
||||
render_event_date_instance(value, allday, format = "dd. DD.MM.YYYY LT", alldayFormat = "dd. DD.MM.YYYY") {
|
||||
@ -530,6 +579,22 @@
|
||||
url_for_image(image, size) {
|
||||
return `${axios.defaults.baseURL}${image.image_url}?s=${size}`
|
||||
},
|
||||
has_access(permission) {
|
||||
if (!this.currentUser.isAuthenticated) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.currentUser.permissions.includes(permission)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.hasOwnProperty("currentAdminUnit") &&
|
||||
this.currentAdminUnit.permissions.includes(permission)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
};
|
||||
{% endblock %}
|
||||
|
||||
@ -486,6 +486,26 @@ def test_references_outgoing(client, seeder: Seeder, utils: UtilActions):
|
||||
utils.get_json_ok(url)
|
||||
|
||||
|
||||
def test_outgoing_relation_read(client, seeder: Seeder, utils: UtilActions):
|
||||
user_id, admin_unit_id = seeder.setup_api_access()
|
||||
(
|
||||
other_user_id,
|
||||
other_admin_unit_id,
|
||||
relation_id,
|
||||
) = seeder.create_any_admin_unit_relation(admin_unit_id)
|
||||
|
||||
url = utils.get_url(
|
||||
"api_v1_organization_outgoing_relation",
|
||||
id=admin_unit_id,
|
||||
target_id=other_admin_unit_id,
|
||||
)
|
||||
response = utils.get_json(url)
|
||||
utils.assert_response_ok(response)
|
||||
assert response.json["id"] == relation_id
|
||||
assert response.json["source_organization"]["id"] == admin_unit_id
|
||||
assert response.json["target_organization"]["id"] == other_admin_unit_id
|
||||
|
||||
|
||||
def test_outgoing_relation_list(client, seeder: Seeder, utils: UtilActions):
|
||||
user_id, admin_unit_id = seeder.setup_api_access()
|
||||
(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user