Working v0

This commit is contained in:
2024-09-27 18:09:38 +02:00
parent f3116a6876
commit 8dfafa9404
25 changed files with 740 additions and 389 deletions
@@ -0,0 +1 @@
{% extends "base_components/glist/template.html" %}
@@ -0,0 +1,7 @@
{% extends "base_components/glist/vue.js" %}
{% load main %}
{% block component %}
{% define 'items' 'items' %}
{{ block.super }}
{% endblock %}
@@ -0,0 +1,40 @@
{% load i18n %}
<div>
<div class="card mt-4 pt-2 ps-lg-2">
<h5 class="card-header">{% trans "Your items" %}</h5>
<div class="card-body">
<ItemList
:crypto_key="crypto_key"
:items="items"
:items_headers="items_headers"
:items_relations="{'type': types}"
group_by="type__name"
@deleteItem="deleteItem"
@createItem="createItem"
@editItem="editItem"
></ItemList>
</div>
</div>
<div class="card mt-4 pt-2 ps-lg-2">
<h5 class="card-header">{% trans "Your types" %}</h5>
<div class="card-body">
<ItemList
:crypto_key="crypto_key"
:items="types"
:items_headers="types_headers"
:items_relations="{}"
group_by="[]"
@deleteItem="deleteType"
@createItem="createType"
@editItem="editType"
></ItemList>
</div>
</div>
</div>
@@ -0,0 +1,282 @@
ItemView = {
template: "#ItemView",
delimiters: ["[[", "]]"],
props: ["crypto_key"],
data: function() {
return {
search: "",
items: [],
items_headers: [],
items_relations: [],
types: [],
types_headers: [],
types_relations: [],
}
},
mounted: function() {
this.reload()
},
computed: {
items_encrypted_fields: function() {
return this.items_headers.filter(e => e.encrypted).map(e => e.value)
},
types_encrypted_fields: function() {
return this.types_headers.filter(e => e.encrypted).map(e => e.value)
},
},
methods: {
reload () {
var self = this
this.$http.get(Urls["items:list"]()).then(response => {
Object.keys(response.data.result).forEach(name => {
self.$set(self, name, response.data.result[name])
})
self.items.forEach(item => {
self.decryptObject(self.items_encrypted_fields, item)
})
self.types.forEach(type => {
self.decryptObject(self.types_encrypted_fields, type)
})
}).catch(err => {
Swal.fire({title: "{{_('Error during loading of items') | escapejs}}", icon: "error", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
})
},
async aDecryptObject (encrypted_fields, obj) {
var self = this
return new Promise((resolve) => {
let promises = encrypted_fields.map(field => {
// Encrypt all necessary fields
if (obj[field] == null) {
return null
}
return new Promise((resolve) => {
return decryptWithKey(self.crypto_key, obj[field]).then(dec => {
resolve({field: field, value: dec})
})
})
}).filter(e => e != null)
Promise.all(promises).then(values => {
values.forEach(value => {
obj[value.field] = value.value
})
resolve(obj)
})
})
},
decryptObject (encrypted_fields, obj) {
var self = this
return new Promise((resolve) => {
let promises = encrypted_fields.map(field => {
// Encrypt all necessary fields
if (obj[field] == null) {
return null
}
return new Promise((resolve) => {
return decryptWithKey(self.crypto_key, obj[field]).then(dec => {
resolve({field: field, value: dec})
})
})
}).filter(e => e != null)
Promise.all(promises).then(values => {
values.forEach(value => {
obj[value.field] = value.value
})
resolve(obj)
})
})
},
object_edition (url_edit, url_create, encrypted_fields, method, obj) {
// Return a Promise
var self = this
return new Promise((resolve) => {
let url = Urls[url_edit](obj.id)
if (obj.id == undefined || obj.id == null) {
url = Urls[url_create]()
}
let promises = encrypted_fields.map(field => {
// Encrypt all necessary fields
if (obj[field] == null) {
return null
}
return new Promise((resolve) => {
return encryptWithKey(self.crypto_key, obj[field]).then(enc => {
resolve({field: field, value: enc})
})
})
}).filter(e => e != null)
Promise.all(promises).then(values => {
values.forEach(value => {
obj[value.field] = value.value
})
self.$http[method](url, obj).then(response => {
if (method == "delete") {
resolve()
} else {
self.decryptObject(encrypted_fields, response.data.object).then(new_obj => {
resolve(new_obj)
})
}
}).catch(err => {
let msg = "{{_('Error during edition') | escapejs}}"
if (method == "delete") {
msg = "{{_('Error during deletion') | escapejs}}"
}
Swal.fire({title: msg, icon: "error", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
})
})
})
},
item_edition (method, item) {
return this.object_edition("items:edit", "items:create", this.items_encrypted_fields, method, item)
},
type_edition (method, item) {
return this.object_edition("items:type.edit", "items:type.create", this.items_encrypted_fields, method, item)
},
createItem (item) {
var self = this
this.item_edition("post", item).then(new_item => {
self.items.push(new_item)
Swal.fire({title: "{{_('Item successfully created!') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
})
},
editItem (index, item) {
var self = this
this.item_edition("post", item).then(new_item => {
// Remove the 'current' (non edited) item from the list
self.items.splice(index, 1)
self.items.push(new_item)
Swal.fire({title: "{{_('Item successfully edited') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
})
},
deleteItem (index) {
var self = this
var item = this.items[index]
this.item_edition("delete", item).then(() => {
self.items.splice(this.items.indexOf(item), 1)
Swal.fire({title: "{{_('Item successfully deleted!') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
})
},
createType (type) {
var self = this
this.type_edition("post", type).then(new_type => {
self.types.push(new_type)
Swal.fire({title: "{{_('Type successfully created!') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
})
},
editType (index, type) {
var self = this
this.type_edition("post", type).then(new_type => {
// Remove the 'current' (non edited) item from the list
self.types.splice(index, 1)
self.types.push(new_type)
Swal.fire({title: "{{_('Type successfully edited') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
})
},
deleteType (index) {
var self = this
var type = this.types[index]
this.type_edition("delete", type).then(() => {
self.types.splice(this.types.indexOf(type), 1)
Swal.fire({title: "{{_('Type successfully deleted!') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
})
},
},
}
@@ -1,5 +0,0 @@
{% load i18n %}
<div>
<input class="form-control" type="text" v-model="item.name">
</div>
@@ -1,4 +0,0 @@
item = {
template: "#item",
props: ["crypto_key", "item"],
}
@@ -1,88 +0,0 @@
{% load i18n %}
<div>
<div class="card mt-4 pt-2 ps-lg-2">
<h5 class="card-header">{% trans "Your items" %}</h5>
<div class="card-body">
<v-data-table
:headers="data.items_headers"
:items="data.items"
:items-per-page="50"
:search="search"
group-by="type"
loading
dense>
<template v-slot:top>
<v-toolbar flat>
<v-text-field v-model="search" append-icon="mdi-magnify" label="Search" single-line hide-details></v-text-field>
<v-divider class="mx-4" insert vertical></v-divider>
<v-spacer></v-spacer>
<v-dialog v-model="dialog" max-width="500px">
<template v-slot:activator="{ on, attrs }">
<v-btn color="primary" dark class="mb-2" v-bind="attrs" v-on="on">{% trans "New item" %}</v-btn>
</template>
<v-card>
<v-card-text>
<v-container>
<v-row>
<v-text-field v-model="editedItem.name" label="Name"></v-text-field>
<v-select
v-model="editedItem.type"
:items="data.types"
label="Type"
item-text="name"
item-value="id"
persistent-hint
>
<template slot="item" slot-scope="data">
[[ data.item.name ]] - [[ data.item.custom_identifier ]]
</template>
</v-select>
<v-textarea v-model="editedItem.description" label="Description"></v-textarea>
<v-text-field v-model="editedItem.custom_identifier" label="Identifier"></v-text-field>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text @click="close">{% trans "Cancel" %}</v-btn>
<v-btn color="blue darken-1" text @click="save">{% trans "Save" %}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="dialogDelete" max-width="500px">
<v-card>
<v-card-title class="text-h5">{% trans "Are you sure you want to delete this item?" %}</v-card-title>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text @click="closeDelete">{% trans "Cancel" %}</v-btn>
<v-btn color="blue darken-1" text @click="deleteItemConfirm">{% trans "OK" %}</v-btn>
<v-spacer></v-spacer>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
</template>
<template v-slot:item.actions="{ item }">
<v-icon small class="mr-2" @click="editItem(item)">mdi-pencil</v-icon>
<v-icon small @click="deleteItem(item)">mdi-delete</v-icon>
</template>
<template v-slot:no-data>
<v-btn color="primary" @click="initialize">{% trans "Reset" %}</v-btn>
</template>
</v-data-table>
</div>
</div>
</div>
@@ -1,191 +0,0 @@
item_list = {
template: "#item_list",
delimiters: ["[[", "]]"],
props: ["crypto_key", "locked"],
data: function() {
return {
dialog: false,
dialogDelete: false,
editedIndex: -1,
defaultItem: {},
editedItem: {},
search: null,
data: {},
}
},
mounted: function() {
var self = this;
this.$http.get(Urls["items:list"]()).then(response => {
Object.keys(response.data.result).forEach(name => {
self.$set(self.data, name, response.data.result[name]);
});
self.data.items.forEach(item => {
self.decryptItem(item);
});
}).catch(err => {
Swal.fire({title: "{{_('Error during loading of items.') | escapejs}}", icon: "error", position:"top-end", showConfirmButton: false, toast: true, timer: 1000});
});
},
computed: {
formTitle () {
return this.editedIndex === -1 ? "{{_('New item') | escapejs}}" : "{{_('Edit item') | escapejs}}"
},
},
watch: {
dialog (val) {
val || this.close()
},
dialogDelete (val) {
val || this.closeDelete()
},
},
methods: {
decryptItem (item) {
this.data.items_encrypted.forEach(field => {
decryptWithKey(this.crypto_key, item[field]).then(dec => {
item[field] = dec;
})
});
return item;
},
editItem (item) {
this.editedIndex = this.data.items.indexOf(item)
this.editedItem = Object.assign({}, item)
this.dialog = true
},
deleteItem (item) {
this.editedIndex = this.data.items.indexOf(item)
this.editedItem = Object.assign({}, item)
this.dialogDelete = true
},
deleteItemConfirm () {
var item = this.data.items[this.editedIndex];
this.item_edition("delete", item).then(response => {
this.data.items.splice(this.data.items.indexOf(item), 1)
Swal.fire({title: "{{_('Item successfully deleted!') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000});
});
this.closeDelete()
},
close () {
this.dialog = false
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
})
},
closeDelete () {
this.dialogDelete = false
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
})
},
item_edition (method, item) {
// Return a Promise
var self = this;
return new Promise((resolve) => {
let url = Urls["items:edit"](item.id)
if (item.id == undefined || item.id == null) {
url = Urls["items:create"]()
}
let promises = self.data.items_encrypted.map(field => {
// Encrypt all necessary fields
if (item[field] == null) {
return null;
}
return new Promise((resolve) => {
return encryptWithKey(self.crypto_key, item[field]).then(enc => {
resolve({field: field, value: enc});
});
});
}).filter(e => e != null);
Promise.all(promises).then(values => {
values.forEach(value => {
item[value.field] = value.value;
});
self.$http[method](url, item).then(response => {
resolve(response.data);
}).catch(err => {
let msg = "{{_('Error during edition of item') | escapejs}}";
if (method == "delete") {
msg = "{{_('Error during deletion of item') | escapejs}}";
}
Swal.fire({title: msg, icon: "error", position:"top-end", showConfirmButton: false, toast: true, timer: 1000});
});
});
});
},
save () {
if (this.editedIndex > -1) {
var self = this;
this.item_edition("post", this.editedItem).then(data => {
self.data.items.splice(this.data.items.indexOf(self.editedItem), 1)
console.log('pre edit', data.item)
new_item = self.decryptItem(data.item);
console.log('edited item', new_item);
// self.data.items.push(self.decryptItem(data.item));
Swal.fire({title: "{{_('Item successfully edited') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000});
});
} else {
var self = this;
this.item_edition("post", this.editedItem).then(data => {
new_item = self.decryptItem(data.item);
console.log('new item', new_item);
// self.data.items.push(self.decryptItem(data.item));
// console.log(data.item);
Swal.fire({title: "{{_('Item successfully created!') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000});
});
}
this.close()
},
}
}
+3 -1
View File
@@ -1,5 +1,5 @@
from django.urls import path
from items.views import item as item_view
from items.views import item_view, type_view
app_name = "items"
@@ -7,4 +7,6 @@ urlpatterns = [
path("", item_view.item_list, name="list"),
path("<uuid:id>", item_view.item_edit, name="edit"),
path("create", item_view.item_edit, {"id": None}, name="create"),
path("type/<uuid:id>", type_view.type_edit, name="type.edit"),
path("type/create", type_view.type_edit, {"id": None}, name="type.create"),
]
@@ -1,6 +1,6 @@
import json
from app.utils.api.api_list import encrypted_fields, header_for_table
from app.utils.api.api_list import header_for_table
from django.contrib.auth.decorators import login_required
from django.db import models
from django.db.models.fields.related import RelatedField
@@ -12,18 +12,15 @@ from items.models import Item, ItemType
def item_list(request):
items = Item.objects.filter(author=request.user.setting)
types = ItemType.objects.filter(author=request.user.setting)
return JsonResponse(
{
"result": {
"items": list(items.serialize()),
"types": list(types.serialize()),
"items_headers": header_for_table(Item),
"types": list(types.serialize()),
"types_headers": header_for_table(ItemType),
"items_encrypted": encrypted_fields(Item),
"types_encrypted": encrypted_fields(ItemType),
},
"count": items.count(),
}
@@ -85,6 +82,6 @@ def item_edit(request, id=None):
return JsonResponse(
{
"item": Item.objects.filter(id=item.id).serialize().first(),
"object": Item.objects.filter(id=item.id).serialize().first(),
}
)
+67
View File
@@ -0,0 +1,67 @@
import json
from django.contrib.auth.decorators import login_required
from django.db import models
from django.db.models.fields.related import RelatedField
from django.http import JsonResponse
from items.models import ItemType
@login_required
def type_edit(request, id=None):
"""Create/edit type view."""
if id:
item = ItemType.objects.filter(id=id, author=request.user.setting).first()
else:
item = ItemType(author=request.user.setting)
if not item:
return JsonResponse({}, status=404)
if request.method == "DELETE":
try:
item.delete()
except Exception:
return JsonResponse({"error": "INVALID_DELETE"}, status=401)
return JsonResponse({})
if request.method != "POST":
return JsonResponse({}, status=405)
try:
data = json.loads(request.body)
except Exception:
return JsonResponse({"error": "INVALID_DATA"}, status=401)
for field in item._meta.fields:
if field.name in item.Serialization.excluded_fields_edit:
continue
if isinstance(field, RelatedField):
# For now, disregard related field (fk, m2m, 1-1)
if isinstance(field, models.ForeignKey):
setattr(item, f"{field.name}_id", data[field.name])
continue
if field.name not in data:
continue
setattr(item, field.name, data[field.name])
try:
item.save()
except Exception:
return JsonResponse({"error": "DATA_INVALID"}, status=401)
return JsonResponse(
{
"object": ItemType.objects.filter(id=item.id).serialize().first(),
}
)