diff --git a/k356/app/settings.py b/k356/app/settings.py
index bf3b656..71da10d 100644
--- a/k356/app/settings.py
+++ b/k356/app/settings.py
@@ -75,6 +75,7 @@ BOWER_INSTALLED_APPS = [
"https://cdn.jsdelivr.net/npm/sweetalert2",
"https://cdn.jsdelivr.net/npm/vue-resource",
"https://unpkg.com/vuex@3.6.2/dist/vuex.js",
+ "vuex-extensions=https://unpkg.com/vuex-extensions@4.1.0/lib/index.js",
"https://cdn.jsdelivr.net/npm/js-cookie@3.0.5/dist/js.cookie.min.js",
"https://unpkg.com/vue-router@3/dist/vue-router.js",
"https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css",
diff --git a/k356/app/utils/api/api_list.py b/k356/app/utils/api/api_list.py
index a662697..ab22438 100644
--- a/k356/app/utils/api/api_list.py
+++ b/k356/app/utils/api/api_list.py
@@ -13,5 +13,6 @@ def header_for_table(model):
"text": "Actions",
"value": "actions",
"sortable": False,
+ "details": False,
},
]
diff --git a/k356/app/utils/models.py b/k356/app/utils/models.py
index 51760e2..cff5aa8 100644
--- a/k356/app/utils/models.py
+++ b/k356/app/utils/models.py
@@ -25,6 +25,8 @@ class BaseQuerySet(models.QuerySet):
"editable": field.name not in self.model.Serialization.excluded_fields_edit,
"field_widget": "v-textarea",
"choices": None,
+ "details": True,
+ "dynamic_field_type": field.name in self.model.Serialization.dynamic_field_type,
}
}
@@ -48,7 +50,6 @@ class BaseQuerySet(models.QuerySet):
if field.choices:
ret[field.name].update(
- text="",
field_widget="v-select",
choices=[
{
@@ -92,6 +93,7 @@ class BaseModel(models.Model):
# Exclude fields from serialization
excluded_fields = []
excluded_fields_edit = ["id", "created_at", "last_modified_at"]
+ dynamic_field_type = []
class Encryption:
fields = ["name", "description", "custom_identifier"]
diff --git a/k356/items/migrations/0005_alter_property_type.py b/k356/items/migrations/0005_alter_property_type.py
new file mode 100644
index 0000000..b6c40c1
--- /dev/null
+++ b/k356/items/migrations/0005_alter_property_type.py
@@ -0,0 +1,36 @@
+# Generated by Django 5.1.1 on 2024-09-30 19:33
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("items", "0004_alter_property_type"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="property",
+ name="type",
+ field=models.CharField(
+ choices=[
+ ("text", "Text"),
+ ("date", "Date"),
+ ("datetime", "Date & time"),
+ ("time", "Time"),
+ ("duration", "Duration"),
+ ("uuid", "UUID"),
+ ("number", "Number"),
+ ("float", "Float"),
+ ("boolean", "Boolean"),
+ ("email", "Email"),
+ ("ipv4", "IPv4 address"),
+ ("ipv6", "IPv6 address"),
+ ("json", "JSON"),
+ ],
+ default="text",
+ max_length=32,
+ ),
+ ),
+ ]
diff --git a/k356/items/migrations/0006_historicalitem_historicalitemrelation_and_more.py b/k356/items/migrations/0006_historicalitem_historicalitemrelation_and_more.py
new file mode 100644
index 0000000..9b77cfc
--- /dev/null
+++ b/k356/items/migrations/0006_historicalitem_historicalitemrelation_and_more.py
@@ -0,0 +1,325 @@
+# Generated by Django 5.1.1 on 2024-10-01 13:41
+
+import django.db.models.deletion
+import simple_history.models
+import uuid
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("items", "0005_alter_property_type"),
+ ("users", "0006_historicalusersettings"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="HistoricalItem",
+ fields=[
+ ("id", models.UUIDField(db_index=True, default=uuid.uuid4, editable=False)),
+ ("custom_identifier", models.TextField(blank=True, max_length=2048, null=True)),
+ ("created_at", models.DateTimeField(blank=True, editable=False)),
+ ("last_modified_at", models.DateTimeField(blank=True, editable=False)),
+ ("name", models.TextField(max_length=2048)),
+ ("description", models.TextField(max_length=2048)),
+ ("history_id", models.AutoField(primary_key=True, serialize=False)),
+ ("history_date", models.DateTimeField(db_index=True)),
+ ("history_change_reason", models.CharField(max_length=100, null=True)),
+ ("history_type", models.CharField(choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")], max_length=1)),
+ (
+ "author",
+ models.ForeignKey(
+ blank=True,
+ db_constraint=False,
+ null=True,
+ on_delete=django.db.models.deletion.DO_NOTHING,
+ related_name="+",
+ to="users.usersettings",
+ ),
+ ),
+ (
+ "history_user",
+ models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="+", to="users.usersettings"),
+ ),
+ (
+ "type",
+ models.ForeignKey(
+ blank=True,
+ db_constraint=False,
+ null=True,
+ on_delete=django.db.models.deletion.DO_NOTHING,
+ related_name="+",
+ to="items.itemtype",
+ ),
+ ),
+ ],
+ options={
+ "verbose_name": "historical item",
+ "verbose_name_plural": "historical items",
+ "ordering": ("-history_date", "-history_id"),
+ "get_latest_by": ("history_date", "history_id"),
+ },
+ bases=(simple_history.models.HistoricalChanges, models.Model),
+ ),
+ migrations.CreateModel(
+ name="HistoricalItemRelation",
+ fields=[
+ ("id", models.UUIDField(db_index=True, default=uuid.uuid4, editable=False)),
+ ("name", models.TextField(blank=True, max_length=2048, null=True)),
+ ("description", models.TextField(blank=True, max_length=2048, null=True)),
+ ("custom_identifier", models.TextField(blank=True, max_length=2048, null=True)),
+ ("created_at", models.DateTimeField(blank=True, editable=False)),
+ ("last_modified_at", models.DateTimeField(blank=True, editable=False)),
+ ("history_id", models.AutoField(primary_key=True, serialize=False)),
+ ("history_date", models.DateTimeField(db_index=True)),
+ ("history_change_reason", models.CharField(max_length=100, null=True)),
+ ("history_type", models.CharField(choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")], max_length=1)),
+ (
+ "author",
+ models.ForeignKey(
+ blank=True,
+ db_constraint=False,
+ null=True,
+ on_delete=django.db.models.deletion.DO_NOTHING,
+ related_name="+",
+ to="users.usersettings",
+ ),
+ ),
+ (
+ "child",
+ models.ForeignKey(
+ blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name="+", to="items.item"
+ ),
+ ),
+ (
+ "history_user",
+ models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="+", to="users.usersettings"),
+ ),
+ (
+ "parent",
+ models.ForeignKey(
+ blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name="+", to="items.item"
+ ),
+ ),
+ ],
+ options={
+ "verbose_name": "historical item relation",
+ "verbose_name_plural": "historical item relations",
+ "ordering": ("-history_date", "-history_id"),
+ "get_latest_by": ("history_date", "history_id"),
+ },
+ bases=(simple_history.models.HistoricalChanges, models.Model),
+ ),
+ migrations.CreateModel(
+ name="HistoricalItemType",
+ fields=[
+ ("id", models.UUIDField(db_index=True, default=uuid.uuid4, editable=False)),
+ ("name", models.TextField(blank=True, max_length=2048, null=True)),
+ ("description", models.TextField(blank=True, max_length=2048, null=True)),
+ ("custom_identifier", models.TextField(blank=True, max_length=2048, null=True)),
+ ("created_at", models.DateTimeField(blank=True, editable=False)),
+ ("last_modified_at", models.DateTimeField(blank=True, editable=False)),
+ ("history_id", models.AutoField(primary_key=True, serialize=False)),
+ ("history_date", models.DateTimeField(db_index=True)),
+ ("history_change_reason", models.CharField(max_length=100, null=True)),
+ ("history_type", models.CharField(choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")], max_length=1)),
+ (
+ "author",
+ models.ForeignKey(
+ blank=True,
+ db_constraint=False,
+ null=True,
+ on_delete=django.db.models.deletion.DO_NOTHING,
+ related_name="+",
+ to="users.usersettings",
+ ),
+ ),
+ (
+ "history_user",
+ models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="+", to="users.usersettings"),
+ ),
+ ],
+ options={
+ "verbose_name": "historical item type",
+ "verbose_name_plural": "historical item types",
+ "ordering": ("-history_date", "-history_id"),
+ "get_latest_by": ("history_date", "history_id"),
+ },
+ bases=(simple_history.models.HistoricalChanges, models.Model),
+ ),
+ migrations.CreateModel(
+ name="HistoricalLinkedProperty",
+ fields=[
+ ("id", models.UUIDField(db_index=True, default=uuid.uuid4, editable=False)),
+ ("name", models.TextField(blank=True, max_length=2048, null=True)),
+ ("description", models.TextField(blank=True, max_length=2048, null=True)),
+ ("custom_identifier", models.TextField(blank=True, max_length=2048, null=True)),
+ ("created_at", models.DateTimeField(blank=True, editable=False)),
+ ("last_modified_at", models.DateTimeField(blank=True, editable=False)),
+ ("value", models.TextField(max_length=2048)),
+ ("history_id", models.AutoField(primary_key=True, serialize=False)),
+ ("history_date", models.DateTimeField(db_index=True)),
+ ("history_change_reason", models.CharField(max_length=100, null=True)),
+ ("history_type", models.CharField(choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")], max_length=1)),
+ (
+ "author",
+ models.ForeignKey(
+ blank=True,
+ db_constraint=False,
+ null=True,
+ on_delete=django.db.models.deletion.DO_NOTHING,
+ related_name="+",
+ to="users.usersettings",
+ ),
+ ),
+ (
+ "history_user",
+ models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="+", to="users.usersettings"),
+ ),
+ (
+ "item",
+ models.ForeignKey(
+ blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name="+", to="items.item"
+ ),
+ ),
+ (
+ "property",
+ models.ForeignKey(
+ blank=True,
+ db_constraint=False,
+ null=True,
+ on_delete=django.db.models.deletion.DO_NOTHING,
+ related_name="+",
+ to="items.property",
+ ),
+ ),
+ ],
+ options={
+ "verbose_name": "historical linked property",
+ "verbose_name_plural": "historical linked propertys",
+ "ordering": ("-history_date", "-history_id"),
+ "get_latest_by": ("history_date", "history_id"),
+ },
+ bases=(simple_history.models.HistoricalChanges, models.Model),
+ ),
+ migrations.CreateModel(
+ name="HistoricalProperty",
+ fields=[
+ ("id", models.UUIDField(db_index=True, default=uuid.uuid4, editable=False)),
+ ("name", models.TextField(blank=True, max_length=2048, null=True)),
+ ("description", models.TextField(blank=True, max_length=2048, null=True)),
+ ("custom_identifier", models.TextField(blank=True, max_length=2048, null=True)),
+ ("created_at", models.DateTimeField(blank=True, editable=False)),
+ ("last_modified_at", models.DateTimeField(blank=True, editable=False)),
+ (
+ "type",
+ models.CharField(
+ choices=[
+ ("text", "Text"),
+ ("date", "Date"),
+ ("datetime", "Date & time"),
+ ("time", "Time"),
+ ("duration", "Duration"),
+ ("uuid", "UUID"),
+ ("number", "Number"),
+ ("float", "Float"),
+ ("boolean", "Boolean"),
+ ("email", "Email"),
+ ("ipv4", "IPv4 address"),
+ ("ipv6", "IPv6 address"),
+ ("json", "JSON"),
+ ],
+ default="text",
+ max_length=32,
+ ),
+ ),
+ ("history_id", models.AutoField(primary_key=True, serialize=False)),
+ ("history_date", models.DateTimeField(db_index=True)),
+ ("history_change_reason", models.CharField(max_length=100, null=True)),
+ ("history_type", models.CharField(choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")], max_length=1)),
+ (
+ "author",
+ models.ForeignKey(
+ blank=True,
+ db_constraint=False,
+ null=True,
+ on_delete=django.db.models.deletion.DO_NOTHING,
+ related_name="+",
+ to="users.usersettings",
+ ),
+ ),
+ (
+ "history_user",
+ models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="+", to="users.usersettings"),
+ ),
+ ],
+ options={
+ "verbose_name": "historical property",
+ "verbose_name_plural": "historical propertys",
+ "ordering": ("-history_date", "-history_id"),
+ "get_latest_by": ("history_date", "history_id"),
+ },
+ bases=(simple_history.models.HistoricalChanges, models.Model),
+ ),
+ migrations.CreateModel(
+ name="HistoricalRelationProperty",
+ fields=[
+ ("id", models.UUIDField(db_index=True, default=uuid.uuid4, editable=False)),
+ ("name", models.TextField(blank=True, max_length=2048, null=True)),
+ ("description", models.TextField(blank=True, max_length=2048, null=True)),
+ ("custom_identifier", models.TextField(blank=True, max_length=2048, null=True)),
+ ("created_at", models.DateTimeField(blank=True, editable=False)),
+ ("last_modified_at", models.DateTimeField(blank=True, editable=False)),
+ ("value", models.TextField(max_length=2048)),
+ ("history_id", models.AutoField(primary_key=True, serialize=False)),
+ ("history_date", models.DateTimeField(db_index=True)),
+ ("history_change_reason", models.CharField(max_length=100, null=True)),
+ ("history_type", models.CharField(choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")], max_length=1)),
+ (
+ "author",
+ models.ForeignKey(
+ blank=True,
+ db_constraint=False,
+ null=True,
+ on_delete=django.db.models.deletion.DO_NOTHING,
+ related_name="+",
+ to="users.usersettings",
+ ),
+ ),
+ (
+ "history_user",
+ models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="+", to="users.usersettings"),
+ ),
+ (
+ "property",
+ models.ForeignKey(
+ blank=True,
+ db_constraint=False,
+ null=True,
+ on_delete=django.db.models.deletion.DO_NOTHING,
+ related_name="+",
+ to="items.property",
+ ),
+ ),
+ (
+ "relation",
+ models.ForeignKey(
+ blank=True,
+ db_constraint=False,
+ null=True,
+ on_delete=django.db.models.deletion.DO_NOTHING,
+ related_name="+",
+ to="items.itemrelation",
+ ),
+ ),
+ ],
+ options={
+ "verbose_name": "historical relation property",
+ "verbose_name_plural": "historical relation propertys",
+ "ordering": ("-history_date", "-history_id"),
+ "get_latest_by": ("history_date", "history_id"),
+ },
+ bases=(simple_history.models.HistoricalChanges, models.Model),
+ ),
+ ]
diff --git a/k356/items/models.py b/k356/items/models.py
index 9277f1c..fb0804b 100644
--- a/k356/items/models.py
+++ b/k356/items/models.py
@@ -6,6 +6,9 @@ from app.utils.models import BaseModel
from users.models import UserSettings
+from simple_history.models import HistoricalRecords
+
+
class ItemBase(BaseModel):
class Meta:
abstract = True
@@ -15,6 +18,15 @@ class ItemBase(BaseModel):
excluded_fields_edit = BaseModel.Serialization.excluded_fields_edit + ["author"]
author = models.ForeignKey(UserSettings, on_delete=models.PROTECT)
+ history = HistoricalRecords(inherit=True, user_model=UserSettings)
+
+ @property
+ def _history_user(self):
+ return self.author
+
+ @_history_user.setter
+ def _history_user(self, value):
+ self.author = value
class ItemType(ItemBase):
@@ -56,7 +68,8 @@ class PropertyType(models.TextChoices):
FLOAT = "float", _("Float")
BOOLEAN = "boolean", _("Boolean")
EMAIL = "email", _("Email")
- IP = "ip", _("IP address")
+ IPV4 = "ipv4", _("IPv4 address")
+ IPV6 = "ipv6", _("IPv6 address")
JSON = "json", _("JSON")
# TODO: Add more property types (location, etc)
@@ -70,6 +83,12 @@ class BaseLinkedProperty(ItemBase):
class Meta:
abstract = True
+ class Serialization(ItemBase.Serialization):
+ dynamic_field_type = ItemBase.Serialization.dynamic_field_type + ["value"]
+
+ class Encryption(ItemBase.Encryption):
+ fields = ItemBase.Encryption.fields + ["value"]
+
property = models.ForeignKey(Property, on_delete=models.CASCADE)
# Value is encrypted too
diff --git a/k356/items/templates/components/DynField/template.html b/k356/items/templates/components/DynField/template.html
new file mode 100644
index 0000000..daf9a7b
--- /dev/null
+++ b/k356/items/templates/components/DynField/template.html
@@ -0,0 +1,70 @@
+{% load i18n %}
+
+
+
+
+
+
+
+
+
+
+ {% trans "Cancel" %}
+
+
+ {% trans "OK" %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% trans "Cancel" %}
+
+
+ {% trans "OK" %}
+
+
+
+
+
+
+ TODO both ...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/k356/items/templates/components/DynField/vue.js b/k356/items/templates/components/DynField/vue.js
new file mode 100644
index 0000000..88ff7d5
--- /dev/null
+++ b/k356/items/templates/components/DynField/vue.js
@@ -0,0 +1,47 @@
+DynField = {
+ template: "#DynField",
+ router_path: "/",
+ delimiters: ["[[", "]]"],
+ props: {
+ field: {
+ default: null,
+ },
+ item: {
+ default: function() {
+ return {}
+ },
+ }
+ },
+
+ data: function() {
+ return {
+ test: null,
+ modal: null,
+ rules: {
+ required: value => !!value || "{{_('Required') | escapejs}}",
+ email: value => {
+ const pattern = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
+ return pattern.test(value) || "{{_('Invalid E-mail') | escapejs}}"
+ },
+ uuid: value => {
+ const pattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
+ return pattern.test(value) || "{{_('Invalid UUID') | escapejs}}"
+ },
+ ipv4: value => {
+ const pattern = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/
+ return pattern.test(value) || "{{_('Invalid IPv4') | escapejs}}"
+ },
+ },
+ }
+ },
+
+ computed: {
+ field_type: function() {
+ if (this.item?.property?.type == undefined) {
+ return "v-textarea"
+ }
+
+ return this.item?.property?.type
+ },
+ }
+}
diff --git a/k356/items/templates/components/ItemDetail/template.html b/k356/items/templates/components/ItemDetail/template.html
index b2149e3..767751a 100644
--- a/k356/items/templates/components/ItemDetail/template.html
+++ b/k356/items/templates/components/ItemDetail/template.html
@@ -2,18 +2,40 @@
-
+
-
+
+
+
+ [[ field.text ]]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @deleteItem="deleteLinkedProperty"
+ @createItem="createLinkedProperty"
+ @editItem="editLinkedProperty"
+ >
@@ -23,12 +45,12 @@
@@ -38,13 +60,13 @@
diff --git a/k356/items/templates/components/ItemDetail/vue.js b/k356/items/templates/components/ItemDetail/vue.js
index c18a050..679bda0 100644
--- a/k356/items/templates/components/ItemDetail/vue.js
+++ b/k356/items/templates/components/ItemDetail/vue.js
@@ -3,95 +3,109 @@ ItemDetail = {
router_path: "/ItemDetail/:id",
delimiters: ["[[", "]]"],
- data: function() {
- return {
- properties: [],
- linked_properties: [],
- children: [],
- parents: [],
- // TODO: Also remove this tedious things
- properties_headers: [],
- linked_properties_headers: [],
- children_headers: [],
- parents_headers: [],
- }
- },
-
computed: {
- // TODO: Remove this by a generic things at some points, this become tedious and repetitive
- properties_efields: function() {
- return this.properties_headers.filter(e => e.encrypted).map(e => e.value)
+ object: function() {
+ return this.$store.state.items.items.find(i => i.id == this.$route.params.id)
},
- linked_properties_efields: function() {
- return this.linked_properties_headers.filter(e => e.encrypted).map(e => e.value)
+ linked_properties: function() {
+ return this.$store.state.linkedProperties.items.filter(lp => lp.item == this.$route.params.id)
},
- children_efields: function() {
- return this.children_headers.filter(e => e.encrypted).map(e => e.value)
+ linked_properties_headers: function() {
+ return this.$store.state.linkedProperties.headers
},
- parents_efields: function() {
- return this.parents_headers.filter(e => e.encrypted).map(e => e.value)
+ properties: function() {
+ return this.$store.state.properties.items.filter(p => this.linked_properties.map(e => e.property).includes(p.id))
},
- },
- mounted: function() {
+ properties_headers: function() {
+ return this.$store.state.properties.headers
+ },
- this.reload()
+ children: function() {
+ return this.$store.state.relations.items.filter(p => p.parent == this.$route.params.id)
+ },
+ parents: function() {
+ return this.$store.state.relations.items.filter(p => p.child == this.$route.params.id)
+ },
+
+ children_headers: function() {
+ return this.$store.state.relations.headers
+ },
+
+ headers: function() {
+ return this.$store.state.items.headers
+ },
+
+ all_items: function() {
+ return this.$store.state.items.items
+ },
+
+ all_properties: function() {
+ return this.$store.state.properties.items
+ },
},
methods: {
- async reload () {
-
- try {
-
- const response = await this.$http.get(Urls["items:details"](this.$route.params.id))
-
- this.properties_headers = response.data.properties_headers
- this.linked_properties_headers = response.data.linked_properties_headers
- this.children_headers = response.data.children_headers
- this.parents_headers = response.data.parents_headers
-
- // TODO: TEDIOUUUUUS
- response.data.parents.forEach(async e => {
- this.parents.push(await this.decryptObject(this.parents_efields, e))
- })
-
- response.data.children.forEach(async e => {
- this.children.push(await this.decryptObject(this.children_efields, e))
- })
-
- response.data.properties.forEach(async e => {
- this.properties.push(await this.decryptObject(this.properties_efields, e))
- })
-
- response.data.linked_properties.forEach(async e => {
- this.linked_properties.push(await this.decryptObject(this.linked_properties_efields, e))
- })
-
- } catch (err) {
-
- Swal.fire({title: "{{_('Error during loading of items') | escapejs}}", icon: "error", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
-
- throw err
-
- }
-
+ linkedPropertyEdition (method, item) {
+ return this.object_edit("items:linked.property.edit", "items:linked.property.create", 'linkedProperties', method, item)
},
- async deleteItem () {
-
+ relationPropertiesEdition (method, item) {
+ return this.object_edit("items:relation.property.edit", "items:relation.property.create", 'relationProperties', method, item)
},
- async createItem () {
-
+ relationEdition (method, item) {
+ return this.object_edit("items:relation.edit", "items:relation.create", 'relations', method, item)
},
- async editItem() {
-
+ async deleteLinkedProperty (item) {
+ await this.linkedPropertyEdition("delete", item)
+ this.$store.commit("linkedProperties/removeItem", item.id)
},
+
+ async createLinkedProperty (item) {
+ item.item = this.$route.params.id
+ const new_item = await this.linkedPropertyEdition("post", item)
+ this.$store.commit("linkedProperties/addItem", new_item)
+ },
+
+ async editLinkedProperty (item) {
+ item.item = this.$route.params.id
+ const new_item = await this.linkedPropertyEdition("post", item)
+ this.$store.commit("linkedProperties/editItem", new_item)
+ },
+
+ async deleteProperty (item) {
+ console.log(item)
+ },
+
+ async createProperty (item) {
+ console.log(item)
+ },
+
+ async editProperty (item) {
+ console.log(item)
+ },
+
+ async deleteRelation (item) {
+ await this.relationEdition("delete", item)
+ this.$store.commit("relations/removeItem", item.id)
+ },
+
+ async createRelation (item) {
+ const new_item = await this.relationEdition("post", item)
+ this.$store.commit("relations/addItem", new_item)
+ },
+
+ async editRelation (item) {
+ const new_item = await this.relationEdition("post", item)
+ this.$store.commit("relations/editItem", new_item)
+ },
+
}
}
diff --git a/k356/items/templates/components/ItemRelationDetail/template.html b/k356/items/templates/components/ItemRelationDetail/template.html
index 6489893..dd1388a 100644
--- a/k356/items/templates/components/ItemRelationDetail/template.html
+++ b/k356/items/templates/components/ItemRelationDetail/template.html
@@ -1 +1,55 @@
-[[ this.$route.params.id ]]
+{% load i18n %}
+
+
+
+
+
+
+
+
+
+
+ [[ field.text ]]
+
+
+
+
+
+
+
+ mdi-eye
+ {% trans "Link" %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/k356/items/templates/components/ItemRelationDetail/vue.js b/k356/items/templates/components/ItemRelationDetail/vue.js
index 8fa3281..4ef1ee7 100644
--- a/k356/items/templates/components/ItemRelationDetail/vue.js
+++ b/k356/items/templates/components/ItemRelationDetail/vue.js
@@ -3,24 +3,55 @@ ItemRelationDetail = {
router_path: "/ItemRelationDetail/:id",
delimiters: ["[[", "]]"],
- data: function() {
- return {
- data: null
+ computed: {
+ object: function() {
+ return this.$store.state.relations.items.find(i => i.id == this.$route.params.id)
+ },
+
+ headers: function() {
+ return this.$store.state.relations.headers
+ },
+
+ relation_properties: function() {
+ return this.$store.state.relationProperties.items.filter(i => i.relation == this.$route.params.id)
+ },
+
+ relation_properties_headers: function() {
+ return this.$store.state.relationProperties.headers
+ },
+
+ all_properties: function() {
+ return this.$store.state.properties.items
}
},
- mounted: function() {
-
- this.reload()
-
- },
-
methods: {
- async reload () {
+ showItem (id) {
+ this.$router.push({ name: 'ItemDetail', params: { id: id }})
+ },
- const response = await this.$http.get(Urls["items:relation.details"](this.$route.params.id))
+ relationPropertyEdition (method, item) {
+ return this.object_edit("items:relation.property.edit", "items:relation.property.create", 'relationProperties', method, item)
+ },
+
+ async deleteRelationProperty (item) {
+ await this.relationPropertyEdition("delete", item)
+ this.$store.commit("relationProperties/removeItem", item.id)
+ },
+
+ async createRelationProperty (item) {
+ item.relation = this.$route.params.id
+ const new_item = await this.relationPropertyEdition("post", item)
+ this.$store.commit("relationProperties/addItem", new_item)
+ },
+
+ async editRelationProperty (item) {
+ item.relation = this.$route.params.id
+ const new_item = await this.relationPropertyEdition("post", item)
+ this.$store.commit("relationProperties/editItem", new_item)
+ },
- }
}
+
}
diff --git a/k356/items/templates/components/ItemView/template.html b/k356/items/templates/components/ItemView/template.html
index 7842790..cf5edbc 100644
--- a/k356/items/templates/components/ItemView/template.html
+++ b/k356/items/templates/components/ItemView/template.html
@@ -9,7 +9,6 @@
:items="items"
:items_headers="items_headers"
:items_relations="{'type': types}"
- :hidden_fields="[]"
group_by="type__name"
@deleteItem="deleteItem"
@createItem="createItem"
@@ -26,8 +25,6 @@
e.encrypted).map(e => e.value)
- },
-
- types_efields: function() {
- return this.types_headers.filter(e => e.encrypted).map(e => e.value)
- },
+ ...Vuex.mapState({
+ items: state => state.items.items,
+ items_headers: state => state.items.headers,
+ types: state => state.types.items,
+ types_headers: state => state.types.headers,
+ }),
},
methods: {
- async reload () {
-
- try {
- const response = await this.$http.get(Urls["items:list"]())
-
- this.items_headers = response.data.result.items_headers
- this.types_headers = response.data.result.types_headers
-
- // Decrypt all item the push
- response.data.result.items.forEach(async item => {
- const new_item = await this.decryptObject(this.items_efields, item)
-
- this.items.push(new_item)
- })
-
- // Decrypt all type the push
- response.data.result.types.forEach(async type => {
- const new_type = await this.decryptObject(this.types_efields, type)
-
- this.types.push(new_type)
- })
-
- } catch (err) {
-
- Swal.fire({title: "{{_('Error during loading of items') | escapejs}}", icon: "error", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
-
- }
-
- },
-
- async object_edit(url_edit, url_create, encrypted_fields, method, obj) {
-
- let url = null
-
- if (obj.id == undefined || obj.id == null) {
- url = Urls[url_create]()
- } else {
- url = Urls[url_edit](obj.id)
- }
-
- try {
-
- const newobj = await this.encryptObject(encrypted_fields, obj)
- const response = await this.$http[method](url, newobj)
-
- if (method != "delete") {
- return await this.decryptObject(encrypted_fields, response.data.object)
- }
-
- } 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})
-
- throw err
-
- }
-
- },
-
item_edition (method, item) {
- return this.object_edit("items:edit", "items:create", this.items_efields, method, item)
+ return this.object_edit("items:edit", "items:create", 'items', method, item)
},
type_edition (method, item) {
- return this.object_edit("items:type.edit", "items:type.create", this.types_efields, method, item)
+ return this.object_edit("items:type.edit", "items:type.create", 'types', method, item)
},
async createItem (item) {
-
- try {
-
- const new_item = await this.item_edition("post", item)
- this.items.push(new_item)
-
- Swal.fire({title: "{{_('Item successfully created!') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
-
- } catch (err) {
-
- }
-
+ const new_item = await this.item_edition("post", item)
+ this.$store.commit("items/addItem", new_item)
},
- async editItem (index, item) {
-
- try {
-
- // Remove the item
- this.items.splice(index, 1)
-
- const new_item = await this.item_edition("post", item)
-
- // Add the new item
- this.items.push(new_item)
-
- Swal.fire({title: "{{_('Item successfully edited') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
-
- } catch (err) {
-
- this.items.push(item)
-
- }
-
+ async editItem (item) {
+ const new_item = await this.item_edition("post", item)
+ this.$store.commit("items/editItem", new_item)
},
- async deleteItem (index) {
-
- var item = this.items[index]
-
- try {
-
- // Remove the item
- this.items.splice(index, 1)
- await this.item_edition("delete", item)
-
- Swal.fire({title: "{{_('Item successfully deleted!') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
-
- } catch (err) {
-
- this.items.push(item)
-
- }
-
+ async deleteItem (item) {
+ await this.item_edition("delete", item)
+ this.$store.commit("items/removeItem", item.id)
},
async createType (type) {
-
- try {
-
- const new_type = await this.type_edition("post", type)
- this.types.push(new_type)
-
- Swal.fire({title: "{{_('Type successfully created!') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
-
- } catch (err) {
-
- }
-
+ const new_type = await this.type_edition("post", type)
+ this.$store.commit("types/addItem", new_type)
},
- async editType (index, type) {
-
- try {
-
- this.types.splice(index, 1)
-
- const new_type = await this.type_edition("post", type)
-
- this.types.push(new_type)
-
- Swal.fire({title: "{{_('Type successfully edited') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
-
- } catch (err) {
-
- this.types.push(type)
-
- }
-
+ async editType (type) {
+ const new_type = await this.type_edition("post", type)
+ this.$store.commit("types/editItem", new_type)
},
- async deleteType (index) {
-
- var type = this.types[index]
-
- try {
-
- // Remove the type
- this.types.splice(index, 1)
-
- await this.type_edition("delete", type)
-
- Swal.fire({title: "{{_('Type successfully deleted!') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
-
- } catch (err) {
-
- this.types.push(type)
-
- }
+ async deleteType (type) {
+ await this.type_edition("delete", type)
+ this.$store.commit("types/removeItem", type.id)
},
},
diff --git a/k356/items/templates/components/LinkedPropertyList/template.html b/k356/items/templates/components/LinkedPropertyList/template.html
new file mode 100644
index 0000000..fb816ff
--- /dev/null
+++ b/k356/items/templates/components/LinkedPropertyList/template.html
@@ -0,0 +1 @@
+{% extends "base_components/glist/template.html" %}
diff --git a/k356/items/templates/components/LinkedPropertyList/vue.js b/k356/items/templates/components/LinkedPropertyList/vue.js
new file mode 100644
index 0000000..c775184
--- /dev/null
+++ b/k356/items/templates/components/LinkedPropertyList/vue.js
@@ -0,0 +1,8 @@
+{% extends "base_components/glist/vue.js" %}
+{% load main %}
+
+{% block component %}
+ {% define 'items' 'items' %}
+ {% define 'show_url' 'PropertyDetail' %}
+ {{ block.super }}
+{% endblock %}
diff --git a/k356/items/templates/components/PropertyDetail/template.html b/k356/items/templates/components/PropertyDetail/template.html
index fdafe63..e04780b 100644
--- a/k356/items/templates/components/PropertyDetail/template.html
+++ b/k356/items/templates/components/PropertyDetail/template.html
@@ -1 +1,61 @@
-PropertyDetail
+{% load i18n %}
+
+
+
+
+
+
+
+
+
+
+ [[ field.text ]]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/k356/items/templates/components/PropertyDetail/vue.js b/k356/items/templates/components/PropertyDetail/vue.js
index 64ba2de..f8d880f 100644
--- a/k356/items/templates/components/PropertyDetail/vue.js
+++ b/k356/items/templates/components/PropertyDetail/vue.js
@@ -1,4 +1,79 @@
PropertyDetail = {
template: "#PropertyDetail",
router_path: "/PropertyDetail/:id",
+ delimiters: ["[[", "]]"],
+
+ computed: {
+ object: function() {
+ return this.$store.state.properties.items.find(i => i.id == this.$route.params.id)
+ },
+
+ headers: function() {
+ return this.$store.state.properties.headers
+ },
+
+ linked_properties: function() {
+ return this.$store.state.linkedProperties.items.filter(i => i.property == this.$route.params.id)
+ },
+
+ linked_properties_headers: function() {
+ return this.$store.state.linkedProperties.headers
+ },
+
+ relation_properties: function() {
+ return this.$store.state.relationProperties.items.filter(i => i.property == this.$route.params.id)
+ },
+
+ relation_properties_headers: function() {
+ return this.$store.state.relationProperties.headers
+ },
+
+ all_items: function() {
+ return this.$store.state.items.items
+ }
+ },
+
+ methods: {
+ linkedPropertyEdition (method, item) {
+ return this.object_edit("items:linked.property.edit", "items:linked.property.create", 'linkedProperties', method, item)
+ },
+
+ relationPropertyEdition (method, item) {
+ return this.object_edit("items:relation.property.edit", "items:relation.property.create", 'relationProperties', method, item)
+ },
+
+ async deleteLinkedProperty (item) {
+ await this.linkedPropertyEdition("delete", item)
+ this.$store.commit("linkedProperties/removeItem", item.id)
+ },
+
+ async createLinkedProperty (item) {
+ item.property = this.$route.params.id
+ const new_item = await this.linkedPropertyEdition("post", item)
+ this.$store.commit("linkedProperties/addItem", new_item)
+ },
+
+ async editLinkedProperty (item) {
+ item.property = this.$route.params.id
+ const new_item = await this.linkedPropertyEdition("post", item)
+ this.$store.commit("linkedProperties/editItem", new_item)
+ },
+
+ async deleteRelationProperty (item) {
+ await this.relationPropertyEdition("delete", item)
+ this.$store.commit("relationProperties/removeItem", item.id)
+ },
+
+ async createRelationProperty (item) {
+ item.property = this.$route.params.id
+ const new_item = await this.relationPropertyEdition("post", item)
+ this.$store.commit("relationProperties/addItem", new_item)
+ },
+
+ async editRelationProperty (item) {
+ item.property = this.$route.params.id
+ const new_item = await this.relationPropertyEdition("post", item)
+ this.$store.commit("relationProperties/editItem", new_item)
+ },
+ }
},
diff --git a/k356/items/templates/components/PropertyView/template.html b/k356/items/templates/components/PropertyView/template.html
index 1035ebd..a2a2ddc 100644
--- a/k356/items/templates/components/PropertyView/template.html
+++ b/k356/items/templates/components/PropertyView/template.html
@@ -9,7 +9,6 @@
:items="properties"
:items_headers="properties_headers"
:items_relations="{}"
- :hidden_fields="[]"
group_by="type"
@deleteItem="deleteItem"
@createItem="createItem"
diff --git a/k356/items/templates/components/PropertyView/vue.js b/k356/items/templates/components/PropertyView/vue.js
index 45d18b0..896d7c7 100644
--- a/k356/items/templates/components/PropertyView/vue.js
+++ b/k356/items/templates/components/PropertyView/vue.js
@@ -4,21 +4,11 @@ PropertyView = {
delimiters: ["[[", "]]"],
props: [],
- data: function() {
- return {
- properties: [],
- properties_headers: [],
- }
- },
-
- mounted: function() {
- this.reload()
- },
-
computed: {
- properties_encrypted_fields: function() {
- return this.properties_headers.filter(e => e.encrypted).map(e => e.value)
- },
+ ...Vuex.mapState({
+ properties: state => state.properties.items,
+ properties_headers: state => state.properties.headers,
+ }),
properties_relations: function() {
return this.properties_headers.filter(e => e.choices != null)
@@ -27,156 +17,23 @@ PropertyView = {
methods: {
- async reload () {
-
- try {
- const response = await this.$http.get(Urls["items:property.list"]())
-
- this.properties_headers = response.data.result.properties_headers
-
- // Decrypt all item the push
- response.data.result.properties.forEach(async item => {
- const new_item = await this.decryptObject(this.properties_encrypted_fields, item)
-
- this.properties.push(new_item)
- })
-
- } catch (err) {
-
- Swal.fire({title: "{{_('Error during loading of properties') | escapejs}}", icon: "error", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
-
- throw err
-
- }
-
- },
-
- async decryptObject (encrypted_fields, obj) {
- // Decrypt all fields and return a new object
-
- var newobj = {}
- await Promise.all(Object.keys(obj).map(async field => {
- if (encrypted_fields.includes(field) && obj[field] != null) {
- newobj[field] = await this.decrypt(obj[field])
- } else {
- newobj[field] = obj[field]
- }
- }))
-
- return newobj
-
- },
-
- async encryptObject (encrypted_fields, obj) {
- // Encrypt all fields and return a new object
-
- var newobj = {}
-
- await Promise.all(Object.keys(obj).map(async field => {
- if (encrypted_fields.includes(field) && obj[field] != null) {
- newobj[field] = await this.encrypt(obj[field])
- } else {
- newobj[field] = obj[field]
- }
- }))
-
- return newobj
-
- },
-
- async object_edit(url_edit, url_create, encrypted_fields, method, obj) {
-
- let url = null
-
- if (obj.id == undefined || obj.id == null) {
- url = Urls[url_create]()
- } else {
- url = Urls[url_edit](obj.id)
- }
-
- try {
-
- const newobj = await this.encryptObject(encrypted_fields, obj)
- const response = await this.$http[method](url, newobj)
-
- if (method != "delete") {
- return await this.decryptObject(encrypted_fields, response.data.object)
- }
-
- } 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})
-
- throw err
-
- }
-
- },
-
property_edition (method, item) {
- return this.object_edit("items:property.edit", "items:property.create", this.properties_encrypted_fields, method, item)
+ return this.object_edit("items:property.edit", "items:property.create", "properties", method, item)
},
async createItem (item) {
-
- try {
-
- const new_item = await this.property_edition("post", item)
- this.properties.push(new_item)
-
- Swal.fire({title: "{{_('Item successfully created!') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
-
- } catch (err) {
-
- }
-
+ const new_item = await this.property_edition("post", item)
+ this.$store.commit("properties/addItem", new_item)
},
- async editItem (index, item) {
-
- try {
-
- // Remove the item
- this.properties.splice(index, 1)
-
- const new_item = await this.property_edition("post", item)
-
- // Add the new item
- this.properties.push(new_item)
-
- Swal.fire({title: "{{_('Item successfully edited') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
-
- } catch (err) {
-
- this.properties.push(item)
-
- }
-
+ async editItem (item) {
+ const new_item = await this.property_edition("post", item)
+ this.$store.commit("properties/editItem", new_item)
},
- async deleteItem (index) {
-
- var item = this.properties[index]
-
- try {
-
- // Remove the item
- this.properties.splice(index, 1)
- await this.property_edition("delete", item)
-
- Swal.fire({title: "{{_('Item successfully deleted!') | escapejs}}", icon: "success", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})
-
- } catch (err) {
-
- this.properties.push(item)
-
- }
-
+ async deleteItem (item) {
+ await this.property_edition("delete", item)
+ this.$store.commit("properties/removeItem", item.id)
},
},
diff --git a/k356/items/templates/components/RelationPropertyList/template.html b/k356/items/templates/components/RelationPropertyList/template.html
new file mode 100644
index 0000000..fb816ff
--- /dev/null
+++ b/k356/items/templates/components/RelationPropertyList/template.html
@@ -0,0 +1 @@
+{% extends "base_components/glist/template.html" %}
diff --git a/k356/items/templates/components/RelationPropertyList/vue.js b/k356/items/templates/components/RelationPropertyList/vue.js
new file mode 100644
index 0000000..62b5464
--- /dev/null
+++ b/k356/items/templates/components/RelationPropertyList/vue.js
@@ -0,0 +1,8 @@
+{% extends "base_components/glist/vue.js" %}
+{% load main %}
+
+{% block component %}
+ {% define 'items' 'items' %}
+ {% define 'show_url' 'ItemDetail' %}
+ {{ block.super }}
+{% endblock %}
diff --git a/k356/items/urls.py b/k356/items/urls.py
index b766fcd..37c956d 100644
--- a/k356/items/urls.py
+++ b/k356/items/urls.py
@@ -19,9 +19,15 @@ urlpatterns = [
path("property/list", property_view.property_list, name="property.list"),
path("property/", property_view.property_edit, name="property.edit"),
path("property/create", property_view.property_edit, {"id": None}, name="property.create"),
+ # Linked property
+ path("property/linked/", property_view.linked_property_edit, name="linked.property.edit"),
+ path("property/linked/create", property_view.linked_property_edit, {"id": None}, name="linked.property.create"),
+ # Relation property
+ path("property/relation/", property_view.relation_property_edit, name="relation.property.edit"),
+ path("property/relation/create", property_view.relation_property_edit, {"id": None}, name="relation.property.create"),
# Relations
- # path("relation/list", relation_view.relation_list, name="relation.list"),
- # path("relation/", relation_view.relation_edit, name="relation.edit"),
+ path("relation/list", relation_view.relation_list, name="relation.list"),
+ path("relation/", relation_view.relation_edit, name="relation.edit"),
path("relation//details", relation_view.relation_details, name="relation.details"),
- # path("relation/create", relation_view.relation_edit, {"id": None}, name="relation.create"),
+ path("relation/create", relation_view.relation_edit, {"id": None}, name="relation.create"),
]
diff --git a/k356/items/views/base.py b/k356/items/views/base.py
index a21a0d6..fd54584 100644
--- a/k356/items/views/base.py
+++ b/k356/items/views/base.py
@@ -41,10 +41,16 @@ def generic_edit(model, request, id=None):
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])
+ # Also allow for nested object (giving the full object instead of the id only)
+ if isinstance(data[field.name], dict):
+ setattr(item, f"{field.name}_id", data[field.name]["id"])
+
+ else:
+ setattr(item, f"{field.name}_id", data[field.name])
+
+ # For now, disregard m2m fields
continue
if field.name not in data:
diff --git a/k356/items/views/item_view.py b/k356/items/views/item_view.py
index 0e7da41..6e50a70 100644
--- a/k356/items/views/item_view.py
+++ b/k356/items/views/item_view.py
@@ -21,7 +21,7 @@ def item_list(request):
"types": list(types.serialize()),
"types_headers": header_for_table(ItemType),
},
- "count": items.count(),
+ "count": items.count() + types.count(),
}
)
@@ -44,6 +44,7 @@ def item_details(request, id):
return JsonResponse(
{
"object": item.serialize(),
+ "headers": header_for_table(Item),
"parents": list(item.parents.serialize()),
"parents_headers": header_for_table(ItemRelation),
"children": list(item.children.serialize()),
diff --git a/k356/items/views/property_view.py b/k356/items/views/property_view.py
index 02d2032..b6eb83f 100644
--- a/k356/items/views/property_view.py
+++ b/k356/items/views/property_view.py
@@ -3,22 +3,28 @@ from django.http import JsonResponse
from app.utils.api.api_list import header_for_table
-from items.models import Property
+from items.models import LinkedProperty, Property, RelationProperty
from items.views.base import generic_edit
@login_required
def property_list(request):
- items = Property.objects.filter(author=request.user.setting)
+ properties = Property.objects.filter(author=request.user.setting)
+ linked_properties = LinkedProperty.objects.filter(author=request.user.setting)
+ relation_properties = RelationProperty.objects.filter(author=request.user.setting)
return JsonResponse(
{
"result": {
- "properties": list(items.serialize()),
+ "properties": list(properties.serialize()),
"properties_headers": header_for_table(Property),
+ "linked_properties": list(linked_properties.serialize()),
+ "linked_properties_headers": header_for_table(LinkedProperty),
+ "relation_properties": list(relation_properties.serialize()),
+ "relation_properties_headers": header_for_table(RelationProperty),
},
- "count": items.count(),
+ "count": properties.count() + linked_properties.count() + relation_properties.count(),
}
)
@@ -28,3 +34,17 @@ def property_edit(request, id=None):
"""Create/edit property view."""
return generic_edit(Property, request, id)
+
+
+@login_required
+def linked_property_edit(request, id=None):
+ """Create/edit linked property view."""
+
+ return generic_edit(LinkedProperty, request, id)
+
+
+@login_required
+def relation_property_edit(request, id=None):
+ """Create/edit relation property view."""
+
+ return generic_edit(RelationProperty, request, id)
diff --git a/k356/items/views/relation_view.py b/k356/items/views/relation_view.py
index 10d1740..dfac7c5 100644
--- a/k356/items/views/relation_view.py
+++ b/k356/items/views/relation_view.py
@@ -4,6 +4,23 @@ from django.http import JsonResponse
from app.utils.api.api_list import header_for_table
from items.models import Item, ItemRelation, Property, RelationProperty
+from items.views.base import generic_edit
+
+
+@login_required
+def relation_list(request):
+
+ relations = ItemRelation.objects.filter(author=request.user.setting)
+
+ return JsonResponse(
+ {
+ "result": {
+ "relations": list(relations.serialize()),
+ "headers": header_for_table(ItemRelation),
+ },
+ "count": relations.count(),
+ }
+ )
@login_required
@@ -27,3 +44,10 @@ def relation_details(request, id):
"relation_properties_headers": header_for_table(RelationProperty),
}
)
+
+
+@login_required
+def relation_edit(request, id=None):
+ """Create/edit relation view."""
+
+ return generic_edit(ItemRelation, request, id)
diff --git a/k356/main/templates/components/Loading/vue.js b/k356/main/templates/components/Loading/vue.js
index 3133aee..59a9d70 100644
--- a/k356/main/templates/components/Loading/vue.js
+++ b/k356/main/templates/components/Loading/vue.js
@@ -9,17 +9,13 @@ Loading = {
}
},
- mounted: function() {
- // FIX: Remove this, this is the key for debugging
- this.generate_aes_key('asd')
- },
-
methods: {
async generate_aes_key (password) {
- const key = await this.deriveKeyFromPassphrase(password, "{{ user_setting.id }}--aes")
+ const key = await this.deriveKeyFromPassphrase(password, "{{ user_setting.id }}--aes")
this.$emit("update_key", key)
+
},
}
diff --git a/k356/pyproject.toml b/k356/pyproject.toml
index 63ddf66..017fa8b 100644
--- a/k356/pyproject.toml
+++ b/k356/pyproject.toml
@@ -62,6 +62,7 @@ authors = []
python = "^3.11"
Django = "^5.0"
django-bower = {git = "https://github.com/ArcaniteSolutions/django-bower.git"}
+django-simple-history = "^3.7"
[tool.poetry.dev-dependencies]
black = "^24.4.0"
diff --git a/k356/templates/base.html b/k356/templates/base.html
index e4eb500..83605fc 100644
--- a/k356/templates/base.html
+++ b/k356/templates/base.html
@@ -41,7 +41,10 @@