Skip to content
Snippets Groups Projects
Commit 1399f251 authored by Jonathan Weth's avatar Jonathan Weth :keyboard:
Browse files

Merge branch 'release-3.0' into...

Merge branch 'release-3.0' into '835-notifaction-send-always-creates-celery-task-with-force-sending'

# Conflicts:
#   CHANGELOG.rst
parents dfa075ac e24b720f
No related branches found
No related tags found
3 merge requests!1237Release 3.0,!1212Resolve "Notifaction.send always creates Celery task with force sending",!1183Release 3.0
Pipeline #124075 failed
......@@ -14,6 +14,8 @@ Added
* GraphQL schema for Rooms
* [Dev] UpdateIndicator Vue Component to display the status of interactive pages
* [Dev] DeleteDialog Vue Component to unify item deletion in the new frontend
Changed
~~~~~~~
......@@ -31,6 +33,8 @@ Fixed
* The `Stop Impersonation` button is not shown due to an oversee when changing the type of the whoAmI query to an object of UserType
* Offline fallback page for legacy pages was misleading sometimes.
* Route changes in the Legacy-Component iframe didn't trigger a scroll to the top
* Query strings did not get passed when navigating legacy pages inside of the SPA.
* Retry button on error 500 page did not trigger a reload of the page.
* When the Celery worker wasn't able to execute all tasks in time, notifications were sent multiple times.
`3.0b3`_ - 2023-03-19
......
......@@ -59,13 +59,14 @@ export default {
const location = this.$refs.contentIFrame.contentWindow.location;
const url = new URL(location);
const path = url.pathname.replace(/^\/django/, "");
const pathWithQueryString = path + encodeURI(url.search);
const routePath =
path.charAt(path.length - 1) === "/" &&
this.$route.path.charAt(path.length - 1) !== "/"
? this.$route.path + "/"
: this.$route.path;
if (path !== routePath) {
this.$router.push(path);
this.$router.push(pathWithQueryString);
}
// Show loader if iframe starts to change its content, even if the $route stays the same
......
<template>
<ApolloMutation
v-if="dialogOpen"
:mutation="gqlMutation"
:variables="{ id: item.id }"
:update="update"
@done="close(true)"
>
<template #default="{ mutate, loading, error }">
<v-dialog v-model="dialogOpen" max-width="500px">
<v-card>
<v-card-title class="text-h5">
<slot name="title">
{{ $t("actions.confirm_deletion") }}
</slot>
</v-card-title>
<v-card-text>
<slot name="body">
<p class="text-body-1">{{ nameOfObject }}</p>
</slot>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="close(false)" :disabled="loading">
<slot name="cancelContent">
{{ $t("actions.cancel") }}
</slot>
</v-btn>
<v-btn
color="error"
text
@click="mutate"
:loading="loading"
:disabled="loading"
>
<slot name="deleteContent">
{{ $t("actions.delete") }}
</slot>
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-snackbar :value="error !== null">
{{ error }}
<template #action="{ attrs }">
<v-btn color="primary" text v-bind="attrs" @click="error = null" icon>
<v-icon>$close</v-icon>
</v-btn>
</template>
</v-snackbar>
</template>
</ApolloMutation>
</template>
<script>
export default {
name: "DeleteDialog",
computed: {
nameOfObject() {
return this.itemAttribute in this.item || {}
? this.item[this.itemAttribute]
: this.item.toString();
},
dialogOpen: {
get() {
return this.value;
},
set(val) {
this.$emit("input", val);
},
},
},
methods: {
update(store) {
if (!this.gqlQuery) {
// There is no GraphQL query to update
return;
}
// Read the data from cache for query
const storedData = store.readQuery({ query: this.gqlQuery });
if (!storedData) {
// There are no data in the cache yet
return;
}
const storedDataKey = Object.keys(storedData)[0];
// Remove item from stored data
const index = storedData[storedDataKey].findIndex(
(m) => m.id === this.item.id
);
storedData[storedDataKey].splice(index, 1);
// Write data back to the cache
store.writeQuery({ query: this.gqlQuery, data: storedData });
},
close(success) {
this.$emit("input", false);
if (success) {
this.$emit("success");
} else {
this.$emit("cancel");
}
},
},
props: {
value: {
type: Boolean,
required: true,
},
item: {
type: Object,
required: false,
default: () => ({}),
},
itemAttribute: {
type: String,
required: false,
default: "name",
},
gqlMutation: {
type: Object,
required: true,
},
gqlQuery: {
type: Object,
required: false,
default: null,
},
},
};
</script>
......@@ -75,7 +75,12 @@
"back": "Back",
"search": "Search",
"edit": "Edit",
"close": "Close"
"close": "Close",
"cancel": "Cancel",
"confirm_deletion": "Are you sure you want to delete this item?",
"delete": "Delete",
"stop_editing": "Stop editing",
"save": "Save"
},
"administration": {
"backend_admin": {
......
from django.db.models import Model
from django.core.exceptions import PermissionDenied
import graphene
from graphene_django import DjangoObjectType
......@@ -24,3 +27,23 @@ class FieldFileType(graphene.ObjectType):
def resolve_absolute_url(root, info, **kwargs):
return info.context.build_absolute_uri(root.url) if root else ""
class DeleteMutation(graphene.Mutation):
"""Mutation to delete an object."""
klass: Model = None
permission_required: str = ""
ok = graphene.Boolean()
class Arguments:
id = graphene.ID() # noqa
@classmethod
def mutate(cls, root, info, **kwargs):
obj = cls.klass.objects.get(pk=kwargs["id"])
if info.context.user.has_perm(cls.permission_required, obj):
obj.delete()
return cls(ok=True)
else:
raise PermissionDenied()
......@@ -16,7 +16,7 @@
{% endblocktrans %}
</p>
{% include "core/partials/admins_list.html" %}
<a href="javascript:window.location.reload()" class="btn green waves-effect waves-light">
<a onClick="window.location.reload();" class="btn secondary waves-effect waves-light">
<i class="material-icons left">refresh</i>
{% trans "Retry" %}
</a>
......
......@@ -100,7 +100,7 @@ django-favicon-plus-reloaded = "^1.1.5"
django-health-check = "^3.12.1"
psutil = "^5.7.0"
celery-progress = "^0.1.0"
django-cachalot = "^2.3.2"
django-cachalot = "^2.5.3"
django-prometheus = "^2.1.0"
django-model-utils = "^4.0.0"
bs4 = "^0.0.1"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment