Compare commits

...

89 Commits
1.4.1 ... 1.5.0

Author SHA1 Message Date
39e06a43a3 build: bump version 1.4.1 -> 1.5.0 2024-12-09 15:10:29 +00:00
Jon
0ceb5a512e Merge pull request #411 from nofusscomputing/feat-next-release 2024-12-10 00:28:06 +09:30
Jon
51a2fe1141 Merge pull request #418 from nofusscomputing/2024-12-08 2024-12-09 23:55:44 +09:30
Jon
8332b31a1a docs(release): added next release version
ref: #418
2024-12-09 23:43:58 +09:30
Jon
c03b7e7d49 feat(python): update django 5.1.2 -> 5.1.4
ref: #408 #418
2024-12-09 23:28:44 +09:30
Jon
ed6cdaef8b docs(pr): add migrations task
ref: #408 #418
2024-12-09 23:26:42 +09:30
Jon
5335758c70 docs(admin): Include new UI
ref: #408 #418
2024-12-09 23:21:36 +09:30
Jon
c14ee4c4be feat(api): If global organization defined, filter from ALL organization fields
This field is not intended to be selectable

ref: #418 closes #406
2024-12-09 22:29:57 +09:30
Jon
b51ce7d513 docs(api): add test info for nav menu
ref: #409 #418
2024-12-08 18:43:52 +09:30
Jon
03c39d2e2f test(api): Nav menu permission checks for settings
ref: #418 closes #409
2024-12-08 18:38:38 +09:30
Jon
10285bedef feat(api): Add nav menu permission checks for settings
ref: #409 #418
2024-12-08 18:38:16 +09:30
Jon
b9e8caecc1 test(api): Nav menu permission checks
ref: #409 #418
2024-12-08 17:30:11 +09:30
Jon
afc0c66602 Merge pull request #417 from nofusscomputing/2024-12-06 2024-12-06 17:26:22 +09:30
Jon
41ffe5b3bc refactor(access): Settings must be an available permissions when setting team permissions
ref: #408 #417
2024-12-06 17:06:38 +09:30
Jon
e158f49a21 refactor(itam): set deviceoperatingsystem model, device field to be type onetoone
ref: #417
2024-12-06 16:40:58 +09:30
Jon
18bb2909a5 docs: update release notes re migration squash
ref: #408 #417
2024-12-06 16:34:13 +09:30
Jon
ca2da06d2c chore: squash previous releases migrations
Every release that occurs is squash ALL migrations to limit the amount of migrations

ref: #408 #417
2024-12-06 16:33:13 +09:30
Jon
17f47040d6 fix(settings): Add missing get_url function to user_settings model
ref: #412 #417 closes #410
2024-12-06 15:57:36 +09:30
Jon
c5faf3115d fix(settings): Add missing get_url function to app_settings model
ref: #410 #412 #417
2024-12-06 15:57:11 +09:30
Jon
3cdd8adf38 test(core): Correct url.selfchecks to use list view
history uses curtom view and has no detail

ref: #410 #412 #417
2024-12-06 15:30:23 +09:30
Jon
39539a4bae test(core): Dont test History for table view
this view has a seperate view, History.

ref: #410 #412 #417
2024-12-06 15:04:46 +09:30
Jon
5de8893330 fix(core): correctr the required parameters for related ticket serializer when fetching own url
ref: #410 #412 #417
2024-12-06 14:49:47 +09:30
Jon
d9b9e32019 test(settings): Dont test user settings for table view
this view not expected to be

ref: #410 #417 closes #412
2024-12-06 14:28:38 +09:30
Jon
3bce54b495 test(steeings): Dont test app settings for table view
this view not expected to be

ref: #410 #412 #417
2024-12-06 14:27:58 +09:30
Jon
34c327b7b6 test(core): Dont test related ticket for table or detail view
this view not expected to be

ref: #410 #412 #417
2024-12-06 14:27:25 +09:30
Jon
8b790b451e test(api): Refactor test so that endpoints not expected to have an endpoint or be rendered in a table wont be tested for it.
ref: #410 #412 #417
2024-12-06 14:24:54 +09:30
Jon
00bebbd8f4 fix(core): Remove requirement that ticket be specified for related tickets get_url
ref: #412 #417
2024-12-06 14:19:04 +09:30
Jon
cebb02de99 feat(api): When fething an items url dueing metadata creation, used named parameters
ref: #412
2024-12-06 14:15:47 +09:30
Jon
ebeea4f526 feat(access): Modify Admin User panel by removing perms and adding teams
ref: #408
2024-12-05 14:30:59 +09:30
Jon
c91bc6ee10 fix(access): Add missing table_fields attribute to team users model
ref: nofusscomputing/centurion_erp_ui#29
2024-12-05 07:23:42 +09:30
Jon
625cb52dd2 Merge pull request #415 from nofusscomputing/2024-12-01 2024-12-05 00:04:47 +09:30
Jon
497adc68f4 fix(api): during metadata navigation permission checks, cater for non-existant keys
ref: #410 #412 #415
2024-12-04 23:37:09 +09:30
Jon
a10f1b3694 test(settings): API Metadata checks for user settings
ref: #410 #412 #415
2024-12-04 23:22:52 +09:30
Jon
11abf177ab test(settings): API Metadata checks for external links
ref: #410 #412 #415
2024-12-04 23:22:34 +09:30
Jon
becf55e22f test(settings): API Metadata checks for app settings
ref: #410 #412 #415
2024-12-04 23:22:22 +09:30
Jon
c57d465c5b test(project_management): API Metadata checks for project type
ref: #410 #412 #415
2024-12-04 23:22:05 +09:30
Jon
741a149f33 test(project_management): API Metadata checks for project task
ref: #410 #412 #415
2024-12-04 23:21:57 +09:30
Jon
f85cc0fd3d test(project_management): API Metadata checks for project state
ref: #410 #412 #415
2024-12-04 23:21:49 +09:30
Jon
80a82605aa test(project_management): API Metadata checks for project milestone
ref: #410 #412 #415
2024-12-04 23:21:40 +09:30
Jon
d9b5ec7d41 test(project_management): API Metadata checks for project
ref: #410 #412 #415
2024-12-04 23:21:31 +09:30
Jon
fcf3d568e4 test(itim): API Metadata checks for problem ticket
ref: #410 #412 #415
2024-12-04 23:21:13 +09:30
Jon
eb3071c93d test(itim): API Metadata checks for incident ticket
ref: #410 #412 #415
2024-12-04 23:21:04 +09:30
Jon
873f241d14 test(itim): API Metadata checks for change ticket
ref: #410 #412 #415
2024-12-04 23:20:56 +09:30
Jon
da5c4d76e9 test(itim): API Metadata checks for service
ref: #410 #412 #415
2024-12-04 23:20:43 +09:30
Jon
8f1e61a7a6 test(itim): API Metadata checks for port
ref: #410 #412 #415
2024-12-04 23:20:34 +09:30
Jon
7f46daeb54 test(itim): API Metadata checks for cluster type
ref: #410 #412 #415
2024-12-04 23:20:26 +09:30
Jon
6291510ba4 test(itim): API Metadata checks for cluster
ref: #410 #412 #415
2024-12-04 23:20:20 +09:30
Jon
871cdc6b5d test(itam): API Metadata checks for software version
ref: #410 #412 #415
2024-12-04 23:20:10 +09:30
Jon
9e78aa5940 test(itam): API Metadata checks for software category
ref: #410 #412 #415
2024-12-04 23:19:59 +09:30
Jon
4df7e57ca7 test(itam): API Metadata checks for software
ref: #410 #412 #415
2024-12-04 23:19:52 +09:30
Jon
b80d8a5c64 test(itam): API Metadata checks for operating system version
ref: #410 #412 #415
2024-12-04 23:19:42 +09:30
Jon
f3ef3be883 test(itam): API Metadata checks for operating system
ref: #410 #412 #415
2024-12-04 23:19:33 +09:30
Jon
250ba96c84 test(itam): API Metadata checks for software
ref: #410 #412 #415
2024-12-04 23:19:19 +09:30
Jon
df5234e8d3 test(itam): API Metadata checks for operating system
ref: #410 #412 #415
2024-12-04 23:19:11 +09:30
Jon
c4459bd21f test(itam): API Metadata checks for device type
ref: #410 #412 #415
2024-12-04 23:18:54 +09:30
Jon
702644adc5 test(itam): API Metadata checks for device OS
ref: #410 #412 #415
2024-12-04 23:18:45 +09:30
Jon
c9caa96f98 test(itam): API Metadata checks for device model
ref: #410 #412 #415
2024-12-04 23:18:36 +09:30
Jon
eb4a132f39 test(itam): API Metadata checks for device
ref: #410 #412 #415
2024-12-04 23:18:28 +09:30
Jon
cb636fff01 test(core): API Metadata checks for ticket comment category
ref: #410 #412 #415
2024-12-04 23:18:14 +09:30
Jon
f8338ff6f0 test(core): API Metadata checks for ticket comment
ref: #410 #412 #415
2024-12-04 23:18:04 +09:30
Jon
47898d7e1d test(core): API Metadata checks for ticket category
ref: #410 #412 #415
2024-12-04 23:17:54 +09:30
Jon
2be5819839 test(core): API Metadata checks for history
ref: #410 #412 #415
2024-12-04 23:17:45 +09:30
Jon
c0d5bfad45 test(core): API Metadata checks for related tickets
ref: #410 #412 #415
2024-12-04 23:17:35 +09:30
Jon
cadb3bcac0 test(core): API Metadata checks for manufacturers
ref: #410 #412 #415
2024-12-04 23:17:19 +09:30
Jon
089f8beef2 test(config_management): API Metadata checks for config group software
ref: #410 #412 #415
2024-12-04 23:17:00 +09:30
Jon
d5771401c8 test(config_management): API Metadata checks for config groups
ref: #410 #412 #415
2024-12-04 23:16:50 +09:30
Jon
a32d942db6 test(access): API Metadata checks for request ticket
ref: #410 #412 #415
2024-12-04 23:16:31 +09:30
Jon
2ef5124ccc test(access): API Metadata checks for kb category
ref: #410 #412 #415
2024-12-04 23:16:17 +09:30
Jon
b54d710c50 test(access): API Metadata checks for kb
ref: #410 #412 #415
2024-12-04 23:16:07 +09:30
Jon
fd3bd7f04e test(api): correct metadata testcases
ref: #412 #415
2024-12-04 23:14:35 +09:30
Jon
61f6996f5e test(access): API Metadata checks for organization
ref: #412 #415
2024-12-04 20:20:35 +09:30
Jon
2b2c719e69 test(api): API Metadata test cases for navigation menu rendering
ref: #412 #415
2024-12-04 20:20:02 +09:30
Jon
f13bdf5a05 test(api): correct logic for test class attribute fetching
ref: #415
2024-12-04 20:11:18 +09:30
Jon
6010973c3b fix(core): Remove superfluous check from ticket viewset
was fething the users default org wich is not required nor was it used

ref: #415 fixes #403
2024-12-04 17:52:11 +09:30
Jon
85face7cc6 fix(access): Team permissions is not a required field
ref: #404 #415
2024-12-04 17:30:19 +09:30
Jon
38ba86b8b5 feat(access): filter permissions available
only show used permissions

ref: #415 closes #404
2024-12-04 17:30:01 +09:30
Jon
d0118e1f6f feat(api): Filter navigation menu by user permissions
if the user has the permission they will have the nav menu.

ref: #409 #415
2024-12-03 17:13:59 +09:30
Jon
827fe14369 fix(core): History query must also be for self, not just children
ref: #414 #415
2024-12-01 11:12:47 +09:30
Jon
6ed4db0502 feat(api): Add API version details to the metadata
ref: #411 nofusscomputing/centurion_erp_ui#29
2024-11-30 16:13:20 +09:30
Jon
deb93378b0 test(access): API Metadata checks for Team User model
ref: #408 #410 #411 #412
2024-11-30 15:00:43 +09:30
Jon
1904c2e28c test(access): API Metadata checks for Team model
ref: #408 #410 #411 #412
2024-11-30 15:00:26 +09:30
Jon
aaf2d23c53 test(api): API Metadata functional Test Cases
ref: #408 #411 #412
2024-11-30 14:56:00 +09:30
Jon
7c9320a84b feat(access): add back and return_url urls to team user metadata
ref: #410 #411
2024-11-30 14:03:39 +09:30
Jon
c8b6a31cd4 feat(access): add back and return_url urls to team metadata
ref: #410 #411
2024-11-30 14:03:31 +09:30
Jon
22615e46ef fix(access): correct team users table to correct data key
ref: #411
2024-11-30 13:58:47 +09:30
Jon
4ae965603c feat(api): Add back url to metadata
ref: #410 #411
2024-11-30 13:58:00 +09:30
Jon
e4ce1b539e feat(api): Add return_url to metadata
ref: #410 #411
2024-11-30 13:57:31 +09:30
Jon
cee396da3f refactor(assistance): make content the first tab for kb articles
ref: #411 closes #405
2024-11-30 12:55:59 +09:30
Jon
0786977633 refactor(api): move metadata url_return -> urls.self
ref: #411 nofusscomputing/centurion_erp_ui#29 nofusscomputing/centurion_erp_ui#33
2024-11-30 06:15:35 +09:30
120 changed files with 4272 additions and 1377 deletions

View File

@ -17,5 +17,5 @@ commitizen:
prerelease_offset: 1
tag_format: $version
update_changelog_on_bump: false
version: 1.4.1
version: 1.5.0
version_scheme: semver

View File

@ -20,6 +20,9 @@
<!-- dont remove tasks below strike through including the checkbox by enclosing in double tidle '~~' -->
- [ ] **Feature Release ONLY** :red_square: Squash migration files :red_square:
_Multiple migration files created as part of this release are to be sqauashed into a few files as possible so as to limit the number of migrations_
- [ ] :firecracker: Contains breaking-change Any Breaking change(s)?
_Breaking Change must also be notated in the commit that introduces it and in [Conventional Commit Format](https://www.conventionalcommits.org/en/v1.0.0/)._

View File

@ -1,3 +1,95 @@
## 1.5.0 (2024-12-09)
### feat
- **python**: update django 5.1.2 -> 5.1.4
- **api**: If global organization defined, filter from ALL organization fields
- **api**: Add nav menu permission checks for settings
- **api**: When fething an items url dueing metadata creation, used named parameters
- **access**: Modify Admin User panel by removing perms and adding teams
- **access**: filter permissions available
- **api**: Filter navigation menu by user permissions
- **api**: Add API version details to the metadata
- **access**: add `back` and `return_url` urls to team user metadata
- **access**: add `back` and `return_url` urls to team metadata
- **api**: Add `back` url to metadata
- **api**: Add `return_url` to metadata
### Fixes
- **settings**: Add missing `get_url` function to user_settings model
- **settings**: Add missing `get_url` function to app_settings model
- **core**: correctr the required parameters for related ticket serializer when fetching own url
- **core**: Remove requirement that ticket be specified for related tickets `get_url`
- **access**: Add missing `table_fields` attribute to team users model
- **api**: during metadata navigation permission checks, cater for non-existant keys
- **core**: Remove superfluous check from ticket viewset
- **access**: Team permissions is not a required field
- **core**: History query must also be for self, not just children
- **access**: correct team users table to correct data key
### Refactoring
- **access**: Settings must be an available permissions when setting team permissions
- **itam**: set deviceoperatingsystem model, device field to be type `onetoone`
- **assistance**: make content the first tab for kb articles
- **api**: move metadata url_return -> urls.self
### Tests
- **api**: Nav menu permission checks for settings
- **api**: Nav menu permission checks
- **core**: Correct `url.self`checks to use list view
- **core**: Dont test History for table view
- **settings**: Dont test user settings for table view
- **steeings**: Dont test app settings for table view
- **core**: Dont test related ticket for table or detail view
- **api**: Refactor test so that endpoints not expected to have an endpoint or be rendered in a table wont be tested for it.
- **settings**: API Metadata checks for user settings
- **settings**: API Metadata checks for external links
- **settings**: API Metadata checks for app settings
- **project_management**: API Metadata checks for project type
- **project_management**: API Metadata checks for project task
- **project_management**: API Metadata checks for project state
- **project_management**: API Metadata checks for project milestone
- **project_management**: API Metadata checks for project
- **itim**: API Metadata checks for problem ticket
- **itim**: API Metadata checks for incident ticket
- **itim**: API Metadata checks for change ticket
- **itim**: API Metadata checks for service
- **itim**: API Metadata checks for port
- **itim**: API Metadata checks for cluster type
- **itim**: API Metadata checks for cluster
- **itam**: API Metadata checks for software version
- **itam**: API Metadata checks for software category
- **itam**: API Metadata checks for software
- **itam**: API Metadata checks for operating system version
- **itam**: API Metadata checks for operating system
- **itam**: API Metadata checks for software
- **itam**: API Metadata checks for operating system
- **itam**: API Metadata checks for device type
- **itam**: API Metadata checks for device OS
- **itam**: API Metadata checks for device model
- **itam**: API Metadata checks for device
- **core**: API Metadata checks for ticket comment category
- **core**: API Metadata checks for ticket comment
- **core**: API Metadata checks for ticket category
- **core**: API Metadata checks for history
- **core**: API Metadata checks for related tickets
- **core**: API Metadata checks for manufacturers
- **config_management**: API Metadata checks for config group software
- **config_management**: API Metadata checks for config groups
- **access**: API Metadata checks for request ticket
- **access**: API Metadata checks for kb category
- **access**: API Metadata checks for kb
- **api**: correct metadata testcases
- **access**: API Metadata checks for organization
- **api**: API Metadata test cases for navigation menu rendering
- **api**: correct logic for test class attribute fetching
- **access**: API Metadata checks for Team User model
- **access**: API Metadata checks for Team model
- **api**: API Metadata functional Test Cases
## 1.4.1 (2024-11-30)
### Fixes

View File

@ -1,3 +1,11 @@
## Version 1.5.0
- When v1.4.0 was release the migrations were not merged. As part of the work conducted on this release the v1.4 migrations have been squashed. This should not have any effect on any system that when they updated to v1.4, they ran the migrations and they **completed successfully**. Upgrading from <1.4.0 to this release should also have no difficulties as the migrations required still exist. There are less of them, however with more work per migration.
!!! Note
If you require the previously squashed migrations for what ever reason. Clone the repo and go to commit 17f47040d6737905a1769eee5c45d9d15339fdbf, which is the commit prior to the squashing which is commit ca2da06d2cd393cabb7e172ad47dfb2dd922d952.
## Version 1.4.0
API redesign in preparation for moving the UI out of centurion to it's [own project](https://github.com/nofusscomputing/centurion_erp_ui). This release introduces a **Feature freeze** to the current UI. Only bug fixes will be done for the current UI.

View File

@ -1,5 +1,6 @@
from django.contrib import admin
from django.contrib.auth.models import Group
from django.contrib.auth.models import Group, User
from django.contrib.auth.admin import UserAdmin
from .models import *
@ -25,6 +26,40 @@ class OrganizationAdmin(admin.ModelAdmin):
list_filter = ["created"]
search_fields = ["team_name"]
admin.site.register(Organization,OrganizationAdmin)
class TeamUserInline(admin.TabularInline):
model = TeamUsers
extra = 0
readonly_fields = ['created', 'modified']
fields = ['team']
fk_name = 'user'
admin.site.unregister(User)
class UsrAdmin(UserAdmin):
fieldsets = (
(None, {"fields": ("username", "password")}),
("Personal info", {"fields": ("first_name", "last_name", "email")}),
(
"Permissions",
{
"fields": (
"is_active",
"is_staff",
"is_superuser",
),
},
),
("Important dates", {"fields": ("last_login", "date_joined")}),
)
inlines = [TeamUserInline]
admin.site.register(User,UsrAdmin)

View File

@ -23,8 +23,6 @@ def permission_queryset():
'chordcounter',
'comment',
'groupresult',
'organization'
'settings',
'usersettings',
]

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2024-10-13 15:27
# Generated by Django 5.1.2 on 2024-12-06 06:47
import access.models
import django.db.models.deletion
@ -9,11 +9,23 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0002_alter_team_options'),
('access', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterModelOptions(
name='organization',
options={'ordering': ['name'], 'verbose_name': 'Organization', 'verbose_name_plural': 'Organizations'},
),
migrations.AlterModelOptions(
name='team',
options={'ordering': ['team_name'], 'verbose_name': 'Team', 'verbose_name_plural': 'Teams'},
),
migrations.AlterModelOptions(
name='teamusers',
options={'ordering': ['user'], 'verbose_name': 'Team User', 'verbose_name_plural': 'Team Users'},
),
migrations.AlterField(
model_name='organization',
name='id',
@ -47,11 +59,31 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='team',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='team',
name='team_name',
field=models.CharField(default='', help_text='Name to give this team', max_length=50, verbose_name='Name'),
field=models.CharField(help_text='Name to give this team', max_length=50, verbose_name='Name'),
),
migrations.AlterField(
model_name='teamusers',
name='id',
field=models.AutoField(help_text='ID of this Team User', primary_key=True, serialize=False, unique=True, verbose_name='ID'),
),
migrations.AlterField(
model_name='teamusers',
name='manager',
field=models.BooleanField(blank=True, default=False, help_text='Is this user to be a manager of this team', verbose_name='manager'),
),
migrations.AlterField(
model_name='teamusers',
name='team',
field=models.ForeignKey(help_text='Team user belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='team', to='access.team', verbose_name='Team'),
),
migrations.AlterField(
model_name='teamusers',
name='user',
field=models.ForeignKey(help_text='User who will be added to the team', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User'),
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-13 06:42
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('access', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='team',
options={'ordering': ['team_name'], 'verbose_name': 'Team', 'verbose_name_plural': 'Teams'},
),
]

View File

@ -1,44 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-16 06:54
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0003_alter_organization_id_alter_organization_manager_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterModelOptions(
name='organization',
options={'ordering': ['name'], 'verbose_name': 'Organization', 'verbose_name_plural': 'Organizations'},
),
migrations.AlterModelOptions(
name='teamusers',
options={'ordering': ['user'], 'verbose_name': 'Team User', 'verbose_name_plural': 'Team Users'},
),
migrations.AlterField(
model_name='teamusers',
name='id',
field=models.AutoField(help_text='ID of this Team User', primary_key=True, serialize=False, unique=True, verbose_name='ID'),
),
migrations.AlterField(
model_name='teamusers',
name='manager',
field=models.BooleanField(blank=True, default=False, help_text='Is this user to be a manager of this team', verbose_name='manager'),
),
migrations.AlterField(
model_name='teamusers',
name='team',
field=models.ForeignKey(help_text='Team user belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='team', to='access.team', verbose_name='Team'),
),
migrations.AlterField(
model_name='teamusers',
name='user',
field=models.ForeignKey(help_text='User who will be added to the team', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.1.2 on 2024-11-07 06:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0004_alter_organization_options_alter_teamusers_options_and_more'),
]
operations = [
migrations.AlterField(
model_name='team',
name='team_name',
field=models.CharField(help_text='Name to give this team', max_length=50, verbose_name='Name'),
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 5.1.2 on 2024-11-20 02:41
import access.models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0005_alter_team_team_name'),
]
operations = [
migrations.AlterField(
model_name='team',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
]

View File

@ -369,7 +369,7 @@ class Team(Group, TenancyObject):
{
"layout": "table",
"name": "Users",
"field": "user",
"field": "users",
},
]
},
@ -489,7 +489,10 @@ class TeamUsers(SaveHistory):
page_layout: list = []
table_fields: list = []
table_fields: list = [
'user',
'manager'
]
def delete(self, using=None, keep_parents=False):

View File

@ -4,10 +4,10 @@ from rest_framework import serializers
from access.models import Organization
from api.serializers import common
from app.serializers.user import UserBaseSerializer
from core import fields as centurion_field
class OrganizationBaseSerializer(serializers.ModelSerializer):
@ -43,7 +43,6 @@ class OrganizationBaseSerializer(serializers.ModelSerializer):
class OrganizationModelSerializer(
common.CommonModelSerializer,
OrganizationBaseSerializer
):
@ -56,6 +55,7 @@ class OrganizationModelSerializer(
'teams': reverse("v2:_api_v2_organization_team-list", request=self._context['view'].request, kwargs={'organization_id': item.pk}),
}
model_notes = centurion_field.MarkdownField( required = False )
class Meta:

View File

@ -6,9 +6,10 @@ from access.models import Team
from api.serializers import common
from access.functions.permissions import permission_queryset
from access.serializers.organization import OrganizationBaseSerializer
from app.serializers.permission import PermissionBaseSerializer
from app.serializers.permission import Permission, PermissionBaseSerializer
from core import fields as centurion_field
@ -74,6 +75,7 @@ class TeamModelSerializer(
team_name = centurion_field.CharField( autolink = True )
permissions = serializers.PrimaryKeyRelatedField(many = True, queryset=permission_queryset(), required = False)
class Meta:

View File

@ -13,6 +13,7 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional
@ -278,3 +279,16 @@ class OrganizationViewSet(
):
pass
class OrganizationMetadata(
ViewSetBase,
MetadataAttributesFunctional,
MetaDataNavigationEntriesFunctional,
TestCase
):
menu_id = 'access'
menu_entry_id = 'organization'

View File

@ -6,10 +6,13 @@ import requests
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser, User
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from django.test import Client, TestCase
from rest_framework.reverse import reverse
from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
@ -199,3 +202,100 @@ class TeamViewSet(
):
pass
class TeamMetadata(
ViewSetBase,
MetadataAttributesFunctional,
TestCase,
):
def test_method_options_request_detail_data_has_key_urls_back(self):
"""Test HTTP/Options Method
Ensure the request data returned has key `urls.back`
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-detail',
kwargs=self.url_view_kwargs
),
content_type='application/json'
)
assert 'back' in response.data['urls']
def test_method_options_request_detail_data_key_urls_back_is_str(self):
"""Test HTTP/Options Method
Ensure the request data key `urls.back` is str
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-detail',
kwargs=self.url_view_kwargs
),
content_type='application/json'
)
assert type(response.data['urls']['back']) is str
def test_method_options_request_list_data_has_key_urls_return_url(self):
"""Test HTTP/Options Method
Ensure the request data returned has key `urls.return_url`
"""
client = Client()
client.force_login(self.view_user)
if self.url_kwargs:
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
response = client.options( url, content_type='application/json' )
assert 'return_url' in response.data['urls']
def test_method_options_request_list_data_key_urls_return_url_is_str(self):
"""Test HTTP/Options Method
Ensure the request data key `urls.return_url` is str
"""
client = Client()
client.force_login(self.view_user)
if self.url_kwargs:
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
response = client.options( url, content_type='application/json' )
assert type(response.data['urls']['return_url']) is str

View File

@ -6,10 +6,13 @@ import requests
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser, User
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from django.test import Client, TestCase
from rest_framework.reverse import reverse
from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
@ -208,4 +211,99 @@ class TeamUserViewSet(
TestCase
):
pass
pass
class TeamUserMetadata(
ViewSetBase,
MetadataAttributesFunctional,
TestCase,
):
def test_method_options_request_detail_data_has_key_urls_back(self):
"""Test HTTP/Options Method
Ensure the request data returned has key `urls.back`
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-detail',
kwargs=self.url_view_kwargs
),
content_type='application/json'
)
assert 'back' in response.data['urls']
def test_method_options_request_detail_data_key_urls_back_is_str(self):
"""Test HTTP/Options Method
Ensure the request data key `urls.back` is str
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-detail',
kwargs=self.url_view_kwargs
),
content_type='application/json'
)
assert type(response.data['urls']['back']) is str
def test_method_options_request_list_data_has_key_urls_return_url(self):
"""Test HTTP/Options Method
Ensure the request data returned has key `urls.return_url`
"""
client = Client()
client.force_login(self.view_user)
if self.url_kwargs:
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
response = client.options( url, content_type='application/json' )
assert 'return_url' in response.data['urls']
def test_method_options_request_list_data_key_urls_return_url_is_str(self):
"""Test HTTP/Options Method
Ensure the request data key `urls.return_url` is str
"""
client = Client()
client.force_login(self.view_user)
if self.url_kwargs:
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
response = client.options( url, content_type='application/json' )
assert type(response.data['urls']['return_url']) is str

View File

@ -1,5 +1,7 @@
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiResponse
from access.models import Organization
from access.serializers.teams import (
Team,
TeamModelSerializer,
@ -124,6 +126,24 @@ class ViewSet( ModelViewSet ):
view_description = 'Teams belonging to a single organization'
def get_back_url(self) -> str:
if(
getattr(self, '_back_url', None) is None
):
return_model = Organization.objects.get(
pk = self.kwargs['organization_id']
)
self._back_url = str(
return_model.get_url( self.request )
)
return self._back_url
def get_queryset(self):
queryset = super().get_queryset()
@ -146,3 +166,21 @@ class ViewSet( ModelViewSet ):
return globals()[str( self.model._meta.verbose_name) + 'ModelSerializer']
def get_return_url(self) -> str:
if getattr(self, '_get_return_url', None):
return self._get_return_url
if self.kwargs.get('pk', None) is None:
return_model = Organization.objects.get(
pk = self.kwargs['organization_id']
)
self._get_return_url = return_model.get_url( self.request )
return self._get_return_url
return None

View File

@ -1,5 +1,7 @@
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiResponse
from access.models import Team
from access.serializers.team_user import (
TeamUsers,
TeamUserModelSerializer,
@ -147,6 +149,24 @@ class ViewSet( ModelViewSet ):
view_description = 'Users belonging to a single team'
def get_back_url(self) -> str:
if(
getattr(self, '_back_url', None) is None
):
return_model =Team.objects.get(
pk = self.kwargs['team_id']
)
self._back_url = str(
return_model.get_url( self.request )
)
return self._back_url
def get_queryset(self):
queryset = super().get_queryset()
@ -172,3 +192,21 @@ class ViewSet( ModelViewSet ):
return globals()[str( self.model._meta.verbose_name).replace(' ', '') + 'ModelSerializer']
def get_return_url(self):
if getattr(self, '_get_return_url', None):
return self._get_return_url
if self.kwargs.get('pk', None) is None:
return_model = Team.objects.get(
pk = self.kwargs['team_id']
)
self._get_return_url = return_model.get_url( self.request )
return self._get_return_url
return None

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2024-10-13 15:27
# Generated by Django 5.1.2 on 2024-12-06 06:47
import django.db.models.deletion
from django.conf import settings

View File

@ -1,5 +1,8 @@
from django.conf import settings
from django.utils.encoding import force_str
from django.contrib.auth.models import ContentType, Permission
from rest_framework import serializers
from rest_framework_json_api.metadata import JSONAPIMetadata
from rest_framework.request import clone_request
@ -64,23 +67,39 @@ class ReactUIMetadata(OverRideJSONAPIMetadata):
metadata["description"] = view.get_view_description()
if 'pk' in view.kwargs:
metadata['urls']: dict = {}
if view.kwargs['pk']:
url_self = None
qs = view.get_queryset()[0]
if hasattr(qs, 'get_url'):
if view.kwargs.get('pk', None) is not None:
qs = view.get_queryset()[0]
if hasattr(qs, 'get_url'):
url_self = qs.get_url( request=request )
metadata['return_url'] = qs.get_url( request )
elif view.kwargs:
metadata['return_url'] = reverse('v2:' + view.basename + '-list', request = view.request, kwargs = view.kwargs )
url_self = reverse('v2:' + view.basename + '-list', request = view.request, kwargs = view.kwargs )
else:
metadata['return_url'] = reverse('v2:' + view.basename + '-list', request = view.request )
url_self = reverse('v2:' + view.basename + '-list', request = view.request )
if url_self:
metadata['urls'].update({'self': url_self})
if view.get_back_url():
metadata['urls'].update({'back': view.get_back_url()})
if view.get_return_url():
metadata['urls'].update({'return_url': view.get_return_url()})
metadata["renders"] = [
@ -123,136 +142,33 @@ class ReactUIMetadata(OverRideJSONAPIMetadata):
metadata['layout'] = view.get_page_layout()
metadata['navigation'] = [
{
"display_name": "Access",
"name": "access",
"pages": [
{
"display_name": "Organization",
"name": "organization",
"link": "/access/organization"
}
]
},
{
"display_name": "Assistance",
"name": "assistance",
"pages": [
{
"display_name": "Requests",
"name": "request",
"icon": "ticket_request",
"link": "/assistance/ticket/request"
},
{
"display_name": "Knowledge Base",
"name": "knowledge_base",
"icon": "information",
"link": "/assistance/knowledge_base"
}
]
},
{
"display_name": "ITAM",
"name": "itam",
"pages": [
{
"display_name": "Devices",
"name": "device",
"icon": "device",
"link": "/itam/device"
},
{
"display_name": "Operating System",
"name": "operating_system",
"link": "/itam/operating_system"
},
{
"display_name": "Software",
"name": "software",
"link": "/itam/software"
}
]
},
{
"display_name": "ITIM",
"name": "itim",
"pages": [
{
"display_name": "Changes",
"name": "ticket_change",
"link": "/itim/ticket/change"
},
{
"display_name": "Clusters",
"name": "cluster",
"link": "/itim/cluster"
},
{
"display_name": "Incidents",
"name": "ticket_incident",
"link": "/itim/ticket/incident"
},
{
"display_name": "Problems",
"name": "ticket_problem",
"link": "/itim/ticket/problem"
},
{
"display_name": "Services",
"name": "service",
"link": "/itim/service"
},
]
},
{
"display_name": "Config Management",
"name": "config_management",
"icon": "ansible",
"pages": [
{
"display_name": "Groups",
"name": "group",
"icon": 'config_management',
"link": "/config_management/group"
}
]
},
{
"display_name": "Project Management",
"name": "project_management",
"icon": 'project',
"pages": [
{
"display_name": "Projects",
"name": "project",
"icon": 'kanban',
"link": "/project_management/project"
}
]
},
build_repo: str = None
{
"display_name": "Settings",
"name": "settings",
"pages": [
{
"display_name": "System",
"name": "setting",
"icon": "system",
"link": "/settings"
},
{
"display_name": "Task Log",
"name": "celery_log",
# "icon": "settings",
"link": "/settings/celery_log"
}
]
}
]
if settings.BUILD_REPO:
build_repo = settings.BUILD_REPO
build_sha: str = None
if settings.BUILD_SHA:
build_sha = settings.BUILD_SHA
build_version: str = 'development'
if settings.BUILD_VERSION:
build_version = settings.BUILD_VERSION
metadata['version']: dict = {
'project_url': build_repo,
'sha': build_sha,
'version': build_version,
}
metadata['navigation'] = self.get_navigation(request.user)
return metadata
@ -334,7 +250,6 @@ class ReactUIMetadata(OverRideJSONAPIMetadata):
field_info["children"] = self.get_serializer_info(field)
if (
# not field_info.get("read_only")
hasattr(field, "choices")
):
field_info["choices"] = [
@ -353,4 +268,238 @@ class ReactUIMetadata(OverRideJSONAPIMetadata):
field.field_name in serializer.included_serializers
)
return field_info
return field_info
_nav = {
'access': {
"display_name": "Access",
"name": "access",
"pages": {
'view_organization': {
"display_name": "Organization",
"name": "organization",
"link": "/access/organization"
}
}
},
'assistance': {
"display_name": "Assistance",
"name": "assistance",
"pages": {
'core.view_ticket_request': {
"display_name": "Requests",
"name": "request",
"icon": "ticket_request",
"link": "/assistance/ticket/request"
},
'view_knowledgebase': {
"display_name": "Knowledge Base",
"name": "knowledge_base",
"icon": "information",
"link": "/assistance/knowledge_base"
}
}
},
'itam': {
"display_name": "ITAM",
"name": "itam",
"pages": {
'view_device': {
"display_name": "Devices",
"name": "device",
"icon": "device",
"link": "/itam/device"
},
'view_operatingsystem': {
"display_name": "Operating System",
"name": "operating_system",
"link": "/itam/operating_system"
},
'view_software': {
"display_name": "Software",
"name": "software",
"link": "/itam/software"
}
}
},
'itim': {
"display_name": "ITIM",
"name": "itim",
"pages": {
'core.view_ticket_change': {
"display_name": "Changes",
"name": "ticket_change",
"link": "/itim/ticket/change"
},
'view_cluster': {
"display_name": "Clusters",
"name": "cluster",
"link": "/itim/cluster"
},
'core.view_ticket_incident': {
"display_name": "Incidents",
"name": "ticket_incident",
"link": "/itim/ticket/incident"
},
'core.view_ticket_problem': {
"display_name": "Problems",
"name": "ticket_problem",
"link": "/itim/ticket/problem"
},
'view_service': {
"display_name": "Services",
"name": "service",
"link": "/itim/service"
},
}
},
'config_management': {
"display_name": "Config Management",
"name": "config_management",
"icon": "ansible",
"pages": {
'view_configgroups': {
"display_name": "Groups",
"name": "group",
"icon": 'config_management',
"link": "/config_management/group"
}
}
},
'project_management': {
"display_name": "Project Management",
"name": "project_management",
"icon": 'project',
"pages": {
'view_project': {
"display_name": "Projects",
"name": "project",
"icon": 'kanban',
"link": "/project_management/project"
}
}
},
'settings': {
"display_name": "Settings",
"name": "settings",
"pages": {
'all_settings': {
"display_name": "System",
"name": "setting",
"icon": "system",
"link": "/settings"
},
'django_celery_results.view_taskresult': {
"display_name": "Task Log",
"name": "celery_log",
# "icon": "settings",
"link": "/settings/celery_log"
}
}
}
}
def get_navigation(self, user) -> list(dict()):
"""Render the navigation menu
Check the users permissions agains `_nav`. if they have the permission, add the
menu entry to the navigation to be rendered,
**No** Menu is to be rendered that contains no menu entries.
Args:
user (User): User object from the request.
Returns:
list(dict()): Rendered navigation menu in the format the UI requires it to be.
"""
nav: list = []
processed_permissions: dict = {}
for group in user.groups.all():
for permission in group.permissions.all():
if str(permission.codename).startswith('view_'):
if not processed_permissions.get(permission.content_type.app_label, None):
processed_permissions.update({permission.content_type.app_label: {}})
if permission.codename not in processed_permissions[permission.content_type.app_label]:
processed_permissions[permission.content_type.app_label].update({str(permission.codename): '_'})
view_settings: list = [
'assistance.view_knowledgebasecategory',
'core.view_manufacturer',
'core.view_ticketcategory',
'core.view_ticketcommentcategory',
'itam.view_devicemodel',
'itam.view_devicetype',
'itam.view_softwarecategory',
'itim.view_clustertype',
'project_management.view_projectstate',
'project_management.view_projecttype',
'settings.view_appsettings',
]
for app, entry in self._nav.items():
new_menu_entry: dict = {}
new_pages: list = []
# if processed_permissions.get(app, None): # doesn't cater for `.` in perm
for permission, page in entry['pages'].items():
if permission == 'all_settings':
for setting_permission in view_settings:
app_permission = str(setting_permission).split('.')
if processed_permissions.get(app_permission[0], None):
if processed_permissions[app_permission[0]].get(app_permission[1], None):
new_pages += [ page ]
break
elif '.' in permission:
app_permission = str(permission).split('.')
if processed_permissions.get(app_permission[0], None):
if processed_permissions[app_permission[0]].get(app_permission[1], None):
new_pages += [ page ]
else:
if processed_permissions.get(app, None):
if processed_permissions[app].get(permission, None):
new_pages += [ page ]
if len(new_pages) > 0:
new_menu_entry = entry.copy()
new_menu_entry.update({ 'pages': new_pages })
nav += [ new_menu_entry ]
return nav

View File

@ -1,8 +1,32 @@
from rest_framework import serializers
from access.serializers.organization import Organization, OrganizationBaseSerializer
from core import fields as centurion_field
from settings.models.app_settings import AppSettings
class OrganizationField(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
""" Queryset Override
Override the base serializer and filter out the `global_organization`
if defined.
"""
app_settings = AppSettings.objects.all()
queryset = Organization.objects.all()
if getattr(app_settings[0], 'global_organization', None):
queryset = queryset.exclude(id=app_settings[0].global_organization.id)
return queryset
class CommonBaseSerializer(serializers.ModelSerializer):
@ -12,5 +36,18 @@ class CommonBaseSerializer(serializers.ModelSerializer):
class CommonModelSerializer(CommonBaseSerializer):
"""Common Model Serializer
model_notes = centurion_field.MarkdownField( required = False )
_**Note:** This serializer is not inherited by the organization Serializer_
_`access.serializers.organization`, this is by design_
This serializer is included within ALL model (Tenancy Model) serilaizers and is intended to be used
to add objects that ALL model serializers will require.
Args:
CommonBaseSerializer (Class): Common base serializer
"""
model_notes = centurion_field.MarkdownField( required = False )
organization = OrganizationField(required = False)

View File

@ -0,0 +1,889 @@
import pytest
from django.test import Client
from rest_framework.reverse import reverse
class MetadataAttributesFunctionalBase:
""" Functional Tests for API, HTTP/Options Method
These tests ensure that **ALL** serializers include the metaclass that adds the required
data to the HTTP Options method.
Metaclass adds data required for the UI to function correctly.
"""
app_namespace: str = None
url_name: str = None
viewset_type: str = 'list'
def test_method_options_request_list_ok(self):
"""Test HTTP/Options Method
Ensure the request returns `OK`.
"""
client = Client()
client.force_login(self.view_user)
if getattr(self, 'url_kwargs', None):
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type)
response = client.options( url, content_type='application/json' )
assert response.status_code == 200
def test_method_options_request_list_data_returned(self):
"""Test HTTP/Options Method
Ensure the request returns data.
"""
client = Client()
client.force_login(self.view_user)
if getattr(self, 'url_kwargs', None):
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type)
response = client.options( url, content_type='application/json' )
assert response.data is not None
def test_method_options_request_list_data_type(self):
"""Test HTTP/Options Method
Ensure the request data returned is of type `dict`
"""
client = Client()
client.force_login(self.view_user)
if getattr(self, 'url_kwargs', None):
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type)
response = client.options( url, content_type='application/json' )
assert type(response.data) is dict
def test_method_options_request_detail_ok(self):
"""Test HTTP/Options Method
Ensure the request returns `OK`.
"""
client = Client()
client.force_login(self.view_user)
if getattr(self, 'url_kwargs', None):
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type)
response = client.options( url, content_type='application/json' )
assert response.status_code == 200
def test_method_options_request_detail_data_returned(self):
"""Test HTTP/Options Method
Ensure the request returns data.
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-detail',
kwargs=self.url_view_kwargs
),
content_type='application/json'
)
assert response.data is not None
def test_method_options_request_detail_data_type(self):
"""Test HTTP/Options Method
Ensure the request data returned is of type `dict`
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-detail',
kwargs=self.url_view_kwargs
),
content_type='application/json'
)
assert type(response.data) is dict
def test_method_options_request_detail_data_has_key_urls(self):
"""Test HTTP/Options Method
Ensure the request data returned has key `urls`
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-detail',
kwargs=self.url_view_kwargs
),
content_type='application/json'
)
assert 'urls' in response.data
def test_method_options_request_detail_data_key_urls_is_dict(self):
"""Test HTTP/Options Method
Ensure the request data key `urls` is dict
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-detail',
kwargs=self.url_view_kwargs
),
content_type='application/json'
)
assert type(response.data['urls']) is dict
def test_method_options_request_detail_data_has_key_urls_self(self):
"""Test HTTP/Options Method
Ensure the request data returned has key `urls.self`
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-detail',
kwargs=self.url_view_kwargs
),
content_type='application/json'
)
assert 'urls' in response.data
def test_method_options_request_detail_data_key_urls_self_is_str(self):
"""Test HTTP/Options Method
Ensure the request data key `urls.self` is a string
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-detail',
kwargs=self.url_view_kwargs
),
content_type='application/json'
)
assert type(response.data['urls']['self']) is str
@pytest.mark.skip(reason='to be written')
def test_method_options_no_field_is_generic(self):
"""Test HTTP/Options Method
Fields are used for the UI to setup inputs correctly.
Ensure all fields at path `.actions.<METHOD>.<name>.type` do not have `GenericField` as the value.
"""
pass
class MetadataAttributesFunctionalTable:
"""Test cases for Metadata
These test cases are for models that are expected to
be rendered in a table.
"""
def test_method_options_request_list_data_has_key_table_fields(self):
"""Test HTTP/Options Method
Ensure the request data returned has key `table_fields`
"""
client = Client()
client.force_login(self.view_user)
if getattr(self, 'url_kwargs', None):
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type)
response = client.options( url, content_type='application/json' )
assert 'table_fields' in response.data
def test_method_options_request_list_data_key_table_fields_is_list(self):
"""Test HTTP/Options Method
Ensure the request data['table_fields'] is of type `list`
"""
client = Client()
client.force_login(self.view_user)
if getattr(self, 'url_kwargs', None):
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type)
response = client.options( url, content_type='application/json' )
assert type(response.data['table_fields']) is list
def test_method_options_request_list_data_key_table_fields_is_list_of_str(self):
"""Test HTTP/Options Method
Ensure the request data['table_fields'] list is of `str`
"""
client = Client()
client.force_login(self.view_user)
if getattr(self, 'url_kwargs', None):
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type)
response = client.options( url, content_type='application/json' )
all_string = True
for item in response.data['table_fields']:
if type(item) is not str:
all_string = False
assert all_string
class MetadataAttributesFunctionalEndpoint:
"""Test cases for Metadata
These test cases are for models that will have an
endpoint. i.e. A Detail view
"""
def test_method_options_request_detail_data_has_key_page_layout(self):
"""Test HTTP/Options Method
Ensure the request data returned has key `layout`
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-detail',
kwargs=self.url_view_kwargs
),
content_type='application/json'
)
assert 'layout' in response.data
def test_method_options_request_detail_data_key_page_layout_is_list(self):
"""Test HTTP/Options Method
Ensure the request data['layout'] is of type `list`
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-detail',
kwargs=self.url_view_kwargs
),
content_type='application/json'
)
assert type(response.data['layout']) is list
def test_method_options_request_detail_data_key_page_layout_is_list_of_dict(self):
"""Test HTTP/Options Method
Ensure the request data['layout'] list is of `dict`
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-detail',
kwargs=self.url_view_kwargs
),
content_type='application/json'
)
all_dict = True
for item in response.data['layout']:
if type(item) is not dict:
all_dict = False
assert all_dict
def test_method_options_request_detail_data_key_page_layout_dicts_key_exists_name(self):
"""Test HTTP/Options Method
Ensure the request data['layout'].x has key `name`
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-detail',
kwargs=self.url_view_kwargs
),
content_type='application/json'
)
has_key = True
for item in response.data['layout']:
if 'name' not in item:
has_key = False
assert has_key
def test_method_options_request_detail_data_key_page_layout_dicts_key_type_name(self):
"""Test HTTP/Options Method
Ensure the request data['layout'].x.[name] is of type `str`
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-detail',
kwargs=self.url_view_kwargs
),
content_type='application/json'
)
all_are_str = True
for item in response.data['layout']:
if type(item['name']) is not str:
all_are_str = False
assert all_are_str
def test_method_options_request_detail_data_key_page_layout_dicts_key_exists_sections(self):
"""Test HTTP/Options Method
Ensure the request data['layout'].x has key `sections`
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-detail',
kwargs=self.url_view_kwargs
),
content_type='application/json'
)
has_key = True
for item in response.data['layout']:
if 'sections' not in item:
has_key = False
assert has_key
def test_method_options_request_detail_data_key_page_layout_dicts_key_type_sections(self):
"""Test HTTP/Options Method
Ensure the request data['layout'].x.[sections] is of type `list`
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-detail',
kwargs=self.url_view_kwargs
),
content_type='application/json'
)
all_are_str = True
for item in response.data['layout']:
if type(item['sections']) is not list:
all_are_str = False
assert all_are_str
class MetadataAttributesFunctional(
MetadataAttributesFunctionalEndpoint,
MetadataAttributesFunctionalTable,
MetadataAttributesFunctionalBase,
):
pass
class MetaDataNavigationEntriesFunctional:
""" Test cases for the Navigation menu
Navigation menu is rendered as part of the API when a HTTP/OPTIONS
request has been made. Each menu entry requires that a user has View
permissions for that entry to be visible.
**No** menu entry is to be returned for **any** user whom does not
have the corresponding view permission.
These test cases are for any model that has a navigation menu entry.
## Tests
- Ensure add user does not have menu entry
- Ensure change user does not have menu entry
- Ensure delete user does not have menu entry
- Ensure the view user has menu entry
- No menu to return without pages for add user
- No menu to return without pages for change user
- No menu to return without pages for delete user
- No menu to return without pages for view user
"""
menu_id: str = None
""" Name of the Menu entry
Match for .navigation[i][name]
"""
menu_entry_id: str = None
"""Name of the menu entry
Match for .navigation[i][pages][i][name]
"""
app_namespace:str = None
"""application namespace"""
url_name: str = None
"""url name"""
url_kwargs: dict = None
"""View URL kwargs"""
add_user = None
""" User with add permission"""
change_user = None
""" User with change permission"""
delete_user = None
""" User with delete permission"""
view_user = None
""" User with view permission"""
def test_navigation_entry_add_user(self):
"""Test HTTP/Options Method Navigation Entry
Ensure that a user with add permission, does not
have the menu entry within navigation
"""
client = Client()
client.force_login(self.add_user)
if getattr(self, 'url_kwargs', None):
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
response = client.options(
url,
content_type='application/json'
)
no_menu_entry_found: bool = True
for nav_menu in response.data['navigation']:
if nav_menu['name'] == self.menu_id:
for menu_entry in nav_menu['pages']:
if menu_entry['name'] == self.menu_entry_id:
no_menu_entry_found = False
assert no_menu_entry_found
def test_navigation_no_empty_menu_add_user(self):
"""Test HTTP/Options Method Navigation Entry
Ensure that a user with add permission, does not
have any nave menu without pages
"""
client = Client()
client.force_login(self.add_user)
if getattr(self, 'url_kwargs', None):
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
response = client.options(
url,
content_type='application/json'
)
no_empty_menu_found: bool = True
for nav_menu in response.data['navigation']:
if len(nav_menu['pages']) == 0:
no_empty_menu_found = False
assert no_empty_menu_found
def test_navigation_entry_change_user(self):
"""Test HTTP/Options Method Navigation Entry
Ensure that a user with change permission, does not
have the menu entry within navigation
"""
client = Client()
client.force_login(self.change_user)
if getattr(self, 'url_kwargs', None):
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
response = client.options(
url,
content_type='application/json'
)
no_menu_entry_found: bool = True
for nav_menu in response.data['navigation']:
if nav_menu['name'] == self.menu_id:
for menu_entry in nav_menu['pages']:
if menu_entry['name'] == self.menu_entry_id:
no_menu_entry_found = False
assert no_menu_entry_found
def test_navigation_no_empty_menu_change_user(self):
"""Test HTTP/Options Method Navigation Entry
Ensure that a user with change permission, does not
have any nave menu without pages
"""
client = Client()
client.force_login(self.change_user)
if getattr(self, 'url_kwargs', None):
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
response = client.options(
url,
content_type='application/json'
)
no_empty_menu_found: bool = True
for nav_menu in response.data['navigation']:
if len(nav_menu['pages']) == 0:
no_empty_menu_found = False
assert no_empty_menu_found
def test_navigation_entry_delete_user(self):
"""Test HTTP/Options Method Navigation Entry
Ensure that a user with delete permission, does not
have the menu entry within navigation
"""
client = Client()
client.force_login(self.delete_user)
if getattr(self, 'url_kwargs', None):
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
response = client.options(
url,
content_type='application/json'
)
no_menu_entry_found: bool = True
for nav_menu in response.data['navigation']:
if nav_menu['name'] == self.menu_id:
for menu_entry in nav_menu['pages']:
if menu_entry['name'] == self.menu_entry_id:
no_menu_entry_found = False
assert no_menu_entry_found
def test_navigation_no_empty_menu_delete_user(self):
"""Test HTTP/Options Method Navigation Entry
Ensure that a user with delete permission, does not
have any nave menu without pages
"""
client = Client()
client.force_login(self.delete_user)
if getattr(self, 'url_kwargs', None):
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
response = client.options(
url,
content_type='application/json'
)
no_empty_menu_found: bool = True
for nav_menu in response.data['navigation']:
if len(nav_menu['pages']) == 0:
no_empty_menu_found = False
assert no_empty_menu_found
def test_navigation_entry_view_user(self):
"""Test HTTP/Options Method Navigation Entry
Ensure that a user with view permission,
has the menu entry within navigation
"""
client = Client()
client.force_login(self.view_user)
if getattr(self, 'url_kwargs', None):
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
response = client.options(
url,
content_type='application/json'
)
menu_entry_found: bool = False
for nav_menu in response.data['navigation']:
if nav_menu['name'] == self.menu_id:
for menu_entry in nav_menu['pages']:
if menu_entry['name'] == self.menu_entry_id:
menu_entry_found = True
assert menu_entry_found
def test_navigation_no_empty_menu_view_user(self):
"""Test HTTP/Options Method Navigation Entry
Ensure that a user with view permission, does not
have any nave menu without pages
"""
client = Client()
client.force_login(self.view_user)
if getattr(self, 'url_kwargs', None):
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
response = client.options(
url,
content_type='application/json'
)
no_empty_menu_found: bool = True
for nav_menu in response.data['navigation']:
if len(nav_menu['pages']) == 0:
no_empty_menu_found = False
assert no_empty_menu_found

File diff suppressed because it is too large Load Diff

View File

@ -82,6 +82,25 @@ class CommonViewSet(
view_name: str = None
def get_back_url(self) -> str:
"""Metadata Back URL
This URL is an optional URL that if required the view must
override this method. If the URL for a back operation
is not the models URL, then this method is used to return
the URL that will be used.
Defining this URL will predominatly be for sub-models. It's
recommended that the `reverse` function
(rest_framework.reverse.reverse) be used with a `request`
object.
Returns:
str: Full url in format `<protocol>://<doman name>.<tld>/api/<API version>/<model url>`
"""
return None
def get_model_documentation(self):
@ -115,6 +134,26 @@ class CommonViewSet(
return self.page_layout
def get_return_url(self) -> str:
"""Metadata return URL
This URL is an optional URL that if required the view must
override this method. If the URL for a cancel operation
is not the models URL, then this method is used to return
the URL that will be used.
Defining this URL will predominatly be for sub-models. It's
recommended that the `reverse` function
(rest_framework.reverse.reverse) be used with a `request`
object.
Returns:
str: Full url in format `<protocol>://<doman name>.<tld>/api/<API version>/<model url>`
"""
return None
def get_table_fields(self):
if len(self.table_fields) < 1:

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2024-10-13 15:27
# Generated by Django 5.1.2 on 2024-12-06 06:47
import access.models
import django.db.models.deletion
@ -8,11 +8,19 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0003_alter_organization_id_alter_organization_manager_and_more'),
('access', '0002_alter_organization_options_alter_team_options_and_more'),
('assistance', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='knowledgebase',
options={'ordering': ['title'], 'verbose_name': 'Knowledge Base', 'verbose_name_plural': 'Knowledge Base Articles'},
),
migrations.AlterModelOptions(
name='knowledgebasecategory',
options={'ordering': ['name'], 'verbose_name': 'Knowledge Base Category', 'verbose_name_plural': 'Knowledge Base Categories'},
),
migrations.RemoveField(
model_name='knowledgebasecategory',
name='slug',
@ -30,7 +38,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='knowledgebase',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='knowledgebasecategory',
@ -50,6 +58,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='knowledgebasecategory',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
]

View File

@ -1,21 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-16 06:54
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assistance', '0002_remove_knowledgebasecategory_slug_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='knowledgebase',
options={'ordering': ['title'], 'verbose_name': 'Knowledge Base', 'verbose_name_plural': 'Knowledge Base Articles'},
),
migrations.AlterModelOptions(
name='knowledgebasecategory',
options={'ordering': ['name'], 'verbose_name': 'Knowledge Base Category', 'verbose_name_plural': 'Knowledge Base Categories'},
),
]

View File

@ -1,27 +0,0 @@
# Generated by Django 5.1.2 on 2024-11-20 02:41
import access.models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0006_alter_team_organization'),
('assistance', '0003_alter_knowledgebase_options_and_more'),
]
operations = [
migrations.AlterField(
model_name='knowledgebase',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='knowledgebasecategory',
name='organization',
field=models.ForeignKey(default=None, help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
preserve_default=False,
),
]

View File

@ -250,6 +250,24 @@ class KnowledgeBase(TenancyObject):
page_layout: dict = [
{
"name": "Content",
"slug": "content",
"sections": [
{
"layout": "single",
"fields": [
'summary',
]
},
{
"layout": "single",
"fields": [
'content',
]
}
]
},
{
"name": "Details",
"slug": "details",

View File

@ -12,6 +12,7 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional
from assistance.models.knowledge_base import KnowledgeBase
@ -211,4 +212,17 @@ class KnowledgeBaseViewSet(
TestCase,
):
pass
pass
class KnowledgeBaseMetadata(
ViewSetBase,
MetadataAttributesFunctional,
MetaDataNavigationEntriesFunctional,
TestCase
):
menu_id = 'assistance'
menu_entry_id = 'knowledge_base'

View File

@ -8,6 +8,7 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
from assistance.models.knowledge_base import KnowledgeBaseCategory
@ -206,3 +207,13 @@ class KnowledgeBaseCategoryViewSet(
):
pass
class KnowledgeBaseCategoryMetadata(
ViewSetBase,
MetadataAttributesFunctional,
TestCase
):
pass

View File

@ -1,5 +1,7 @@
from django.test import TestCase
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional
from core.tests.abstract.test_ticket_viewset import Ticket, TicketViewSetBase, TicketViewSetPermissionsAPI, TicketViewSet
@ -29,3 +31,16 @@ class TicketRequestViewSet(
):
pass
class TicketRequestMetadata(
ViewSetBase,
MetadataAttributesFunctional,
MetaDataNavigationEntriesFunctional,
TestCase
):
menu_id = 'assistance'
menu_entry_id = 'request'

View File

@ -1,17 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-13 06:42
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('config_management', '0003_alter_configgroups_options_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='configgroups',
options={'ordering': ['name'], 'verbose_name': 'Config Group', 'verbose_name_plural': 'Config Groups'},
),
]

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2024-10-13 15:27
# Generated by Django 5.1.2 on 2024-12-06 06:47
import access.models
import config_management.models.groups
@ -9,12 +9,25 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0003_alter_organization_id_alter_organization_manager_and_more'),
('config_management', '0005_alter_configgroupsoftware_options'),
('itam', '0014_alter_softwarecategory_options'),
('access', '0002_alter_organization_options_alter_team_options_and_more'),
('config_management', '0003_alter_configgroups_options_and_more'),
('itam', '0004_alter_deviceoperatingsystem_device_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='configgroups',
options={'ordering': ['name'], 'verbose_name': 'Config Group', 'verbose_name_plural': 'Config Groups'},
),
migrations.AlterModelOptions(
name='configgroupsoftware',
options={'ordering': ['-action', 'software'], 'verbose_name': 'Config Group Software', 'verbose_name_plural': 'Config Group Softwares'},
),
migrations.AddField(
model_name='configgroups',
name='hosts',
field=models.ManyToManyField(blank=True, help_text='Hosts that are part of this group', to='itam.device', verbose_name='Hosts'),
),
migrations.AlterField(
model_name='configgrouphosts',
name='group',
@ -43,7 +56,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='configgrouphosts',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='configgroups',
@ -73,7 +86,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='configgroups',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='configgroups',
@ -83,7 +96,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='configgroupsoftware',
name='action',
field=models.CharField(blank=True, choices=[('1', 'Install'), ('0', 'Remove')], default=None, help_text='ACtion to perform with this software', max_length=1, null=True, verbose_name='Action'),
field=models.IntegerField(blank=True, choices=[(1, 'Install'), (0, 'Remove')], default=None, help_text='ACtion to perform with this software', null=True, verbose_name='Action'),
),
migrations.AlterField(
model_name='configgroupsoftware',
@ -108,7 +121,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='configgroupsoftware',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='configgroupsoftware',

View File

@ -1,17 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-13 06:51
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('config_management', '0004_alter_configgroups_options'),
]
operations = [
migrations.AlterModelOptions(
name='configgroupsoftware',
options={'ordering': ['-action', 'software'], 'verbose_name': 'Config Group Software', 'verbose_name_plural': 'Config Group Softwares'},
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-16 06:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('config_management', '0006_alter_configgrouphosts_group_and_more'),
('itam', '0015_alter_device_device_model_alter_device_device_type_and_more'),
]
operations = [
migrations.AddField(
model_name='configgroups',
name='hosts',
field=models.ManyToManyField(blank=True, help_text='Hosts that are part of this group', to='itam.device', verbose_name='Hosts'),
),
]

View File

@ -1,75 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-16 06:54
from django.db import migrations, models
def migrate_to_configgroups_hosts(apps, schema_editor):
if schema_editor.connection.alias != "default":
return
print('')
ConfigGroups = apps.get_model('config_management', 'ConfigGroups')
ConfigGroupHosts = apps.get_model('config_management', 'ConfigGroupHosts')
current_data = ConfigGroupHosts.objects.all()
for host in current_data:
print(f'Begin migrating host {host.host} in group {host.group}:')
config_group = ConfigGroups.objects.get(pk = host.group.id)
print(f' migrate {host.host} in group {config_group}')
config_group.hosts.add( host.host )
try:
was_migrated = ConfigGroups.objects.get(pk = host.group.id)
if host.host in was_migrated.hosts.all():
print(f' successfully migrated {host.id} {host.host} to new table')
ConfigGroupHosts.objects.get(pk = host.id).delete()
try:
ConfigGroupHosts.objects.get(pk = host.id)
print(f' Error Failed to remove old data for host {host.host}')
except ConfigGroupHosts.DoesNotExist:
print(f' Old data removed')
except ConfigGroupHosts.DoesNotExist:
print(f' Error, {host.host} was not migrated to new table')
old_data = ConfigGroupHosts.objects.all()
if len(old_data) == 0:
print(f'Successfully migrated data to new table, removing old table')
migrations.DeleteModel("ConfigGroupHosts")
class Migration(migrations.Migration):
dependencies = [
('config_management', '0007_configgroups_hosts'),
]
operations = [
migrations.RunPython(migrate_to_configgroups_hosts),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-17 03:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('config_management', '0008_move_data_configgroup_hosts'),
]
operations = [
migrations.AlterField(
model_name='configgroupsoftware',
name='action',
field=models.IntegerField(blank=True, choices=[(1, 'Install'), (0, 'Remove')], default=None, help_text='ACtion to perform with this software', null=True, verbose_name='Action'),
),
]

View File

@ -1,31 +0,0 @@
# Generated by Django 5.1.2 on 2024-11-20 02:41
import access.models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0006_alter_team_organization'),
('config_management', '0009_alter_configgroupsoftware_action'),
]
operations = [
migrations.AlterField(
model_name='configgrouphosts',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='configgroups',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='configgroupsoftware',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
]

View File

@ -12,6 +12,7 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional
from config_management.models.groups import ConfigGroups
@ -207,3 +208,16 @@ class ConfigGroupsViewSet(
):
pass
class ConfigGroupsMetadata(
ViewSetBase,
MetadataAttributesFunctional,
MetaDataNavigationEntriesFunctional,
TestCase
):
menu_id = 'config_management'
menu_entry_id = 'group'

View File

@ -12,6 +12,7 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
from config_management.models.groups import ConfigGroups, ConfigGroupSoftware, Software, SoftwareVersion
@ -247,3 +248,13 @@ class ConfigGroupSoftwareViewSet(
):
pass
class ConfigGroupSoftwareMetadata(
ViewSetBase,
MetadataAttributesFunctional,
TestCase
):
pass

View File

@ -1,23 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-11 16:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0006_ticket_milestone_ticket_opened_by_and_more'),
]
operations = [
migrations.AlterField(
model_name='history',
name='after',
field=models.JSONField(blank=True, default=None, help_text='JSON Object After Change', null=True),
),
migrations.AlterField(
model_name='history',
name='before',
field=models.JSONField(blank=True, default=None, help_text='JSON Object before Change', null=True),
),
]

View File

@ -1,7 +1,9 @@
# Generated by Django 5.1.2 on 2024-10-13 15:27
# Generated by Django 5.1.2 on 2024-12-06 06:47
import access.fields
import access.models
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
@ -9,19 +11,39 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0003_alter_organization_id_alter_organization_manager_and_more'),
('config_management', '0006_alter_configgrouphosts_group_and_more'),
('core', '0009_alter_notes_options'),
('itam', '0014_alter_softwarecategory_options'),
('itim', '0005_alter_cluster_cluster_type_alter_cluster_id_and_more'),
('access', '0002_alter_organization_options_alter_team_options_and_more'),
('config_management', '0004_alter_configgroups_options_and_more'),
('core', '0006_ticket_milestone_ticket_opened_by_and_more'),
('itam', '0004_alter_deviceoperatingsystem_device_and_more'),
('itim', '0005_alter_port_options_alter_cluster_cluster_type_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterModelOptions(
name='history',
options={'ordering': ['-created'], 'verbose_name': 'History', 'verbose_name_plural': 'History'},
),
migrations.AlterModelOptions(
name='manufacturer',
options={'ordering': ['name'], 'verbose_name': 'Manufacturer', 'verbose_name_plural': 'Manufacturers'},
),
migrations.AlterModelOptions(
name='notes',
options={'ordering': ['-created'], 'verbose_name': 'Note', 'verbose_name_plural': 'Notes'},
),
migrations.AlterModelOptions(
name='relatedtickets',
options={'ordering': ['id'], 'verbose_name': 'Related Ticket', 'verbose_name_plural': 'Related Tickets'},
),
migrations.AlterModelOptions(
name='ticketcomment',
options={'ordering': ['created', 'ticket', 'parent_id'], 'verbose_name': 'Ticket Comment', 'verbose_name_plural': 'Ticket Comments'},
),
migrations.AlterField(
model_name='history',
name='action',
field=models.IntegerField(choices=[('1', 'Create'), ('2', 'Update'), ('3', 'Delete')], default=None, help_text='History action performed', null=True, verbose_name='Action'),
field=models.IntegerField(choices=[(1, 'Create'), (2, 'Update'), (3, 'Delete')], default=None, help_text='History action performed', null=True, verbose_name='Action'),
),
migrations.AlterField(
model_name='history',
@ -78,6 +100,11 @@ class Migration(migrations.Migration):
name='model_notes',
field=models.TextField(blank=True, default=None, help_text='Tid bits of information', null=True, verbose_name='Notes'),
),
migrations.AlterField(
model_name='manufacturer',
name='modified',
field=access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, help_text='Date and time of last modification', verbose_name='Modified'),
),
migrations.AlterField(
model_name='manufacturer',
name='name',
@ -86,7 +113,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='manufacturer',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='notes',
@ -116,7 +143,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='notes',
name='note',
field=models.TextField(blank=True, default=None, help_text='The tid bit you wish to add', null=True, verbose_name='Note'),
field=models.TextField(help_text='The tid bit you wish to add', verbose_name='Note'),
),
migrations.AlterField(
model_name='notes',
@ -126,7 +153,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='notes',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='notes',
@ -151,7 +178,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='relatedtickets',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='ticket',
@ -161,7 +188,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='ticket',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='ticketcategory',
@ -176,12 +203,12 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='ticketcategory',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='ticketcomment',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='ticketcommentcategory',
@ -196,11 +223,11 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='ticketcommentcategory',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='ticketlinkeditem',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-13 06:58
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('core', '0007_alter_history_after_alter_history_before'),
]
operations = [
migrations.AlterModelOptions(
name='manufacturer',
options={'ordering': ['name'], 'verbose_name': 'Manufacturer', 'verbose_name_plural': 'Manufacturers'},
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-13 07:02
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('core', '0008_alter_manufacturer_options'),
]
operations = [
migrations.AlterModelOptions(
name='notes',
options={'ordering': ['-created'], 'verbose_name': 'Note', 'verbose_name_plural': 'Notes'},
),
]

View File

@ -1,22 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-18 03:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0010_alter_history_action_alter_history_after_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='history',
options={'ordering': ['-created'], 'verbose_name': 'History', 'verbose_name_plural': 'History'},
),
migrations.AlterField(
model_name='history',
name='action',
field=models.IntegerField(choices=[(1, 'Create'), (2, 'Update'), (3, 'Delete')], default=None, help_text='History action performed', null=True, verbose_name='Action'),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-19 02:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0011_alter_history_options_alter_history_action'),
]
operations = [
migrations.AlterField(
model_name='notes',
name='note',
field=models.TextField(default='-', help_text='The tid bit you wish to add', verbose_name='Note'),
preserve_default=False,
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-19 03:58
import access.fields
import django.utils.timezone
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('core', '0012_alter_notes_note'),
]
operations = [
migrations.AlterField(
model_name='manufacturer',
name='modified',
field=access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, help_text='Date and time of last modification', verbose_name='Modified'),
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-26 13:00
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('core', '0013_alter_manufacturer_modified'),
]
operations = [
migrations.AlterModelOptions(
name='ticketcomment',
options={'ordering': ['created', 'ticket', 'parent_id'], 'verbose_name': 'Comment', 'verbose_name_plural': 'Comments'},
),
]

View File

@ -1,21 +0,0 @@
# Generated by Django 5.1.2 on 2024-11-03 14:21
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('core', '0014_alter_ticketcomment_options'),
]
operations = [
migrations.AlterModelOptions(
name='relatedtickets',
options={'ordering': ['id'], 'verbose_name': 'Related Ticket', 'verbose_name_plural': 'Related Tickets'},
),
migrations.AlterModelOptions(
name='ticketcomment',
options={'ordering': ['created', 'ticket', 'parent_id'], 'verbose_name': 'Ticket Comment', 'verbose_name_plural': 'Ticket Comments'},
),
]

View File

@ -1,56 +0,0 @@
# Generated by Django 5.1.2 on 2024-11-20 02:41
import access.models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0006_alter_team_organization'),
('core', '0015_alter_relatedtickets_options_and_more'),
]
operations = [
migrations.AlterField(
model_name='manufacturer',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='notes',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='relatedtickets',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='ticket',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='ticketcategory',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='ticketcomment',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='ticketcommentcategory',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='ticketlinkeditem',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
]

View File

@ -1186,11 +1186,7 @@ class RelatedTickets(TenancyObject):
]
def get_url( self, ticket_id, request = None ) -> str:
if not ticket_id:
return ''
def get_url( self, request = None ) -> str:
if request:
@ -1198,7 +1194,7 @@ class RelatedTickets(TenancyObject):
"v2:_api_v2_ticket_related-detail",
request = request,
kwargs={
'ticket_id': ticket_id,
'ticket_id': self.from_ticket_id.id,
'pk': self.id
}
)
@ -1206,7 +1202,7 @@ class RelatedTickets(TenancyObject):
return reverse(
"v2:_api_v2_ticket_related-detail",
kwargs={
'ticket_id': ticket_id,
'ticket_id': self.from_ticket_id.id,
'pk': self.id
}
)

View File

@ -81,7 +81,7 @@ class RelatedTicketModelSerializer(RelatedTicketBaseSerializer):
ticket_id = int(self._kwargs['context']['view'].kwargs['ticket_id'])
return {
'_self': item.get_url( ticket_id = ticket_id, request = request ),
'_self': item.get_url( request = request ),
}

View File

@ -12,6 +12,7 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
from core.models.manufacturer import Manufacturer
@ -207,3 +208,13 @@ class ManufacturerViewSet(
):
pass
class ManufacturerMetadata(
ViewSetBase,
MetadataAttributesFunctional,
TestCase
):
pass

View File

@ -15,16 +15,15 @@ from api.tests.abstract.api_permissions_viewset import (
APIPermissionDelete,
APIPermissionView,
)
from api.tests.abstract.test_metadata_functional import (
MetadataAttributesFunctionalBase
)
from core.models.ticket.ticket import Ticket, RelatedTickets
class RelatedTicketsPermissionsAPI(
APIPermissionDelete,
APIPermissionView,
TestCase,
):
class ViewSetBase:
model = RelatedTickets
@ -209,6 +208,13 @@ class RelatedTicketsPermissionsAPI(
class RelatedTicketsPermissionsAPI(
ViewSetBase,
APIPermissionDelete,
APIPermissionView,
TestCase,
):
def test_add_has_permission_post_not_allowed(self):
""" Check correct permission for add
@ -273,3 +279,13 @@ class RelatedTicketsPermissionsAPI(
"""
pass
class RelatedTicketsMetadata(
ViewSetBase,
MetadataAttributesFunctionalBase,
TestCase
):
pass

View File

@ -12,6 +12,10 @@ from django.test import Client, TestCase
from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissionView
from api.tests.abstract.test_metadata_functional import (
MetadataAttributesFunctionalBase,
MetadataAttributesFunctionalEndpoint,
)
from core.models.history import History
@ -19,7 +23,7 @@ from itam.models.device import Device
class HistoryPermissionsAPI(APIPermissionView, TestCase):
class ViewSetBase:
model = History
@ -218,6 +222,15 @@ class HistoryPermissionsAPI(APIPermissionView, TestCase):
user = self.different_organization_user
)
class HistoryPermissionsAPI(
ViewSetBase,
APIPermissionView,
TestCase
):
def test_view_list_has_permission(self):
""" Check correct permission for view
@ -326,3 +339,61 @@ class HistoryPermissionsAPI(APIPermissionView, TestCase):
pass
class HistoryMetadata(
ViewSetBase,
MetadataAttributesFunctionalEndpoint,
MetadataAttributesFunctionalBase,
TestCase
):
def test_method_options_request_detail_data_has_key_urls_self(self):
"""Test HTTP/Options Method
This is a custom test of a test with the same name.
history view has no detail view, due to using a custom
view "history",
Ensure the request data returned has key `urls.self`
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-list',
kwargs=self.url_kwargs
),
content_type='application/json'
)
assert 'urls' in response.data
def test_method_options_request_detail_data_key_urls_self_is_str(self):
"""Test HTTP/Options Method
This is a custom test of a test with the same name.
history view has no detail view, due to using a custom
view "history",
Ensure the request data key `urls.self` is a string
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-list',
kwargs=self.url_kwargs
),
content_type='application/json'
)
assert type(response.data['urls']['self']) is str

View File

@ -12,6 +12,7 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
from core.models.ticket.ticket_category import TicketCategory
@ -203,3 +204,13 @@ class TicketCategoryViewSet(
):
pass
class TicketCategoryMetadata(
ViewSetBase,
MetadataAttributesFunctional,
TestCase
):
pass

View File

@ -6,6 +6,7 @@ from django.test import Client, TestCase
from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
from core.models.ticket.ticket_comment import Ticket, TicketComment
@ -13,10 +14,7 @@ from settings.models.user_settings import UserSettings
class TicketCommentPermissionsAPI(
APIPermissions,
TestCase
):
class ViewSetBase:
""" Test Cases common to ALL ticket types """
model = TicketComment
@ -230,6 +228,11 @@ class TicketCommentPermissionsAPI(
)
class TicketCommentPermissionsAPI(
ViewSetBase,
APIPermissions,
TestCase
):
def test_returned_results_only_user_orgs(self):
"""Test not required
@ -238,3 +241,13 @@ class TicketCommentPermissionsAPI(
"""
pass
class TicketCommentMetadata(
ViewSetBase,
MetadataAttributesFunctional,
TestCase
):
pass

View File

@ -12,6 +12,7 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
from core.models.ticket.ticket_comment_category import TicketCommentCategory
@ -203,3 +204,13 @@ class TicketCommentCategoryViewSet(
):
pass
class TicketCommentCategoryMetadata(
ViewSetBase,
MetadataAttributesFunctional,
TestCase
):
pass

View File

@ -1,3 +1,5 @@
from django.db.models import Q
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse
from api.viewsets.common import ModelViewSet
@ -46,9 +48,10 @@ class ViewSet(ModelViewSet):
queryset = super().get_queryset()
self.queryset = queryset.filter(
item_parent_class = self.kwargs['model_class'],
item_parent_pk = self.kwargs['model_id']
).order_by('-created')
Q(item_pk = self.kwargs['model_id'], item_class = self.kwargs['model_class'])
|
Q(item_parent_pk = self.kwargs['model_id'], item_parent_class = self.kwargs['model_class'])
)
return self.queryset

View File

@ -251,17 +251,6 @@ class TicketViewSet(ModelViewSet):
if (
self.action == 'list'
):
user_settings = UserSettings.objects.get(
user = self.request.user
)
organization = user_settings.default_organization.id
elif (
self.action == 'create'
):

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2024-10-13 15:27
# Generated by Django 5.1.2 on 2024-12-06 06:47
import access.models
import django.db.models.deletion
@ -9,11 +9,52 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0003_alter_organization_id_alter_organization_manager_and_more'),
('itam', '0014_alter_softwarecategory_options'),
('access', '0002_alter_organization_options_alter_team_options_and_more'),
('core', '0007_alter_history_options_alter_manufacturer_options_and_more'),
('itam', '0004_alter_deviceoperatingsystem_device_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='device',
options={'ordering': ['name', 'organization'], 'verbose_name': 'Device', 'verbose_name_plural': 'Devices'},
),
migrations.AlterModelOptions(
name='devicemodel',
options={'ordering': ['manufacturer', 'name'], 'verbose_name': 'Device Model', 'verbose_name_plural': 'Device Models'},
),
migrations.AlterModelOptions(
name='deviceoperatingsystem',
options={'ordering': ['device'], 'verbose_name': 'Device Operating System', 'verbose_name_plural': 'Device Operating Systems'},
),
migrations.AlterModelOptions(
name='devicesoftware',
options={'ordering': ['-action', 'software'], 'verbose_name': 'Device Software', 'verbose_name_plural': 'Device Softwares'},
),
migrations.AlterModelOptions(
name='devicetype',
options={'ordering': ['name'], 'verbose_name': 'Device Type', 'verbose_name_plural': 'Device Types'},
),
migrations.AlterModelOptions(
name='operatingsystem',
options={'ordering': ['name'], 'verbose_name': 'Operating System', 'verbose_name_plural': 'Operating Systems'},
),
migrations.AlterModelOptions(
name='operatingsystemversion',
options={'ordering': ['name'], 'verbose_name': 'Operating System Version', 'verbose_name_plural': 'Operating System Versions'},
),
migrations.AlterModelOptions(
name='software',
options={'ordering': ['name', 'publisher__name'], 'verbose_name': 'Software', 'verbose_name_plural': 'Softwares'},
),
migrations.AlterModelOptions(
name='softwarecategory',
options={'ordering': ['name'], 'verbose_name': 'Software Category', 'verbose_name_plural': 'Software Categories'},
),
migrations.AlterModelOptions(
name='softwareversion',
options={'ordering': ['name'], 'verbose_name': 'Software Version', 'verbose_name_plural': 'Software Versions'},
),
migrations.AlterField(
model_name='device',
name='device_model',
@ -52,7 +93,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='device',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='devicemodel',
@ -82,12 +123,12 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='devicemodel',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='deviceoperatingsystem',
name='device',
field=models.ForeignKey(help_text='Device for the Operating System', on_delete=django.db.models.deletion.CASCADE, to='itam.device', verbose_name='Device'),
field=models.ForeignKey(help_text='Device for the Operating System', on_delete=django.db.models.deletion.CASCADE, to='itam.device', unique=True, verbose_name='Device'),
),
migrations.AlterField(
model_name='deviceoperatingsystem',
@ -117,13 +158,18 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='deviceoperatingsystem',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='deviceoperatingsystem',
name='version',
field=models.CharField(help_text='Version detected as installed', max_length=15, verbose_name='Installed Version'),
),
migrations.AlterField(
model_name='devicesoftware',
name='action',
field=models.IntegerField(blank=True, choices=[(1, 'Install'), (0, 'Remove')], default=None, help_text='Action to perform', null=True, verbose_name='Action'),
),
migrations.AlterField(
model_name='devicesoftware',
name='device',
@ -134,6 +180,11 @@ class Migration(migrations.Migration):
name='id',
field=models.AutoField(help_text='ID of this item', primary_key=True, serialize=False, unique=True, verbose_name='ID'),
),
migrations.AlterField(
model_name='devicesoftware',
name='installed',
field=models.DateTimeField(blank=True, help_text='Date detected as installed', null=True, verbose_name='Date Installed'),
),
migrations.AlterField(
model_name='devicesoftware',
name='installedversion',
@ -152,13 +203,18 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='devicesoftware',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='devicesoftware',
name='software',
field=models.ForeignKey(help_text='Software Name', on_delete=django.db.models.deletion.CASCADE, to='itam.software', verbose_name='Software'),
),
migrations.AlterField(
model_name='devicesoftware',
name='version',
field=models.ForeignKey(blank=True, default=None, help_text='Version to install', null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.softwareversion', verbose_name='Desired Version'),
),
migrations.AlterField(
model_name='devicetype',
name='id',
@ -182,7 +238,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='devicetype',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='operatingsystem',
@ -207,7 +263,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='operatingsystem',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='operatingsystem',
@ -237,12 +293,12 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='operatingsystemversion',
name='operating_system',
field=models.ForeignKey(help_text='Operating system this version applies to', on_delete=django.db.models.deletion.CASCADE, to='itam.operatingsystem', verbose_name='Operaating System'),
field=models.ForeignKey(help_text='Operating system this version applies to', on_delete=django.db.models.deletion.CASCADE, to='itam.operatingsystem', verbose_name='Operating System'),
),
migrations.AlterField(
model_name='operatingsystemversion',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='software',
@ -272,7 +328,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='software',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='software',
@ -302,7 +358,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='softwarecategory',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='softwareversion',
@ -327,7 +383,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='softwareversion',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='softwareversion',

View File

@ -1,34 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-11 16:03
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('itam', '0004_alter_deviceoperatingsystem_device_and_more'),
]
operations = [
migrations.AlterField(
model_name='devicesoftware',
name='action',
field=models.CharField(blank=True, choices=[('1', 'Install'), ('0', 'Remove')], default=None, help_text='Action to perform', max_length=1, null=True, verbose_name='Action'),
),
migrations.AlterField(
model_name='devicesoftware',
name='installed',
field=models.DateTimeField(blank=True, help_text='Date detected as installed', null=True, verbose_name='Date Installed'),
),
migrations.AlterField(
model_name='devicesoftware',
name='installedversion',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='installedversion', to='itam.softwareversion', verbose_name='Installed Version'),
),
migrations.AlterField(
model_name='devicesoftware',
name='version',
field=models.ForeignKey(blank=True, default=None, help_text='Version to install', null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.softwareversion', verbose_name='Desired Version'),
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-13 06:42
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('itam', '0005_alter_devicesoftware_action_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='device',
options={'ordering': ['name', 'organization'], 'verbose_name': 'Device', 'verbose_name_plural': 'Devices'},
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 5.1.2 on 2024-12-06 07:09
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('itam', '0005_alter_device_options_alter_devicemodel_options_and_more'),
]
operations = [
migrations.AlterField(
model_name='deviceoperatingsystem',
name='device',
field=models.OneToOneField(help_text='Device for the Operating System', on_delete=django.db.models.deletion.CASCADE, to='itam.device', verbose_name='Device'),
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-13 07:26
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('itam', '0006_alter_device_options'),
]
operations = [
migrations.AlterModelOptions(
name='devicemodel',
options={'ordering': ['manufacturer', 'name'], 'verbose_name': 'Device Model', 'verbose_name_plural': 'Device Models'},
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-13 07:33
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('itam', '0007_alter_devicemodel_options'),
]
operations = [
migrations.AlterModelOptions(
name='deviceoperatingsystem',
options={'ordering': ['device'], 'verbose_name': 'Device Operating System', 'verbose_name_plural': 'Device Operating Systems'},
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-13 07:39
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('itam', '0008_alter_deviceoperatingsystem_options'),
]
operations = [
migrations.AlterModelOptions(
name='devicesoftware',
options={'ordering': ['-action', 'software'], 'verbose_name': 'Device Software', 'verbose_name_plural': 'Device Softwares'},
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-13 07:42
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('itam', '0009_alter_devicesoftware_options'),
]
operations = [
migrations.AlterModelOptions(
name='devicetype',
options={'ordering': ['name'], 'verbose_name': 'Device Type', 'verbose_name_plural': 'Device Types'},
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-13 07:51
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('itam', '0010_alter_devicetype_options'),
]
operations = [
migrations.AlterModelOptions(
name='operatingsystem',
options={'ordering': ['name'], 'verbose_name': 'Operating System', 'verbose_name_plural': 'Operating Systems'},
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-13 07:57
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('itam', '0011_alter_operatingsystem_options'),
]
operations = [
migrations.AlterModelOptions(
name='operatingsystemversion',
options={'ordering': ['name'], 'verbose_name': 'Operating System Version', 'verbose_name_plural': 'Operating System Versions'},
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-13 08:06
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('itam', '0012_alter_operatingsystemversion_options'),
]
operations = [
migrations.AlterModelOptions(
name='software',
options={'ordering': ['name', 'publisher__name'], 'verbose_name': 'Software', 'verbose_name_plural': 'Softwares'},
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-13 08:15
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('itam', '0013_alter_software_options'),
]
operations = [
migrations.AlterModelOptions(
name='softwarecategory',
options={'ordering': ['name'], 'verbose_name': 'Software Category', 'verbose_name_plural': 'Software Categories'},
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-17 03:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('itam', '0015_alter_device_device_model_alter_device_device_type_and_more'),
]
operations = [
migrations.AlterField(
model_name='devicesoftware',
name='action',
field=models.IntegerField(blank=True, choices=[(1, 'Install'), (0, 'Remove')], default=None, help_text='Action to perform', null=True, verbose_name='Action'),
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-20 07:26
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('itam', '0016_alter_devicesoftware_action'),
]
operations = [
migrations.AlterModelOptions(
name='softwareversion',
options={'ordering': ['name'], 'verbose_name': 'Software Version', 'verbose_name_plural': 'Software Versions'},
),
]

View File

@ -1,71 +0,0 @@
# Generated by Django 5.1.2 on 2024-11-20 02:43
import access.models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0006_alter_team_organization'),
('itam', '0017_alter_softwareversion_options'),
]
operations = [
migrations.AlterField(
model_name='device',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='devicemodel',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='deviceoperatingsystem',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='devicesoftware',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='devicetype',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='operatingsystem',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='operatingsystemversion',
name='operating_system',
field=models.ForeignKey(help_text='Operating system this version applies to', on_delete=django.db.models.deletion.CASCADE, to='itam.operatingsystem', verbose_name='Operating System'),
),
migrations.AlterField(
model_name='operatingsystemversion',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='software',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='softwarecategory',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='softwareversion',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 5.1.2 on 2024-11-20 02:43
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('itam', '0018_alter_device_organization_and_more'),
]
operations = [
migrations.AlterField(
model_name='deviceoperatingsystem',
name='device',
field=models.ForeignKey(help_text='Device for the Operating System', on_delete=django.db.models.deletion.CASCADE, to='itam.device', unique=True, verbose_name='Device'),
),
]

View File

@ -662,7 +662,7 @@ class DeviceOperatingSystem(DeviceCommonFields, SaveHistory):
verbose_name_plural = 'Device Operating Systems'
device = models.ForeignKey(
device = models.OneToOneField(
Device,
blank = False,
help_text = 'Device for the Operating System',

View File

@ -6,9 +6,10 @@ from django.shortcuts import reverse
from django.test import Client, TestCase
from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional
from itam.models.device import Device
@ -198,4 +199,17 @@ class DeviceViewSet(
TestCase
):
pass
pass
class DeviceMetadata(
ViewSetBase,
MetadataAttributesFunctional,
MetaDataNavigationEntriesFunctional,
TestCase
):
menu_id = 'itam'
menu_entry_id = 'device'

View File

@ -8,6 +8,7 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
from itam.models.device import DeviceModel
@ -199,3 +200,13 @@ class DeviceModelViewSet(
):
pass
class DeviceModelMetadata(
ViewSetBase,
MetadataAttributesFunctional,
TestCase
):
pass

View File

@ -8,6 +8,7 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
from itam.serializers.device_operating_system import Device, DeviceOperatingSystem, DeviceOperatingSystemModelSerializer
from itam.models.operating_system import OperatingSystem, OperatingSystemVersion
@ -257,3 +258,13 @@ class DeviceOperatingViewSet(
):
pass
class DeviceOperatingMetadata(
ViewSetBase,
MetadataAttributesFunctional,
TestCase
):
pass

View File

@ -8,6 +8,7 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
from itam.models.device import DeviceType
@ -198,4 +199,14 @@ class DeviceTypeViewSet(
TestCase,
):
pass
pass
class DeviceTypeMetadata(
ViewSetBase,
MetadataAttributesFunctional,
TestCase
):
pass

View File

@ -8,6 +8,7 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissionView
from api.tests.abstract.api_serializer_viewset import SerializerView
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
from itam.serializers.device_operating_system import Device, DeviceOperatingSystem, DeviceOperatingSystemModelSerializer
from itam.models.operating_system import OperatingSystem, OperatingSystemVersion
@ -180,3 +181,13 @@ class OperatingSystemInstallsViewSet(
):
pass
class OperatingSystemInstallsMetadata(
ViewSetBase,
MetadataAttributesFunctional,
TestCase
):
pass

View File

@ -8,6 +8,7 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
from itam.models.device import Device, DeviceSoftware
from itam.models.software import Software
@ -228,4 +229,14 @@ class SoftwareInstallsViewSet(
TestCase
):
pass
pass
class SoftwareInstallsMetadata(
ViewSetBase,
MetadataAttributesFunctional,
TestCase
):
pass

View File

@ -8,6 +8,7 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional
from itam.models.operating_system import OperatingSystem
@ -198,4 +199,17 @@ class OperatingSystemViewSet(
TestCase,
):
pass
pass
class OperatingSystemMetadata(
ViewSetBase,
MetadataAttributesFunctional,
MetaDataNavigationEntriesFunctional,
TestCase
):
menu_id = 'itam'
menu_entry_id = 'operating_system'

View File

@ -8,6 +8,7 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
from itam.models.operating_system import OperatingSystem, OperatingSystemVersion
@ -205,3 +206,13 @@ class OperatingSystemVersionPermissionsAPI(ViewSetBase, APIPermissions, TestCase
class OperatingSystemVersionViewSetBase(ViewSetBase, SerializersTestCases, TestCase):
pass
class OperatingSystemVersionMetadata(
ViewSetBase,
MetadataAttributesFunctional,
TestCase
):
pass

View File

@ -8,6 +8,7 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional
from itam.models.software import Software
@ -191,3 +192,16 @@ class SoftwarePermissionsAPI(ViewSetBase, APIPermissions, TestCase):
class SoftwareViewSet(ViewSetBase, SerializersTestCases, TestCase):
pass
class SoftwareMetadata(
ViewSetBase,
MetadataAttributesFunctional,
MetaDataNavigationEntriesFunctional,
TestCase
):
menu_id = 'itam'
menu_entry_id = 'software'

View File

@ -8,6 +8,7 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
from itam.models.software import SoftwareCategory
@ -190,4 +191,14 @@ class SoftwareCategoryPermissionsAPI(ViewSetBase, APIPermissions, TestCase):
class SoftwareCategoryViewSet(ViewSetBase, SerializersTestCases, TestCase):
pass
pass
class SoftwareCategoryMetadata(
ViewSetBase,
MetadataAttributesFunctional,
TestCase
):
pass

View File

@ -8,6 +8,7 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
from itam.models.software import Software, SoftwareVersion
@ -203,4 +204,14 @@ class SoftwareVersionPermissionsAPI(ViewSetBase, APIPermissions, TestCase):
class SoftwareVersionViewSet(ViewSetBase, SerializersTestCases, TestCase):
pass
pass
class SoftwareVersionMetadata(
ViewSetBase,
MetadataAttributesFunctional,
TestCase
):
pass

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2024-10-13 15:27
# Generated by Django 5.1.2 on 2024-12-06 06:47
import access.models
import django.db.models.deletion
@ -8,11 +8,15 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0003_alter_organization_id_alter_organization_manager_and_more'),
('access', '0002_alter_organization_options_alter_team_options_and_more'),
('itim', '0004_alter_service_config_key_variable'),
]
operations = [
migrations.AlterModelOptions(
name='port',
options={'ordering': ['number', 'protocol'], 'verbose_name': 'Port', 'verbose_name_plural': 'Ports'},
),
migrations.AlterField(
model_name='cluster',
name='cluster_type',
@ -36,7 +40,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='cluster',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='cluster',
@ -61,7 +65,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='clustertype',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='port',
@ -81,7 +85,12 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='port',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='port',
name='protocol',
field=models.CharField(choices=[('TCP', 'TCP'), ('UDP', 'UDP')], help_text='Layer 4 Network Protocol', max_length=3, verbose_name='Protocol'),
),
migrations.AlterField(
model_name='service',
@ -101,6 +110,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='service',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
]

View File

@ -1,22 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-21 11:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('itim', '0005_alter_cluster_cluster_type_alter_cluster_id_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='port',
options={'ordering': ['number', 'protocol'], 'verbose_name': 'Port', 'verbose_name_plural': 'Ports'},
),
migrations.AlterField(
model_name='port',
name='protocol',
field=models.CharField(choices=[('TCP', 'TCP'), ('UDP', 'UDP')], help_text='Layer 4 Network Protocol', max_length=3, verbose_name='Protocol'),
),
]

View File

@ -1,36 +0,0 @@
# Generated by Django 5.1.2 on 2024-11-20 02:41
import access.models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0006_alter_team_organization'),
('itim', '0006_alter_port_options_alter_port_protocol'),
]
operations = [
migrations.AlterField(
model_name='cluster',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='clustertype',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='port',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='service',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
]

View File

@ -8,6 +8,7 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional
from itim.models.clusters import Cluster
@ -190,4 +191,17 @@ class ClusterPermissionsAPI(ViewSetBase, APIPermissions, TestCase):
class ClusterViewSet(ViewSetBase, SerializersTestCases, TestCase):
pass
pass
class ClusterMetadata(
ViewSetBase,
MetadataAttributesFunctional,
MetaDataNavigationEntriesFunctional,
TestCase
):
menu_id = 'itim'
menu_entry_id = 'cluster'

View File

@ -8,6 +8,7 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
from itim.models.clusters import ClusterType
@ -191,4 +192,14 @@ class ClusterTypePermissionsAPI(ViewSetBase, APIPermissions, TestCase):
class ClusterTypeViewSet(ViewSetBase, SerializersTestCases, TestCase):
pass
pass
class ClusterTypeMetadata(
ViewSetBase,
MetadataAttributesFunctional,
TestCase
):
pass

View File

@ -8,6 +8,7 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
from itim.models.services import Port
@ -193,4 +194,14 @@ class PortPermissionsAPI(ViewSetBase, APIPermissions, TestCase):
class PortViewSet(ViewSetBase, SerializersTestCases, TestCase):
pass
pass
class PortMetadata(
ViewSetBase,
MetadataAttributesFunctional,
TestCase
):
pass

View File

@ -8,6 +8,7 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional
from itam.models.device import Device
@ -212,4 +213,17 @@ class ServicePermissionsAPI(ViewSetBase, APIPermissions, TestCase):
class ServiceViewSet(ViewSetBase, SerializersTestCases, TestCase):
pass
pass
class ServiceMetadata(
ViewSetBase,
MetadataAttributesFunctional,
MetaDataNavigationEntriesFunctional,
TestCase
):
menu_id = 'itim'
menu_entry_id = 'service'

View File

@ -1,6 +1,7 @@
from django.test import TestCase
from core.tests.abstract.test_ticket_viewset import Ticket, TicketViewSetBase, TicketViewSetPermissionsAPI, TicketViewSet
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional
class ViewSetBase( TicketViewSetBase ):
@ -28,3 +29,16 @@ class TicketChangeViewSet(
):
pass
class TicketChangeMetadata(
ViewSetBase,
MetadataAttributesFunctional,
MetaDataNavigationEntriesFunctional,
TestCase
):
menu_id = 'itim'
menu_entry_id = 'ticket_change'

View File

@ -1,6 +1,7 @@
from django.test import TestCase
from core.tests.abstract.test_ticket_viewset import Ticket, TicketViewSetBase, TicketViewSetPermissionsAPI, TicketViewSet
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional
class ViewSetBase( TicketViewSetBase ):
@ -28,3 +29,16 @@ class TicketIncidentViewSet(
):
pass
class TicketIncidentMetadata(
ViewSetBase,
MetadataAttributesFunctional,
MetaDataNavigationEntriesFunctional,
TestCase
):
menu_id = 'itim'
menu_entry_id = 'ticket_incident'

View File

@ -1,6 +1,7 @@
from django.test import TestCase
from core.tests.abstract.test_ticket_viewset import Ticket, TicketViewSetBase, TicketViewSetPermissionsAPI, TicketViewSet
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional
class ViewSetBase( TicketViewSetBase ):
@ -28,3 +29,16 @@ class TicketProblemViewSet(
):
pass
class TicketProblemMetadata(
ViewSetBase,
MetadataAttributesFunctional,
MetaDataNavigationEntriesFunctional,
TestCase
):
menu_id = 'itim'
menu_entry_id = 'ticket_problem'

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2024-10-13 15:27
# Generated by Django 5.1.2 on 2024-12-06 06:47
import access.models
import django.db.models.deletion
@ -9,7 +9,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0003_alter_organization_id_alter_organization_manager_and_more'),
('project_management', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
@ -48,7 +47,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='project',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='projectmilestone',
@ -68,7 +67,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='projectmilestone',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='projectstate',
@ -83,7 +82,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='projectstate',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
migrations.AlterField(
model_name='projecttype',
@ -98,6 +97,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='projecttype',
name='organization',
field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
]

Some files were not shown because too many files have changed in this diff Show More