Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ca 197 show an error message to the user when he tries to create an object with the same name as another object in the same scope #138

Merged
7 changes: 1 addition & 6 deletions backend/core/base_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,7 @@ def clean(self) -> None:
if not self.is_unique_in_scope(scope=scope, fields_to_check=_fields_to_check):
for field in _fields_to_check:
if not self.is_unique_in_scope(scope=scope, fields_to_check=[field]):
field_errors[field] = ValidationError(
_(
f"{getattr(self, field)} is already in use in this scope. Please choose another value."
),
code="unique",
)
field_errors[field] = f"{getattr(self, field)} is already used in this scope. Please choose another value."
super().clean()
if field_errors:
raise ValidationError(field_errors)
Expand Down
14 changes: 12 additions & 2 deletions backend/core/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ def update(self, instance: models.Model, validated_data: Any) -> models.Model:
raise serializers.ValidationError(
{"urn": "Imported objects cannot be modified"}
)
return super().update(instance, validated_data)
try:
object_updated = super().update(instance, validated_data)
return object_updated
except Exception as e:
logger.error(e)
raise serializers.ValidationError(e.args[0])

def create(self, validated_data: Any):
logger.debug("validated data", **validated_data)
Expand All @@ -42,7 +47,12 @@ def create(self, validated_data: Any):
"folder": "You do not have permission to create objects in this folder"
}
)
return super().create(validated_data)
try:
object_created = super().create(validated_data)
return object_created
except Exception as e:
logger.error(e)
raise serializers.ValidationError(e.args[0])

class Meta:
model: models.Model
Expand Down
21 changes: 20 additions & 1 deletion frontend/src/lib/components/Forms/Form.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte';
import type { AnyZodObject } from 'zod';

import type { ModalStore } from '@skeletonlabs/skeleton';
import { getModalStore } from '@skeletonlabs/skeleton';

const modalStore: ModalStore = getModalStore();

export let data: SuperValidated<AnyZodObject>;
export let dataType: 'form' | 'json';
export let invalidateAll = true; // set to false to keep form data using muliple forms on a page
Expand All @@ -13,12 +18,26 @@

export let debug = false; // set to true to enable SuperDebug component

function handleFormUpdated({
form,
closeModal
}: {
form: any;
closeModal: boolean;
}) {
if (closeModal && form.valid) {
$modalStore[0] ? modalStore.close() : null;
}
}

export const _form = superForm(data, {
dataType: dataType,
invalidateAll: invalidateAll,
applyAction: applyAction,
resetForm: resetForm,
validators: validators
validators: validators,
onUpdated: ({ form }) => handleFormUpdated({ form, closeModal: true })

});

const { form, message /*, tainted*/, delayed, errors, allErrors, enhance } = _form;
Expand Down
2 changes: 0 additions & 2 deletions frontend/src/lib/components/Forms/ModelForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
import { browser } from '$app/environment';
import { page } from '$app/stores';
import * as m from '$paraglide/messages.js';
import { localItems, toCamelCase } from '$lib/utils/locales';
import { languageTag } from '$paraglide/runtime';

export let form: SuperValidated<AnyZodObject>;
export let model: ModelInfo;
Expand Down
20 changes: 10 additions & 10 deletions frontend/src/routes/(app)/[model=urlmodel]/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '$lib/utils/crud';
import { modelSchema } from '$lib/utils/schemas';
import { fail, type Actions } from '@sveltejs/kit';
import { message, setError, superValidate } from 'sveltekit-superforms/server';
import { setError, superValidate } from 'sveltekit-superforms/server';
import { localItems, toCamelCase } from '$lib/utils/locales';
import * as m from '$paraglide/messages';
import { languageTag } from '$paraglide/runtime';
Expand Down Expand Up @@ -125,22 +125,22 @@ export const actions: Actions = {
}
}

const model: string = urlParamModelVerboseName(event.params.model!);
const model: string = event.params.model ? urlParamModelVerboseName(event.params.model) : '';
// TODO: reference newly created object
if (model === 'User') {
return message(createForm, m.successfullyCreatedObject({object: localItems(languageTag())[model.toLowerCase()].toLowerCase()}));
setFlash({ type: 'success', message: m.successfullyCreatedObject({object: localItems(languageTag())[toCamelCase(model)].toLowerCase()}) }, event);
}
return message(createForm, m.successfullyCreatedObject({object: localItems(languageTag())[toCamelCase(model.toLowerCase())].toLowerCase()}));
setFlash({ type: 'success', message: m.successfullyCreatedObject({object: localItems(languageTag())[toCamelCase(model)].toLowerCase()}) }, event);
}
return { createForm };
},
delete: async ({ request, fetch, params }) => {
const formData = await request.formData();
delete: async (event) => {
const formData = await event.request.formData();
const schema = z.object({ id: z.string().uuid() });
const deleteForm = await superValidate(formData, schema);

const id = deleteForm.data.id;
const endpoint = `${BASE_API_URL}/${params.model}/${id}/`;
const endpoint = `${BASE_API_URL}/${event.params.model}/${id}/`;

if (!deleteForm.valid) {
console.log(deleteForm.errors);
Expand All @@ -151,7 +151,7 @@ export const actions: Actions = {
const requestInitOptions: RequestInit = {
method: 'DELETE'
};
const res = await fetch(endpoint, requestInitOptions);
const res = await event.fetch(endpoint, requestInitOptions);
if (!res.ok) {
const response = await res.json();
console.log(response);
Expand All @@ -160,9 +160,9 @@ export const actions: Actions = {
}
return fail(400, { form: deleteForm });
}
const model: string = urlParamModelVerboseName(params.model!);
const model: string = urlParamModelVerboseName(event.params.model!);
// TODO: reference object by name instead of id
return message(deleteForm, m.successfullyDeletedObject({object: localItems(languageTag())[toCamelCase(model.toLowerCase())].toLowerCase(), id: id}));
setFlash({ type: 'success', message: m.successfullyDeletedObject({object: localItems(languageTag())[toCamelCase(toCamelCase(model))].toLowerCase()}) }, event);
}
return { deleteForm };
}
Expand Down
52 changes: 2 additions & 50 deletions frontend/src/routes/(app)/[model=urlmodel]/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
<script lang="ts">
import { page } from '$app/stores';
import CreateModal from '$lib/components/Modals/CreateModal.svelte';
import ModelTable from '$lib/components/ModelTable/ModelTable.svelte';
import type {
ModalComponent,
ModalSettings,
ModalStore,
ToastStore
} from '@skeletonlabs/skeleton';
import { getModalStore, getToastStore } from '@skeletonlabs/skeleton';
import { superForm } from 'sveltekit-superforms/client';
import { getModalStore } from '@skeletonlabs/skeleton';
import type { PageData } from './$types';
import * as m from '$paraglide/messages';
import { localItems, capitalizeFirstLetter } from '$lib/utils/locales';
Expand All @@ -18,52 +15,7 @@
export let data: PageData;

const modalStore: ModalStore = getModalStore();
const toastStore: ToastStore = getToastStore();

function handleFormUpdated({
form,
pageStatus,
closeModal
}: {
form: any;
pageStatus: number;
closeModal: boolean;
}) {
if (closeModal && form.valid) {
$modalStore[0] ? modalStore.close() : null;
}
if (form.message) {
const toast: { message: string; background: string } = {
message: form.message,
background: pageStatus === 200 ? 'variant-filled-success' : 'variant-filled-error'
};
toastStore.trigger(toast);
}
}

let { form: deleteForm, message: deleteMessage } = {
form: {},
message: {}
};

let { form: createForm, message: createMessage } = {
form: {},
message: {}
};

// NOTE: This is a workaround for an issue we had with getting the return value from the form actions after switching pages in route /[model=urlmodel]/ without a full page reload.
// invalidateAll() did not work.
$: {
({ form: createForm, message: createMessage } = superForm(data.createForm, {
onUpdated: ({ form }) =>
handleFormUpdated({ form, pageStatus: $page.status, closeModal: true })
}));
({ form: deleteForm, message: deleteMessage } = superForm(data.deleteForm, {
onUpdated: ({ form }) =>
handleFormUpdated({ form, pageStatus: $page.status, closeModal: true })
}));
}


function modalCreateForm(): void {
const modalComponent: ModalComponent = {
ref: CreateModal,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ export const actions: Actions = {
if (response.non_field_errors) {
setError(form, 'non_field_errors', response.non_field_errors);
}
Object.entries(response).forEach(([key, value]) => {
setError(form, key, value);
});
return fail(400, { form: form });
}
const createdObject = await res.json();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,11 @@
}/${value.id}`}
<a href={itemHref} class="anchor">{value.str}</a>
{:else}
{value.str ?? value}
{#if localItems(languageTag())[toCamelCase(value.str ?? value)]}
{localItems(languageTag())[toCamelCase(value.str ?? value)]}
{:else}
{value.str ?? value}
{/if}
{/if}
{:else}
--
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import RiskScenarioItem from '$lib/components/RiskMatrix/RiskScenarioItem.svelte';
import * as m from '$paraglide/messages';
import { languageTag } from '$paraglide/runtime';
import { localItems, toCamelCase } from '$lib/utils/locales.js';

export let data;
const showRisks = true;
Expand Down Expand Up @@ -211,7 +212,7 @@
<ul>
<li class="pb-1">
<span class="font-semibold">{m.status()}:</span>
{risk_assessment.status}
{localItems(languageTag())[risk_assessment.status]}
</li>
<li class="pb-1">
<span class="font-semibold">{m.authors()}:</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ export const actions: Actions = {
if (response.non_field_errors) {
setError(form, 'non_field_errors', response.non_field_errors);
}
Object.entries(response).forEach(([key, value]) => {
setError(form, key, value);
});
return fail(400, { form: form });
}
const model: string = urlParamModelVerboseName(URLModel);
Expand Down
Loading