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

Introduced new model of AliasEntry #1343

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions airone/lib/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,6 @@ def add_entry(self, user: User, name: str, schema: Entity, values={}, is_public=

return entry


class AironeViewTest(AironeTestCase):
def setUp(self):
super(AironeViewTest, self).setUp()

self.client = Client()

def _do_login(self, uname, is_superuser=False) -> User:
# create test user to authenticate
user = User(username=uname, is_superuser=is_superuser)
Expand All @@ -171,6 +164,13 @@ def admin_login(self) -> User:
def guest_login(self, uname="guest") -> User:
return self._do_login(uname)


class AironeViewTest(AironeTestCase):
def setUp(self):
super(AironeViewTest, self).setUp()

self.client = Client()

def open_fixture_file(self, fname):
test_file_path = inspect.getfile(self.__class__)
test_base_path = os.path.dirname(test_file_path)
Expand Down
9 changes: 9 additions & 0 deletions entity/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,12 @@ def save(self, *args, **kwargs) -> None:
if max_entities and Entity.objects.count() >= max_entities:
raise RuntimeError("The number of entities is over the limit")
return super(Entity, self).save(*args, **kwargs)

def is_available(self, name: str) -> bool:
from entry.models import AliasEntry, Entry

if Entry.objects.filter(name=name, schema=self, is_active=True).exists():
return False
if AliasEntry.objects.filter(name=name, entry__schema=self, entry__is_active=True).exists():
return False
return True
25 changes: 25 additions & 0 deletions entry/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1452,6 +1452,20 @@ def __init__(self, *args, **kwargs):
super(Entry, self).__init__(*args, **kwargs)
self.objtype = ACLObjType.Entry

def add_alias(self, name):
# validate name that is not duplicated with other Item names and Aliases in this model
if not self.schema.is_available(name):
raise ValueError("Specified name has already been used by other Item or Alias")

return AliasEntry.objects.create(name=name, entry=self)

def delete_alias(self, name):
alias = AliasEntry.objects.filter(
name=name, entry__schema=self.schema, entry__is_active=True
).first()
if alias:
alias.delete()

def add_attribute_from_base(self, base: EntityAttr, request_user: User):
if not isinstance(base, EntityAttr):
raise TypeError('Variable "base" is incorrect type')
Expand Down Expand Up @@ -2421,3 +2435,14 @@ def value(self):
return self.raw_value
case _:
print("TODO implement it")


class AliasEntry(models.Model):
name = models.CharField(max_length=200)

# This indicates alias of this Entry
entry = models.ForeignKey(
Entry,
related_name="aliases",
on_delete=models.CASCADE,
)
4 changes: 2 additions & 2 deletions entry/tests/test_model.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
from datetime import date, datetime, timezone
from unittest import skip

from django.conf import settings
from django.db.models import Q

from acl.models import ACLBase
from airone.lib.acl import ACLObjType, ACLType
from airone.lib.drf import ExceedLimitError
from airone.lib.elasticsearch import AdvancedSearchResultRecord, AttrHint
from airone.lib.test import AironeTestCase
from airone.lib.types import AttrType
from entity.models import Entity, EntityAttr
from entry.models import Attribute, AttributeValue, Entry
from entry.models import Attribute, AttributeValue, Entry, AliasEntry

Check failure on line 14 in entry/tests/test_model.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (F401)

entry/tests/test_model.py:14:60: F401 `entry.models.AliasEntry` imported but unused
from entry.services import AdvancedSearchService
from entry.settings import CONFIG
from group.models import Group
Expand Down Expand Up @@ -5018,4 +5018,4 @@
settings.MAX_ENTRIES = None
Entry.objects.create(
name=f"entry-{max_entries}", created_user=self._user, schema=self._entity
)
)

Check failure on line 5021 in entry/tests/test_model.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (W292)

entry/tests/test_model.py:5021:10: W292 No newline at end of file
66 changes: 66 additions & 0 deletions entry/tests/test_model_alias.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from airone.lib.test import AironeTestCase
from entry.models import AliasEntry
from user.models import User

Check failure on line 3 in entry/tests/test_model_alias.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (F401)

entry/tests/test_model_alias.py:3:25: F401 `user.models.User` imported but unused


class ModelTest(AironeTestCase):
def setUp(self):
super(ModelTest, self).setUp()

# create common Instances in this test
self.user = self.admin_login()
self.model = self.create_entity(self.user, "Kingdom")
self.item = self.add_entry(self.user, "Shin", self.model)

def test_create_alias(self):
# add alias for this item
self.item.add_alias("Li Shin")

# check added alias item was actually created
self.assertTrue(AliasEntry.objects.filter(name="Li Shin", entry=self.item).exists())

def test_create_alias_with_other_item(self):
# add alias for this item
AliasEntry.objects.create(name="Li Shin", entry=self.item)
AliasEntry.objects.create(name="Captain", entry=self.item)

# add alias for other item
other_item = self.add_entry(self.user, "Hyou", self.model)
with self.assertRaises(ValueError) as e:
other_item.add_alias("Captain")
self.assertEqual(str(e), "Specified name has already been used by other Item or Alias")

with self.assertRaises(ValueError) as e:
other_item.add_alias("Shin")
self.assertEqual(str(e), "Specified name has already been used by other Item or Alias")

def test_create_alias_with_other_entity(self):
# add alias for this item
AliasEntry.objects.create(name="Captain", entry=self.item)

# add alias for other entity
other_model = self.create_entity(self.user, "Country")
other_item = self.add_entry(self.user, "China", other_model)
other_item.add_alias("Captain")

# check added alias item was actually created
self.assertEqual(other_item.aliases.filter(name="Captain").count(), 1)

def test_delete_alias(self):
# add alias for this item
AliasEntry.objects.create(name="Li Shin", entry=self.item)

# delete alias for this item
self.item.delete_alias("Li Shin")

# check deleted alias item was actually deleted
self.assertFalse(AliasEntry.objects.filter(name="Li Shin", entry=self.item).exists())

def test_list_alias(self):
# add alias for this item
AliasEntry.objects.create(name="Li Shin", entry=self.item)
AliasEntry.objects.create(name="Captain", entry=self.item)

# check list alias
self.assertEqual(self.item.aliases.count(), 2)
self.assertEqual(["Li Shin", "Captain"], [alias.name for alias in self.item.aliases.all()])
4 changes: 4 additions & 0 deletions frontend/src/components/entity/EntityControlMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
editEntityPath,
entitiesPath,
restoreEntryPath,
listAliasPath,
topPath,
entityEntriesPath,
aclHistoryPath,
Expand Down Expand Up @@ -94,6 +95,9 @@ export const EntityControlMenu: FC<Props> = ({
<MenuItem component={Link} to={entityEntriesPath(entityId)}>
<Typography>アイテム一覧</Typography>
</MenuItem>
<MenuItem component={Link} to={listAliasPath(entityId)}>
<Typography>エイリアス一覧</Typography>
</MenuItem>
<MenuItem component={Link} to={editEntityPath(entityId)}>
<Typography>編集</Typography>
</MenuItem>
Expand Down
88 changes: 88 additions & 0 deletions frontend/src/pages/ListAliasEntryPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import AppsIcon from "@mui/icons-material/Apps";
import { Box, Container, IconButton, Grid } from "@mui/material";
import { useSnackbar } from "notistack";
import React, { FC, useState } from "react";
import { useNavigate } from "react-router-dom";

import { EntityControlMenu } from "components/entity/EntityControlMenu";

Check failure on line 7 in frontend/src/pages/ListAliasEntryPage.tsx

View workflow job for this annotation

GitHub Actions / lint

There should be at least one empty line between import groups

Check failure on line 7 in frontend/src/pages/ListAliasEntryPage.tsx

View workflow job for this annotation

GitHub Actions / lint

`components/entity/EntityControlMenu` import should occur after import of `components/entity/EntityBreadcrumbs`
import { useAsyncWithThrow } from "../hooks/useAsyncWithThrow";
import { useTypedParams } from "../hooks/useTypedParams";

Check failure on line 9 in frontend/src/pages/ListAliasEntryPage.tsx

View workflow job for this annotation

GitHub Actions / lint

There should be at least one empty line between import groups
import { EntryImportModal } from "components/entry/EntryImportModal";

Check failure on line 10 in frontend/src/pages/ListAliasEntryPage.tsx

View workflow job for this annotation

GitHub Actions / lint

There should be no empty line within import group

Check failure on line 10 in frontend/src/pages/ListAliasEntryPage.tsx

View workflow job for this annotation

GitHub Actions / lint

`components/entry/EntryImportModal` import should occur after import of `components/entity/EntityBreadcrumbs`

import { Loading } from "components/common/Loading";

Check failure on line 12 in frontend/src/pages/ListAliasEntryPage.tsx

View workflow job for this annotation

GitHub Actions / lint

'Loading' is defined but never used

Check failure on line 12 in frontend/src/pages/ListAliasEntryPage.tsx

View workflow job for this annotation

GitHub Actions / lint

'Loading' is defined but never used
import { PageHeader } from "components/common/PageHeader";
import { SubmitButton } from "components/common/SubmitButton";

Check failure on line 14 in frontend/src/pages/ListAliasEntryPage.tsx

View workflow job for this annotation

GitHub Actions / lint

'SubmitButton' is defined but never used

Check failure on line 14 in frontend/src/pages/ListAliasEntryPage.tsx

View workflow job for this annotation

GitHub Actions / lint

'SubmitButton' is defined but never used
import {

Check failure on line 15 in frontend/src/pages/ListAliasEntryPage.tsx

View workflow job for this annotation

GitHub Actions / lint

`components/entry/CopyForm` import should occur after import of `components/entity/EntityBreadcrumbs`
CopyForm as DefaultCopyForm,
CopyFormProps,
} from "components/entry/CopyForm";
import { EntryBreadcrumbs } from "components/entry/EntryBreadcrumbs";
import { usePrompt } from "hooks/usePrompt";
import { aironeApiClient } from "repository/AironeApiClient";
import { entityEntriesPath, entryDetailsPath } from "routes/Routes";
import { EntityBreadcrumbs } from "components/entity/EntityBreadcrumbs";

interface Props {
CopyForm?: FC<CopyFormProps>;
}

export const ListAliasEntryPage: FC<Props> = ({ CopyForm = DefaultCopyForm }) => {
const navigate = useNavigate();
const { enqueueSnackbar } = useSnackbar();

const [entityAnchorEl, setEntityAnchorEl] =
useState<HTMLButtonElement | null>(null);
const [openImportModal, setOpenImportModal] = React.useState(false);

const { entityId } = useTypedParams<{
entityId: number;
}>();

const entity = useAsyncWithThrow(async () => {
return await aironeApiClient.getEntity(entityId);
}, [entityId]);

// (TODO) get all corresponding aliases of entity

return (
<Box>
<EntityBreadcrumbs entity={entity.value} title="エイリアス設定" />

<PageHeader title={entity.value?.name ?? ""} description="エイリアス設定">
<Box width="50px">
<IconButton
id="entity_menu"
onClick={(e) => {
setEntityAnchorEl(e.currentTarget);
}}
>
<AppsIcon />
</IconButton>
<EntityControlMenu
entityId={entityId}
anchorElem={entityAnchorEl}
handleClose={() => setEntityAnchorEl(null)}
setOpenImportModal={setOpenImportModal}
/>
</Box>
</PageHeader>

<Container>
{/* show all Aliases that are associated with each Items */}
<Grid container spacing={2}>
<Grid item xs={4} sx={{ height: "500px" }}>
<>xs=8</>
</Grid>
<Grid item xs={8} sx={{ height: "500px" }}>
<>xs=4</>
</Grid>
</Grid>
</Container>

<EntryImportModal
openImportModal={openImportModal}
closeImportModal={() => setOpenImportModal(false)}
/>
</Box>
);
};
6 changes: 6 additions & 0 deletions frontend/src/routes/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from "react-router-dom";

import { ACLHistoryPage } from "../pages/ACLHistoryPage";
import { ListAliasEntryPage } from "../pages/ListAliasEntryPage";
import { EntryCopyPage } from "../pages/EntryCopyPage";
import { EntryDetailsPage } from "../pages/EntryDetailsPage";
import { EntryRestorePage } from "../pages/EntryRestorePage";
Expand Down Expand Up @@ -52,6 +53,7 @@ import {
groupsPath,
jobsPath,
loginPath,
listAliasPath,
newEntityPath,
newEntryPath,
newGroupPath,
Expand Down Expand Up @@ -133,6 +135,10 @@ export const AppRouter: FC<Props> = ({ customRoutes }) => {
path={entityHistoryPath(":entityId")}
element={<EntityHistoryPage />}
/>
<Route
path={listAliasPath(":entityId")}
element={<ListAliasEntryPage />}
/>
<Route path={newEntityPath()} element={<EntityEditPage />} />
<Route
path={editEntityPath(":entityId")}
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/routes/Routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ export const showEntryHistoryPath = (
// entities
export const entityHistoryPath = (entityId: number | string) =>
basePath + `entities/${entityId}/history`;
export const listAliasPath = (entityId: number | string) =>
basePath + `entities/${entityId}/alias`;
export const newEntityPath = () => basePath + "entities/new";
export const editEntityPath = (entityId: number | string) =>
basePath + `entities/${entityId}`;
Expand Down
Loading