Various relational updates

This commit is contained in:
rijkvanzanten
2020-08-31 19:22:25 -04:00
parent 3cd22d416c
commit 56ba0eec64
10 changed files with 93 additions and 45 deletions

View File

@@ -105,7 +105,7 @@ router.patch(
let results: any = [];
for (const field of req.body) {
await service.updateField(req.params.collection, field, req.accountability);
await service.updateField(req.params.collection, field);
const updatedField = await service.readOne(req.params.collection, field.field);
@@ -120,17 +120,13 @@ router.patch(
'/:collection/:field',
validateCollection,
useCollection('directus_fields'),
// @todo: validate field
asyncHandler(async (req, res) => {
const exists = await schemaInspector.hasColumn(req.collection, req.params.field);
if (exists === false) throw new ForbiddenException();
const service = new FieldsService({ accountability: req.accountability });
const fieldData: Partial<Field> & { field: string; type: typeof types[number] } = req.body;
if (!fieldData.field) fieldData.field = req.params.field;
await service.updateField(req.params.collection, fieldData, req.accountability);
await service.updateField(req.params.collection, fieldData);
const updatedField = await service.readOne(req.params.collection, req.params.field);
@@ -143,11 +139,7 @@ router.delete(
validateCollection,
useCollection('directus_fields'),
asyncHandler(async (req, res) => {
const exists = await schemaInspector.hasColumn(req.collection, req.params.field);
if (exists === false) throw new ForbiddenException();
const service = new FieldsService({ accountability: req.accountability });
await service.deleteField(req.params.collection, req.params.field, req.accountability);
res.status(200).end();

View File

@@ -1,7 +1,6 @@
import database, { schemaInspector } from '../database';
import { Field } from '../types/field';
import { uniq } from 'lodash';
import { Accountability, AbstractServiceOptions, FieldMeta } from '../types';
import { Accountability, AbstractServiceOptions, FieldMeta, Relation } from '../types';
import ItemsService from '../services/items';
import { ColumnBuilder } from 'knex';
import getLocalType from '../utils/get-local-type';
@@ -172,6 +171,10 @@ export default class FieldsService {
field: Partial<Field> & { field: string; type: typeof types[number] },
table?: CreateTableBuilder // allows collection creation to
) {
if (this.accountability && this.accountability.admin !== true) {
throw new ForbiddenException('Only admins can perform this action.');
}
/**
* @todo
* Check if table / directus_fields row already exists
@@ -198,7 +201,11 @@ export default class FieldsService {
/** @todo research how to make this happen in SQLite / Redshift */
async updateField(collection: string, field: RawField, accountability?: Accountability) {
async updateField(collection: string, field: RawField) {
if (this.accountability && this.accountability.admin !== true) {
throw new ForbiddenException('Only admins can perform this action.');
}
if (field.schema) {
await this.knex.schema.alterTable(collection, (table) => {
let column: ColumnBuilder;
@@ -251,12 +258,35 @@ export default class FieldsService {
}
/** @todo save accountability */
async deleteField(collection: string, field: string, accountability?: Accountability) {
await database('directus_fields').delete().where({ collection, field });
async deleteField(collection: string, field: string) {
if (this.accountability && this.accountability.admin !== true) {
throw new ForbiddenException('Only admins can perform this action.');
}
await database.schema.table(collection, (table) => {
table.dropColumn(field);
});
await this.knex('directus_fields').delete().where({ collection, field });
if (await schemaInspector.hasColumn(collection, field)) {
await this.knex.schema.table(collection, (table) => {
table.dropColumn(field);
});
}
const relations = await this.knex
.select<Relation[]>('*')
.from('directus_relations')
.where({ many_collection: collection, many_field: field })
.orWhere({ one_collection: collection, one_field: field });
for (const relation of relations) {
const isM2O = relation.many_collection === collection && relation.many_field === field;
if (isM2O) {
await this.knex('directus_relations').delete().where({ many_collection: collection, many_field: field });
await this.deleteField(relation.one_collection, relation.one_field);
} else {
await this.knex('directus_relations').update({ one_field: null }).where({ one_collection: collection, one_field: field });
}
}
}
public addColumnToTable(table: CreateTableBuilder, field: Field) {

View File

@@ -12,6 +12,9 @@
<v-list-item-content>
<render-template :template="template" :item="item" :collection="relatedCollection" />
</v-list-item-content>
<v-list-item-icon>
<v-icon name="launch" small />
</v-list-item-icon>
</v-list-item>
</v-list>
</v-menu>

View File

@@ -8,5 +8,18 @@ export default defineInterface(({ i18n }) => ({
component: InterfaceOneToMany,
types: ['alias'],
relationship: 'o2m',
options: [],
options: [
{
field: 'fields',
type: 'json',
name: i18n.tc('field', 0),
meta: {
interface: 'tags',
width: 'full',
options: {
placeholder: i18n.t('readable_fields_copy'),
},
},
},
],
}));

View File

@@ -67,7 +67,7 @@ import useCollection from '@/composables/use-collection';
import { useCollectionsStore, useRelationsStore, useFieldsStore } from '@/stores/';
import ModalDetail from '@/views/private/components/modal-detail';
import ModalBrowse from '@/views/private/components/modal-browse';
import { Filter } from '@/types';
import { Filter, Field } from '@/types';
import { Header } from '@/components/v-table/types';
export default defineComponent({
@@ -91,7 +91,7 @@ export default defineComponent({
},
fields: {
type: Array as PropType<string[]>,
required: true,
default: () => [],
},
disabled: {
type: Boolean,
@@ -194,7 +194,7 @@ export default defineComponent({
async function fetchCurrent() {
loading.value = true;
let fields = [...props.fields];
let fields = [...(props.fields.length > 0 ? props.fields : getDefaultFields())];
if (fields.includes(relatedPrimaryKeyField.value.field) === false) {
fields.push(relatedPrimaryKeyField.value.field);
@@ -306,7 +306,7 @@ export default defineComponent({
watch(
() => props.fields,
() => {
tableHeaders.value = props.fields
tableHeaders.value = (props.fields.length > 0 ? props.fields : getDefaultFields())
.map((fieldKey) => {
const field = fieldsStore.getField(relatedCollection.value.collection, fieldKey);
@@ -517,6 +517,11 @@ export default defineComponent({
},
]);
}
function getDefaultFields(): string[] {
const fields = fieldsStore.getFieldsForCollection(relatedCollection.value.collection);
return fields.slice(0, 3).map((field: Field) => field.field);
}
},
});
</script>

View File

@@ -226,7 +226,7 @@ import BookmarkAdd from '@/views/private/components/bookmark-add';
import BookmarkEdit from '@/views/private/components/bookmark-edit';
import router from '@/router';
import marked from 'marked';
import { usePermissionsStore } from '@/stores';
import { usePermissionsStore, useUserStore } from '@/stores';
type Item = {
[field: string]: any;
@@ -253,6 +253,7 @@ export default defineComponent({
},
},
setup(props) {
const userStore = useUserStore();
const permissionsStore = usePermissionsStore();
const layout = ref<LayoutComponent | null>(null);
@@ -476,6 +477,9 @@ export default defineComponent({
function usePermissions() {
const batchEditAllowed = computed(() => {
const admin = userStore.state?.currentUser?.role.admin === true;
if (admin) return true;
const updatePermissions = permissionsStore.state.permissions.find(
(permission) => permission.action === 'update' && permission.collection === collection.value
);
@@ -484,6 +488,8 @@ export default defineComponent({
const batchSoftDeleteAllowed = computed(() => {
if (!currentCollection.value?.meta?.soft_delete_field) return false;
const admin = userStore.state?.currentUser?.role.admin === true;
if (admin) return true;
const updatePermissions = permissionsStore.state.permissions.find(
(permission) => permission.action === 'update' && permission.collection === collection.value
@@ -495,6 +501,9 @@ export default defineComponent({
});
const batchDeleteAllowed = computed(() => {
const admin = userStore.state?.currentUser?.role.admin === true;
if (admin) return true;
const deletePermissions = permissionsStore.state.permissions.find(
(permission) => permission.action === 'delete' && permission.collection === collection.value
);
@@ -502,6 +511,9 @@ export default defineComponent({
});
const createAllowed = computed(() => {
const admin = userStore.state?.currentUser?.role.admin === true;
if (admin) return true;
const createPermissions = permissionsStore.state.permissions.find(
(permission) => permission.action === 'create' && permission.collection === collection.value
);

View File

@@ -8,11 +8,7 @@
</div>
<div class="field">
<div class="type-label">{{ $t('related_collection') }}</div>
<v-select
:placeholder="$t('select_one')"
:items="items"
v-model="relations[0].one_collection"
/>
<v-select :placeholder="$t('select_one')" :items="items" v-model="relations[0].one_collection" />
</div>
<v-input disabled :value="fieldData.field" />
<v-input disabled :value="relatedPrimary" />
@@ -78,10 +74,7 @@ export default defineComponent({
const availableCollections = computed(() => {
return orderBy(
collectionsStore.state.collections.filter((collection) => {
return (
collection.collection.startsWith('directus_') === false &&
collection.collection !== props.collection
);
return collection.collection.startsWith('directus_') === false;
}),
['collection'],
['asc']

View File

@@ -80,16 +80,15 @@ export default defineComponent({
if (!state.relations[0].many_collection) return [];
return fieldsStore.state.fields
.filter((field) => {
if (field.collection !== state.relations[0].many_collection) return false;
// Make sure the selected field matches the type of primary key of the current
// collection. Otherwise you aren't able to properly save the primary key
if (!field.schema || field.type !== currentCollectionPrimaryKey.value.type) return false;
return true;
})
.map((field) => field.field);
.filter((field) => field.collection === state.relations[0].many_collection)
.map((field) => ({
text: field.field,
value: field.field,
disabled:
!field.schema ||
field.schema?.is_primary_key ||
field.type !== currentCollectionPrimaryKey.value.type,
}));
});
const collectionMany = computed({

View File

@@ -7,6 +7,7 @@
import { useFieldsStore, useRelationsStore } from '@/stores/';
import { reactive, watch } from '@vue/composition-api';
import { clone } from 'lodash';
const fieldsStore = useFieldsStore();
const relationsStore = useRelationsStore();
@@ -47,7 +48,7 @@ function initLocalStore(
const isExisting = field !== '+';
if (isExisting) {
const existingField = fieldsStore.getField(collection, field);
const existingField = clone(fieldsStore.getField(collection, field));
state.fieldData.field = existingField.field;
state.fieldData.type = existingField.type;

View File

@@ -10,7 +10,7 @@ export default function getRelatedCollection(collection: string, field: string)
const type = fieldInfo.type.toLowerCase();
// o2m | m2m
if (type === 'alias') {
if (['o2m', 'm2m', 'alias'].includes(type)) {
return relations[0].many_collection;
}