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

Merged
merged 27 commits into from
Dec 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b8b721f
Introduced new model of AliasEntry
userlocalhost Dec 12, 2024
054c68a
Added test for create alias
hinashi Dec 12, 2024
41504a4
Fixed failed test for entry alias
hinashi Dec 12, 2024
d13d4b1
Added page component for AliasEntry
userlocalhost Dec 12, 2024
7ecc839
Wrote new API handler to retrieve Aliases which are related with spec…
userlocalhost Dec 16, 2024
4f7afc3
Added rest of CRUD API handler for Alias except for updating
userlocalhost Dec 17, 2024
40331aa
Added aliases to the entity entries API
hinashi Dec 18, 2024
250d402
Fixed failed test
hinashi Dec 18, 2024
e935f2a
Fixed lint error
hinashi Dec 18, 2024
d203059
Considering Alias for creating Item (at APIv2 processing)
userlocalhost Dec 18, 2024
34003d1
Added description of Alias at the CHANGELOG
userlocalhost Dec 18, 2024
9e226a4
Considering Alias for creating Item (at traditional processing)
userlocalhost Dec 18, 2024
70ed9f8
Considering Alias for creating Item (at traditional processing)
userlocalhost Dec 19, 2024
8e90f14
Added new API handler /entry/api/v2/alias/bulk/
userlocalhost Dec 19, 2024
6d405fb
Fixed alias entry page
hinashi Dec 20, 2024
00a292d
Added validation when restore item
hinashi Dec 24, 2024
0d1259c
Fixed problem not to be able to update Item because of inflexible dup…
userlocalhost Dec 24, 2024
360af92
Fixed an error when item was updated through traditioanl API
userlocalhost Dec 24, 2024
a2bc911
Fixed validate name for alias entry
hinashi Dec 24, 2024
186e131
Fixed test problem that make same name Item in a test
userlocalhost Dec 24, 2024
16d30dd
Update APIClient package version because of changing API inteface
userlocalhost Dec 24, 2024
2337bce
Fixed failed test
hinashi Dec 24, 2024
06c5f72
Updated apiclient package
hinashi Dec 24, 2024
29ea763
Fixed api response format
hinashi Dec 25, 2024
c3aa13a
Updated api client package
hinashi Dec 25, 2024
db7ca62
Fixed failed frontend test
hinashi Dec 25, 2024
c0b4203
Merge branch 'master' into feature/entry/introduce_alias
hinashi Dec 25, 2024
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
## In development

### Added
* Introduced a new feature to identify Item using another names that are related with
specific Item (it is named as Alias).
Contributed by @hinashi, @userlocalhost

* Enable to pass general external parameters(`extendedGeneralParameters`) to React-UI
implementation via Django template.
Contributed by @userlocalhost, @hinashi
Expand Down
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
18 changes: 18 additions & 0 deletions api_v1/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,23 @@ def side_effect():
# checking that CREATING flag is unset after finishing this processing
self.assertFalse(entry.get_status(Entry.STATUS_CREATING))

def test_create_entry_when_duplicated_alias_exists(self):
user = self.guest_login()

model = self.create_entity(user, "Mountain")
item = self.add_entry(user, "Everest", model)
item.add_alias("Chomolungma")

# send request to try to create Item that has duplicated name with another Alias
params = {
"entity": model.name,
"name": "Chomolungma",
"attrs": {},
}
resp = self.client.post("/api/v1/entry", json.dumps(params), "application/json")
self.assertEqual(resp.status_code, 400)
self.assertEqual(resp.json(), {"result": "Duplicate named Alias is existed"})

def test_post_entry_with_invalid_params(self):
admin = self.admin_login()

Expand Down Expand Up @@ -395,6 +412,7 @@ def test_post_entry_with_invalid_params(self):
# is created at the last request to create 'valid-entry'.
params = {"name": "valid-entry", "entity": entity.name, "attrs": {"ref": "r-1"}}
resp = self.client.post("/api/v1/entry", json.dumps(params), "application/json")
print("[onix-test] resp: %s" % str(resp.content.decode("utf-8")))
self.assertEqual(resp.status_code, 200)

entry = Entry.objects.get(schema=entity, name="valid-entry")
Expand Down
21 changes: 21 additions & 0 deletions api_v1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,30 @@ def _update_entry_name(entry):
status=status.HTTP_400_BAD_REQUEST,
)

# Get target Item from ID
entry = Entry.objects.get(id=sel.validated_data["id"])

# Check user has permission to update this Item
if not request.user.has_permission(entry, ACLType.Writable):
return Response(
{"result": "Permission denied to update entry"},
status=status.HTTP_400_BAD_REQUEST,
)

# Abort updating processing when duplicated named Alias exists
if not sel.validated_data["entity"].is_available(
sel.validated_data["name"], [entry.id]
):
return Response(
{"result": "Duplicate named Alias is existed"},
status=status.HTTP_400_BAD_REQUEST,
)

will_notify_update_entry = _update_entry_name(entry)

elif Entry.objects.filter(**entry_condition).exists():
entry = Entry.objects.get(**entry_condition)

if not request.user.has_permission(entry, ACLType.Writable):
return Response(
{"result": "Permission denied to update entry"},
Expand All @@ -95,6 +109,13 @@ def _update_entry_name(entry):
will_notify_update_entry = _update_entry_name(entry)

else:
# Abort creating Item when duplicated named Alias exists
if not sel.validated_data["entity"].is_available(entry_condition["name"]):
return Response(
{"result": "Duplicate named Alias is existed"},
status=status.HTTP_400_BAD_REQUEST,
)

# This is the processing just in case for safety not to create duplicate Entries
# when multiple requests passed through existance check. Even through multiple
# requests coming here, Django prevents from creating multiple Entries.
Expand Down
2 changes: 1 addition & 1 deletion apiclient/typescript-fetch/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dmm-com/airone-apiclient-typescript-fetch",
"version": "0.1.0",
"version": "0.2.1",
"description": "AirOne APIv2 client in TypeScript",
"main": "src/autogenerated/index.ts",
"scripts": {
Expand Down
Binary file added dmm-com-pagoda-core-1.0.2.tgz
Binary file not shown.
15 changes: 15 additions & 0 deletions entity/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import List

from django.conf import settings
from django.db import models
from simple_history.models import HistoricalRecords
Expand Down Expand Up @@ -107,3 +109,16 @@ 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, exclude_item_ids: List[int] = []) -> bool:
from entry.models import AliasEntry, Entry

if (
Entry.objects.filter(name=name, schema=self, is_active=True)
.exclude(id__in=exclude_item_ids)
.exists()
):
return False
if AliasEntry.objects.filter(name=name, entry__schema=self, entry__is_active=True).exists():
return False
return True
23 changes: 23 additions & 0 deletions entity/tests/test_api_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -2961,6 +2961,29 @@ def test_create_entry(self):
search_result = self._es.search(body={"query": {"term": {"name": entry.name}}})
self.assertEqual(search_result["hits"]["total"]["value"], 1)

def test_create_entry_when_duplicated_alias_exists(self):
# make an Item and Alias to prevent to creating another Item
entry: Entry = self.add_entry(self.user, "Everest", self.entity)
entry.add_alias("Chomolungma")

resp = self.client.post(
"/entity/api/v2/%s/entries/" % self.entity.id,
json.dumps({"name": "Chomolungma"}),
"application/json",
)
self.assertEqual(resp.status_code, 400)
self.assertEqual(
resp.json(),
{
"non_field_errors": [
{
"message": "A duplicated named Alias exists in this model",
"code": "AE-220000",
}
]
},
)

def test_create_entry_without_permission_entity(self):
params = {
"name": "entry1",
Expand Down
31 changes: 30 additions & 1 deletion entry/api_v2/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from airone.lib.types import AttrDefaultValue, AttrType
from entity.api_v2.serializers import EntitySerializer
from entity.models import Entity, EntityAttr
from entry.models import Attribute, AttributeValue, Entry
from entry.models import AliasEntry, Attribute, AttributeValue, Entry
from entry.settings import CONFIG as CONFIG_ENTRY
from group.models import Group
from job.models import Job, JobStatus
Expand Down Expand Up @@ -214,6 +214,22 @@ class AttrTypeField(serializers.IntegerField):
schema = EntityAttributeTypeSerializer()


class EntryAliasSerializer(serializers.ModelSerializer):
class Meta:
model = AliasEntry
fields = [
"id",
"name",
"entry",
]

def validate(self, params):
if not params["entry"].schema.is_available(params["name"]):
raise DuplicatedObjectExistsError("A duplicated named Alias exists in this model")

return params


class EntryBaseSerializer(serializers.ModelSerializer):
# This attribute toggle privileged mode that allow user to CRUD Entry without
# considering permission. This must not change from program, but declare in a
Expand All @@ -222,6 +238,7 @@ class EntryBaseSerializer(serializers.ModelSerializer):

schema = EntitySerializer(read_only=True)
deleted_user = UserBaseSerializer(read_only=True, allow_null=True)
aliases = EntryAliasSerializer(many=True, read_only=True)

class Meta:
model = Entry
Expand All @@ -233,6 +250,7 @@ class Meta:
"deleted_user",
"deleted_time",
"updated_time",
"aliases",
]
extra_kwargs = {
"id": {"read_only": True},
Expand All @@ -245,12 +263,15 @@ def validate_name(self, name: str):
schema = self.instance.schema
else:
schema = self.get_initial()["schema"]

# Check there is another Item that has same name
if name and Entry.objects.filter(name=name, schema=schema, is_active=True).exists():
# In update case, there is no problem with the same name
if not (self.instance and self.instance.name == name):
raise DuplicatedObjectExistsError("specified name(%s) already exists" % name)
if "\t" in name:
raise InvalidValueError("Names containing tab characters cannot be specified.")

return name

def _validate(self, schema: Entity, name: str, attrs: list[dict[str, Any]]):
Expand All @@ -276,6 +297,11 @@ def _validate(self, schema: Entity, name: str, attrs: list[dict[str, Any]]):
"mandatory attrs id(%s) is not specified" % mandatory_attr.id
)

exclude_items = [self.instance.id] if self.instance else []
# Check there is another Alias that has same name
if not schema.is_available(name, exclude_items):
raise DuplicatedObjectExistsError("A duplicated named Alias exists in this model")

# check attrs
for attr in attrs:
# check attrs id
Expand All @@ -296,6 +322,9 @@ def _validate(self, schema: Entity, name: str, attrs: list[dict[str, Any]]):
"validate_entry", schema.name, user, schema.name, name, attrs, self.instance
)

def get_aliases(self, obj: Entry):
return obj.aliases.all()


@extend_schema_field({})
class AttributeValueField(serializers.Field):
Expand Down
34 changes: 33 additions & 1 deletion entry/api_v2/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,39 @@
"<int:pk>/histories/",
views.EntryAPI.as_view(
{
"get": "list",
"get": "list_histories",
}
),
),
path(
"<int:pk>/alias/",
views.EntryAPI.as_view(
{
"get": "list_alias",
}
),
),
path(
"alias/",
views.EntryAliasAPI.as_view(
{
"post": "create",
}
),
),
path(
"alias/bulk/",
views.EntryAliasAPI.as_view(
{
"post": "bulk_create",
}
),
),
path(
"alias/<int:pk>",
views.EntryAliasAPI.as_view(
{
"delete": "destroy",
}
),
),
Expand Down
Loading
Loading