Compare commits

...

1191 Commits

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
75cf55fe6a build: bump version 1.4.0 -> 1.4.1 2024-11-30 06:12:29 +00:00
Jon
b160c034e5 fix(itam): When validating device config, only do so if there is config defined
ref: fixes #407
2024-11-30 15:30:02 +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
1093c55c19 build: bump version 1.3.1 -> 1.4.0 2024-11-28 15:54:17 +00:00
Jon
069251d68c Merge pull request #344 from nofusscomputing/feature-v1-3 2024-11-29 01:13:43 +09:30
Jon
8050a7a8f0 Merge pull request #401 from nofusscomputing/related-ticket 2024-11-29 01:03:45 +09:30
Jon
2f259eacd4 docs(development): added model page_layout and table_fields attributes
ref: #248 #401 closes #347
2024-11-29 00:51:27 +09:30
Jon
184f419905 test(project_management): Ensure that project field completed exists when API v2 is rendere
ref: #401 fixes #327
2024-11-29 00:19:51 +09:30
Jon
545d4176b2 fix(project_management): Correct All tickets query for calculating project completion
ref: #327 #401
2024-11-29 00:19:11 +09:30
Jon
1d9d426601 feat(project_management): add project completed field
ref: #248 #401
2024-11-29 00:13:52 +09:30
Jon
bcf353bee6 test(core): Ensure a ticket cant be related to itself
ref: #401 fixes #326
2024-11-28 23:51:55 +09:30
Jon
795c6985a7 fix(core): Prevent a ticket from being related to itself
ref: #326 #401
2024-11-28 23:51:14 +09:30
Jon
d923490ff3 Merge pull request #397 from nofusscomputing/359-returned-results 2024-11-28 03:26:43 +09:30
Jon
de987f1fee fix(core): when fetching ticket serializer set org=None
ref: #248 #397 closes #359
2024-11-28 03:16:52 +09:30
Jon
b291b989b1 fix(core): use the view pk to filter self out for ticket category update
ref: #248 #359
2024-11-28 02:58:45 +09:30
Jon
ba85c694ba test(itam): correct test setup for device note viewset
ref: #248 #359 #397
2024-11-28 02:58:45 +09:30
Jon
ae2891f0ac test(settings): Ensure items returned are from users orgs only for API v2 endpoints
ref: #248 #359 #397
2024-11-28 02:58:45 +09:30
Jon
346b41cc26 test(project_management): Ensure items returned are from users orgs only for API v2 endpoints
ref: #248 #359 #397
2024-11-28 02:58:45 +09:30
Jon
3d980de05c test(itim): Correct test case for ticket category returned serializer checks
ref: #248 #359
2024-11-28 02:58:45 +09:30
Jon
4db65bcbad test(core): Correct test case for ticket category returned serializer checks
ref: #248 #359
2024-11-28 02:58:45 +09:30
Jon
baa4f68004 test(core): Ensure items returned are from users orgs only for API v2 endpoints
ref: #248 #359 #397
2024-11-28 02:58:44 +09:30
Jon
dd3c8e51d2 test(config_management): Ensure items returned are from users orgs only for API v2 endpoints
ref: #248 #359 #397
2024-11-28 02:58:44 +09:30
Jon
42621e33a2 test(assistance): Ensure items returned are from users orgs only for API v2 endpoints
ref: #248 #359 #397
2024-11-28 02:58:44 +09:30
Jon
4544c0768d test(access): Ensure items returned are from users orgs only for API v2 endpoints
ref: #248 #359 #397
2024-11-28 02:58:44 +09:30
Jon
0cda597a2d test(itam): Ensure items returned are from users orgs only for API v2 endpoints
ref: #248 #359 #397
2024-11-28 02:58:44 +09:30
Jon
c3547a49eb test: Test Case for viewsets that confirms returned results from user orgs only
ref: #248 #397
2024-11-28 02:58:44 +09:30
Jon
579e55af08 docs: update roadmap
ref: #397
2024-11-28 02:58:44 +09:30
Jon
82ed07e62c test(itam): update device model test name Device -> DeviceModel
ref: #15 #392
2024-11-28 02:22:29 +09:30
Jon
e10a4972af feat(api): Implement Sanity error handling for uncaught exceptions
ref: #15 #392 closes #387
2024-11-28 02:22:29 +09:30
Jon
e17de299f1 test(Core): Ticket linked items API V2 Serializer returned checks
ref: #15 #392 closes #393
2024-11-28 02:22:29 +09:30
Jon
ab6a965ef1 test(Core): Remove duplicate test for ticket linked items
ref: #15 #392 #393
2024-11-28 02:22:29 +09:30
Jon
c09baf0204 test(assistance): Project Taask ticket Viewset Serializer checks
ref: #15 #392 #393
2024-11-28 02:22:29 +09:30
Jon
0d9cab032b test(assistance): Problem ticket Viewset Serializer checks
ref: #15 #392 #393
2024-11-28 02:22:29 +09:30
Jon
3a132e2cd9 test(assistance): Incident ticket Viewset Serializer checks
ref: #15 #392 #393
2024-11-28 02:22:29 +09:30
Jon
f9209e8bc7 test(assistance): Change ticket Viewset Serializer checks
ref: #15 #392 #393
2024-11-28 02:22:29 +09:30
Jon
2429a94c7d test(assistance): Request ticket Viewset Serializer checks
ref: #15 #392 #393
2024-11-28 02:22:29 +09:30
Jon
7acb73f9da test(core): Ticket Test Cases for Viewset Serializer returned
ref: #15 #392 #393
2024-11-28 02:22:29 +09:30
Jon
87108e9d05 fix(core): Ensure for update of ticket the correct serializer is selected
ref: #15 #392 #393
2024-11-28 02:22:29 +09:30
Jon
faf441b338 test: during delete operation dont include data
ref: #15 #392
2024-11-28 02:22:29 +09:30
Jon
cad845267c fix(core): dont exclude self for ticket comment category if not exists
ref: #392
2024-11-28 02:22:29 +09:30
Jon
2647bbd522 chore: correct linting error
ref: #392
2024-11-28 02:22:29 +09:30
Jon
ad54494df0 test: Add ViewSet Returned Serializer Checks to a majority of models
more to come

ref: #15 #248 #392 #393
2024-11-28 02:22:29 +09:30
Jon
95ac6a4277 test: Test Cases to confirm the correct serializer is returned from ViewSet
ref: #15 #248 #392 #393
2024-11-28 02:22:29 +09:30
Jon
8cca6e3a9e test: Added skipped test for checking model mandatory values.
see test __doc__ for skip reason

ref: #15 #248 #392
2024-11-28 02:22:29 +09:30
Jon
e9298d7b89 feat(itam): Split device software serializer to include seperate software installs serializer
ref: #248 #392 closes #386
2024-11-28 02:22:29 +09:30
Jon
89c3feee18 fix(itam): Add Operating System API v2 field typo
ref: #248 #391
2024-11-28 02:22:28 +09:30
Jon
59f842b3aa feat(itam): Add Operating System Installs API v2 endpoint
ref: #248 #391
2024-11-28 02:22:28 +09:30
Jon
426f7ab512 test(itam): Operating System Installs API v2 Field checks
ref: #15 #248 #391
2024-11-28 02:22:28 +09:30
Jon
9f1e04f078 test(itam): Software Installs API v2 Permission checks
ref: #15 #248 #391
2024-11-28 02:22:28 +09:30
Jon
5fe1e39e0d test(itam): Operating_system Installs API v2 Validation checks
ref: #15 #248 #391
2024-11-28 02:22:28 +09:30
Jon
dd4e6242b5 refactor(itam): update device software serializer validator
ref: #248 #391
2024-11-28 02:22:28 +09:30
Jon
86f4a2f00f test(itam): Software Installs API v2 Permission checks
ref: #15 #248 #391
2024-11-28 02:22:28 +09:30
Jon
3188818247 test(itam): Software Installs API v2 Validation checks
ref: #15 #248 #391
2024-11-28 02:22:28 +09:30
Jon
ad7f3870d0 refactor(itam): update device software serializer validator
ref: #248 #391
2024-11-28 02:22:28 +09:30
Jon
79fbd400e5 test(itam): Software Installs API v2 Field checks
ref: #248 #391
2024-11-28 02:22:28 +09:30
Jon
2ac9137068 feat(itam): based off of the request kwaargs, adjust device serializer fields accordingly
for a device, make field read only
for software make field read only

ref: #248 #391
2024-11-28 02:22:28 +09:30
Jon
56b5c2c210 feat(itam): Add Software installs endpoint
ref: #248 #391
2024-11-28 02:22:28 +09:30
Jon
3c88a556d3 feat(itim): add cluster and device to Services in new UI
ref: #248 #390 nofusscomputing/centurion_erp_ui#29
2024-11-28 02:22:28 +09:30
Jon
f1780cca7a feat(config_management): add hosts to new UI
ref: #248 #390 nofusscomputing/centurion_erp_ui#29
2024-11-28 02:22:28 +09:30
Jon
6fe6c58828 feat(api): add ticket icons
ref: #248 #390 nofusscomputing/centurion_erp_ui#29
2024-11-28 02:22:28 +09:30
Jon
96c4c59bae docs(release_notes): Notate API v2 is beta and subject to change
ref: #248 #390
2024-11-28 02:22:28 +09:30
Jon
058eb47df7 test(core): Ticket serializer checks corrected to use project_id within mock view
ref: #248 #390
2024-11-28 02:22:28 +09:30
Jon
e2c059cacd test(core): Ticket comment serializer checks corrected to use mock view
ref: #248 #390
2024-11-28 02:22:28 +09:30
Jon
49554f7b68 fix(core): Enusure project_task serializer sets the project_id
ref: #248 #390 nofusscomputing/centurion_erp_ui#29
2024-11-28 02:22:28 +09:30
Jon
92b1222df3 feat(itim): Add nodes and devices to detail view
ref: #248 #390 nofusscomputing/centurion_erp_ui#29
2024-11-28 02:22:28 +09:30
Jon
5b8da99ba2 fix(itam): device os serializer not to show org and device
these fields supplied by the serializer

ref: #248 #390
2024-11-28 02:22:28 +09:30
Jon
7551a38f49 feat(api): return_url to default to list view
ref: #248 #390
2024-11-28 02:22:28 +09:30
Jon
09f432f900 test(core): Ticket comment category field checks corrected
ref: #248 #390
2024-11-28 02:22:28 +09:30
Jon
2e6db419b2 feat(base): move setting SECURE_SSL_REDIRECT = True to etc/settings
ref: #248 #390
2024-11-28 02:22:28 +09:30
Jon
c6a1790b7d feat(base): use senisible settings for SSL
ref: #248 #390
2024-11-28 02:22:28 +09:30
Jon
855d5b0062 fix(core): ticket comment to use model serializer for meta
ref: #248 #390
2024-11-28 02:22:28 +09:30
Jon
a35684d1d2 test(itam): Update Device Operating System history checks to cater for unique device constratint
ref: #248 #390 #362
2024-11-28 02:22:28 +09:30
Jon
f81373b832 test(itam): Device Operating System API field checks checks
ref: #248 #390 closes #362 closes #366
2024-11-28 02:22:28 +09:30
Jon
744f448423 test(itim): Device Operating System API v2 ViewSet permission checks
ref: #248 #390 #362
2024-11-28 02:22:28 +09:30
Jon
a7b0ace2ef test(itam): Device Operating System Serializer Validation checks
ref: #248 #390 #362
2024-11-28 02:22:28 +09:30
Jon
f4c06da385 feat(itam): Add device operating system API v2 endpoint
ref: #248 #390 #362
2024-11-28 02:22:28 +09:30
Jon
bd7c0a4901 test(core): remove duplicate functional slash commands
ref: #248 #390 #366
2024-11-28 02:22:28 +09:30
Jon
06fff3b2df refactor(itam): ensure device is unique for device os model
ref: #248 #390 #366
2024-11-28 02:22:28 +09:30
Jon
aebd0f3580 refactor: ensure filed organization is required
ref: #248 #390 #366
2024-11-28 02:22:28 +09:30
Jon
3afc63d8fc refactor(config_management): config_group ref to use full model name
ref: #248 #390 #366
2024-11-28 02:22:28 +09:30
Jon
5f7b6ef9eb refactor: update serializers to use model get_url function
ref: #248 #390 #366
2024-11-28 02:22:28 +09:30
Jon
581598dabf fix(core): add kwargs to notes
ref: #248 #390 #366
2024-11-28 02:22:27 +09:30
Jon
0c3c6adaea fix(core): correct get_url function notes
ref: #248 #390 #366
2024-11-28 02:22:27 +09:30
Jon
84c65419c2 fix(core): add missing dep to notes
ref: #248 #390 #366
2024-11-28 02:22:27 +09:30
Jon
7930af269d fix(access): correct team users get_url
ref: #248 #390 #366
2024-11-28 02:22:27 +09:30
Jon
c2751f9ae0 fix(access): correct team get_url requires kwargs
ref: #248 #390 #366
2024-11-28 02:22:27 +09:30
Jon
21c70225c8 fix(core): correct notes get_url
ref: #248 #390 #366
2024-11-28 02:22:27 +09:30
Jon
11fa10214c fix(access): correct team get_url
ref: #248 #390 #366
2024-11-28 02:22:27 +09:30
Jon
048956fd22 fix(core): ticket model url requires kwargs
ref: #248 #390
2024-11-28 02:22:27 +09:30
Jon
81e63cccd8 fix(core): ticket comment model url requires kwargs
ref: #248 #390
2024-11-28 02:22:27 +09:30
Jon
20f6b4f368 refactor(core): ticket comment url name updated to match model name
ref: #248 #390
2024-11-28 02:22:27 +09:30
Jon
be2699e2e0 fix(core): dont attempt to fetch org for ticket comment if no data supplied
ref: #248 #390
2024-11-28 02:22:27 +09:30
Jon
463774a718 test: model get_url function checks
ref: #248 #391 nofusscomputing/centurion_erp_ui#29 closes #366
2024-11-28 02:22:27 +09:30
Jon
05f03efc64 refactor: Add function get_url to tenancy models
ref: #248 #366 #391 nofusscomputing/centurion_erp_ui#29
2024-11-28 02:22:27 +09:30
Jon
f420bc6f6a chore: update nav icons
ref: #248 #390
2024-11-28 02:22:27 +09:30
Jon
a60cf6c288 fix(core): Always set the organization to the ticket org when adding a ticket comment when org not specified.
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#23
2024-11-28 02:22:27 +09:30
Jon
10bade6633 feat(api): Add return URL to metadata if model has attribute get_url
ref: #248 #385 #388 #389 nofusscomputing/centurion_erp_ui#23
2024-11-28 02:22:27 +09:30
Jon
96670857e9 fix(api): Ensure queryset filters to actual item if pk is defined
ref: #248 #385 #388 #389 nofusscomputing/centurion_erp_ui#23
2024-11-28 02:22:27 +09:30
Jon
7a5c55a46e fix(core): Automagic fetch data for fields and only require ticket id to link item to ticket
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#23
2024-11-28 02:22:27 +09:30
Jon
18aa58b85e fix(core): Always set the organization to the ticket org when adding a ticket comment.
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#23
2024-11-28 02:22:27 +09:30
Jon
e0168640cf feat(config_management): Add field child group count to table fields for groups
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#23
2024-11-28 02:22:27 +09:30
Jon
5899bb17ca fix(config_management): show parent groups only on index
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#23
2024-11-28 02:22:26 +09:30
Jon
16927fc732 feat(itam): Add page_layout to SoftwareVersion model
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#23
2024-11-28 02:22:26 +09:30
Jon
5129a8de5c fix(core): Set notes _self url to empty val then attempt to sset
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#23 fixes nofusscomputing/centurion_erp_ui#24
2024-11-28 02:22:26 +09:30
Jon
1e556f1011 feat(itam): Add page_layout to OperatingSystemVersion model
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#23
2024-11-28 02:22:26 +09:30
Jon
20c09ec9ee feat(project_management): Add page_layout to Milestone model
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#23
2024-11-28 02:22:26 +09:30
Jon
279967d26c feat(settings): Add page_layout to AppSettings model
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#23
2024-11-28 02:22:26 +09:30
Jon
56dd268e20 fix(core): Ensure API v1 Ticket sets the ticket type prior to validation
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#23
2024-11-28 02:22:26 +09:30
Jon
6668c6ae35 fix(core): Dont attempt to use ticket instance organization if it's a new ticket being created
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#23
2024-11-28 02:22:26 +09:30
Jon
228e36d3cd fix(access): Ensure organization is a mandatory field
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#23
2024-11-28 02:22:26 +09:30
Jon
46441aa667 feat(access): render team_name field as anchor
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#23
2024-11-28 02:22:26 +09:30
Jon
d7c24c3910 feat(api): Support setting char field as an anchor field using .urls._self
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#23
2024-11-28 02:22:26 +09:30
Jon
e94e28ad33 feat(api): Added abilty to specify a css class for markdown field
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#4 nofusscomputing/centurion_erp_ui#26
2024-11-28 02:22:26 +09:30
Jon
55b123a095 fix(core): Ensure ticket and comment bodies are set to required
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#4 nofusscomputing/centurion_erp_ui#26
2024-11-28 02:22:26 +09:30
Jon
e42a009014 feat: Add timezone support
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#4 nofusscomputing/centurion_erp_ui#26
2024-11-28 02:22:26 +09:30
Jon
2b25e2bb02 refactor(api): set fields that are for markdown to use the markdown field
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#26
2024-11-28 02:22:26 +09:30
Jon
f345dd366c feat(api): Add a Common Model serializer to be inherited by all model serializers
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#26
2024-11-28 02:22:26 +09:30
Jon
c54c91f4bf feat(core): new field type markdown
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#26
2024-11-28 02:22:26 +09:30
Jon
5be205a611 feat(core): new field type char
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#26
2024-11-28 02:22:26 +09:30
Jon
696b6e7b16 feat(core): add RElated Items choices to metadata
ref: #248 #385 #388 nofusscomputing/centurion_erp_ui#26
2024-11-28 02:22:26 +09:30
Jon
8837811c18 chore: add UI to docker-compose.yaml
ref: #248 #385
2024-11-28 02:22:26 +09:30
Jon
c4f396af2e fix(core): correct navigation metadata
ref: #248 #385
2024-11-28 02:22:26 +09:30
Jon
b258800884 test(core): move unit tests that check functionality to func test for ticket
ref: #15 #248 #385
2024-11-28 02:22:26 +09:30
Jon
98c753d0e0 chore(compose): updaate docker compose file for testing
ref: #248 #383
2024-11-28 02:22:26 +09:30
Jon
cd0bcf6731 chore(api): Correct DRF Swagger Docs errors
ref: #248 #361 #383
2024-11-28 02:22:25 +09:30
Jon
e196033821 fix(task): Ensure if inventory RX is a string, serialize it
ref: #248 #383
2024-11-28 02:22:25 +09:30
Jon
3dac12c96f refactor(task): Adjust inventory to use API v2 serializer
ref: #248 #383
2024-11-28 02:22:25 +09:30
Jon
06b936b355 test(itam): Inventory API v2 Serializer Checks
ref: #15 #248 #383
2024-11-28 02:22:25 +09:30
Jon
1c5fb0de18 feat(itam): Add Inventory API v2 endpoint
ref: #248 #383
2024-11-28 02:22:25 +09:30
Jon
5987e62063 test(core): Ensure that when ticket is assigned it's status is updated to assigned
ref: #15 #375 #383
2024-11-28 02:22:25 +09:30
Jon
ad9ed13bc4 test(settings): External Link API ViewSet permission checks
ref: #15 #248 #383
2024-11-28 02:22:25 +09:30
Jon
bfbaac0c21 test(access): External Link API v2 Serializer Checks
ref: #15 #248 #383
2024-11-28 02:22:25 +09:30
Jon
198232a43f test(functional): Move request ticket checks from unit
ref: #15 #375 #383
2024-11-28 02:22:25 +09:30
Jon
f695f14e14 test(functional): Move functional test cases to relevant functional test dir
ref: #15 #375 #382
2024-11-28 02:22:25 +09:30
Jon
506c5354cc test(access): Organization API v2 Serializer Checks, only super user can create
ref: #15 #248 #368 #382
2024-11-28 02:22:25 +09:30
Jon
3f92afef13 fix(access): Team User serializer not to capture exceptions
exceptions used to display issue to user

ref: #248 #368 #382
2024-11-28 02:22:25 +09:30
Jon
48541f117e fix(access): Team User team and user fields required when creating, don't use default value.
ref: #248 #368 #382
2024-11-28 02:22:25 +09:30
Jon
873411c875 test(access): Team User API v2 Serializer Checks
ref: #15 #248 #368 #382
2024-11-28 02:22:25 +09:30
Jon
b25bd4cb34 test(access): Team API v2 Serializer Checks
ref: #15 #248 #368 #382
2024-11-28 02:22:25 +09:30
Jon
211ead9900 fix(access): Team name required when creating, don't use default value.
ref: #248 #368 #382
2024-11-28 02:22:25 +09:30
Jon
febf2718ea fix(access): Dont capture exceptions within team serializer
exceptions are used to display the error to the user

ref: #248 #368 #382
2024-11-28 02:22:25 +09:30
Jon
e31511c93b test(access): Organization API v2 Serializer Checks
ref: #15 #248 #368 #382
2024-11-28 02:22:25 +09:30
Jon
d64d1635d8 test(project_management): Organization API v2 ViewSet permission checks
ref: #15 #248 #368 #382
2024-11-28 02:22:25 +09:30
Jon
0eb88038a4 feat(api): Depreciate API V1 endpoint /api/config
API v2 endpint is /itam/device/x on path .config or .rendered_config

ref: #248 #345 #382
2024-11-28 02:22:25 +09:30
Jon
a47da4d957 fix(core): Ensure import user can set field opened_by when importing tickets
ref: #248 #382
2024-11-28 02:22:25 +09:30
Jon
0b258b3638 test(core): Ensure test setup correctly for ticket checks
ref: #15 #248 #382
2024-11-28 02:22:25 +09:30
Jon
89b0a6b003 fix(core): Correct duration slash command regex
ref: #248 #376 #382
2024-11-28 02:22:25 +09:30
Jon
da9799cb3c test(core): Spend Slash command Checks.
ref: #15 #248 #382 closes #376
2024-11-28 02:22:25 +09:30
Jon
e28d25b137 test(core): Relate Slash command Checks.
ref: #15 #248 #376 #381
2024-11-28 02:22:25 +09:30
Jon
8fbbf124df test(core): Ensure that an item that may be linked to a ticket, when its deleted, the ticket link is removed
ref: #15 #248 #376 #381 fixes #336
2024-11-28 02:22:25 +09:30
Jon
c415d53708 fix(core): When an item that may be linked to a ticket is deleted, remove the ticket link
ref: #15 #248 #336 #376 #381
2024-11-28 02:22:25 +09:30
Jon
86d4f7684f feat(core): New signal for cleaning linked ticket items when the item is deleted
ref: #15 #248 #336 #376 #381
2024-11-28 02:22:24 +09:30
Jon
e89dff1c2f test(core): Ensure a non-existing item cant be Linked to a Ticket.
ref: #15 #248 #336 #376 #381
2024-11-28 02:22:24 +09:30
Jon
5ef5103ea9 test(core): Action command Related Item Ticket Slash command checks.
ref: #15 #248
2024-11-28 02:22:24 +09:30
Jon
5dabf00980 test(core): Blocked by Slash command Checks.
ref: #15 #248 #376 #381
2024-11-28 02:22:24 +09:30
Jon
ce170ff9aa test(core): Blocks Slash command Checks.
ref: #15 #248 #376 #381
2024-11-28 02:22:24 +09:30
Jon
df73e86c88 fix(core): Related ticket slash command requires model to be imported
ref: #248 #376 #381
2024-11-28 02:22:24 +09:30
Jon
72fe8b8422 test(core): Related Item Ticket Slash command checks.
ref: #15 #248 #376 #381
2024-11-28 02:22:24 +09:30
Jon
7a4edc69ba test(project_management): Project Task API v2 Serializer Checks
ref: #15 #248 #378 closes #368
2024-11-28 02:22:24 +09:30
Jon
5e6d675cb9 test(itim): Incident Ticket API v2 Serializer Checks
ref: #15 #248 #368 #378
2024-11-28 02:22:24 +09:30
Jon
80575e02c7 test(itim): Problem Ticket API v2 Serializer Checks
ref: #15 #248 #368 #378
2024-11-28 02:22:24 +09:30
Jon
84dafcae87 test(itim): Change Ticket API v2 Serializer Checks
ref: #15 #248 #368 #378
2024-11-28 02:22:24 +09:30
Jon
ed87241763 test(core): Request Ticket API v2 Serializer Checks
ref: #15 #248 #368 #378
2024-11-28 02:22:24 +09:30
Jon
18db1f58ff test(core): Common Ticket Test Cases for API v2 serializers
ref: #15 #248 #368 #378
2024-11-28 02:22:24 +09:30
Jon
552bce4d47 fix(core): correct missing or incomplete ticket model fields
ref: #248 #378
2024-11-28 02:22:24 +09:30
Jon
3bb7978d15 fix(core): When creating a ticket, by default give it a status of new
ref: #248 #378
2024-11-28 02:22:24 +09:30
Jon
e771631a04 fix(core): Ensure that when creating a ticket an organization is specified
ref: #248 #378
2024-11-28 02:22:24 +09:30
Jon
5821c5b33b fix(core): Correct Ticket read-only fields
ref: #248 #378
2024-11-28 02:22:24 +09:30
Jon
b1a42e01bf fix(core): Correct inheritence order for ticket serializers
ref: #248 #378
2024-11-28 02:22:24 +09:30
Jon
1b286d0873 test(project_management): Project Task API field checks
ref: #15 #248 #368 #377
2024-11-28 02:22:24 +09:30
Jon
85a9cf17cd test(itim): Problem Ticket API field checks
ref: #15 #248 #368 #377
2024-11-28 02:22:24 +09:30
Jon
3ef7d175c1 test(itim): Incident Ticket API field checks
ref: #15 #248 #368 #377
2024-11-28 02:22:24 +09:30
Jon
67fa708edf test(itim): Change Ticket API field checks
ref: #15 #248 #368 #377
2024-11-28 02:22:24 +09:30
Jon
b53f4aa770 test(assistance): Update request field checks to cater for project and milestone as dicts
ref: #15 #248 #368 #377
2024-11-28 02:22:24 +09:30
Jon
bf1e211c22 feat(core): Show milestone using base serializer for all ticket types
ref: #248 #368 #377
2024-11-28 02:22:24 +09:30
Jon
39a2f4c303 feat(core): Show project using base serializer for all ticket types
ref: #248 #368 #377
2024-11-28 02:22:24 +09:30
Jon
8c782b19ce test(project_management): Ensure ticket assigned project for all API v2 ViewSet permission checks
ref: #15 #248 #368 #377
2024-11-28 02:22:24 +09:30
Jon
ec3ab3e055 test(project_management): PRoject_task API v2 ViewSet permission checks
ref: #15 #248 #368 #377
2024-11-28 02:22:24 +09:30
Jon
525e826857 test(itim): Problem Ticket API v2 ViewSet permission checks
ref: #15 #248 #368 #377
2024-11-28 02:22:24 +09:30
Jon
e8629b2e1c test(itim): Incident Ticket API v2 ViewSet permission checks
ref: #15 #248 #368 #377
2024-11-28 02:22:24 +09:30
Jon
fe56fab6fd test(itim): Change Ticket API v2 ViewSet permission checks
ref: #15 #248 #368 #377
2024-11-28 02:22:24 +09:30
Jon
66d7d513ae fix(core): Ensure Organization can be set when creating a ticket
ref: #248 #368 #377
2024-11-28 02:22:24 +09:30
Jon
a00cc52ff0 fix(core): Ensure that when fetching ticket permission, spaces are replaced with '_'
ref: #248 #368 #377
2024-11-28 02:22:24 +09:30
Jon
13ab073f99 fix(core): Ticket serializer org validator to access correct data
ref: #248 #368 #377
2024-11-28 02:22:24 +09:30
Jon
a2cac47414 feat(core): Add Parse error to exceptions
ref: #248 #368 #377
2024-11-28 02:22:23 +09:30
Jon
f522e6f9c1 feat(core): Ticket serializer to ensure user who opens ticket is subscribed to it
ref: #248 #368 #377
2024-11-28 02:22:23 +09:30
Jon
11ce8cc864 feat(core): Ticket serializer to validate milestone
ref: #248 #368 #377
2024-11-28 02:22:23 +09:30
Jon
352b34294c feat(core): Ticket serializer to validate organization
ref: #248 #368 #377
2024-11-28 02:22:23 +09:30
Jon
6428e96c09 fix(core): Add project URL to all Ticket Types
ref: #248 #377
2024-11-28 02:22:23 +09:30
Jon
9908253a7e fix(core): Add Ticket Category URL to all Ticket Types
ref: #248 #377
2024-11-28 02:22:23 +09:30
Jon
74c6ee24cf fix(core): When obtaining ticket type use it's enum value
ref: #248 #377
2024-11-28 02:22:23 +09:30
Jon
74d55fb81e feat(itim): Add Project Task API v2 endpoint
ref: #248 #377
2024-11-28 02:22:23 +09:30
Jon
7fc5138fcd feat(itim): Add Problem Ticket API v2 endpoint
ref: #248 #377
2024-11-28 02:22:23 +09:30
Jon
73d7338e7a feat(itim): Add Incident Ticket API v2 endpoint
ref: #248 #377
2024-11-28 02:22:23 +09:30
Jon
189b81106d feat(itim): Add Change Ticket API v2 endpoint
ref: #248 #377
2024-11-28 02:22:23 +09:30
Jon
223e78ae07 docs(views): update to denote dynamic permissions
ref: #248 #345 #374
2024-11-28 02:22:22 +09:30
Jon
d20a1460da feat(api): Depreciate v1 API Endpoint Assistance
ref: #248 #345 #374
2024-11-28 02:22:22 +09:30
Jon
429f3a9a94 docs(views): update to denote dynamic permissions
ref: #248 #345 #374
2024-11-28 02:22:22 +09:30
Jon
a04cfeef86 feat(api): Depreciate v1 API Endpoint Request Ticket
ref: #248 #345 #374
2024-11-28 02:22:22 +09:30
Jon
8cd442ea25 feat(api): Depreciate v1 API Endpoint Assistance
ref: #248 #345 #374
2024-11-28 02:22:22 +09:30
Jon
8e21cb5a85 feat(api): Depreciate v1 API Endpoint Ticket Comments
ref: #248 #345 #374
2024-11-28 02:22:22 +09:30
Jon
91e38a80f7 feat(api): Depreciate v1 API Endpoint Ticket Comment Categories
ref: #248 #345 #374
2024-11-28 02:22:22 +09:30
Jon
df55cf0450 feat(api): Depreciate v1 API Endpoint Ticket Categories
ref: #248 #345 #374
2024-11-28 02:22:22 +09:30
Jon
dac01ace32 refactor(core): Move ticket validation from is_valid -> validate method
ref: #248 #368 #374
2024-11-28 02:22:22 +09:30
Jon
e39ec70236 fix(core): Ensure triage and import permissions are catered for Tickets
ref: #248 #368 #374
2024-11-28 02:22:22 +09:30
Jon
4acfe5f313 test(core): fix broken tests from 8b701785b3 changes
ref: #15 #248 #368 #374
2024-11-28 02:22:22 +09:30
Jon
7d3a4c7c63 test(core): Item Ticket API v2 Serializer checks
ref: #15 #248 #368 #374
2024-11-28 02:22:22 +09:30
Jon
08e13a728a test(core): Item Linked Ticket API v2 ViewSet permission checks
ref: #15 #248 #368 #374
2024-11-28 02:22:22 +09:30
Jon
f27e0379c2 refactor(core): Ensure Ticket Linked Serializer works for Item Tickets
ref: #248 #368 #374
2024-11-28 02:22:22 +09:30
Jon
effa2904f8 fix(core): Ensure Ticket Linked Item slash command works for ticket comments
ref: #248 #368 #374
2024-11-28 02:22:21 +09:30
Jon
9fe4883f91 refactor(core): Ticket Linked Item slash command to use serializer
ref: #248 #368 #374
2024-11-28 02:22:21 +09:30
Jon
e61b883c14 fix(core): Only use Import Serializer on Ticket Comment Create if user has perms
ref: #248 #368 #374
2024-11-28 02:22:21 +09:30
Jon
04ae338864 refactor(core): Related ticket slash command to use serializer
ref: #248 #368 #374
2024-11-28 02:22:21 +09:30
Jon
bf56b271d7 feat(core): Ensure Related Tickets validate against duplicate entries
ref: #248 #368 #374
2024-11-28 02:22:21 +09:30
Jon
daa8dbe04b feat(core): Add MethodNot Allowed to Centurion exceptions
ref: #15 #248 #368 #374
2024-11-28 02:22:21 +09:30
Jon
7d62d6b1c7 test(core): Related Ticket API v2 Serializer checks
ref: #15 #248 #368 #374
2024-11-28 02:22:21 +09:30
Jon
f45019024b fix(core): Ensure related ticket slash command works for ticket comments
ref: #15 #248 #368 #374
2024-11-28 02:22:21 +09:30
Jon
2c934d4eaf fix(api): Ensure METHOD_NOT_ALLOWED exception is thrown
ref: #15 #248 #368 #374
2024-11-28 02:22:21 +09:30
Jon
0965f56719 test(core): Related Ticket API v2 ViewSet permission checks
ref: #15 #248 #368 #374
2024-11-28 02:22:21 +09:30
Jon
80b8cdb356 test(core): Ticket Comment API v2 Serializer checks
ref: #15 #248 #373
2024-11-28 02:22:21 +09:30
Jon
8479f8c30b feat(core): Determine serializer from action and user permissions for Ticket Comments
ref: #248 #373
2024-11-28 02:22:21 +09:30
Jon
821ba0edbf feat(core): Add custom exception class
ref: #248 #373
2024-11-28 02:22:21 +09:30
Jon
a75a56eb96 feat(core): Ensure ticket comment Serializer validates for existance of comment_type and ticket id
ref: #248 #373
2024-11-28 02:22:21 +09:30
Jon
fe5aac0218 feat(core): Ensure ticket comment Serializer is picked based off of comment_type
ref: #248 #373
2024-11-28 02:22:21 +09:30
Jon
32e3a97b09 test(core): Ticket Linked Item API v2 Serializer checks
ref: #15 #248 #373
2024-11-28 02:22:21 +09:30
Jon
7b70fd30b3 feat(core): Ensure that ticket linked item validates if ticket supplied
ref: #15 #248 #373
2024-11-28 02:22:21 +09:30
Jon
14776a0334 feat(core): Ensure that ticket comment category cant assign self as parent
ref: #15 #248 #373
2024-11-28 02:22:21 +09:30
Jon
0404b52924 test(core): Ticket Comment Category API v2 Serializer checks
ref: #15 #248 #373
2024-11-28 02:22:21 +09:30
Jon
fdd50c3208 feat(core): Ensure that ticket category cant assign self as parent
ref: #15 #248 #373
2024-11-28 02:22:21 +09:30
Jon
4c927efeef test(core): Ticket Category API v2 Serializer checks
ref: #15 #248 #373
2024-11-28 02:22:20 +09:30
Jon
6b6b70d653 test(itim): Ticket Linked Item API field checks
ref: #15 #248 #365
2024-11-28 02:22:20 +09:30
Jon
863b2d46c6 fix(core): Correct serializer item field to be for view serializer ONLY
ref: #248 #365
2024-11-28 02:22:20 +09:30
Jon
b972ea1f97 test(itim): Service Ticket URL API field checks
ref: #15 #248 #365
2024-11-28 02:22:20 +09:30
Jon
e220303d06 test(itim): Cluster Ticket URL API field checks
ref: #15 #248 #365
2024-11-28 02:22:20 +09:30
Jon
0b37f8f2b3 test(itam): Software Ticket URL API field checks
ref: #15 #248 #365
2024-11-28 02:22:20 +09:30
Jon
da414d741f test(itam): Operating System Ticket URL API field checks
ref: #15 #248 #365
2024-11-28 02:22:20 +09:30
Jon
f1332cecf4 test(itam): Device Ticket URL API field checks
ref: #15 #248 #365
2024-11-28 02:22:20 +09:30
Jon
31af109742 test(config_management): Group Ticket URL API field checks
ref: #15 #248 #365
2024-11-28 02:22:20 +09:30
Jon
d4aa3e673f fix(config_management): Correct ticket url in group serializer
ref: #248 #365
2024-11-28 02:22:20 +09:30
Jon
fffe78a4ed fix(core): Add missing ticket comment category url
ref: #248 #365
2024-11-28 02:22:20 +09:30
Jon
95f9a2620f test(core): Ticket Comment API v2 ViewSet permission checks
ref: #15 #248 #365
2024-11-28 02:22:20 +09:30
Jon
7869ff4478 test(core): Ticket Comment Category API v2 ViewSet permission checks
ref: #15 #248 #365
2024-11-28 02:22:20 +09:30
Jon
c06d09f507 test(core): Ticket Category API v2 ViewSet permission checks
ref: #15 #248 #365
2024-11-28 02:22:20 +09:30
Jon
846eb79c6e test(assistance): Request Ticket API v2 ViewSet permission checks
ref: #15 #248 #365
2024-11-28 02:22:19 +09:30
Jon
28805ed727 test(core): Ticket Common API v2 ViewSet permission checks
Test cases common to ALL ticket types

ref: #15 #248 #365
2024-11-28 02:22:19 +09:30
Jon
858217d2a2 test(core): Ticket Comment Category API field checks
ref: #15 #248 #365
2024-11-28 02:22:19 +09:30
Jon
848c342397 feat(core): Add Ticket Comment Category API v2 endpoint
ref: #248 #365
2024-11-28 02:22:19 +09:30
Jon
71ad05e051 test(core): Related Tickets API field checks
ref: #15 #248 #365 #367
2024-11-28 02:22:19 +09:30
Jon
5cb329c282 test(itim): Service Linked Tickets API field checks
ref: #15 #248 #365
2024-11-28 02:22:19 +09:30
Jon
0ba1a34ee7 test(itim): Cluster Linked Tickets API field checks
ref: #15 #248 #365
2024-11-28 02:22:19 +09:30
Jon
68ee0b3701 test(itam): Software Linked Tickets API field checks
ref: #15 #248 #365
2024-11-28 02:22:19 +09:30
Jon
873f8e16f2 test(itam): Operating System Linked Tickets API field checks
ref: #15 #248 #365
2024-11-28 02:22:19 +09:30
Jon
5381b96ad0 test(itam): device Linked Tickets API field checks
ref: #15 #248 #365
2024-11-28 02:22:19 +09:30
Jon
76320251a1 test(core): Config Group Linked Tickets API field checks
ref: #15 #248 #365
2024-11-28 02:22:19 +09:30
Jon
805cc888a5 test(core): Linked Ticket Common API field checks
test cases common to ALL linked tickets

ref: #15 #248 #365
2024-11-28 02:22:18 +09:30
Jon
5589b67e24 test(core): Ticket Linked Items API field checks
ref: #15 #248 #365
2024-11-28 02:22:18 +09:30
Jon
e0baa57735 test(core): Ticket Comment API field checks
ref: #15 #248 #365
2024-11-28 02:22:18 +09:30
Jon
dd8cbc9da4 refactor(core): Ticket Comments to use a single API Endpoint
ref: #248 #365
2024-11-28 02:22:18 +09:30
Jon
811e723006 test(core): Ticket Category API field checks
ref: #15 #248 #365
2024-11-28 02:22:18 +09:30
Jon
10703067fa test(assistance): Request Ticket API field checks
ref: #15 #248 #360
2024-11-28 02:22:18 +09:30
Jon
38e1742f15 test(core): Ticket Common API field checks
test cases common to ALL ticket types

ref: #15 #248 #360
2024-11-28 02:22:18 +09:30
Jon
487cbd8e54 fix(core): Add missing permissions function to ticket viewset
ref: #248 #365
2024-11-28 02:22:18 +09:30
Jon
b5bc45fa2b fix(core): Ensure that when checking linked ticket class name, spaces are replaced
ref: #248 #365
2024-11-28 02:22:18 +09:30
Jon
a4e62b3718 fix(core): Ensure item tickets class can have underscore in name
ref: #248 #365
2024-11-28 02:22:18 +09:30
Jon
564bae99b1 feat(core): Add Item Ticket API v2 endpoint
added for cluster, config group, device, service, software and operating system.

ref: #248 #365
2024-11-28 02:22:18 +09:30
Jon
c36d36be0b feat(core): Add Related Ticket API v2 endpoint
ref: #248 #365
2024-11-28 02:22:17 +09:30
Jon
da5d19cbcb fix: Dont attempt to access request within serializers when no context is present
ref: #248 #365 #366
2024-11-28 02:22:17 +09:30
Jon
db8a815dc0 feat(core): Add Ticket Linked Item API v2 endpoint
ref: #248 #365
2024-11-28 02:22:17 +09:30
Jon
0b00193bed feat(core): Add url function to Ticket Linked Items model
ref: #248 #365 #366
2024-11-28 02:22:17 +09:30
Jon
8f13047a1f feat(itim): Add url function to Service model
ref: #248 #365 #366
2024-11-28 02:22:17 +09:30
Jon
faa368331c feat(itim): Add url function to Cluster model
ref: #248 #365 #366
2024-11-28 02:22:17 +09:30
Jon
0eef42b3e6 feat(itam): Add url function to Software model
ref: #248 #365 #366
2024-11-28 02:22:17 +09:30
Jon
e6a5e446ab feat(itam): Add url function to Operating System model
ref: #248 #365 #366
2024-11-28 02:22:17 +09:30
Jon
43f90251b0 feat(itam): Add url function to Device model
ref: #248 #365 #366
2024-11-28 02:22:17 +09:30
Jon
d57d4ad96a feat(config_management): Add url function to Config Groups model
ref: #248 #365 #366
2024-11-28 02:22:17 +09:30
Jon
4542301446 feat(core): Add Ticket Comment API v2 endpoint
ref: #248 #365
2024-11-28 02:22:17 +09:30
Jon
a4bfb3a7e8 fix(core): Add Ticket Category API v2 endpoint to urls
ref: #248 #365
2024-11-28 02:22:17 +09:30
Jon
34a9d202c3 fix(core): Correct ticket comment model name
ref: #248 #365
2024-11-28 02:22:17 +09:30
Jon
f5eb4c25b2 feat(core): Add Ticket Category API v2 endpoint
ref: #248 #365
2024-11-28 02:22:17 +09:30
Jon
0612c2350d fix(api): Ensure read-only fields have choices added to metadata
ref: #248 #365
2024-11-28 02:22:16 +09:30
Jon
726a3ac406 fix(api): Correct inheritance order for ModelViewSet
ref: #248 #365
2024-11-28 02:22:16 +09:30
Jon
d8e6437241 feat(assistance): Add Request Ticket API v2 endpoint
ref: #248 #365
2024-11-28 02:22:16 +09:30
Jon
5fe2269e98 feat(api): Custom exception UnknownTicketType
for use when attempting to detect ticket type

ref: #248 #365
2024-11-28 02:22:16 +09:30
Jon
68c22966bc feat(core): Add Base Ticket Serializer and ViewSet
ref: #248 #365
2024-11-28 02:22:16 +09:30
Jon
d137ea663a docs(api): Add filter and remove footer schemas from swagger ui
ref: #248 #265
2024-11-28 02:22:16 +09:30
Jon
268e3294a2 feat(api): Setup API to be correctly versioned
ref: #248 #365
2024-11-28 02:22:16 +09:30
Jon
cdacd70bf1 test(settings): Celery Log API v2 ViewSet permission checks
ref: #15 #248 #360
2024-11-28 02:22:16 +09:30
Jon
4412ff14e7 test(settings): Celery Log API field checks
ref: #15 #248 #360
2024-11-28 02:22:16 +09:30
Jon
50edfd5997 test(settings): User Settings API v2 ViewSet permission checks
ref: #15 #248 #360
2024-11-28 02:22:16 +09:30
Jon
9137758294 test(settings): User Settings API field checks
ref: #15 #248 #360
2024-11-28 02:22:16 +09:30
Jon
94045d136f test(settings): App Settings API v2 ViewSet permission checks
ref: #15 #248 #360
2024-11-28 02:22:16 +09:30
Jon
768fd8d640 test(settings): App Settings API field checks
ref: #15 #248 #360
2024-11-28 02:22:16 +09:30
Jon
926349e04e feat(settings): Add get_organization function to app settings model
ref: #248 #360
2024-11-28 02:22:16 +09:30
Jon
ec16910ec6 feat(settings): Add Celery Task Logs API v2 endpoint
ref: #248 #360
2024-11-28 02:22:16 +09:30
Jon
20dc72d564 feat(api): Added ability to specify table fields within the viewset.
required for models that are external to centurion

ref: #248 #360
2024-11-28 02:22:16 +09:30
Jon
00c2826d9a feat(settings): Add User Settings API v2 endpoint
ref: #248 #360
2024-11-28 02:22:15 +09:30
Jon
2077becc89 fix(settings): Populate user_settings Meta
ref: #248 #360
2024-11-28 02:22:15 +09:30
Jon
86008e9cbd feat(settings): Add App Settings API v2 endpoint
ref: #248 #360
2024-11-28 02:22:15 +09:30
Jon
fb0905c44a fix(settings): Populate app_settings Meta
ref: #248 #360
2024-11-28 02:22:15 +09:30
Jon
4336d90dc0 chore(settings): remove extra fields declaration from external_links
ref: #248 #360
2024-11-28 02:22:15 +09:30
Jon
7322667a99 fix(project_management): For Project use a separate Import Serializer
ref: #248 #357
2024-11-28 02:22:15 +09:30
Jon
6cb99609cd fix(project_management): use the post data dict for fetching edit organisation
ref: #248 #357
2024-11-28 02:22:15 +09:30
Jon
bad610be36 test(project_management): Project API v2 ViewSet permission checks for import user
ref: #15 #248 #357
2024-11-28 02:22:15 +09:30
Jon
f53c6d0f6d test(project_management): Project Serializer Validation clean up
ref: #15 #248 #357
2024-11-28 02:22:15 +09:30
Jon
5fd3123c9b fix(project_management): use the post data or existing object for fetching edit organisation
ref: #248 #357
2024-11-28 02:22:15 +09:30
Jon
fa3698aa2b fix(project_management): Dont use init to adjust read_only_fields for project
ref: #248 #357
2024-11-28 02:22:15 +09:30
Jon
82a06e57b1 test(project_management): Project Type API v2 ViewSet permission checks
ref: #15 #248 #357
2024-11-28 02:22:15 +09:30
Jon
a09fb4c8cd test(project_management): Project Type Serializer Validation checks
ref: #15 #248 #357
2024-11-28 02:22:14 +09:30
Jon
91444172aa test(project_management): Project Type API field checks
ref: #15 #248 #357
2024-11-28 02:22:14 +09:30
Jon
745983dfab test(project_management): Project State API v2 ViewSet permission checks
ref: #15 #248 #357
2024-11-28 02:22:14 +09:30
Jon
58216073d7 test(project_management): Project state Serializer Validation checks
ref: #15 #248 #357
2024-11-28 02:22:14 +09:30
Jon
7263b3a8a3 test(project_management): Project state API field checks
ref: #15 #248 #357
2024-11-28 02:22:14 +09:30
Jon
8cc3adf3c2 test(project_management): Project Milestone API v2 ViewSet permission checks
ref: #15 #248 #357
2024-11-28 02:22:14 +09:30
Jon
dff794c433 test(project_management): Project milestone Serializer Validation checks
ref: #15 #248 #357
2024-11-28 02:22:14 +09:30
Jon
c89a8e8007 test(project_management): add trace output to Project serializer
tests are passing locally and not on GH actions

ref: #15 #248 #357
2024-11-28 02:22:14 +09:30
Jon
89588211fd test(project_management): Project Milestone API field checks
ref: #15 #248 #357
2024-11-28 02:22:14 +09:30
Jon
ab41c96182 test(project_management): Project API v2 ViewSet permission checks
ref: #15 #248 #357
2024-11-28 02:22:14 +09:30
Jon
9327c6b377 test(project_management): Project Serializer Validation checks
ref: #15 #248 #357
2024-11-28 02:22:14 +09:30
Jon
036dbdeba3 fix(project_management): if user not hav org specified dont attempt to access
ref: #358
2024-11-28 02:22:13 +09:30
Jon
b1b127b9f4 test(project_management): Project API field checks
ref: #15 #248 #357
2024-11-28 02:22:13 +09:30
Jon
77e09e8a13 feat(project_management): Add remaining Project base serializers for API v2
ref: #248 #357
2024-11-28 02:22:13 +09:30
Jon
663f496dc7 feat(project_management): Project Validation for API v2
ref: #248 #357
2024-11-28 02:22:13 +09:30
Jon
85a4158413 fix(project_management): for project serializer (api v1) ensure org is id
ref: #248 #357
2024-11-28 02:22:13 +09:30
Jon
ce566a8928 feat(project_management): Add Project Type API v2 endpoint
ref: #248 #357
2024-11-28 02:22:13 +09:30
Jon
9ecd545d1f feat(project_management): Add Project State API v2 endpoint
ref: #248 #357
2024-11-28 02:22:13 +09:30
Jon
11e3b04b46 feat(project_management): Add Project Milestone API v2 endpoint
ref: #248 #357
2024-11-28 02:22:13 +09:30
Jon
a708644809 feat(project_management): Add Project API v2 endpoint
ref: #248 #357
2024-11-28 02:22:13 +09:30
Jon
e524d4d43d test(itim): Port API v2 ViewSet permission checks
ref: #15 #248 #356
2024-11-28 02:22:13 +09:30
Jon
48b5754dcf feat(itim): Port Serializer Validations
ref: #15 #248 #356
2024-11-28 02:22:13 +09:30
Jon
814c4b2beb test(itim): Port API field checks
ref: #15 #248 #356
2024-11-28 02:22:13 +09:30
Jon
2dbee2a058 test(itim): Service API v2 ViewSet permission checks
ref: #15 #248 #356
2024-11-28 02:22:12 +09:30
Jon
ed34ed34cb feat(itim): Service Serializer Validations
ref: #15 #248 #356
2024-11-28 02:22:12 +09:30
Jon
5fe2b9e646 test(itim): Service Serializer Validation checks
ref: #15 #248 #356
2024-11-28 02:22:12 +09:30
Jon
a230edf25a fix(itim): Ensure service config from template is not gathered if not defined
ref: #15 #248 #356
2024-11-28 02:22:12 +09:30
Jon
a7c9ff4cee test(itim): Service API field checks
ref: #15 #248 #356
2024-11-28 02:22:12 +09:30
Jon
6e34e33c00 test(itim): Cluster Type API v2 ViewSet permission checks
ref: #15 #248 #356
2024-11-28 02:22:12 +09:30
Jon
a92bfd427f test(itim): Cluster Type Serializer Validation checks
ref: #15 #248 #353
2024-11-28 02:22:12 +09:30
Jon
0803b2c766 test(itam): Cluster Type API field checks
ref: #15 #248 #356
2024-11-28 02:22:12 +09:30
Jon
800b5d87cf test(itim): Cluster API ViewSet permission checks
ref: #15 #248 #356
2024-11-28 02:22:12 +09:30
Jon
bfe3f10535 test(itim): Cluster Serializer Validation checks
ref: #15 #248 #353
2024-11-28 02:22:12 +09:30
Jon
176f1c1073 fix(itim): Ensure params passed to super when validating cluster
ref: #248 #356
2024-11-28 02:22:12 +09:30
Jon
8f68345bb3 test(itam): Cluster API field checks
ref: #15 #248 #356
2024-11-28 02:22:12 +09:30
Jon
fe1816156a feat(itim): Ensure cluster cant assign itself as parent on api v2 endpoint
ref: #248 #356
2024-11-28 02:22:12 +09:30
Jon
d945092153 fix(itim): Correct Device Service API v2 endpoint
ref: #248 #356
2024-11-28 02:22:11 +09:30
Jon
cf31f198c8 test(itam): remove Device Ticket API field checks
tickets api endpooint not yet available

ref: #15 #248 #356
2024-11-28 02:22:11 +09:30
Jon
b4f3f0ec48 test(itam): Device Service API field checks
ref: #15 #248 #356
2024-11-28 02:22:11 +09:30
Jon
cfedd4b74e feat(itim): Add Port API v2 endpoint
ref: #248 #356
2024-11-28 02:22:11 +09:30
Jon
06362f226c feat(itim): Add Cluster API v2 endpoint
ref: #248 #356
2024-11-28 02:22:11 +09:30
Jon
02822cc70d feat(itim): Add Cluster Type API v2 endpoint
ref: #248 #356
2024-11-28 02:22:11 +09:30
Jon
f1c5ebca71 feat(itim): Add Service API v2 endpoint
ref: #248 #356
2024-11-28 02:22:11 +09:30
Jon
e180c3aa54 feat(itam): Depreciate API v1 Software Endpoint
ref: #248 #354
2024-11-28 02:22:11 +09:30
Jon
e504393c09 test(itam): Device Software API ViewSet permission checks
ref: #15 #248 #354
2024-11-28 02:22:11 +09:30
Jon
d6eebc1cab test(itam): Device Software Serializer Validation checks
ref: #15 #248 #353
2024-11-28 02:22:11 +09:30
Jon
5a7dc5afd1 test(itam): Device Software API field checks
ref: #15 #248 #354
2024-11-28 02:22:11 +09:30
Jon
613b904648 fix(itam): Don't attempt to include manufacturer in name for Device Model if not defined
ref: #248 #354
2024-11-28 02:22:11 +09:30
Jon
ae7355ba35 test(itam): Device Model API ViewSet permission checks
ref: #15 #248 #354
2024-11-28 02:22:10 +09:30
Jon
f44b97248f test(itam): Device Model Serializer Validation checks
ref: #15 #248 #353
2024-11-28 02:22:10 +09:30
Jon
7d73de2264 test(itam): Device Model API field checks
ref: #15 #248 #354
2024-11-28 02:22:10 +09:30
Jon
6ba172e2dc test(itam): Device Type API ViewSet permission checks
ref: #15 #248 #354
2024-11-28 02:22:10 +09:30
Jon
1f9c665d96 fix(itam): Ensure software version model has page_layout field
ref: #15 #248 #353
2024-11-28 02:22:10 +09:30
Jon
39bc70558a test(itam): Device Type Serializer Validation checks
ref: #15 #248 #353
2024-11-28 02:22:10 +09:30
Jon
fbbd809ef5 test(itam): Device Type API field checks
ref: #15 #248 #354
2024-11-28 02:22:10 +09:30
Jon
01ecc68384 test(itam): Software Version Tenancy Model Checks
ref: #15 #248 #354
2024-11-28 02:22:10 +09:30
Jon
523c82d72f test(itam): Software Version API ViewSet permission checks
ref: #15 #248 #354
2024-11-28 02:22:10 +09:30
Jon
4f66af4bbb test(itam): Software Version Serializer Validation checks
ref: #15 #248 #353
2024-11-28 02:22:10 +09:30
Jon
2908f6bd4c test(itam): Software Version API field checks
ref: #15 #248 #354
2024-11-28 02:22:10 +09:30
Jon
35e547268b test(itam): Software Category Version API ViewSet permission checks
ref: #15 #248 #354
2024-11-28 02:22:10 +09:30
Jon
ef5dd3dc21 test(itam): Software Category Serializer Validation checks
ref: #15 #248 #353
2024-11-28 02:22:10 +09:30
Jon
65926210b2 test(itam): Software Category Version API field checks
ref: #15 #248 #354
2024-11-28 02:22:09 +09:30
Jon
cd78a6b12f test(itam): Operating System Version API ViewSet permission checks
ref: #15 #248 #354
2024-11-28 02:22:09 +09:30
Jon
8b155eb895 test(itam): Operating System Version Serializer Validation checks
ref: #15 #248 #353
2024-11-28 02:22:09 +09:30
Jon
92825d3b34 test(itam): Operating System Version API field checks
ref: #15 #248 #354
2024-11-28 02:22:09 +09:30
Jon
794fb6a733 test(itam): Software API ViewSet permission checks
ref: #15 #248 #354
2024-11-28 02:22:09 +09:30
Jon
23bcdce938 test(itam): Software Serializer Validation checks
ref: #15 #248 #353
2024-11-28 02:22:09 +09:30
Jon
7bd50c6805 test(itam): Software API field checks
ref: #15 #248 #354
2024-11-28 02:22:09 +09:30
Jon
911ba67459 test(itam): Operating System Serializer Validation checks
ref: #15 #248 #353
2024-11-28 02:22:09 +09:30
Jon
85cde78203 test(itam): Operating_system API ViewSet permission checks
ref: #15 #248 #354
2024-11-28 02:22:09 +09:30
Jon
efa805816f test(itam): Operating System API field checks
ref: #15 #248 #354
2024-11-28 02:22:09 +09:30
Jon
cb73866cdc test(itam): Device API field checks
ref: #15 #248 #354
2024-11-28 02:22:09 +09:30
Jon
b7bbf02dff test(itam): Device Serializer Validation checks
ref: #15 #248 #353
2024-11-28 02:22:09 +09:30
Jon
9e83ec9adb test(core): Device API ViewSet permission checks
ref: #15 #248 #354
2024-11-28 02:22:08 +09:30
Jon
0314684064 feat(core): Add Operating System Version API v2 endpoint
ref: #248 #355
2024-11-28 02:22:08 +09:30
Jon
5ac063d8c9 feat(core): Add Operating System API v2 endpoint
ref: #248 #355
2024-11-28 02:22:08 +09:30
Jon
873dc71c08 test: enure correct type checks for url
ref: #15 #248 #354
2024-11-28 02:22:08 +09:30
Jon
09b2ea378b feat(core): Add External Link API v2 endpoint
ref: #248 #355
2024-11-28 02:22:08 +09:30
Jon
538265f526 feat(itam): Add Device Software API v2 endpoint
ref: #248 #355
2024-11-28 02:22:08 +09:30
Jon
4dc0f31e69 feat(itam): Add Device API v2 endpoint
ref: #248 #355
2024-11-28 02:22:08 +09:30
Jon
1daa0ca219 feat(itam): Add Device Type API v2 endpoint
ref: #248 #355
2024-11-28 02:22:08 +09:30
Jon
318d342d2b feat(itam): Add Software Version API v2 endpoint
ref: #248 #355
2024-11-28 02:22:08 +09:30
Jon
947112ba39 feat(itam): Depreciate API v1 device endpoint
ref: #248 #355
2024-11-28 02:22:08 +09:30
Jon
e54d7cfeb2 feat(itam): Add Software API v2 endpoint
ref: #248 #355
2024-11-28 02:22:08 +09:30
Jon
bcb0ce42df feat(itam): Add Device Model API v2 endpoint
ref: #248 #355
2024-11-28 02:22:08 +09:30
Jon
f0b14cfa66 feat(itam): Add Device API v2 endpoint
ref: #248 #355
2024-11-28 02:22:08 +09:30
Jon
d1c66318b2 test(core): Manufacturer API ViewSet permission checks
ref: #15 #248 #354
2024-11-28 02:22:07 +09:30
Jon
5f4a09da25 test(core): Manufacturer Serializer Validation checks
ref: #15 #248 #353
2024-11-28 02:22:07 +09:30
Jon
32ac01dc55 test(assistance): Manufacturer API field checks
ref: #15 #248 #354
2024-11-28 02:22:07 +09:30
Jon
9884312d47 feat(itim): Add Service Notes API v2 endpoint
ref: #248 #354
2024-11-28 02:22:07 +09:30
Jon
3f41fc19d2 feat(core): Add Software Notes API v2 endpoint
ref: #248 #354
2024-11-28 02:22:07 +09:30
Jon
5d953771d7 feat(core): Add Manufacturer API v2 endpoint
ref: #248 #354
2024-11-28 02:22:07 +09:30
Jon
94119b1f9f fix(core): notes field must be mandatory
ref: #354
2024-11-28 02:22:07 +09:30
Jon
4841a36968 test(assistance): Notes API field checks
ref: #15 #49 #248 #354
2024-11-28 02:22:07 +09:30
Jon
319eaadbc2 test(core): Notes Serializer Validation checks
ref: #15 #49 #248 #354
2024-11-28 02:22:07 +09:30
Jon
df5a185986 test(itim): Service Note API ViewSet permission checks
ref: #15 #49 #248 #354
2024-11-28 02:22:07 +09:30
Jon
3e210ed217 test(itam): Softwaare Note API ViewSet permission checks
ref: #15 #49 #248 #354
2024-11-28 02:22:07 +09:30
Jon
0ce5b0d98c test(itam): Operating System Note API ViewSet permission checks
ref: #15 #49 #248 #354
2024-11-28 02:22:06 +09:30
Jon
a0b013d44e test(config_management): Device Note API ViewSet permission checks
ref: #15 #49 #248 #354
2024-11-28 02:22:06 +09:30
Jon
9f81c49119 test: Adjust tests to cater for action choices now being an integer
ref: #15 #46 #248 #354
2024-11-28 02:22:06 +09:30
Jon
2363064b1a feat(itim): Add Service base serializer
ref: #248 #354
2024-11-28 02:22:06 +09:30
Jon
419725f13f feat(itam): Add operating system Base Serializer
ref: #248 #354
2024-11-28 02:22:06 +09:30
Jon
4a2ad114d7 test(config_management): Config Groups Note API ViewSet permission checks
ref: #15 #49 #248 #354
2024-11-28 02:22:06 +09:30
Jon
c7d4f2b496 feat(config_management): Add Notes API v2 endpoint
ref: #248 #354
2024-11-28 02:22:06 +09:30
Jon
e8279dd363 refactor(core): Adjust action choices to be integer
ref: #248 #354
2024-11-28 02:22:06 +09:30
Jon
0ed6133698 test(config_management): History API ViewSet permission checks
ref: #15 #248 #354
2024-11-28 02:22:06 +09:30
Jon
2dd01111d9 feat(config_management): Add History API v2 endpoint
ref: #248 #354
2024-11-28 02:22:06 +09:30
Jon
40f110e7e5 fix(core): Add missing attributes name to history model
ref: #248 #354
2024-11-28 02:22:06 +09:30
Jon
b9b2142cc1 fix(config_management): ensure validation uses software.id for config group software serializer
ref: #248 #353
2024-11-28 02:22:06 +09:30
Jon
71f746dba6 feat(config_management): Depreciate API v1 config endpoint
ref: #248 #353
2024-11-28 02:22:06 +09:30
Jon
f019791fa4 test(config_management): Config Groups Software API ViewSet permission checks
ref: #15 #248 #353
2024-11-28 02:22:05 +09:30
Jon
99313d7a8d feat(config_management): Add config groups to config api endpoint
ref: #248 #353
2024-11-28 02:22:05 +09:30
Jon
114272471e test(config_management): Config Groups Software Serializer Validation checks
ref: #15 #248 #353
2024-11-28 02:22:05 +09:30
Jon
7d3576a879 feat(config_management): Add Device Base Serializer
ref: #248 #353
2024-11-28 02:22:05 +09:30
Jon
5ab7ce05bc feat(itam): Add Software Version Base Serializer
ref: #248 #348
2024-11-28 02:22:05 +09:30
Jon
99550e7ab3 feat(itam): Add Software Base Serializer
ref: #248 #348
2024-11-28 02:22:05 +09:30
Jon
bd11d82107 test(config_management): Config Groups Software Serializer Validation checks
ref: #15 #248 #353
2024-11-28 02:22:05 +09:30
Jon
3482b7dd0a feat(config_management): Add Config Group Software API v2 endpoint
ref: #248 #348
2024-11-28 02:22:05 +09:30
Jon
cb6f15b933 refactor(config_management): Adjust rendered config str -> dict
ref: #248 #353
2024-11-28 02:22:05 +09:30
Jon
976a5f0706 refactor(itam): Software Action field changed char -> integer
ref: #248 #353
2024-11-28 02:22:05 +09:30
Jon
7d35db030c refactor(itam): rename dir viewset -> viewsets
ref: #248 #353
2024-11-28 02:22:05 +09:30
Jon
0c9a9f5ae1 refactor(config_management): move config_group_hosts to related table
ref: #353
2024-11-28 02:22:05 +09:30
Jon
8219bf6c9d chore(vscode): add debug for migrations
ref: #353
2024-11-28 02:22:04 +09:30
Jon
a0ac0e4839 refactor: update model fields
ref: #353
2024-11-28 02:22:04 +09:30
Jon
fae48f2c5a fix(config_management): Config Groups Serializer Validation checks
ref: #15 #248 #353
2024-11-28 02:22:04 +09:30
Jon
518142492f test(config_management): Config Groups Serializer Validation checks
ref: #15 #248 #353
2024-11-28 02:22:04 +09:30
Jon
cc6770278f test(config_management): Config Groups API ViewSet permission checks
ref: #15 #248 #353
2024-11-28 02:22:04 +09:30
Jon
8b091e3c79 test(assistance): Config Group API field checks
ref: #15 #248 #353
2024-11-28 02:22:04 +09:30
Jon
10704457b0 feat(config_management): Add Config Group API v2 endpoint
ref: #248 #348
2024-11-28 02:22:04 +09:30
Jon
5b9ed1db2c refactor(config_management): update serializer dir name
ref: #248
2024-11-28 02:22:04 +09:30
Jon
47d5e40315 feat(assistance): Ensure Knowledge Base Category cant assign self as parent category
ref: #15 #248 #352
2024-11-28 02:22:04 +09:30
Jon
9278668e58 test(assistance): Knowledge Base Category Serializer Validation checks
ref: #15 #248 #352
2024-11-28 02:22:04 +09:30
Jon
56235e6ffe fix(assistance): Correct Knowledge Base Category serializer Validation
ref: #248 #348
2024-11-28 02:22:04 +09:30
Jon
ed6cf305ae test(assistance): ensure is_valid raises exceptions for Knowledge Base Serializer Validation checks
ref: #15 #248 #352
2024-11-28 02:22:04 +09:30
Jon
df213c2015 feat(assistance): Knowledge Base Serializer Validation method added
ref: #248 #352
2024-11-28 02:22:03 +09:30
Jon
ea4952bd3e fix(itam): Correct inventory validation response data
ref: #352
2024-11-28 02:22:03 +09:30
Jon
138f8237fe test(assistance): Knowledge Base Serializer Validation checks
ref: #15 #248 #352
2024-11-28 02:22:03 +09:30
Jon
a4369c4a00 fix(itam): Correct inventory api upload to use API exceptions instead of django base
ref: #352
2024-11-28 02:22:03 +09:30
Jon
42e38e26d9 fix(assistance): Add missing fields display_name and model_notes to Knowledge Base Category serializer
ref: #248 #352
2024-11-28 02:22:03 +09:30
Jon
073330015c test(assistance): Knowledge Base Category API field checks
ref: #15 #248 #352
2024-11-28 02:22:03 +09:30
Jon
80e981d40f test(assistance): Knowledge Base API field checks
ref: #15 #248 #352
2024-11-28 02:22:03 +09:30
Jon
6d58f33a67 refactor(access): add name to modified field
ref: #352
2024-11-28 02:22:03 +09:30
Jon
a87b313fdf fix(assistance): correct KB category serializer validation
ref: #15 #248 #352
2024-11-28 02:22:03 +09:30
Jon
8b31a96508 test(access): correct organization permission checks to have HTTP/403 not HTTP/405
ref: #15 #248 #352
2024-11-28 02:22:03 +09:30
Jon
a32fe28a0d test(assistance): Knowledge Base Category API ViewSet permission checks
ref: #15 #248 #352
2024-11-28 02:22:03 +09:30
Jon
308b5168d8 test(assistance): Knowledge Base API ViewSet permission checks
ref: #15 #248 #352
2024-11-28 02:22:03 +09:30
Jon
62ca58e820 refactor(api): Adjust viewset common so that page_layout is available for base
ref: #248 #348
2024-11-28 02:22:02 +09:30
Jon
94f3925127 fix(assistance): Correct Knowledge Base serialaizer Validation
ref: #248 #348
2024-11-28 02:22:02 +09:30
Jon
b8cafeb99b feat(assistance): Add Knowledge Base Category API v2 endpoint
ref: #248 #348
2024-11-28 02:22:02 +09:30
Jon
61450b442d feat(assistance): Add Knowledge Base API v2 endpoint
ref: #248 #348
2024-11-28 02:22:02 +09:30
Jon
7e92760340 fix(api): on permission check error, return authorized=false
ref: #248 #352
2024-11-28 02:22:00 +09:30
Jon
3cb9aa6f59 refactor(assistance): Correct viewset dir name to viwsets
ref: #248 #352
2024-11-28 02:20:45 +09:30
Jon
a77e01f86f feat(api): Depreciate API v1 permission endpoint
ref: #15 #248 #348
2024-11-28 02:20:45 +09:30
Jon
6f7638d9cf test(base): User API ViewSet permission checks
ref: #15 #248 #348
2024-11-28 02:20:45 +09:30
Jon
2b24f29837 test(base): Permission API ViewSet permission checks
ref: #15 #248 #348
2024-11-28 02:20:45 +09:30
Jon
835e5258a5 test(base): Content Type API ViewSet permission checks
ref: #15 #248 #348
2024-11-28 02:20:44 +09:30
Jon
84de741f53 fix(access): Add missing parameters to Team User fields
ref: #15 #248 #348
2024-11-28 02:20:44 +09:30
Jon
59e34cae4d test(access): Add missing test cases to Team Users Model
ref: #15 #248 #348
2024-11-28 02:20:44 +09:30
Jon
4b873a4e44 test(access): Team Users API v2 field checks
ref: #15 #248 #348
2024-11-28 02:20:44 +09:30
Jon
80d307b2a5 test(access): Team User API ViewSet permission checks
ref: #15 #248 #348
2024-11-28 02:20:44 +09:30
Jon
9beb9a9d2c feat(access): Add Team Users API endpoint
ref: #248 #348
2024-11-28 02:20:44 +09:30
Jon
636f6c5b58 docs: remove links for files removed
ref: #348
2024-11-28 02:20:44 +09:30
Jon
0877fcf7e9 fix(api): Add missing organization url routes
ref: #15 #248 #348
2024-11-28 02:20:44 +09:30
Jon
0faa6a5a18 docs(api): Add serializer dev docs
ref: #15 #248 #348
2024-11-28 02:20:44 +09:30
Jon
fb7fda7ea2 test(access): Team API v2 field checks
ref: #15 #248 #348
2024-11-28 02:20:44 +09:30
Jon
b6acba9930 test(api): API Response Field checks Abstract Class added
ref: #15 #248 #348
2024-11-28 02:20:44 +09:30
Jon
af3a84f0dc test(access): Organization API v2 field checks
ref: #15 #248 #348
2024-11-28 02:20:44 +09:30
Jon
200909fb82 test(access): Team API ViewSet permission checks
ref: #15 #248 #348
2024-11-28 02:20:44 +09:30
Jon
1d198dd2df test(access): Organization API ViewSet permission checks
ref: #15 #248 #348
2024-11-28 02:20:43 +09:30
Jon
c34dd9f2a4 fix(access): ensure org id is an integer during permission checks
ref: #348
2024-11-28 02:20:43 +09:30
Jon
2690e17f93 test(api): API Permission ViewSet Abstract Class added
ref: #15 #248 #345
2024-11-28 02:20:43 +09:30
Jon
5edbdd76f4 docs(swagger): refine normalisation of api v1/v2 docs
ref:  #248 #345 #346
2024-11-28 02:20:43 +09:30
Jon
e917fbf68d feat(access): Depreciate Team API v1 endpoint
ref: #248 #348 #343
2024-11-28 02:20:43 +09:30
Jon
606477cb0d feat(access): Depreciate Organization API v1 endpoint
ref: #248 #348 #343
2024-11-28 02:20:43 +09:30
Jon
8da3a04730 feat(access): Add Organization API endpoint
ref: #248 #348
2024-11-28 02:20:43 +09:30
Jon
c6c8bfd045 feat(base): Add Team API endpoint
ref: #248 #348
2024-11-28 02:20:43 +09:30
Jon
0bd057b436 feat(base): Add Permission API endpoint
ref: #248 #348
2024-11-28 02:20:43 +09:30
Jon
bb93ef3f1d feat(base): Add Content Type API endpoint
ref: #248 #348
2024-11-28 02:20:43 +09:30
Jon
79f17a7d57 feat(api): Add Read Only abstract ViewSet
ref:  #248 #348
2024-11-28 02:20:43 +09:30
Jon
b9301e4697 feat(base): Add user API endpoint
ref:  #248 #348
2024-11-28 02:20:43 +09:30
Jon
074d12b99b docs(swagger): normalize api v1/v2 docs
ref:  #248 #348
2024-11-28 02:20:43 +09:30
Jon
1073b2228e docs(release_notes): fluff out feature freeze details
ref: #346
2024-11-28 02:20:43 +09:30
Jon
a05cf021c1 test(access): Team custom tests to ensure that during model field creation, attribute verbose_name is defined and not empty
as team extends group, filtering of group fields is required so they are not checked when testing

ref:  #248 #345 #346
2024-11-28 02:20:43 +09:30
Jon
c185c192a7 test(itim): port placeholder test for invalid port number
ref: #346
2024-11-28 02:20:42 +09:30
Jon
77ef69488b test: use correct logic when testin field parameters as not being empty or none
ref: #346
2024-11-28 02:20:42 +09:30
Jon
a1625517d1 fix(access): if permission_required attribute doesn't exist during permission check, return empty list
ref: #346
2024-11-28 02:20:42 +09:30
Jon
32d5008f63 fix: Ensure all Model fields are created with attributes help_text and verbose_name
ref:  #248 #346
2024-11-28 02:20:42 +09:30
Jon
e765b03d3b docs: add new requirements for creating a model.
ref:  #248 #345 #346
2024-11-28 02:20:42 +09:30
Jon
01da3f6fd0 test: Ensure that during model field creation, attribute verbose_name is defined and not empty
ref:  #248 #345 #346
2024-11-28 02:20:42 +09:30
Jon
ad72cc4f7d test: Ensure that during model field creation, attribute help_text is defined and not empty
ref:  #248 #345 #346
2024-11-28 02:20:42 +09:30
Jon
1f9070c420 fix(api): correct logic for permission check to use either queryset or get_queryset
ref:  #248 #345 #346
2024-11-28 02:20:27 +09:30
Jon
783f063ef0 feat(api): add v2 endpoint
ref:  #248 #345 #346
2024-11-28 02:19:25 +09:30
Jon
31af310d3d fix(settings): Add attribute table_fields to External Links model
ref:  #248 #345 #346
2024-11-28 02:19:25 +09:30
Jon
630223b15a fix(settings): Add attribute page_layout to External Links model
ref:  #248 #345 #346
2024-11-28 02:19:25 +09:30
Jon
d64d744f9f fix(settings): Add missing attribute Meta.verbose_name to External Links model
ref:  #248 #345 #346
2024-11-28 02:19:25 +09:30
Jon
f67051fb15 fix(settingns): Add missing attribute Meta.ordering to External Links model
ref:  #248 #345 #346
2024-11-28 02:19:25 +09:30
Jon
4a5991f9db feat(project_management): Add attribute table_fields to Project Type model
ref:  #248 #345 #346
2024-11-28 02:19:25 +09:30
Jon
c0a0bc544a feat(project_management): Add attribute page_layout to Project Type model
ref:  #248 #345 #346
2024-11-28 02:19:25 +09:30
Jon
3f03b8710f feat(project_management): Add attribute table_fields to Project State model
ref:  #248 #345 #346
2024-11-28 02:19:25 +09:30
Jon
9f8d2acd99 feat(project_management): Add attribute page_layout to Project State model
ref:  #248 #345 #346
2024-11-28 02:19:25 +09:30
Jon
dc6f9d3f17 feat(project_management): Add attribute page_layout to Project Milestone model
ref:  #248 #345 #346
2024-11-28 02:19:25 +09:30
Jon
4e358a0541 feat(project_management): Add attribute table_fields to Project Milestone model
ref:  #248 #345 #346
2024-11-28 02:19:25 +09:30
Jon
8ffffab395 feat(project_management): Add attribute table_fields to Project model
ref:  #248 #345 #346
2024-11-28 02:19:25 +09:30
Jon
396566c3be feat(project_management): Add attribute page_layout to Project model
ref:  #248 #345 #346
2024-11-28 02:19:25 +09:30
Jon
5cbb081462 feat(itim): Add attribute table_fields to Service model
ref:  #248 #345 #346
2024-11-28 02:19:24 +09:30
Jon
8058369276 feat(itim): Add attribute page_layout to Service model
ref:  #248 #345 #346
2024-11-28 02:19:24 +09:30
Jon
c205a75ce0 feat(itim): Add attribute table_fields to Service Port model
ref:  #248 #345 #346
2024-11-28 02:19:24 +09:30
Jon
3998325acd feat(itim): Add attribute page_layout to Service Port model
ref:  #248 #345 #346
2024-11-28 02:19:24 +09:30
Jon
8f410b370d feat(itim): Add attribute table_fields to Cluster Type model
ref:  #248 #345 #346
2024-11-28 02:19:24 +09:30
Jon
790cbaf452 feat(itim): Add attribute page_layout to Cluster Type model
ref:  #248 #345 #346
2024-11-28 02:19:24 +09:30
Jon
54796badc9 feat(itim): Add attribute table_field to Cluster model
ref:  #248 #345 #346
2024-11-28 02:19:24 +09:30
Jon
939424788f feat(itim): Add attribute page_layout to Cluster model
ref:  #248 #345 #346
2024-11-28 02:19:24 +09:30
Jon
73517733b5 feat(itam): Add attribute table_field to Software Category model
ref:  #248 #345 #346
2024-11-28 02:19:24 +09:30
Jon
5565670495 fix(itam): Add missing attribute Meta.verbose_name to Software Category model
ref:  #248 #345 #346
2024-11-28 02:19:24 +09:30
Jon
66b60e02e6 fix(itam): Add missing attribute Meta.ordering to Software Category model
ref:  #248 #345 #346
2024-11-28 02:19:24 +09:30
Jon
d98cd846bf feat(itam): Add attribute table_fields to Software model
ref:  #248 #345 #346
2024-11-28 02:19:24 +09:30
Jon
baef2e9287 feat(itam): Add attribute page_layout to Software model
ref:  #248 #345 #346
2024-11-28 02:19:24 +09:30
Jon
dbde9144d9 fix(itam): Add missing attribute Meta.verbose_name to Software model
ref:  #248 #345 #346
2024-11-28 02:19:24 +09:30
Jon
1d60e1105a fix(itam): Add missing attribute Meta.ordering to Software model
ref:  #248 #345 #346
2024-11-28 02:19:24 +09:30
Jon
b1fc59be3a feat(itam): Add attribute table_fields to Operating System Version model
ref:  #248 #345 #346
2024-11-28 02:19:24 +09:30
Jon
1e4d6087ff feat(itam): Add attribute page_layout to Operating System Version model
ref:  #248 #345 #346
2024-11-28 02:19:24 +09:30
Jon
c45da0f676 fix(itam): Add missing attribute Meta.verbose_name to Operating System Version model
ref:  #248 #345 #346
2024-11-28 02:19:24 +09:30
Jon
c1daab6069 fix(itam): Add missing attribute Meta.ordering to Operating System Version model
ref:  #248 #345 #346
2024-11-28 02:19:24 +09:30
Jon
372e4c190f feat(itam): Add attribute table_field to Operating System model
ref:  #248 #345 #346
2024-11-28 02:19:24 +09:30
Jon
5cc7ba0237 feat(itam): Add attribute page_layout to Operating System model
ref:  #248 #345 #346
2024-11-28 02:19:23 +09:30
Jon
5042124803 fix(itam): Add missing attribute Meta.verbose_name to Operating System model
ref:  #248 #345 #346
2024-11-28 02:19:23 +09:30
Jon
ce58d13ab5 fix(itam): Add missing attribute Meta.ordering to Operating System model
ref:  #248 #345 #346
2024-11-28 02:19:23 +09:30
Jon
61bfffa3ca feat(itam): Add attribute table_fields to "Device Type" model
ref:  #248 #345 #346
2024-11-28 02:19:23 +09:30
Jon
0615977024 feat(itam): Add attribute page_layout to "Device Type" model
ref:  #248 #345 #346
2024-11-28 02:19:23 +09:30
Jon
2563f6f8e5 fix(itam): Add missing attribute Meta.verbose_name to "Device Type" model
ref:  #248 #345 #346
2024-11-28 02:19:23 +09:30
Jon
21a50f16ae fix(itam): Add missing attribute Meta.ordering to "Device Software" model
ref:  #248 #345 #346
2024-11-28 02:19:23 +09:30
Jon
f362e3493f feat(itam): Add attribute page_layout to "Device Software" model
ref:  #248 #345 #346
2024-11-28 02:19:23 +09:30
Jon
f24e645d55 fix(itam): Add missing attribute Meta.verbose_name to "Device Software" model
ref:  #248 #345 #346
2024-11-28 02:19:23 +09:30
Jon
26640926a8 feat(itam): Add attribute table_fields to "Device Software" model
ref:  #248 #345 #346
2024-11-28 02:19:23 +09:30
Jon
da49b98c60 feat(itam): Add attribute page_layout to "Device Software" model
ref:  #248 #345 #346
2024-11-28 02:19:23 +09:30
Jon
ab69d8d174 fix(itam): Add missing attribute Meta.verbose_name to "Device Software" model
ref:  #248 #345 #346
2024-11-28 02:19:23 +09:30
Jon
2f0a2e282b fix(itam): Add missing attribute Meta.ordering to "Device Software" model
ref:  #248 #345 #346
2024-11-28 02:19:23 +09:30
Jon
b57df0d5bc fix(itama): Add missing attribute Meta.verbose_name to "Device Model" model
ref:  #248 #345 #346
2024-11-28 02:19:22 +09:30
Jon
6ecaa08782 feat(core): Add attribute table_fields to Ticket Comment Category model
ref:  #248 #345 #346
2024-11-28 02:19:22 +09:30
Jon
f134b6828f feat(core): Add attribute page_layout to Ticket Comment Category model
ref:  #248 #345 #346
2024-11-28 02:19:22 +09:30
Jon
5776a61ff4 feat(core): Add attribute page_layout to Ticket comment model
ref:  #248 #345 #346
2024-11-28 02:19:22 +09:30
Jon
a1bd6a81b3 feat(core): Add attribute table_fields to Ticket Category model
ref:  #248 #345 #346
2024-11-28 02:19:22 +09:30
Jon
f814151d33 feat(core): Add attribute page_layout to Ticket Category model
ref:  #248 #345 #346
2024-11-28 02:19:22 +09:30
Jon
1d1e295af7 feat(core): Add attribute page_layout to Ticket model
this model does not require this to be filled out as it uses a custom view

ref:  #248 #345 #346
2024-11-28 02:19:22 +09:30
Jon
a78f7660e2 feat(core): Add attribute page_layout to Notes model
ref:  #248 #345 #346
2024-11-28 02:19:22 +09:30
Jon
8fa468f735 fix(core): Add missing attribute Meta.verbose_name to Notes model
ref:  #248 #345 #346
2024-11-28 02:19:22 +09:30
Jon
65759827c8 feat(core): Add attribute table_fields to Manufacturer model
ref:  #248 #345 #346
2024-11-28 02:19:22 +09:30
Jon
98ded4c748 feat(core): Add attribute page_layout to Manufacturer model
ref:  #248 #345 #346
2024-11-28 02:19:22 +09:30
Jon
18f8e515d8 fix(core): Add missing attribute Meta.verbose_name to Manufacturer model
ref:  #248 #345 #346
2024-11-28 02:19:21 +09:30
Jon
8121879165 feat(access): Add attribute table_fields to Config Group Software model
ref:  #248 #345 #346
2024-11-28 02:19:21 +09:30
Jon
066d8b903a feat(access): Add attribute page_layout to Config Group Software model
ref:  #248 #345 #346
2024-11-28 02:19:21 +09:30
Jon
11fd7f8c6c fix(access): Add missing attribute Meta.verbos_name to Config Group Software model
ref:  #248 #345 #346
2024-11-28 02:19:21 +09:30
Jon
3b358599ad feat(access): Add attribute table_fields to Config Groups model
ref:  #248 #345 #346
2024-11-28 02:19:21 +09:30
Jon
7dcde1926e feat(access): Add attribute page_layout to Config Groups model
ref:  #248 #345 #346
2024-11-28 02:19:21 +09:30
Jon
9e3a61a890 fix(access): Add missing attribute Meta.verbos_name to Config Groups model
ref:  #248 #345 #346
2024-11-28 02:19:21 +09:30
Jon
3080b1c1b7 fix(access): Add missing attribute Meta.ordering Config Groups model
ref:  #248 #345 #346
2024-11-28 02:19:21 +09:30
Jon
8e0af707cf feat(access): Add attribute table_fields to KB model
ref:  #248 #345 #346
2024-11-28 02:19:21 +09:30
Jon
d5a4a570e3 feat(access): Add attribute page_layout to KB model
ref:  #248 #345 #346
2024-11-28 02:19:21 +09:30
Jon
8824fcebdf feat(access): Add attribute table_fields to KB Category model
ref:  #248 #345 #346
2024-11-28 02:19:21 +09:30
Jon
e52a9c0003 feat(access): Add attribute page_layout to KB Category model
ref:  #248 #345 #346
2024-11-28 02:19:21 +09:30
Jon
ebf51da951 feat(access): Add attribute table_fields to Team model
ref:  #248 #345 #346
2024-11-28 02:19:21 +09:30
Jon
af27b55ad7 feat(access): Add attribute page_layout to Team model
ref:  #248 #345 #346
2024-11-28 02:19:21 +09:30
Jon
617bbcc724 fix(access): Add missing meta field verbose_name to Team model
ref:  #248 #345 #346
2024-11-28 02:19:21 +09:30
Jon
72c42f07cb test(api): Ensure models have Meta.ordering set and not empty
ref:  #345 #346
2024-11-28 02:19:21 +09:30
Jon
67b8648a69 test(api): viewset documentation attr check
ref:  #345 #346
2024-11-28 02:19:21 +09:30
Jon
37176458ac feat(core): Add table_fields to Ticket Model
ref:  #345 #346
2024-11-28 02:19:21 +09:30
Jon
ad1b35dfc7 fix(api): during permission checking if request is HTTP/Options and user is authenticated, allow access
ref:  #345 #346
2024-11-28 02:19:21 +09:30
Jon
cf8014a26b fix(api): during permission checking dont attempt to access view obj if it doesn't exist
ref:  #345 #346
2024-11-28 02:18:26 +09:30
Jon
030bb13396 test(api): fix index import to correct viewset
ref:  #345 #346
2024-11-28 02:18:26 +09:30
Jon
fe6a405a41 test(itam): Add index viewset checks
ref:  #345 #346
2024-11-28 02:18:26 +09:30
Jon
97afeeb45d feat(itam): Add v2 endpoint ITAM
ref:  #345 #346
2024-11-28 02:18:26 +09:30
Jon
d23c2907cf feat(base): Add User Serializer
ref:  #345 #346
2024-11-28 02:18:26 +09:30
Jon
8f9682b0c4 test(Settings): Add index viewset checks
ref:  #345 #346
2024-11-28 02:18:26 +09:30
Jon
244ae6c3f9 feat(settings): Add v2 endpoint Settings
ref:  #345 #346
2024-11-28 02:18:26 +09:30
Jon
1b5411136d test(project_management): Add index viewset checks
ref:  #345 #346
2024-11-28 02:18:26 +09:30
Jon
11cf3a11fc feat(project_management): Add v2 endpoint Project Management
ref:  #345 #346
2024-11-28 02:18:26 +09:30
Jon
3a3ec331d7 test(itim): Add index viewset checks
ref:  #345 #346
2024-11-28 02:18:26 +09:30
Jon
1df9589d55 feat(itim): Add v2 endpoint ITIM
ref:  #345 #346
2024-11-28 02:18:26 +09:30
Jon
039ae89814 test(config_management): Add index viewset checks
ref:  #345 #346
2024-11-28 02:18:26 +09:30
Jon
976f5da446 feat(config_management): Add v2 endpoint Config Management
ref:  #345 #346
2024-11-28 02:18:26 +09:30
Jon
af9d99774e test(assistance): Add index viewset checks
ref:  #345 #346
2024-11-28 02:18:26 +09:30
Jon
aa57005013 feat(assistance): Add v2 endpoint Assistance
ref:  #345 #346
2024-11-28 02:18:25 +09:30
Jon
374c18d997 test(access): Add index viewset checks
ref:  #345 #346
2024-11-28 02:18:25 +09:30
Jon
73d56692d1 feat(access): Add v2 endpoint Access
ref:  #345 #346
2024-11-28 02:18:25 +09:30
Jon
8b0bcfa886 feat(itim): Add table_fields to Service Model
ref:  #345 #346
2024-11-28 02:18:25 +09:30
Jon
4a27360b7c fix(itam): Add missing model.Meta attributes ordering and verbose_name
ref:  #345 #346
2024-11-28 02:18:25 +09:30
Jon
a93e5625be feat(core): Add table_fields to Device Software Model
ref:  #345 #346
2024-11-28 02:18:25 +09:30
Jon
fa9a36dffa refactor(itam): Cleanup Device Software model field names.
ref:  #345 #346
2024-11-28 02:18:25 +09:30
Jon
766706268b feat(core): Add table_fields to Notes Model
ref:  #345 #346
2024-11-28 02:18:25 +09:30
Jon
36d044dc43 refactor(core): Change history fields after and before to be JSON fields
ref:  #345 #346
2024-11-28 02:18:25 +09:30
Jon
b827dfac61 feat(core): Add table_fields to History Model
ref:  #345 #346
2024-11-28 02:18:25 +09:30
Jon
b5c1e85258 feat(itam): Add table_fields and page_layout to Device Model
ref:  #345 #346
2024-11-28 02:18:25 +09:30
Jon
f8a1087af3 feat(itam): Add table_fields and page_layout to Device Model
ref:  #345 #346
2024-11-28 02:18:25 +09:30
Jon
a776fcc760 feat(core): Add table_fields to Ticket Linked Item
ref:  #345 #346
2024-11-28 02:18:25 +09:30
Jon
8f2726aafb feat(core): Add table_fields to Ticket Comment
ref:  #345 #346
2024-11-28 02:18:25 +09:30
Jon
95675da022 feat(core): Add table_fields to Ticket
ref:  #345 #346
2024-11-28 02:18:25 +09:30
Jon
3488d200b6 feat(core): Add attribute staatus_badge to ticket model
ref:  #345 #346
2024-11-28 02:18:25 +09:30
Jon
20b77b4b1e feat(access): Add table_fields and page_layout to Organization
ref:  #345 #346
2024-11-28 02:18:25 +09:30
Jon
d545efc0d3 feat(api): Add React UI metadata class
adds required items to HTTP/Options request

ref:  #345 #346
2024-11-28 02:18:25 +09:30
Jon
8a70ec1452 test(api): Add API v2 Endpoint
ref: #248 #345 #346
2024-11-28 02:18:25 +09:30
Jon
b8c127d144 feat(api): Add API v2 Endpoint
ref: #248 #345 #346
2024-11-28 02:18:25 +09:30
Jon
447c46eb47 refactor(api): Split common ViewSet class into index/model classes
ref: #345 #346
2024-11-28 02:18:25 +09:30
Jon
9fe671e43c test(api): ViewSet checks
ref: #345 #346
2024-11-28 02:18:25 +09:30
Jon
3bfd2d4ef6 feat(api): add API login template to use current login form
ref: #345 #346
2024-11-28 02:18:25 +09:30
Jon
65a47db81d feat(api): Update API template to use name Centurion
ref: #345 #346
2024-11-28 02:18:24 +09:30
Jon
b471718b6a feat(itam): Add category property to device software model
ref: #345 #346
2024-11-28 02:18:24 +09:30
Jon
50e6a24a4d feat(itam): Add action badge property to device software model
ref: #345 #346
2024-11-28 02:18:24 +09:30
Jon
935dfb7faa feat(itam): Add status badge property to device model
ref: #345 #346
2024-11-28 02:18:24 +09:30
Jon
849b8da7eb refactor(itam): remove requirement to specify the pk when fetching config
ref: #345 #346
2024-11-28 02:18:24 +09:30
Jon
991fb3432c feat(core): Add a icon serializer field.
ref: #345 #346
2024-11-28 02:18:24 +09:30
Jon
c0cf657bea feat(core): Add a badge serializer field.
ref: #345 #346
2024-11-28 02:18:24 +09:30
Jon
07ceae6471 test: Ensure Models have attribute page_layout
ref: #345 #346
2024-11-28 02:18:24 +09:30
Jon
5a88b23ae7 test: Ensure Models have attribute table_fields
ref: #345 #346
2024-11-28 02:18:24 +09:30
Jon
d9783477ce test: Ensure Models have meta attribute verbose_name
ref: #345 #346
2024-11-28 02:18:24 +09:30
Jon
7b7b12d08e feat(api): Add common ViewSet class for inheritence
ref: #345 #346
2024-11-28 02:18:24 +09:30
Jon
cbe01e6b77 feat: Add dependency django-cors-headers
ref: #344
2024-11-28 02:18:24 +09:30
Jon
68c787fab4 docs(release_notes): add api v1 depreciaation note
ref: #344
2024-11-28 02:18:24 +09:30
26b0bfac70 build: bump version 1.3.0 -> 1.3.1 2024-11-27 16:32:01 +00:00
Jon
d68d6f85d2 Merge pull request #399 from nofusscomputing/fix-ticket-different-org-view 2024-11-28 01:50:41 +09:30
Jon
61f34876ed fix(core): Ensure user cant view tickets in orgs they are not part of
ref: #399
2024-11-28 01:40:24 +09:30
Jon
ed0e57c8a1 test(access): Add dummy functional test for CI to complete
ref: #382
2024-11-07 18:41:27 +09:30
Jon
49dabbd941 chore(docs): Add Functional Test Badges
ref: #382
2024-11-07 17:56:20 +09:30
4b8353350b build: bump version 1.2.2 -> 1.3.0 2024-10-31 06:44:57 +00:00
Jon
464af55612 Merge pull request #371 from nofusscomputing/feature-production-webserver 2024-10-31 16:01:13 +09:30
Jon
9f826d7142 docs: Update release notes
ref: #371 closes #363
2024-10-31 15:50:57 +09:30
Jon
ea8a054005 fix(docker): Ensure SupervisorD daemon config directory exists.
ref: #363 #371
2024-10-31 15:19:50 +09:30
Jon
8479130ef1 feat(docker): Add worker service config for SupervisorD
ref: #363 #371
2024-10-31 15:00:55 +09:30
Jon
4303232543 refactor(docker): Switch to entrypoint
ref: #363 #371
2024-10-31 14:58:18 +09:30
Jon
0cd4a2bab4 fix(docker): use alias for static
ref: #363 #371
2024-10-30 04:07:58 +09:30
Jon
0b4fc25462 fix(access): testing of param causing gunicorn to fail
ref: #363 #371
2024-10-30 02:39:42 +09:30
Jon
f17d74f8dc fix(docker): place nginx conf in correct path
ref: #363 #371
2024-10-30 02:39:03 +09:30
Jon
07be745bbe fix(docker): gunicorn must call method
ref: #363 #371
2024-10-30 02:37:49 +09:30
Jon
510ab69af8 fix(docker): Ensure NginX config applied after it's installed
ref: #363 #371
2024-10-30 01:50:41 +09:30
Jon
b9349e6590 fix(docker): Add proxy params for NginX
ref: #363 #371
2024-10-30 01:24:35 +09:30
Jon
4fd3abb9d6 fix(docker): Make centurion the default nginx conf
ref: #363 #371
2024-10-30 01:24:13 +09:30
Jon
ac562e7490 feat(docker): ensure supervisor starts
ref: #363 #371
2024-10-30 00:57:02 +09:30
Jon
4fe5916a76 feat(docker): use correct file location for nginx config
ref: #363 #370 #371
2024-10-30 00:39:03 +09:30
Jon
b858825838 feat(docker): Fail the build if django is not found
ref: #363 #370 #371
2024-10-30 00:31:10 +09:30
Jon
5d92a3315f fix(docker): Correct NginX start command
ref: #363 #371
2024-10-30 00:30:34 +09:30
Jon
7a0f85c556 feat(docker): Install NginX to serve site
ref: #363 #371
2024-10-30 00:12:20 +09:30
Jon
cfa284d4ad feat(docker): Add supervisord for install
ref: #363 #371
2024-10-30 00:10:10 +09:30
Jon
447e985740 feat(docker): Add gunicorn for install
ref: #363 #371
2024-10-30 00:08:54 +09:30
Jon
7d872b97f2 feat: update docker image alpine 3.19 ->3.20
ref: #363 #371
2024-10-30 00:06:57 +09:30
ca3b99cb3a build: bump version 1.2.1 -> 1.2.2 2024-10-29 13:00:55 +00:00
Jon
8d4f686f6c Merge pull request #369 from nofusscomputing/fix-docker-container 2024-10-29 22:23:56 +09:30
Jon
6f57bd84e7 fix(docker): adjust pyyaml to >-6.0.1
no python modules are being installed

ref: #369
2024-10-29 19:05:39 +09:30
6351acabe5 build: bump version 1.2.0 -> 1.2.1 2024-10-22 09:16:31 +00:00
Jon
9d6ea1d7c3 refactor(project_management): dont order queryset for project
this is done at modelMeta

ref: #358
2024-10-22 18:36:21 +09:30
Jon
035c6ed60c fix(project_management): Ensure user cant see projects for organizations they are apart of
ref: #358
2024-10-22 18:31:21 +09:30
31e88b2f96 build: bump version 1.1.0 -> 1.2.0 2024-10-11 14:59:15 +00:00
Jon
28f51d3bb6 Merge pull request #259 from nofusscomputing/feature-v1-2 2024-10-12 00:11:22 +09:30
Jon
c634695e4e feat: update django 5.0.8 -> 5.1.2
ref: #259
2024-10-11 23:56:10 +09:30
Jon
983921fc22 chore: squash migrations
squash all to limit number of migrations required

ref: #259
2024-10-11 23:41:03 +09:30
Jon
9fc5b0eb09 Merge pull request #341 from nofusscomputing/340-project-name-length 2024-10-05 12:23:15 +09:30
Jon
b1fc8e0f98 feat(settings): Add API filter and search
ref: #341
2024-10-05 11:42:39 +09:30
Jon
551473feb7 feat(core): Add API filter of fields external_system and external_ref for projects
ref: #341
2024-10-05 11:42:39 +09:30
Jon
105cb63d71 feat(core): Add API filter of fields external_system and external_ref to tickets
ref: #341
2024-10-05 11:42:39 +09:30
Jon
1dda4a9fb5 feat(project_management): increase project field length 50 -> 100 chars
ref: #341 closes #340
2024-10-05 11:42:39 +09:30
Jon
c53ec9ec5d Merge pull request #339 from nofusscomputing/321-ticket-field-length 2024-09-30 13:20:05 +09:30
Jon
a44b2479e3 feat(core): increase ticket title field length 50 -> 100 chars
ref: #339  closes #321
2024-09-30 13:09:02 +09:30
Jon
ec26e16132 Merge pull request #309 from nofusscomputing/ticket-work 2024-09-22 12:28:09 +09:30
Jon
59a930f934 feat(core): Add ability track ticket estimation time for completion
ref: #296 #309 #312
2024-09-21 15:48:06 +09:30
Jon
c7701bb2df feat(core): Add ability to delete a ticket
ref: #296 #309
2024-09-21 14:31:07 +09:30
Jon
4ac0da6ba2 fix: ensure model mandatory fields don't specify a default value
ref: #309 fixes #306
2024-09-21 14:03:47 +09:30
Jon
19ad262617 feat(core): [Templating Engine] Add template tag concat_strings
ref: #296 #309
2024-09-21 13:13:07 +09:30
Jon
0e987088a3 feat(itim): Add ticket tab to services
shows related tickets

ref: #296 #309
2024-09-21 13:12:28 +09:30
Jon
c74b89e0d6 feat(itim): Add ticket tab to clusters
shows related tickets

ref: #296 #309
2024-09-21 13:12:16 +09:30
Jon
e762713416 feat(itam): Add ticket tab to software
shows related tickets

ref: #296 #309
2024-09-21 13:12:01 +09:30
Jon
facdd0111b feat(itam): Add ticket tab to operating systems
shows related tickets

ref: #296 #309
2024-09-21 13:11:48 +09:30
Jon
280abb8841 feat(itam): Add ticket tab to devices
shows related tickets

ref: #296 #309
2024-09-21 13:11:24 +09:30
Jon
064f74736f feat(config_management): Add ticket tab to conf groups
ref: #296 #309
2024-09-21 13:10:36 +09:30
Jon
d26868eced refactor(core): Ticket Linked ref render as template
ref: #296 #309
2024-09-21 12:28:00 +09:30
Jon
b5ec42fc56 fix(api): Ensure user is set to current user for ticket comment
ref: #296 #309
2024-09-21 12:01:58 +09:30
Jon
104575780a test(core): Ticket Linked item view checks
ref: #296 #309
2024-09-21 11:45:24 +09:30
Jon
1c1f4ecdfa test(core): Ticket Linked item permission checks
ref: #296 #309
2024-09-21 11:45:13 +09:30
Jon
582ee4031d docs(core): correct typos
ref: #309
2024-09-21 10:57:24 +09:30
Jon
fca8ad5a78 Merge pull request #308 from nofusscomputing/linked-item 2024-09-20 17:22:46 +09:30
Jon
fff3a96889 fix(core): remove org field when editing a ticket
ref: #308
2024-09-20 16:57:53 +09:30
Jon
dfdc5bac9d feat(core): Add slash command link for linking items to tickets
ref: #296 #308
2024-09-20 16:57:34 +09:30
Jon
c022551427 feat(core): Add to markdown rendering model references
ref: #296 #308
2024-09-20 15:26:38 +09:30
Jon
76954c019b feat(core): Ability to link items to all ticket types
ref: #296 #308
2024-09-20 15:25:42 +09:30
Jon
c3de79050e feat(core): add model ticket linked items
ref: #296 #308
2024-09-20 12:39:54 +09:30
Jon
3cce436938 Merge pull request #301 from nofusscomputing/293-missing-project-fields 2024-09-18 17:03:21 +09:30
Jon
ed2bf96626 chore(core): during validation, Catch random errors and show user stack trace
ref: #301 #305
2024-09-18 16:59:17 +09:30
Jon
3693ddadad fix(core): during validation, if subscribed users not specified, use empty list
ref: #301 closes #305
2024-09-18 16:58:42 +09:30
Jon
8866f94fab docs(project_management): interim project pages
ref: #295 #300
2024-09-18 16:43:25 +09:30
Jon
0ae1395d92 test(project_management): Project Milestone api permission checks
ref: #301 closes #285 closes #295
2024-09-18 15:19:08 +09:30
Jon
cf73323dd3 feat(project_management): Add project milestones api endpoint
ref: #285 #301
2024-09-18 14:53:33 +09:30
Jon
f019c50e44 feat(project_management): Add import_project permission and add api serializer
ref: #295 #301
2024-09-18 13:20:44 +09:30
Jon
3e56c9861c feat(core): great odins beard, remove the checkbox formatting
ref: #301
2024-09-18 12:36:58 +09:30
Jon
d6f475a009 feat(project_management): Add field is_deleted to projects
preparation for the purge permission.

ref: #301 closes #293
2024-09-18 12:30:18 +09:30
Jon
b2f20766de feat(project_management): Calculate project completion percentage and display
ref: #293 #301
2024-09-18 12:16:22 +09:30
Jon
81e98cfd6f Merge pull request #300 from nofusscomputing/295-project-work 2024-09-18 00:57:59 +09:30
Jon
8cae85badc fix(core): add missing pagination to ticket comment categories index
ref: #300 fixes #291
2024-09-18 00:52:11 +09:30
Jon
d6e0fd0b46 fix(core): add missing pagination to ticket categories index
ref: #300 fixes #289
2024-09-18 00:51:48 +09:30
Jon
8d7d79c29b feat(core): order project categories with parent name if applicable
ref: #300 closes #290
2024-09-18 00:46:53 +09:30
Jon
92d3692f85 fix(project_management): Ensure project type and state show on index page
ref: #295 #300
2024-09-18 00:20:07 +09:30
Jon
7f094a9f96 test(project_management): Project TYpe tenancy model checks
ref: #295 #300 closes #294
2024-09-18 00:11:17 +09:30
Jon
7c9d6ced6a test(project_management): Project Type view checks
ref: #294 #295 #300
2024-09-18 00:10:36 +09:30
Jon
9b747b08da test(project_management): Project Type permission checks
ref: #294 #295 #300
2024-09-18 00:10:18 +09:30
Jon
f4695bad1e test(project_management): Project Type core history checks
ref: #294 #295 #300
2024-09-18 00:10:01 +09:30
Jon
744f2f380f test(project_management): Project Type tenancy object checks
ref: #294 #295 #300
2024-09-18 00:09:41 +09:30
Jon
22c6b9d3fe feat(project_management): Add Project Type to the UI
ref: #294 #295 #300
2024-09-18 00:05:15 +09:30
Jon
c1aeb3a258 chore(project_management): cleanup Project State
ref: #294 #295 #300
2024-09-17 23:56:47 +09:30
Jon
0eb6a8bde9 test(project_management): Project State permission checks
ref: #294 #295 #300
2024-09-17 17:21:58 +09:30
Jon
477f089de3 test(project_management): Project State tenancy model checks
ref: #294 #295 #300
2024-09-17 17:21:26 +09:30
Jon
64faffa7b6 test(project_management): Project State view checks
ref: #294 #295 #300
2024-09-17 17:21:16 +09:30
Jon
2196db9479 test(project_management): Project State core history checks
ref: #294 #295 #300
2024-09-17 17:20:59 +09:30
Jon
14d949228d test(project_management): Project State tenancy object checks
ref: #294 #295 #300
2024-09-17 17:20:44 +09:30
Jon
f8e96a556d feat(project_management): Add Project State to the UI
ref: #294 #295 #300
2024-09-17 17:20:00 +09:30
Jon
5ad974f947 revert(core): revert ticket class defined in dir init
reef: #300
2024-09-17 17:14:11 +09:30
Jon
9c9009ff52 test(project_management): Project type API permission checks
ref: #294 #295 #300
2024-09-17 14:09:12 +09:30
Jon
bd09412f6f test(project_management): Project state API permission checks
ref: #294 #295 #300
2024-09-17 14:05:15 +09:30
Jon
82d48fe27b Merge pull request #299 from nofusscomputing/294-feat-project-state-type 2024-09-17 13:54:57 +09:30
Jon
1315cc584b feat(project_management): add priority field to project model, form and api endpoint
ref: #293 #299
2024-09-17 13:50:06 +09:30
Jon
17bed1ef7a Merge pull request #298 from nofusscomputing/294-feat-project-state-type 2024-09-17 13:26:34 +09:30
Jon
f0dd7bc256 feat(project_management): add organization field to project form and api endpoint
ref: #293 #298
2024-09-17 13:21:08 +09:30
Jon
5b356c3c11 feat(project_management): add project_type field to project form
ref: #294 #298
2024-09-17 13:14:53 +09:30
Jon
fbbae64ff2 feat(project_management): add external_ref and external_system field to project model
ref: #294 #298
2024-09-17 13:12:54 +09:30
Jon
34b85441e2 feat(project_management): add project type field to project model
ref: #294 #298
2024-09-17 13:12:26 +09:30
Jon
e9b122cf8c feat(project_management): add project type api endpoint
ref: #294 #298
2024-09-17 13:07:50 +09:30
Jon
3f0853654e feat(project_management): new model project type
ref: #294 #298
2024-09-17 13:06:15 +09:30
Jon
0de451af70 feat(project_management): add project state api endpoint
ref: #294 #298
2024-09-17 12:50:43 +09:30
Jon
a4926f8a0d feat(project_management): add project state field to project model
ref: #294 #298
2024-09-17 12:49:53 +09:30
Jon
1314f9a1ff feat(project_managemenet): new model project state
ref: #294 #298
2024-09-17 12:46:48 +09:30
Jon
d5e344f67c Merge pull request #292 from nofusscomputing/project-milestone 2024-09-16 16:27:54 +09:30
Jon
d8654fae6d test(project_management): Project miletone skipped api checks
ref: #285 #292
2024-09-16 16:22:14 +09:30
Jon
033f47b6b9 feat(project_management): add field external system to projects
ref: #292
2024-09-16 16:05:37 +09:30
Jon
383bca4ff9 refactor(core): migrate ticket enums to own class
ref: #292
2024-09-16 15:59:54 +09:30
Jon
22f7b1e7c5 fix(core): Add replacement function within ticket validation as cleaned_data attribute replacement
ref: #292
2024-09-16 13:57:57 +09:30
Jon
d5ad03546c fix(core): Ensure the ticket clears project field on project removal
ref: #292
2024-09-16 12:48:32 +09:30
Jon
f79076ddef fix(core): Remove ticket fields user has no access to
ref: #292
2024-09-16 12:42:37 +09:30
Jon
7a31498e91 refactor(core): Ticket validation errors setup for both api and ui
ref: #285 #292
2024-09-16 12:42:00 +09:30
Jon
95f9d90877 feat(core): validate field milestone for all ticket types
ref: #285 #292
2024-09-16 12:41:04 +09:30
Jon
3bac0c19ac feat(core): Add field milestone to all ticket types
ref: #292
2024-09-16 03:50:18 +09:30
Jon
b8f4123185 refactor(core): for tickets use validation for organization field
ref: #292
2024-09-16 03:49:05 +09:30
Jon
d2e9c838de refactor(core): refine ticket field permission and validation
ref: #292
2024-09-16 03:45:38 +09:30
Jon
5cd51ba00e test(project_management): Project Milestone tenancy model checks
ref: #285 #292
2024-09-14 16:12:56 +09:30
Jon
a3fafdafbd test(project_management): Project Milestone view checks
ref: #285 #292
2024-09-14 16:12:31 +09:30
Jon
df9ad069c4 test(project_management): Project Milestone ui permission checks
ref: #285 #292
2024-09-14 16:12:04 +09:30
Jon
2ff3dab014 test(project_management): Project Milestone core history checks
ref: #285 #292
2024-09-14 16:11:39 +09:30
Jon
ae9526ef57 test(project_management): Project Milestone Tenancy object checks
ref: #285 #292
2024-09-14 16:11:28 +09:30
Jon
5e235617e0 feat(project_management): Add project milestones
ref: #285 #292
2024-09-14 16:09:07 +09:30
Jon
a373247cda Merge pull request #284 from nofusscomputing/ticket-categories 2024-09-14 13:26:40 +09:30
Jon
c81e319aac feat(core): Add slash command "related ticket" for ticket and ticket comments
ref: #284 closes #287
2024-09-14 13:19:39 +09:30
Jon
d05537a619 fix(core): correct logic for slash command /spend
ref: #284 c#286
2024-09-14 11:20:47 +09:30
Jon
7894ac5522 docs(core): Add slash command /spend for ticket and ticket comments
ref: #284 closes #286
2024-09-13 22:27:48 +09:30
Jon
64577cf806 feat(core): Suffix username to action comments
ref: #250 #284
2024-09-13 21:59:04 +09:30
Jon
c3307152e8 feat(core): Add slash command /spend for ticket and ticket comments
ref: #284 closes #286
2024-09-13 21:58:19 +09:30
Jon
a7e99eb5b4 refactor: reduce action comment spacing
ref: #24 #284
2024-09-13 21:07:35 +09:30
Jon
c45aae7048 chore: docs linting errors
ref: #284
2024-09-13 16:22:43 +09:30
Jon
9cb3afeb30 feat(core): Disable HTML tag rendering for markdown
ref: #284 closes #271
2024-09-13 16:19:42 +09:30
Jon
6e7e6587c2 docs: update roadmap
ref: #284
2024-09-13 15:01:41 +09:30
Jon
5f3c7296b7 feat(project_management): remove requirement for code field to be populated
ref: #14 #284
2024-09-13 14:39:50 +09:30
Jon
2e15e61059 fix(project_management): correct project view permissions
ref: #14 #284
2024-09-13 14:35:37 +09:30
Jon
574357b60a test(core): Project tenancy model checks
ref: #14 #284
2024-09-13 14:35:09 +09:30
Jon
1576605acb test(core): Project view checks
ref: #14 #284
2024-09-13 14:34:57 +09:30
Jon
9d564ffbb2 test(core): Project UI permission checks
ref: #14 #284
2024-09-13 14:34:47 +09:30
Jon
b56f3236fd test(core): Project API permission checks
ref: #14 #284
2024-09-13 14:34:38 +09:30
Jon
6e566b8840 test(core): Project history checks
ref: #14 #284
2024-09-13 14:34:28 +09:30
Jon
34a1a19089 test(core): Project Tenancy object checks
ref: #14 #284
2024-09-13 14:34:19 +09:30
Jon
80dc797651 test(core): Ticket comment category API permission checks
ref: #284 closes #283
2024-09-13 13:59:11 +09:30
Jon
f2a4223d25 feat(core): Add ticket comment category API endpoint
ref: #283 #284
2024-09-13 13:54:42 +09:30
Jon
902aaf31dd fix(core): Correct view permissions for ticket comment category
ref: #283 #284
2024-09-13 13:41:20 +09:30
Jon
1be23148d7 test(core): add missing ticket category view checks
ref: #283 #284
2024-09-13 13:40:57 +09:30
Jon
88d6a73454 test(core): ticket comment category tenancy model checks
ref: #283 #284
2024-09-13 13:40:26 +09:30
Jon
01c57b37ad test(core): ticket comment category view checks
ref: #283 #284
2024-09-13 13:40:03 +09:30
Jon
9fbb88fa5f test(core): ticket comment category ui permission checks
ref: #283 #284
2024-09-13 13:38:15 +09:30
Jon
a0b0d79777 test(core): ticket comment category history checks
ref: #283 #284
2024-09-13 13:38:03 +09:30
Jon
f3dccd3b84 test(core): ticket comment category tenancy model checks
ref: #283 #284
2024-09-13 13:37:49 +09:30
Jon
56b715797e feat(core): Ability to assign categories to ticket comments
ref: #14 #96 #93 #95 #90 #283 #283 #284
2024-09-13 13:30:36 +09:30
Jon
11948c9500 feat(core): Add ticket comment categories
ref: #14 #96 #93 #95 #90 #283 #284
2024-09-13 13:14:25 +09:30
Jon
1161bf79aa fix(core): correct url typo for ticket category API endpoint
ref: #283 #284
2024-09-13 12:45:33 +09:30
Jon
40f564b32a fix(core): dont attempt to modify field for ticket category API list
ref: #283 #284
2024-09-13 12:45:13 +09:30
Jon
4fdabc16ba test(core): ticket category API permission checks
ref: #283 #284
2024-09-13 12:38:26 +09:30
Jon
e68dbdfb4c test(core): ticket category history checks
ref: #283 #284
2024-09-13 12:38:14 +09:30
Jon
f2898037b0 test(core): ticket category tenancy model checks
ref: #283 #284
2024-09-13 12:37:48 +09:30
Jon
5d116c7224 feat(core): Extend all ticket endpoints to contain ticket categories
ref: #14 #96 #93 #95 #90 #283 #284
2024-09-13 12:25:30 +09:30
Jon
2a31815267 feat(core): Add ticket category API endpoint
ref: #14 #96 #93 #95 #90 #283 #284
2024-09-13 12:24:36 +09:30
Jon
ded6a72072 test(core): ticket category model checks
ref: #14 #96 #93 #95 #90 #283 #284
2024-09-13 11:26:50 +09:30
Jon
7d80857d8d test(core): view checks
ref: #14 #96 #93 #95 #90 #283 #284
2024-09-13 11:26:27 +09:30
Jon
297e318243 test(core): ui permissions
ref: #14 #96 #93 #95 #90 #283 #284
2024-09-13 11:26:14 +09:30
Jon
6cc992f6d6 fix(core): Dont attempt to render ticket category if none
ref: #14 #96 #93 #95 #90 #283 #284
2024-09-13 11:25:37 +09:30
Jon
6402897329 fix(core): Correct the delete permission
ref: #14 #96 #93 #95 #90 #283 #284
2024-09-13 11:25:14 +09:30
Jon
5f7d0e474e feat(core): Ability to assign categories to tickets
ref: #14 #96 #93 #95 #90 #283 #284
2024-09-13 11:03:40 +09:30
Jon
09bb2d8e27 feat(core): Addpage titles to view abstract classes
ref: #283
2024-09-13 10:43:04 +09:30
Jon
e28dbea05b feat(core): Add ticket categories
ref: #283 #284
2024-09-13 10:42:16 +09:30
Jon
9d8c894cff Merge pull request #270 from nofusscomputing/feat-2024-09-11 2024-09-12 18:41:47 +09:30
Jon
9942348ba3 docs(development): add markdown refs
ref: #14 #96 #93 #95 #90 #270 closes #250
2024-09-12 17:58:55 +09:30
Jon
2e98eda8a4 fix(core): correct project task reply link for comments
ref: #14 #96 #93 #95 #90 #250 #270
2024-09-12 17:21:43 +09:30
Jon
2ab2b65fc2 refactor(core): update markdown styles
ref: #270 #271
2024-09-12 17:09:39 +09:30
Jon
3e684b117f refactor(core): migrate ticket number rendering as markdown_it plugin
ref: #270 #271
2024-09-12 16:28:27 +09:30
Jon
51f28a6cf8 refactor(core): move markdown functions out of ticket model
ref: #270 ##271
2024-09-12 16:16:29 +09:30
Jon
212e864db1 feat(core): during markdown render, if ticket ID not found return the tag
ref: #270
2024-09-12 12:40:46 +09:30
Jon
0adfd95ced fea(core): Add opened_by user as subscribed to ticket when creating
ref: #14 #96 #93 #95 #90 #250 #270
2024-09-12 12:40:35 +09:30
Jon
c9d05152c9 feat(core): Add heading anchor plugin to markdown
ref: #270
2024-09-12 00:49:26 +09:30
Jon
a8b21d7c74 feat(core): correct markdown formatting for KB articles
ref: #270
2024-09-12 00:04:51 +09:30
Jon
97874b73f6 docs: correct date
ref: #270
2024-09-11 22:51:58 +09:30
Jon
948713d13d test(core): correct project tests for triage user
ref: #14 #96 #93 #95 #90 #250 #270
2024-09-11 22:45:58 +09:30
Jon
63146aa41c feat(core): remove project field from being editable when creating project task
ref: #14 #270
2024-09-11 22:31:55 +09:30
Jon
c7f69ad7c1 feat(core): Add admonition style
ref: #14 #96 #93 #95 #90 #250 #270 closes #272
2024-09-11 22:19:23 +09:30
Jon
008f8c1554 feat(project_management): Validate project task has project set
ref: #14 #96 #93 #95 #90 #250 #270
2024-09-11 21:20:59 +09:30
Jon
122216dbe4 chore(core): remove unused markdown import
ref: #14 #96 #93 #95 #90 #250 #270
2024-09-11 21:15:21 +09:30
Jon
c0ac09b928 fix(core): correct project task comment buttons
ref: #14 #96 #93 #95 #90 #250 #270
2024-09-11 20:16:05 +09:30
Jon
cfda7e5e1e feat(core): set project ID to match url kwarg
ref: #14 #96 #93 #95 #90 #250 #270
2024-09-11 20:15:37 +09:30
Jon
91af43adba feat(core): Add action comment on title change
ref: #14 #96 #93 #95 #90 #250 #270
2024-09-11 19:14:44 +09:30
Jon
bfb7176db3 feat(core): Add task listts plugin to markdowm
ref: #14 #96 #93 #95 #90 #250 #270
2024-09-11 19:14:22 +09:30
Jon
411cd5d4a3 feat(core): Add footnote plugin to markdowm
ref: #14 #96 #93 #95 #90 #250 #270
2024-09-11 19:14:07 +09:30
Jon
91aa87d122 feat(core): Add admonition plugin to markdowm
ref: #14 #96 #93 #95 #90 #250 #270
2024-09-11 19:13:58 +09:30
Jon
14bdc67a4a feat(core): Add table extension to markdowm
ref: #14 #96 #93 #95 #90 #250 #270
2024-09-11 19:13:35 +09:30
Jon
b86b1fd1ad feat(core): Add strikethrough extension to markdowm
ref: #14 #96 #93 #95 #90 #250 #270
2024-09-11 19:13:25 +09:30
Jon
00ec5179f9 feat(core): Add linkify extension to markdowm
ref: #14 #96 #93 #95 #90 #250 #270
2024-09-11 19:13:02 +09:30
Jon
200c9d8d8d feat(core): move markdown parser py-markdown -> markdown-it
py-markdown was missing a lot of the common/gfm items.

ref: #14 #96 #93 #95 #90 #250 #270
2024-09-11 19:12:05 +09:30
Jon
b69d210759 chore(core): remove superuser clause
ref: #250 #96 #93 #95 #90 #270
2024-09-11 15:26:06 +09:30
Jon
eb4a58ed01 fix(project_management): correct comment reply url name
ref: #14 #270
2024-09-11 15:21:55 +09:30
Jon
bc2f30ac9b feat(core): Add organization column to ticket pages
ref: #250 #96 #93 #95 #90 #270
2024-09-11 15:21:19 +09:30
Jon
e87bbe9ed8 fix(core): Generate the correct edit url for tickets
ref: #250 #96 #93 #95 #90 #270
2024-09-11 15:20:54 +09:30
Jon
68785ef6c0 fix(core): Generate the correct comment urls for tickets
ref: #250 #96 #93 #95 #90 #270
2024-09-11 15:20:21 +09:30
Jon
b78e2adb09 fix(core): Redirect to correct url for itim tickets after adding comment
ref: #250 #96 #93 #95 #90 #270
2024-09-11 15:19:03 +09:30
Jon
26c985e683 feat(core): Allow super-user to edit ticket comment source
ref: #250 #96 #93 #95 #90 #270
2024-09-11 14:07:59 +09:30
Jon
58e2b9f7f5 feat(core): Render linked tickets the same as the rendered markdown link
ref: #250 #96 #93 #95 #90 #270
2024-09-11 13:50:17 +09:30
Jon
34f2d4c4d4 chore(core): remove model history link
not required as the history is saved as action comments

ref: #250 #96 #93 #95 #90 #270
2024-09-11 13:18:27 +09:30
Jon
56c3b9d7de feat(core): Add project task link for related project task
ref: #250 #96 #93 #95 #90 #270
2024-09-11 13:14:07 +09:30
Jon
c83ffe542e fix(core): correct linked tickets hyperlink address
ref: #250 #96 #93 #95 #90 #270
2024-09-11 13:09:24 +09:30
Jon
b07872c8c2 feat(project_management): Add project duration field
ref: #14 #270
2024-09-11 12:50:31 +09:30
Jon
1cc196fd06 feat(core): Add external ref to tickets if populated
ref: #250 #96 #93 #95 #90 #270
2024-09-11 12:36:47 +09:30
Jon
dd68bfbea8 refactor(core): Adjust test layout for itsm and project field based permissions
ref: #250 #96 #93 #95 #90 #264 #268
2024-09-11 12:18:24 +09:30
Jon
ea5888f39f Merge pull request #267 from nofusscomputing/feat-ticket-comments 2024-09-10 17:11:33 +09:30
Jon
78607a0bf9 test(core): Project task permission checks
ref: #250 #96 #93 #95 #90 #264 #267
2024-09-10 17:01:57 +09:30
Jon
fa9cff390a feat(core): Add project task permissions
ref: #250 #96 #93 #95 #90 #264 #267 fixes #269
2024-09-10 17:01:24 +09:30
Jon
2613a132a6 fix(core): order ticket comments by creation date
oldest first..

ref: #250 #96 #93 #95 #90 #264 #267 fixes #269
2024-09-10 16:26:04 +09:30
Jon
7d8b54a980 fix(core): Ensure for both ticket and comment, external details are unique.
ref: #250 #96 #93 #95 #90 #264 #267 fixes #268
2024-09-10 16:09:44 +09:30
Jon
6371fa03a1 chore(project_management): remove non-ticket based project tasks
project tasks scope was moved to a type of ticket.

ref: #14 #250 #267
2024-09-10 15:52:34 +09:30
Jon
daa872d2e7 feat(project_management): Add project tasks
ref: #14 #250 #267
2024-09-10 15:38:30 +09:30
Jon
4d1600e396 refactor(project_management): migrate projects to new style for views
ref: #14 #267
2024-09-10 15:27:05 +09:30
Jon
63d33c287c feat(api): Add project tasks endpoint
ref: #14 #267
2024-09-10 13:42:03 +09:30
Jon
ae72d4ab6a feat(api): Add projects endpoint
ref: #14 #267
2024-09-10 13:37:13 +09:30
Jon
8a747d1d1f feat(api): Add project management endpoint
ref: #14 #267
2024-09-10 13:33:21 +09:30
Jon
55e512efb8 test(core): Ticket comment API permission checks
ref: #250 #96 #93 #95 #90 #264 #267
2024-09-10 12:16:16 +09:30
Jon
f09e7b77db fix(core): Ensure on ticket comment create and update a response is returned
ref: #250 #96 #93 #95 #90 #264 #267
2024-09-10 12:16:03 +09:30
Jon
4177f71972 test(core): Ticket comment permission checks
ref: #250 #96 #93 #95 #90 #264 #267
2024-09-10 11:42:10 +09:30
Jon
0c3e38c543 test(core): Ticket comment Views
ref: #250 #96 #93 #95 #90 #264 #267
2024-09-10 10:58:54 +09:30
Jon
94dd555e9b test(core): Tenancy model tests for ticket comment
ref: #250 #96 #93 #95 #90 #264 #267
2024-09-10 10:53:31 +09:30
Jon
10bffe0f0f Merge pull request #266 from nofusscomputing/feat-tickets 2024-09-09 18:00:35 +09:30
Jon
aa6baf94a6 feat(core): support negative numbers when Calculating ticket duration for ticket meta and comments
enables time to be subtracted when negative value added to duration field.

ref: #250 #96 #93 #95 #90 #264 #266
2024-09-09 17:59:22 +09:30
Jon
44604d98ab feat(core): Caclulate ticket duration for ticket meta and comments
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-09 17:36:17 +09:30
Jon
63077dfa26 feat(core): Add edit details to ticket and comments
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-09 16:39:11 +09:30
Jon
0794e5b58f test(core): ensure history for ticket models is not saved
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-09 16:13:43 +09:30
Jon
c67e1430bd feat(core): Don't save model history for ticket models
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-09 16:13:22 +09:30
Jon
f3b249d18f test: Ensure tenancy models save model history
ref: #266 #250
2024-09-09 16:11:40 +09:30
Jon
857b8781cb feat(core): add option to allow the prevention of history saving for tenancy models
ref: #266 #250
2024-09-09 16:10:55 +09:30
Jon
118d41a53b test(core): remove duplicated tenancy object tests for ticket model
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-09 16:08:16 +09:30
Jon
2cb21ae4a7 test(core): correct triage user test names for allowed field permissions
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-09 16:07:39 +09:30
Jon
7e0bd630b5 test(core): project field permission check for triage user
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-09 15:39:37 +09:30
Jon
a57e977131 feat(core): Add project field to tickets allowed fields
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-09 15:38:41 +09:30
Jon
082a351c17 test(core): Ticket Action comment checks for related tickets
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-09 14:54:30 +09:30
Jon
a47e1977f0 fix(core): Ensure related tricket action comment is trimmed
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-09 14:52:39 +09:30
Jon
b0a4d2ca84 test(core): Ticket Action comment checks for subscribing team
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-09 14:18:48 +09:30
Jon
afceaca736 test(core): Ticket Action comment checks for subscribing user
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-09 14:18:22 +09:30
Jon
c59dc7d2bf test(core): Ticket Action comment checks for unassigning team
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-09 14:06:45 +09:30
Jon
e7015570d5 test(core): Ticket Action comment checks for assigning team
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-09 14:06:17 +09:30
Jon
a68a9e7ef3 fix(core): Team assigned to ticket status update
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-09 14:05:39 +09:30
Jon
3ea84f008b test(core): Ticket Action comment checks for un-assigning user
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-09 13:48:09 +09:30
Jon
47aeac846b test(core): Ticket Action comment checks for assigning user
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-09 13:47:58 +09:30
Jon
69124cff08 chore(core): Remove field '_django_version' from history save
must have been introduced in django 5.0.8

ref: #250 #96 #93 #95 #90 #264 #266
2024-09-09 13:44:59 +09:30
Jon
a99c1bb418 refactor(core): REmove constraint on setting user for ticket comment
required so that tests can run. ToDo: add tests to ensure that user is set.

ref: #250 #96 #93 #95 #90 #264 #266
2024-09-09 13:44:04 +09:30
Jon
b80ca93ced test(core): Add ticket project field permission check
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-09 12:13:05 +09:30
Jon
8998292a0f fix(api): ensure ticket_type is set from view var
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-09 12:02:36 +09:30
Jon
bc39b1b8b5 test(core): ensure ticket_type tests dont have change value that matches ticket type
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-08 18:17:24 +09:30
Jon
b93d3d2175 chore: add test to makefile
ref: #266
2024-09-08 18:10:54 +09:30
Jon
b1277c98ab fix(core): Add ticket fields to ticket types
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-08 18:10:31 +09:30
Jon
c2eaf120b6 fix(core): During ticket form validation confirm if value specified/different then default
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-08 18:09:35 +09:30
Jon
41158e495f fix(core): Correctly set the ticket type initial value
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-08 18:05:17 +09:30
Jon
3261342c4f test(core): field based permission tests for add, change, import and triage user
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-08 16:46:17 +09:30
Jon
8b4068ac7e fix(core): prevent import user from having permssions within UI
only allow import user to have API permissions.

ref: #250 #96 #93 #95 #90 #264 #266
2024-09-08 16:41:43 +09:30
Jon
27958f5e7a refactor(core): cache fields allowed during ticket validation
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-08 16:41:31 +09:30
Jon
819dc01451 refactor(core): dont require specifying ticket status
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-08 13:13:36 +09:30
Jon
8277e05205 feat(core): Update ticket status when assigned/unassigned users/teams
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-07 14:33:01 +09:30
Jon
f4d96c78e7 feat(core): Create action comment for subscribed users/teams
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-07 14:32:24 +09:30
Jon
a08d74cd3c feat(core): Create action comment for assigned users/teams
ref: #250 #96 #93 #95 #90 #264 #266
2024-09-07 14:30:22 +09:30
Jon
878d2509cd Merge pull request #265 from nofusscomputing/264-api-refactor-tickets 2024-09-06 18:12:00 +09:30
Jon
09247246bb fix(api): correct ticket view links
ref: #250 #96 #93 #95 #90 #264 #265
2024-09-06 18:01:22 +09:30
Jon
685b8266e4 feat(core): adding of more ticket status icons
ref: #250 #96 #93 #95 #90 #264 #265
2024-09-06 17:58:14 +09:30
Jon
7a2f7fdf3d refactor(core): move id to end for rendered ticket link.
ref: #250 #96 #93 #95 #90 #264 #265
2024-09-06 17:20:32 +09:30
Jon
ecaa24192f fix(core): Correct display of ticket status within ticket interface
ref: #250 #96 #93 #95 #90 #264 #265
2024-09-06 17:10:06 +09:30
Jon
f49cc9c286 refactor(api): Ticket (change, incident, problem and request) to static api endpoints
ref: #250 #96 #93 #95 #90 #264 #265
2024-09-06 16:45:26 +09:30
Jon
53ae19eda8 test(api): Ticket (change, incident, problem and request) api permission checks
ref: #250 #96 #93 #95 #90 #264 #265
2024-09-06 10:26:55 +09:30
Jon
d8361bf741 feat(api): Ticket endpoint dynamic permissions
ref: #250 #96 #93 #95 #90 #264 #265
2024-09-06 10:26:16 +09:30
Jon
d70f04c63d refactor(api): make ticket status field mandatory
ref: #250 #96 #93 #95 #90 #264 #265
2024-09-06 10:25:47 +09:30
Jon
0f4b9fef9e refactor(api): Move core tickets to own ticket endpoints
require so that permissions can be dynamic

ref: #250 #96 #93 #95 #90 #265 closes #264
2024-09-04 11:59:38 +09:30
Jon
05c18702ff Merge pull request #263 from nofusscomputing/250-ticket-tests 2024-09-03 17:51:15 +09:30
Jon
754c311580 feat(core): add ticket status badge
ref: #250 #96 #93 #95 #90 #263
2024-09-03 17:50:41 +09:30
Jon
f6dd5a3156 chore: remove empty settings model
ref: #263
2024-09-03 17:00:46 +09:30
Jon
53489ec43b feat(access): add ability to fetch dynamic permissions
ref: #250 #96 #93 #95 #90 #263
2024-09-03 17:00:22 +09:30
Jon
da8d97a274 test(core): interim ticket unit tests
ref: #250 #96 #93 #95 #90 #263
2024-09-03 16:59:24 +09:30
Jon
8161d67a1f test(itam): Ensure if an attempt to add an existing device via API, it's not recreated and is returned.
ref: #262 #263
2024-09-03 15:22:35 +09:30
Jon
381d59c18f refactor(core): During form validation for a ticket, use defaults if not defined for mandatory fields
ref: #250 #96 #93 #95 #90 #263
2024-09-03 14:56:36 +09:30
Jon
55a40fcf4d refactor(core): Ticket form ticket_type to use class var
ref: #250 #96 #93 #95 #90 #263
2024-09-03 14:56:01 +09:30
Jon
cfc690f1c2 feat(core): Add delete view for ticket types: request, incident, change and problem
ref: #250 #96 #93 #95 #90 #263
2024-09-03 14:54:58 +09:30
Jon
a3bfa921e8 test: correct typo in test description for test_model_add_has_permission
ref: #263
2024-09-03 14:53:23 +09:30
Jon
c670f017a0 fix(api): Ensure if device found it is returned
ref: #262
2024-09-03 13:51:12 +09:30
Jon
f70c5a28af Merge pull request #260 from nofusscomputing/257-ticket-comment-validation 2024-09-03 12:12:47 +09:30
Jon
c3d64a031d feat(api): when attempting to create a device and it's found within DB, dont recreate, return it.
DB matches: name and uuid then name and serial number. first found is returned.

ref: #260 closes #262
2024-09-03 12:07:35 +09:30
Jon
eb94729277 feat(core): When solution comment posted to ticket update status to solved
ref: #250 #96 #93 #95 #90 #257 #260
2024-09-02 16:06:43 +09:30
Jon
c339f17c5c feat(core): Add opened by column to ticket indexes
ref: #250 #96 #93 #95 #90
2024-09-02 15:39:18 +09:30
Jon
d7dd2d6d8b feat(core): permit user to add comment to own ticket
ref: #250 #96 #93 #95 #90 #257
2024-09-02 15:38:49 +09:30
Jon
342fe7da9e fix(core): Ensure status field remains as part of ticket
ref: #250 #96 #93 #95 #90
2024-09-02 15:37:52 +09:30
Jon
910a002201 feat(core): Allow OP to edit own Ticket Comment
ref: #250 #96 #93 #95 #90 closes #257
2024-09-02 15:16:23 +09:30
Jon
5f6c36e823 feat(core): Ticket Comment form submission validation
ref: #250 #96 #93 #95 #90 #257
2024-09-02 14:54:54 +09:30
Jon
cf577bbb4f feat(core): Ticket Comment can be edited by owner
ref: #250 #96 #93 #95 #90 #257
2024-09-02 14:39:38 +09:30
Jon
b8253ae9ba feat(core): Ticket Comment source hidden for non-triage users
ref: #250 #96 #93 #95 #90 #257
2024-09-02 14:39:01 +09:30
Jon
978bcf3b45 refactor(core): cache permission check for ticket types
ref: #250 #96 #93 #95 #90 #257
2024-09-02 14:38:26 +09:30
Jon
f76f81a312 feat(core): When fetching allowed ticket comment fields, check against permissions
ref: #250 #96 #93 #95 #90 #257
2024-09-02 13:39:58 +09:30
Jon
5793295e1a feat(core): pass request to ticket comment form
ref: #250 #96 #93 #95 #90 #257
2024-09-02 13:36:58 +09:30
Jon
6df22314c9 feat(itam): Accept device UUID in any case.
when saving, normalise to lowercase

ref: #260 closes #261
2024-09-02 13:29:29 +09:30
Jon
d7c3e051de refactor(core): Move allowed fields logic to own function
ref: #250 #96 #93 #95 #90 #257
2024-09-02 12:33:54 +09:30
Jon
057a39091d Merge pull request #252 from nofusscomputing/250-ticket-model 2024-09-01 17:12:27 +09:30
Jon
ba8b618b7d chore(core): update validate field permission docstring
ref: #250 #252 #96 #93 #95 #90 #115
2024-09-01 17:01:29 +09:30
Jon
0b86ded4f5 chore(core): Add Ticket Comment validation class
ref: #250 #252 #96 #93 #95 #90 #115 #257
2024-09-01 16:58:24 +09:30
Jon
523341cf4a docs(core): Add some ticketing docs
ref: #250 #252 #96 #93 #95 #90 #115
2024-09-01 16:44:52 +09:30
Jon
ee17095a14 chore(core): squash ticket migrations
ref: #250 #252 #96 #93 #95 #90 #115
2024-09-01 14:40:09 +09:30
Jon
7829f4b7d8 feat(core): Add ticket status icon
ref: #250 #252 #96 #93 #95 #90 #115
2024-09-01 13:52:24 +09:30
Jon
058e057088 feat(core): Enable ticket comment created date can be set when an import user
ref: #250 #252 #96 #93 #95 #90 #115
2024-09-01 13:52:24 +09:30
Jon
c8d7b52fbf fix(core): Correct modified field to correct type for ticket comment
ref: #250 #252 #96 #93 #95 #90 #115
2024-09-01 13:52:24 +09:30
Jon
9732656556 fix(api): Filter ticket comments to match ticket
ref: #250 #252 #96 #93 #95 #90 #115
2024-09-01 13:52:24 +09:30
Jon
967b9251e2 feat(api): Set default values for ticket comment form to match ticket
ref: #250 #252 #96 #93 #95 #90 #115
2024-09-01 13:52:24 +09:30
Jon
3ba89a926b fix(core): Correct modified field to correct type
ref: #250 #252 #96 #93 #95 #90 #115
2024-09-01 13:52:24 +09:30
Jon
b04b6fe645 fix(core): Ensure new ticket can be created
ref: #250 #252 #96 #93 #95 #90 #115
2024-09-01 13:52:24 +09:30
Jon
1829395a8a fix(core): Add ticket_type field to import_permissions
ref: #250 #252 #96 #93 #95 #90 #115
2024-09-01 13:52:24 +09:30
Jon
6f2d431ae1 fix(core): Ensure that the organization field is available
ref: #250 #252 #96 #93 #95 #90 #115
2024-09-01 13:52:24 +09:30
Jon
9132608aaf feat(core): render ticket number #\d+ links within markdown
ref: #250 #252 #96 #93 #95 #90 #115
2024-08-31 17:51:25 +09:30
Jon
f0b604b5dc feat(core): Use common function for markdown rendering for ticket objects
ref: #250 #252 #96 #93 #95 #90 #115
2024-08-31 16:58:56 +09:30
Jon
d1b9283a9a test: Add view must have function get_initial
organization is set here for tenancy objects

ref: #252
2024-08-31 16:03:28 +09:30
Jon
fe353904d8 test(itam): Refactor Device tests organization field to be editable.
ref: #252
2024-08-31 15:29:40 +09:30
Jon
011a6c156e test: Ensure tests add organization to tenancy objects on creation
ref: #252
2024-08-31 15:28:42 +09:30
Jon
8662feb1c7 feat(api): Ensure device can add/edit organization
ref: #252
2024-08-31 15:27:08 +09:30
Jon
097b3fe8b6 feat(core): Add api validation for ticket
ref: #250 #252 #96 #93 #95 #90 #115
2024-08-31 14:34:20 +09:30
Jon
7f138d4b68 feat(core): Ensure for tenancy objects that the organization is set
ref: #252
2024-08-31 13:19:49 +09:30
Jon
6532d0e0d7 fix(core): dont remove hidden fields on ticket comment form
ref: #250 #252 #96 #93 #95 #90 #115
2024-08-31 13:17:32 +09:30
Jon
8242d9f269 fix(core): Correct ticket comment permissions
ref: #250 #252 #96 #93 #95 #90 #115
2024-08-31 13:17:04 +09:30
Jon
28fe89e048 feat(core): Ticket comment orgaanization set to ticket organization
ref: #250 #252 #96 #93 #95 #90 #115
2024-08-31 13:16:02 +09:30
Jon
b709839c38 refactor(access): Add definable parameters to organization mixin
ref: #252
2024-08-31 12:25:09 +09:30
Jon
638ea466f0 docs(core): initial docs pages for v1.2
ref: #250 #252 #96 #93 #95 #90 #115
2024-08-31 11:53:45 +09:30
Jon
31bc1e4e76 fix(access): correct permission check to cater for is_global=None
ref: #250 #252 #96 #93 #95 #90 #115
2024-08-31 11:25:08 +09:30
Jon
0535674a96 docs(core): document get_dynamic_permissions function
ref: #252
2024-08-31 11:22:13 +09:30
Jon
5f3b12a472 chore(core): clean up ticket css
ref: #250 #252 #96 #93 #95 #90 #115
2024-08-31 11:16:10 +09:30
Jon
6ec16cbeb0 feat(core): colour code related ticket background to ticket type
ref: #250 #252 #96 #93 #95 #90 #115
2024-08-30 18:20:47 +09:30
Jon
1665e519a4 fix(core): return correct redirect path for related ticket form
ref: #250 #252 #96 #93 #95 #90 #115
2024-08-30 15:50:19 +09:30
Jon
95979c6095 feat(core): Validate ticket related and prevent duel related entries
ref: #250 #252 #96 #93 #95 #90 #115
2024-08-30 15:49:42 +09:30
Jon
8b004466d1 feat(core): Validate ticket status field for all ticket types
ref: #250 #252 #96 #93 #95 #90 #115
2024-08-30 15:45:15 +09:30
Jon
3f1f2fd8d4 feat(core): Add ticket action comments on ticket update
ref: #250 #252 #96 #93 #95 #90 #115
2024-08-30 15:00:36 +09:30
Jon
96ed198efc fix(core): use from ticket title for "blocked by"
ref: #250 #252 #96 #93 #95 #90 #115
2024-08-30 12:30:14 +09:30
Jon
6a52730b49 feat(core): Add Title bar to ticket form
ref: #250 #252 #96 #93 #95 #90 #115
2024-08-30 12:23:05 +09:30
Jon
5c4a802017 feat(core): Add field level permission and validation checks
ref: #250 #252 #96 #93 #95 #90 #11
2024-08-28 17:44:41 +09:30
Jon
e59a08b351 refactor(access): cache user_organizations on lookup
ref: #252
2024-08-28 17:39:16 +09:30
Jon
2a7857b60d refactor(access): cache object_organization on lookup
ref: #252
2024-08-28 17:38:29 +09:30
Jon
09afd7f165 feat(core): Add permission checking to Tickets form
ref: #250 #252 #96 #93 #95 #90 #115
2024-08-27 17:18:52 +09:30
Jon
e63bec83e8 feat(access): add dynamic permissions to Tenancy Permissions
ref: #252 #250
2024-08-27 17:09:57 +09:30
Jon
5d74ddfee5 fix(access): Don't query for is_global=None within TenancyManager
ref: #252
2024-08-27 17:05:50 +09:30
Jon
3c44561b19 chore(development): Add makefile
ref: #252 #248
2024-08-26 15:14:04 +09:30
Jon
8edb209d16 feat(api): Add Tickets endpoint
ref: #252 #248
2024-08-26 15:13:12 +09:30
Jon
81bd635ca4 feat(itim): Add Problem ticket to navigation
ref: #250 #252 #93
2024-08-25 17:52:41 +09:30
Jon
6ff3fe5949 feat(itim): Add Incident ticket to navigation
ref: #250 #252 #93
2024-08-25 17:52:20 +09:30
Jon
31067aab95 feat(itim): Add Change ticket to navigation
ref: #250 #252 #90
2024-08-25 17:51:53 +09:30
Jon
7b3a007862 feat(assistance): Add Request ticket to navigation
ref: #250 #252 #96
2024-08-25 17:48:17 +09:30
Jon
c5a5c393a8 feat(core): add basic ticketing system
ref: #250 #252 #96 #93 #95 #90 #115
2024-08-25 17:45:55 +09:30
Jon
52db44eac7 feat(development): add option for including additional stylesheets
ref: #252
2024-08-25 11:31:43 +09:30
Jon
cb9c782d0c chore(base): remove dev apps from debug added urls
ref: #252
2024-08-24 17:03:01 +09:30
Jon
b8c4a540fa fix(core): ensure is_global check does not process null value
ref: #252
2024-08-24 17:00:59 +09:30
Jon
91d85d93c7 chore(core): Add initial comment model file
ref: #252 #250
2024-08-23 18:06:32 +09:30
Jon
73ca0feb55 chore(core): Add initial ticket model file
ref: #252 #250
2024-08-23 18:06:32 +09:30
Jon
64fd8b5686 feat(ui): add project management icon
!31
2024-08-23 18:03:52 +09:30
Jon
58a9532c70 chore: update to cater for recent dev changes and class inheritance
!31 !40 !42
2024-08-23 18:03:52 +09:30
Jon
3c206f5aef feat(project_management): Add manager and users for projects and tasks
!30 #14
2024-08-23 18:03:52 +09:30
Jon
9bc4f186d5 feat(project_management): Project task view "view"
!30 #14
2024-08-23 18:03:52 +09:30
Jon
f883d4190a feat(project_management): Project task edit view
!30 #14
2024-08-23 18:03:52 +09:30
Jon
c6fc2d3e7c feat(project_management): Project task delete view
!30 #14
2024-08-23 18:03:52 +09:30
Jon
e4d1bb4d3c feat(project_management): Project task add view
!30 #14
2024-08-23 18:03:52 +09:30
Jon
376faf3d5a feat(project_management): Add project task model
!30 #14
2024-08-23 18:03:52 +09:30
Jon
6c0ca9cb86 chore(project_management): add urls to include
!30
2024-08-23 18:03:52 +09:30
Jon
326753d0ff chore(project_management): Add doc link to project view page
!30 #14
2024-08-23 18:02:53 +09:30
Jon
47f95ddae2 chore(project_management): add placeholder code for project notes
!30 #14
2024-08-23 18:02:53 +09:30
Jon
86fc1448a6 feat(project_management): save project history
!30 #14
2024-08-23 18:02:53 +09:30
Jon
a91ae337c4 feat(project_management): add project delete page
!30 #14
2024-08-23 18:00:45 +09:30
Jon
58466fa490 feat(project_management): add project edit page
!30 #14
2024-08-23 18:00:45 +09:30
Jon
de78a30a5d feat(project_management): add project view page
!30 #14
2024-08-23 18:00:45 +09:30
Jon
f7d61696d1 feat(project_management): add project add page
!30 #14
2024-08-23 18:00:45 +09:30
Jon
e35ccd360b feat(project_management): add project index page
!30
2024-08-23 18:00:45 +09:30
Jon
5f3a778002 feat(project_management): add interim project model
!30
2024-08-23 18:00:45 +09:30
dcba456af3 build: bump version 1.0.0 -> 1.1.0 2024-08-23 08:26:37 +00:00
Jon
6d98006a37 Merge pull request #210 from nofusscomputing/v1-1 2024-08-23 17:47:59 +09:30
Jon
4ac0c157bc feat(itim): Dont attempt to apply cluster type config if no type specified.
ref: #247 #71
2024-08-23 17:45:44 +09:30
Jon
e696129f0b feat(itim): Service config rendered as part of cluster config
ref: #247 #125 closes #69
2024-08-23 17:45:44 +09:30
Jon
cf5c512a64 feat(itim): dont force config key, validate when it's required
ref: #247 #69
2024-08-23 17:45:44 +09:30
Jon
32f45f2d5f feat(itim): Services assignable to cluster
ref: #247 #125
2024-08-23 17:45:44 +09:30
Jon
66b8bd5a74 feat(itim): Ability to add configuration to cluster type
ref: #247 closes #71
2024-08-23 17:45:44 +09:30
Jon
bfb20dab0f feat(itim): Ability to add external link to cluster
ref: #244 #71 #6
2024-08-23 17:45:44 +09:30
Jon
6b28569bca fix(settings): return the rendering of external links to models
ref: #244
2024-08-23 17:45:44 +09:30
Jon
79b2c668fa docs(itim): cluster user docs
ref: #244 #71
2024-08-23 17:45:44 +09:30
Jon
5d660694c3 refactor(itim): Add Cluster type to index page
ref: #244 #71
2024-08-23 17:45:44 +09:30
Jon
e70d0392c0 chore(itim): Add Cluster icon to navigation
ref: #244 #71
2024-08-23 17:45:44 +09:30
Jon
caa47a3bb6 test(itim): Cluster Types unit tests
ref: #244 #71
2024-08-23 17:45:44 +09:30
Jon
75203c022a feat(itim): Ability to add and configure Cluster Types
ref: #244 #71
2024-08-23 17:45:44 +09:30
Jon
b65e577017 chore(itim): add placeholder for assigning service to a cluster
ref: #244 #71 #125
2024-08-23 17:45:44 +09:30
Jon
45ef81481f test(itim): Cluster unit tests
ref: #244 #71
2024-08-23 17:45:44 +09:30
Jon
f9dee4465b feat(itim): Add cluster to history save
ref: #244 #71
2024-08-23 17:45:44 +09:30
Jon
8ec1ea2a4c feat(itim): prevent cluster from setting itself as parent
ref: #244 #71
2024-08-23 17:45:44 +09:30
Jon
17df9d1fa3 fix(core): Ensure when saving history json is correctly formatted
ref: #244
2024-08-23 17:45:44 +09:30
Jon
24967ae3a6 fix(itim): Fix name typo in Add Service button
ref: #244
2024-08-23 17:45:44 +09:30
Jon
30bd8aa483 feat(itim): Ability to add and configure cluster
ref: #244 #71
2024-08-23 17:45:44 +09:30
Jon
efce9c0219 chore: update migrations from previous days work
ref: #244 #71 #245
2024-08-23 17:45:44 +09:30
Jon
0020550dde feat(itam): Track if device is virtual
ref: #244 closes #245
2024-08-23 17:45:44 +09:30
Jon
04a9cde47e feat(api): Endpoint to fetch user permissions
ref: #244 closes #164
2024-08-23 17:45:44 +09:30
Jon
e472022c91 feat(development): Add function to filter permissions to those used by centurion
ref: #244 #164
2024-08-23 17:45:44 +09:30
Jon
d778cd0e83 feat(development): Add new template tag choice_ids for string list casting
Ref: #244 closes #243
2024-08-23 17:45:44 +09:30
Jon
1f76da8709 refactor(itam): Knowledge Base now uses details template
#242 closes #231
2024-08-23 17:45:44 +09:30
Jon
7ddc0abce6 test(itam): Correct Device Type Model permissions test to use "change" view
#242 #234
2024-08-23 17:45:44 +09:30
Jon
a2af58ae09 refactor(itam): Device Type now uses details template
#242 closes #234
2024-08-23 17:45:44 +09:30
Jon
8e71bb932e test(itam): Correct Operating System Model permissions test to use "change" view
#242 #229
2024-08-23 17:45:44 +09:30
Jon
8c1f033b1c refactor(itam): Operating System now uses details template
#242 closes #229
2024-08-23 17:45:44 +09:30
Jon
eb4df77614 refactor(itim): Service Port now uses details template
#242 closes #238
2024-08-23 17:45:44 +09:30
Jon
fed6eee951 test(config_management): Correct Device Model permissions test to use "change" view
#242 #235
2024-08-23 17:45:44 +09:30
Jon
6a0b507c3b refactor(itam): Device Model now uses details template
#242 closes #235
2024-08-23 17:45:44 +09:30
Jon
47b2e61987 test(config_management): Correct Config Group permissions test to use "change" view
#242 #230
2024-08-23 17:45:44 +09:30
Jon
28259b329e refactor(config_management): Config Groups now uses details template
#242 closes #230
2024-08-23 17:45:44 +09:30
Jon
4391aa3ea8 test(itam): Correct Software Category permissions test to use "change" view
#242 #236
2024-08-23 17:45:44 +09:30
Jon
4a4c8e94e4 refactor(itam): Software Categories now uses details template
#242 closes #236
2024-08-23 17:45:44 +09:30
Jon
d41cc312bb test(core): Correct manufacturer permissions test to use "change" view
.#242 #232
2024-08-23 17:45:44 +09:30
Jon
12abc741d2 refactor(itam): manufacturer now uses details template
#242 closes #232
2024-08-23 17:45:44 +09:30
Jon
a8262e0a54 test(itam): Correct software permissions test to use "change" view
#240 #233
2024-08-23 17:45:44 +09:30
Jon
2011c212ba refactor(itam): software now uses details template
#240 closes #233
2024-08-23 17:45:44 +09:30
Jon
564871ca3c chore(itam): remove commented code from device form
#240 closes #227
2024-08-23 17:45:44 +09:30
Jon
95bb15238a fix: Ensure tenancy models have Meta.verbose_name_plural attribute
.#240 closes #239
2024-08-23 17:45:44 +09:30
Jon
cafc5ce6e2 test(model): test for checking if Meta sub-class has variable verbose_name_plural
#239 #240
2024-08-23 17:45:44 +09:30
Jon
68c3b64424 fix(base): Use correct url for back button
.#227 #240
2024-08-23 17:45:44 +09:30
Jon
300fe283d6 refactor(itam): device now use details template
#227 #240
2024-08-23 17:45:44 +09:30
Jon
ac6408c3bb feat(development): Render model_name_plural as part of back button
#227 #239
2024-08-23 17:45:44 +09:30
Jon
750e323947 feat(development): add to form field model_name_plural
#227 #239
2024-08-23 17:45:44 +09:30
Jon
4cca9d9904 feat(development): render heading if section included
#227
2024-08-23 17:45:44 +09:30
Jon
955081f155 chore: add Merge/Pull request template
#226
2024-08-23 17:45:44 +09:30
Jon
01e47c889b docs(roadmap): update completed features
#226
2024-08-23 17:45:43 +09:30
Jon
2cd4d387a7 docs(base): detail view template
. #24 #226 closes #22
2024-08-23 17:45:43 +09:30
Jon
ea8c60ccc5 refactor(itim): services now use details template
. #22 #226
2024-08-23 17:45:43 +09:30
Jon
4ecf5236c1 feat(base): create detail view templates
purpose is to aid in the development of a detail form

#22 #24 #226
2024-08-23 17:45:43 +09:30
Jon
eb919f2d5e docs: initial adding of template page
#22
2024-08-23 17:45:43 +09:30
Jon
485dd43b58 chore: add services navigation icon
!43 #69
2024-08-23 17:45:43 +09:30
Jon
fd4da657fb fix(itim): ensure that the service template config is also rendered as part of device config
!43 #69
2024-08-23 17:45:43 +09:30
Jon
acc6879fb1 docs: fluff the port and services
!43 closes #69
2024-08-23 17:45:43 +09:30
Jon
d339fdb645 fix(itim): dont render link if no device
!43 #69
2024-08-23 17:45:43 +09:30
Jon
53a720a802 feat(itam): Render Service Config with device config
!43 #69
2024-08-23 17:45:43 +09:30
Jon
0b04cdcfbf feat(itam): Display deployed services for devices
!43 #69
2024-08-23 17:45:43 +09:30
Jon
b5d2fe70ff feat(itim): Prevent circular service dependencies
!43 #69
2024-08-23 17:45:43 +09:30
Jon
6d6f1c5401 feat(itim): Port number validation to check for valid port numbers
!43 #69
2024-08-23 17:45:43 +09:30
Jon
7b8b8a6394 feat(itim): Prevent Service template from being assigned as dependent service
!43 #69
2024-08-23 17:45:43 +09:30
Jon
2a3373a19b feat(itim): Add service template support
!43 #69
2024-08-23 17:45:43 +09:30
Jon
eb320c4e95 fix(itim): Dont show self within service dependencies
!43 #69
2024-08-23 17:45:43 +09:30
Jon
0b220424bb feat(itim): Ports for service management
!43 #69
2024-08-23 17:45:43 +09:30
Jon
a948ec7bd7 feat(itim): Service Management
!43 #69
2024-08-23 17:45:43 +09:30
Jon
56196f721d fix(assistance): Only return distinct values when limiting KB articles
!43 #10
2024-08-23 17:45:43 +09:30
Jon
3d06112860 docs(assistance): document kb categories for user
!43 closes #10
2024-08-23 17:45:43 +09:30
Jon
05484d9e02 feat(assistance): Filter KB articles to target user
only intended to filter for users whom dont have change perm.

!43 #10
2024-08-23 17:45:43 +09:30
Jon
b73807a140 feat(assistance): Add date picker to date fields for KB articles
!43 #10
2024-08-23 17:45:43 +09:30
Jon
215c5e464c feat(assistance): Dont display expired articles for "view" users
!43 #10
2024-08-23 17:45:43 +09:30
Jon
cf2dce320c docs(assistance): document kb for user
!43 #10
2024-08-23 17:45:43 +09:30
Jon
32cdcc38b5 feat(base): add code highlighting to markdown
!43 #10
2024-08-23 17:45:43 +09:30
Jon
4b3ea06f70 feat(assistance): Categorised Knowledge base articles
!43 #10
2024-08-23 17:45:43 +09:30
Jon
2e7a6a42b4 docs(assistance): added pages for knowledgebase
!43 #10
2024-08-23 17:45:43 +09:30
Jon
be0ec86c48 chore(base): rename information -> assistance
!43 #10
2024-08-23 17:45:43 +09:30
Jon
8c493e8fa3 feat(itim): Add menu entry
!43 #69 #71
2024-08-23 17:45:43 +09:30
Jon
9668e811c5 feat(itam): Ability to add device configuration
!43 fixes #44
2024-08-23 17:45:43 +09:30
Jon
28ce99f46a test(external_link): add tests
!43 fixes #6
2024-08-23 17:45:43 +09:30
Jon
9b4dbc58f3 feat(settings): New model to allow adding templated links to devices and software
!43 #6
2024-08-23 17:45:43 +09:30
Jon
f295f15034 docs: move settings pages into sub-directory
!43 #6
2024-08-23 17:45:43 +09:30
4c41994068 build: bump version 1.0.0-b14 -> 1.0.0 2024-08-23 08:03:31 +00:00
Jon
6f7b3ffad6 chore: add github pr template
ref: #213
2024-08-20 14:21:00 +09:30
Jon
cc97128e25 ci: add mkdocs workflow
ref: #213
2024-08-20 12:28:43 +09:30
Jon
b6ba3d38dc docs: migrate project links to github
ref: #213
2024-08-20 12:08:59 +09:30
Jon
18b788844a ci: update gitlab-ci to current head
ref: #209
2024-08-19 16:53:31 +09:30
9d5464b5a9 build: bump version 1.0.0-b13 -> 1.0.0-b14 2024-08-12 06:02:07 +00:00
Jon
7848397ae2 Merge pull request #224 from nofusscomputing/223-fix-api-team-mnotes
CRUD operation for team notes
2024-08-12 15:29:21 +09:30
Jon
f298ce94bf test(access): test field model_notes
closes #223
2024-08-12 15:15:28 +09:30
Jon
3cace8943e fix(api): ensure model_notes is an available field
#223
2024-08-12 15:14:58 +09:30
Jon
aa40d68c88 build: add test to changelog
#209
2024-08-11 19:30:45 +09:30
c0f186db89 build: bump version 1.0.0-b12 -> 1.0.0-b13 2024-08-11 08:01:20 +00:00
Jon
6b35e7808c Merge pull request #222 from nofusscomputing/153-model-validations
fix: Audit models for validations
2024-08-11 17:21:20 +09:30
Jon
467f6fca6b fix(itam): Ensure device name is formatted according to RFC1035 2.3.1
see https://datatracker.ietf.org/doc/html/rfc1035#autoid-6

#222 closes #153
2024-08-11 17:09:06 +09:30
Jon
f86b2d5216 fix(itam): Ensure device UUID is correctly formatted
#153 #222
2024-08-11 17:08:56 +09:30
Jon
e29d8e1ec1 fix(config_management): Ensure that config group can't set self as parent
interface already filters self out, however check still to be done.

. #153 #222
2024-08-11 17:07:39 +09:30
Jon
0fc5f41391 fix(settings): ensure that the api token cant be saved to notes field
#153
2024-08-11 16:26:19 +09:30
Jon
4b29448d84 Merge pull request #220 from nofusscomputing/162-api-field-validtion
test: api field checks
2024-08-11 14:37:46 +09:30
Jon
e9fe4896df ci: mirror repo to gitlab
. #220 closes #214
2024-08-11 14:29:02 +09:30
Jon
b9d32a2c16 docs(tests): update testing docs explaining test types
#220 closes #162
2024-08-11 12:57:33 +09:30
Jon
d6bd99c5de docs: update project badges to reflect github hosted project
. #220
2024-08-11 12:38:20 +09:30
Jon
7de5ab12bf docs(readme): correct status badge icon to github
. #220
2024-08-11 12:37:52 +09:30
Jon
3fe09fb8f9 test(software): api field checks
. #162 #220
2024-08-11 12:27:57 +09:30
Jon
eb6b03f731 docs(development): added api field test note
. #220
2024-08-11 12:05:46 +09:30
791 changed files with 95977 additions and 2084 deletions

View File

@ -1,8 +1,21 @@
---
commitizen:
name: cz_conventional_commits
customize:
change_type_map:
feature: Features
fix: Fixes
refactor: Refactoring
test: Tests
change_type_order:
- BREAKING CHANGE
- feat
- fix
- test
- refactor
commit_parser: ^(?P<change_type>feat|fix|test|refactor|perf|BREAKING CHANGE)(?:\((?P<scope>[^()\r\n]*)\)|\()?(?P<breaking>!)?:\s(?P<message>.*)?
name: cz_customize
prerelease_offset: 1
tag_format: $version
update_changelog_on_bump: false
version: 1.0.0-b12
version: 1.5.0
version_scheme: semver

44
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1,44 @@
### :books: Summary
<!-- your summary here emojis ref: https://github.com/yodamad/gitlab-emoji -->
### :link: Links / References
<!--
using a list as any links to other references or links as required. if relevant, describe the link/reference
Include any issues or related merge requests. Note: dependent MR's also to be added to "Merge request dependencies"
-->
### :construction_worker: Tasks
- [ ] Add your tasks here if required (delete)
<!-- 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/)._
- [ ] :notebook: Release notes updated
- [ ] :blue_book: Documentation written
_All features to be documented within the correct section(s). Administration, Development and/or User_
- [ ] :checkered_flag: Milestone assigned
- [ ] :gear: :test_tube: [Functional Test(s) Written](https://nofusscomputing.com/projects/centurion_erp/development/testing/)
- [ ] :test_tube: [Unit Test(s) Written](https://nofusscomputing.com/projects/centurion_erp/development/testing/)
_ensure test coverage delta is not less than zero_
- [ ] :page_facing_up: Roadmap updated

View File

@ -10,10 +10,23 @@ on:
tags:
- '*'
env:
GIT_SYNC_URL: "https://${{ secrets.GITLAB_USERNAME_ROBOT }}:${{ secrets.GITLAB_TOKEN_ROBOT }}@gitlab.com/nofusscomputing/projects/centurion_erp.git"
jobs:
mkdocs:
name: 'MKDocs'
permissions:
pull-requests: write
contents: write
statuses: write
checks: write
actions: write
uses: nofusscomputing/action_mkdocs/.github/workflows/reusable_mkdocs.yaml@development
docker:
name: 'Docker'
uses: nofusscomputing/action_docker/.github/workflows/docker.yaml@development
@ -31,3 +44,77 @@ jobs:
uses: nofusscomputing/action_python/.github/workflows/python.yaml@development
secrets:
WORKFLOW_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
gitlab-mirror:
if: ${{ github.repository == 'nofusscomputing/centurion_erp' }}
runs-on: ubuntu-latest
steps:
- name: Checks
shell: bash
run: |
if [ "0${{ env.GIT_SYNC_URL }}" == "0" ]; then
echo "[ERROR] you must define variable GIT_SYNC_URL for mirroring this repository.";
exit 1;
fi
- name: clone
shell: bash
run: |
git clone --mirror https://github.com/${{ github.repository }} repo;
ls -la repo/
- name: add remote
shell: bash
run: |
cd repo;
echo "**************************************** - git remote -v";
git remote -v;
echo "****************************************";
git remote add destination $GIT_SYNC_URL;
- name: push branches
shell: bash
run: |
cd repo;
echo "**************************************** - git branch";
git branch;
echo "****************************************";
# git push destination --all --force;
git push destination --mirror || true;
# - name: push tags
# shell: bash
# run: |
# cd repo;
# echo "**************************************** - git tag";
# git tag;
# echo "****************************************";
# git push destination --tags --force;

6
.gitignore vendored
View File

@ -9,3 +9,9 @@ artifacts/
volumes/
build/
pages/
node_modules/
.markdownlint-cli2.jsonc
.markdownlint.json
package-lock.json
package.json
**.junit.xml

View File

@ -35,3 +35,5 @@
- [ ] [Unit Test(s) Written](https://nofusscomputing.com/projects/centurion_erp/development/testing/)
_ensure test coverage delta is not less than zero_
- [ ] :page_facing_up: Roadmap updated

View File

@ -7,5 +7,6 @@
"streetsidesoftware.code-spell-checker",
"qwtel.sqlite-viewer",
"jebbs.markdown-extended",
"william-voyek.vscode-nginx",
]
}

36
.vscode/launch.json vendored
View File

@ -5,7 +5,7 @@
"version": "0.2.0",
"configurations": [
{
"name": "Debug: Django",
"name": "Centurion",
"type": "debugpy",
"request": "launch",
"args": [
@ -16,9 +16,41 @@
"autoStartBrowser": false,
"program": "${workspaceFolder}/app/manage.py"
},
{
"name": "Debug: Gunicorn",
"type": "debugpy",
"request": "launch",
"module": "gunicorn",
"args": [
"--access-logfile",
"-",
"--workers",
"3",
"--bind",
"0.0.0.0:8002",
"app.wsgi:application",
],
"django": true,
"autoStartBrowser": false,
"cwd": "${workspaceFolder}/app"
},
{
"name": "Migrate",
"type": "debugpy",
"request": "launch",
"args": [
"migrate"
],
"django": true,
"autoStartBrowser": false,
"program": "${workspaceFolder}/app/manage.py"
},
{
"name": "Debug: Celery",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "celery",
"console": "integratedTerminal",

View File

@ -17,4 +17,5 @@
"ITSM"
],
"cSpell.language": "en-AU",
"jest.enable": false,
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,84 @@
# Contribution Guide
Development of this project has been setup to be done from VSCodium. The following additional requirements need to be met:
- npm has been installed. _required for `markdown` linting_
`sudo apt install -y --no-install-recommends npm`
- setup of other requirements can be done with `make prepare`
- **ALL** Linting must pass for Merge to be conducted.
_`make lint`_
## TL;DR
from the root of the project to start a test server use:
``` bash
# activate python venv
source /tmp/centurion_erp/bin/activate
# enter app dir
cd app
# Start dev server can be viewed at http://127.0.0.1:8002
python manage.py runserver 8002
# Run any migrations, if required
python manage.py migrate
# Create a super suer if required
python manage.py createsuperuser
```
## Makefile
!!! tip "TL;DR"
Common make commands are `make prepare` then `make docs` and `make lint`
Included within the root of the repository is a makefile that can be used during development to check/run different items as is required during development. The following make targets are available:
- `prepare`
_prepare the repository. init's all git submodules and sets up a python virtual env and other make targets_
- `docs`
_builds the docs and places them within a directory called build, which can be viewed within a web browser_
- `lint`
_conducts all required linting_
- `docs-lint`
_lints the markdown documents within the docs directory for formatting errors that MKDocs may/will have an issue with._
- `clean`
_cleans up build artifacts and removes the python virtual environment_
> this doc is yet to receive a re-write
## Docker Container
within the `deploy/` directory there is a docker compose file. running `docker compose up` from this directory will launch a full stack deployment locally containing Centurion API, User Interface, a worker and a RabbitMQ server. once launched you can navigate to `http://127.0.0.1/` to start browsing the site.
You may need to run migrations if your not mounting your own DB. to do this run `docker exec -ti centurion-erp python manage.py migrate`
# Old working docs
## Dev Environment
It's advised to setup a python virtual env for development. this can be done with the following commands.
@ -30,6 +108,9 @@ python3 manage.py createsuperuser
# If model changes
python3 manage.py makemigrations --noinput
# To update code highlight run
pygmentize -S default -f html -a .codehilite > project-static/code.css
```
Updates to python modules will need to be captured with SCM. This can be done by running `pip freeze > requirements.txt` from the running virtual environment.

View File

@ -4,7 +4,7 @@
<br>
![Project Status - Active](https://img.shields.io/badge/Project%20Status-Active-green?logo=gitlab&style=plastic)
![Project Status - Active](https://img.shields.io/badge/Project%20Status-Active-green?logo=github&style=plastic)
[![Docker Pulls](https://img.shields.io/docker/pulls/nofusscomputing/centurion-erp?style=plastic&logo=docker&color=0db7ed)](https://hub.docker.com/r/nofusscomputing/centurion-erp) [![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/centurion-erp)](https://artifacthub.io/packages/container/centurion-erp/centurion-erp)
@ -32,9 +32,14 @@ This project is hosted on [Github](https://github.com/NofussComputing/centurion_
**Stable Branch**
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/nofusscomputing/centurion_erp/ci.yaml?branch=master&style=plastic&logo=github&label=Build&color=%23000) ![GitHub Release](https://img.shields.io/github/v/release/nofusscomputing/centurion_erp?sort=date&style=plastic&logo=github&label=Release&color=000) ![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fnofusscomputing%2F.github%2Fmaster%2Frepositories%2Fnofusscomputing%2Fcenturion_erp%2Fmaster%2Fbadge_endpoint_coverage.json&style=plastic)
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/nofusscomputing/centurion_erp/ci.yaml?branch=master&style=plastic&logo=github&label=Build&color=%23000) ![GitHub Release](https://img.shields.io/github/v/release/nofusscomputing/centurion_erp?sort=date&style=plastic&logo=github&label=Release&color=000)
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fnofusscomputing%2F.github%2Fmaster%2Frepositories%2Fnofusscomputing%2Fcenturion_erp%2Fmaster%2Fbadge_endpoint_coverage.json&style=plastic)
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fnofusscomputing%2F.github%2Fmaster%2Frepositories%2Fnofusscomputing%2Fcenturion_erp%2Fmaster%2Fbadge_endpoint_unit_test.json)
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fnofusscomputing%2F.github%2Fmaster%2Frepositories%2Fnofusscomputing%2Fcenturion_erp%2Fmaster%2Fbadge_endpoint_coverage_functional.json&style=plastic)
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fnofusscomputing%2F.github%2Fmaster%2Frepositories%2Fnofusscomputing%2Fcenturion_erp%2Fmaster%2Fbadge_endpoint_functional_test.json)
----
@ -43,9 +48,13 @@ This project is hosted on [Github](https://github.com/NofussComputing/centurion_
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/nofusscomputing/centurion_erp/ci.yaml?branch=development&style=plastic&logo=github&label=Build&color=%23000) ![GitHub Release](https://img.shields.io/github/v/release/nofusscomputing/centurion_erp?include_prereleases&sort=date&style=plastic&logo=github&label=Release&color=000) ![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fnofusscomputing%2F.github%2Fmaster%2Frepositories%2Fnofusscomputing%2Fcenturion_erp%2Fdevelopment%2Fbadge_endpoint_coverage.json&style=plastic)
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/nofusscomputing/centurion_erp/ci.yaml?branch=development&style=plastic&logo=github&label=Build&color=%23000) ![GitHub Release](https://img.shields.io/github/v/release/nofusscomputing/centurion_erp?include_prereleases&sort=date&style=plastic&logo=github&label=Release&color=000)
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fnofusscomputing%2F.github%2Fmaster%2Frepositories%2Fnofusscomputing%2Fcenturion_erp%2Fdevelopment%2Fbadge_endpoint_coverage.json&style=plastic)
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fnofusscomputing%2F.github%2Fmaster%2Frepositories%2Fnofusscomputing%2Fcenturion_erp%2Fdevelopment%2Fbadge_endpoint_unit_test.json)
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fnofusscomputing%2F.github%2Fmaster%2Frepositories%2Fnofusscomputing%2Fcenturion_erp%2Fdevelopment%2Fbadge_endpoint_coverage_functional.json&style=plastic)
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fnofusscomputing%2F.github%2Fmaster%2Frepositories%2Fnofusscomputing%2Fcenturion_erp%2Fdevelopment%2Fbadge_endpoint_functional_test.json)
----
<br>

View File

@ -1,7 +1,57 @@
# Version 1.0.0
## 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.
API v2 is a beta release and is subject to change. On completion of the new UI, API v2 will more likely than not be set as stable.
- A large emphasis is being placed upon API stability. This is being achieved by ensuring the following:
- Actions can only be carried out by users whom have the correct permissions
- fields are of the correct type and visible when required as part of the API response
- Data validations work and notify the user of any issue
We are make the above possible by ensuring a more stringent test policy.
- New API will be at path `api/v2`.
- API v1 is now **Feature frozen** with only bug fixes being completed. It's recommended that you move to and start using API v2 as this has feature parity with API v1.
- API v1 is **depreciated**
- Depreciation of **ALL** API urls. API v1 Will be [removed in v2.0.0](https://github.com/nofusscomputing/centurion_erp/issues/343) release of Centurion.
# Version 1.3.0
!!! danger "Security"
As is currently the recommended method of deployment, the Centurion Container must be deployed behind a reverse proxy the conducts the SSL termination.
This release updates the docker container to be a production setup for deployment of Centurion. Prior to this version Centurion ERP was using a development setup for the webserver.
- Docker now uses SupervisorD for container
- Gunicorn WSGI setup for Centurion with NginX as the webserver.
- Container now has a health check.
- To setup container as "Worker", set `IS_WORKER='True'` environmental variable within container. _**Note:** You can still use command `celery -A app worker -l INFO`, although **not** recommended as the container health check will not be functioning_
## Version 1.0.0
Initial Release of Centurion ERP.
## Breaking changes
### Breaking changes
- Nil

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

@ -11,12 +11,20 @@ class AutoCreatedField(models.DateTimeField):
"""
help_text = 'Date and time of creation'
verbose_name = 'Created'
def __init__(self, *args, **kwargs):
kwargs.setdefault("editable", False)
kwargs.setdefault("default", now)
kwargs.setdefault("help_text", self.help_text)
kwargs.setdefault("verbose_name", self.verbose_name)
super().__init__(*args, **kwargs)
@ -28,6 +36,18 @@ class AutoLastModifiedField(AutoCreatedField):
"""
help_text = 'Date and time of last modification'
verbose_name = 'Modified'
def __init__(self, *args, **kwargs):
kwargs.setdefault("help_text", self.help_text)
kwargs.setdefault("verbose_name", self.verbose_name)
super().__init__(*args, **kwargs)
def pre_save(self, model_instance, add):
value = now()
@ -45,6 +65,20 @@ class AutoSlugField(models.SlugField):
"""
help_text = 'slug for this field'
verbose_name = 'Slug'
def __init__(self, *args, **kwargs):
kwargs.setdefault("help_text", self.help_text)
kwargs.setdefault("verbose_name", self.verbose_name)
super().__init__(*args, **kwargs)
def pre_save(self, model_instance, add):
if not model_instance.slug or model_instance.slug == '_':

View File

@ -1,13 +1,13 @@
from django import forms
from django.contrib.auth.models import Permission
from django.db.models import Q
from django.forms import inlineformset_factory
from app import settings
from .team_users import TeamUsersForm, TeamUsers
from access.models import Team
from access.functions import permissions
from app import settings
from core.forms.common import CommonModelForm
@ -66,37 +66,4 @@ class TeamForm(CommonModelForm):
self.fields['permissions'].widget.attrs = {'style': "height: 200px;"}
apps = [
'access',
'config_management',
'core',
'django_celery_results',
'itam',
'settings',
]
exclude_models = [
'appsettings',
'chordcounter',
'groupresult',
'organization'
'settings',
'usersettings',
]
exclude_permissions = [
'add_organization',
'add_taskresult',
'change_organization',
'change_taskresult',
'delete_organization',
'delete_taskresult',
]
self.fields['permissions'].queryset = Permission.objects.filter(
content_type__app_label__in=apps,
).exclude(
content_type__model__in=exclude_models
).exclude(
codename__in = exclude_permissions
)
self.fields['permissions'].queryset = permissions.permission_queryset()

View File

@ -0,0 +1,44 @@
from django.contrib.auth.models import Permission
def permission_queryset():
"""Filter Permissions to those used within the application
Returns:
list: Filtered queryset that only contains the used permissions
"""
apps = [
'access',
'assistance',
'config_management',
'core',
'django_celery_results',
'itam',
'itim',
'settings',
]
exclude_models = [
'appsettings',
'chordcounter',
'comment',
'groupresult',
'usersettings',
]
exclude_permissions = [
'add_organization',
'add_taskresult',
'change_organization',
'change_taskresult',
'delete_organization',
'delete_taskresult',
]
return Permission.objects.filter(
content_type__app_label__in=apps,
).exclude(
content_type__model__in=exclude_models
).exclude(
codename__in = exclude_permissions
)

View File

@ -0,0 +1,89 @@
# Generated by Django 5.1.2 on 2024-12-06 06:47
import access.models
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('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',
field=models.AutoField(help_text='ID of this item', primary_key=True, serialize=False, unique=True, verbose_name='ID'),
),
migrations.AlterField(
model_name='organization',
name='manager',
field=models.ForeignKey(help_text='Manager for this organization', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Manager'),
),
migrations.AlterField(
model_name='organization',
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='organization',
name='name',
field=models.CharField(help_text='Name of this Organization', max_length=50, unique=True, verbose_name='Name'),
),
migrations.AlterField(
model_name='team',
name='is_global',
field=models.BooleanField(default=False, help_text='Is this a global object?', verbose_name='Global Object'),
),
migrations.AlterField(
model_name='team',
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='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'),
),
migrations.AlterField(
model_name='team',
name='team_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

@ -10,6 +10,19 @@ from .models import Organization, Team
class OrganizationMixin():
"""Base Organization class"""
parent_model: str = None
""" Parent Model
This attribute defines the parent model for the model in question. The parent model when defined
will be used as the object to obtain the permissions from.
"""
parent_model_pk_kwarg: str = 'pk'
"""Parent Model kwarg
This value is used to define the kwarg that is used as the parent objects primary key (pk).
"""
request = None
user_groups = []
@ -26,20 +39,24 @@ class OrganizationMixin():
parent_model (Model): with PK from kwargs['pk']
"""
return self.parent_model.objects.get(pk=self.kwargs['pk'])
return self.parent_model.objects.get(pk=self.kwargs[self.parent_model_pk_kwarg])
def object_organization(self) -> int:
id = None
if hasattr(self, '_object_organization'):
return int(self._object_organization)
try:
if hasattr(self, 'get_queryset'):
self.get_queryset()
if hasattr(self, 'parent_model'):
if self.parent_model:
obj = self.get_parent_obj()
id = obj.get_organization().id
@ -61,6 +78,10 @@ class OrganizationMixin():
id = 0
if hasattr(self, 'instance') and id is None: # Form Instance
id = self.instance.get_organization()
except AttributeError:
@ -84,6 +105,10 @@ class OrganizationMixin():
pass
if id is not None:
self._object_organization = id
return id
@ -99,9 +124,13 @@ class OrganizationMixin():
is_member = False
if organization in self.user_organizations():
if organization is None:
return True
return False
if int(organization) in self.user_organizations():
is_member = True
return is_member
@ -111,6 +140,10 @@ class OrganizationMixin():
Override of 'PermissionRequiredMixin' method so that this mixin can obtain the required permission.
"""
if not hasattr(self, 'permission_required'):
return []
if self.permission_required is None:
raise ImproperlyConfigured(
f"{self.__class__.__name__} is missing the "
@ -147,6 +180,10 @@ class OrganizationMixin():
user_organizations = []
if hasattr(self, '_user_organizations'):
return self._user_organizations
teams = Team.objects
for group in self.request.user.groups.all():
@ -157,18 +194,41 @@ class OrganizationMixin():
user_organizations = user_organizations + [team.organization.id]
if len(user_organizations) > 0:
self._user_organizations = user_organizations
return user_organizations
# ToDo: Ensure that the group has access to item
def has_organization_permission(self, organization: int=None) -> bool:
def has_organization_permission(self, organization: int = None, permissions_required: list = None) -> bool:
""" Check if user has permission within organization.
Args:
organization (int, optional): Organization to check. Defaults to None.
permissions_required (list, optional): if doing object level permissions, pass in required permission. Defaults to None.
Returns:
bool: True for yes.
"""
has_permission = False
if permissions_required is None:
permissions_required = self.get_permission_required()
if not organization:
organization = self.object_organization()
else:
organization = int(organization)
if self.is_member(organization) or organization == 0:
groups = Group.objects.filter(pk__in=self.user_groups)
@ -182,7 +242,7 @@ class OrganizationMixin():
assembled_permission = str(permission["content_type__app_label"]) + '.' + str(permission["codename"])
if assembled_permission in self.get_permission_required() and (team['organization_id'] == organization or organization == 0):
if assembled_permission in permissions_required and (team['organization_id'] == organization or organization == 0):
return True
@ -242,15 +302,23 @@ class OrganizationMixin():
return True
perms = self.get_permission_required()
if permissions_required:
if self.has_organization_permission():
perms = permissions_required
else:
perms = self.get_permission_required()
if self.has_organization_permission(permissions_required = perms):
return True
if self.request.user.has_perms(perms) and len(self.kwargs) == 0 and str(self.request.method).lower() == 'get':
if self.request.user.has_perms(perms) and str(self.request.method).lower() == 'get':
return True
if len(self.kwargs) == 0 or (len(self.kwargs) == 1 and 'ticket_type' in self.kwargs):
return True
for required_permission in self.permission_required:
@ -327,6 +395,12 @@ class OrganizationPermission(AccessMixin, OrganizationMixin):
if not request.user.is_authenticated:
return self.handle_no_permission()
if len(self.permission_required) == 0:
if hasattr(self, 'get_dynamic_permissions'):
self.permission_required = self.get_dynamic_permissions()
if len(self.permission_required) > 0:

View File

@ -3,6 +3,8 @@ from django.db import models
from django.contrib.auth.models import User, Group, Permission
from django.forms import ValidationError
from rest_framework.reverse import reverse
from .fields import *
from core.middleware.get_request import get_request
@ -12,12 +14,10 @@ from core.mixin.history_save import SaveHistory
class Organization(SaveHistory):
class Meta:
verbose_name = "Organization"
verbose_name_plural = "Organizations"
ordering = ['name']
def __str__(self):
return self.name
def save(self, *args, **kwargs):
if self.slug == '_':
@ -26,28 +26,34 @@ class Organization(SaveHistory):
super().save(*args, **kwargs)
id = models.AutoField(
blank=False,
help_text = 'ID of this item',
primary_key=True,
unique=True,
blank=False
verbose_name = 'ID'
)
name = models.CharField(
blank = False,
help_text = 'Name of this Organization',
max_length = 50,
unique = True,
verbose_name = 'Name'
)
manager = models.ForeignKey(
User,
on_delete=models.SET_NULL,
blank = False,
help_text = 'Manager for this organization',
null = True,
help_text = 'Organization Manager'
on_delete=models.SET_NULL,
verbose_name = 'Manager'
)
model_notes = models.TextField(
blank = True,
default = None,
help_text = 'Tid bits of information',
null= True,
verbose_name = 'Notes',
)
@ -62,6 +68,62 @@ class Organization(SaveHistory):
def get_organization(self):
return self
def __str__(self):
return self.name
table_fields: list = [
'nbsp',
'name',
'created',
'modified',
'nbsp'
]
page_layout: list = [
{
"name": "Details",
"slug": "details",
"sections": [
{
"layout": "double",
"left": [
'name',
'manager',
'created',
'modified',
],
"right": [
'model_notes',
]
}
]
},
{
"name": "Teams",
"slug": "teams",
"sections": [
{
"layout": "table",
"field": "teams"
}
]
},
{
"name": "Notes",
"slug": "notes",
"sections": []
}
]
def get_url( self, request = None ) -> str:
if request:
return reverse("v2:_api_v2_organization-detail", request=request, kwargs={'pk': self.id})
return reverse("v2:_api_v2_organization-detail", kwargs={'pk': self.id})
class TenancyManager(models.Manager):
@ -106,7 +168,9 @@ class TenancyManager(models.Manager):
if request:
user = request.user._wrapped if hasattr(request.user,'_wrapped') else request.user
# user = request.user._wrapped if hasattr(request.user,'_wrapped') else request.user
user = request.user
if user.is_authenticated:
@ -124,13 +188,22 @@ class TenancyManager(models.Manager):
user_organizations += [ team_user.team.organization.id ]
# if len(user_organizations) > 0 and not user.is_superuser and self.model.is_global is not None:
if len(user_organizations) > 0 and not user.is_superuser:
return super().get_queryset().filter(
models.Q(organization__in=user_organizations)
|
models.Q(is_global = True)
)
if getattr(self.model, 'is_global', False) is True:
return super().get_queryset().filter(
models.Q(organization__in=user_organizations)
|
models.Q(is_global = True)
)
else:
return super().get_queryset().filter(
models.Q(organization__in=user_organizations)
)
return super().get_queryset()
@ -165,23 +238,36 @@ class TenancyObject(SaveHistory):
raise ValidationError('You must provide an organization')
id = models.AutoField(
blank=False,
help_text = 'ID of the item',
primary_key=True,
unique=True,
verbose_name = 'ID'
)
organization = models.ForeignKey(
Organization,
on_delete=models.CASCADE,
blank = False,
null = True,
help_text = 'Organization this belongs to',
null = False,
on_delete = models.CASCADE,
validators = [validatate_organization_exists],
verbose_name = 'Organization'
)
is_global = models.BooleanField(
blank = False,
default = False,
blank = False
help_text = 'Is this a global object?',
verbose_name = 'Global Object'
)
model_notes = models.TextField(
blank = True,
default = None,
null= True,
help_text = 'Tid bits of information',
null = True,
verbose_name = 'Notes',
)
@ -189,15 +275,59 @@ class TenancyObject(SaveHistory):
return self.organization
def get_url( self, request = None ) -> str:
"""Fetch the models URL
If URL kwargs are required to generate the URL, define a `get_url_kwargs` that returns them.
Args:
request (object, optional): The request object that was made by the end user. Defaults to None.
Returns:
str: Canonical URL of the model if the `request` object was provided. Otherwise the relative URL.
"""
model_name = str(self._meta.verbose_name.lower()).replace(' ', '_')
if request:
return reverse(f"v2:_api_v2_{model_name}-detail", request=request, kwargs = self.get_url_kwargs() )
return reverse(f"v2:_api_v2_{model_name}-detail", kwargs = self.get_url_kwargs() )
def get_url_kwargs(self) -> dict:
"""Fetch the URL kwargs
Returns:
dict: kwargs required for generating the URL with `reverse`
"""
return {
'pk': self.id
}
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
if self.organization is None:
raise ValidationError('Organization not defined')
super().save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
class Team(Group, TenancyObject):
class Meta:
# proxy = True
verbose_name_plural = "Teams"
ordering = ['team_name']
def __str__(self):
return self.name
class Meta:
ordering = [ 'team_name' ]
verbose_name = 'Team'
verbose_name_plural = "Teams"
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
@ -208,17 +338,79 @@ class Team(Group, TenancyObject):
team_name = models.CharField(
verbose_name = 'Name',
blank = False,
help_text = 'Name to give this team',
max_length = 50,
unique = False,
default = ''
verbose_name = 'Name',
)
created = AutoCreatedField()
modified = AutoLastModifiedField()
page_layout: dict = [
{
"name": "Details",
"slug": "details",
"sections": [
{
"layout": "double",
"left": [
'organization',
'team_name',
'created',
'modified',
],
"right": [
'model_notes',
]
},
{
"layout": "table",
"name": "Users",
"field": "users",
},
]
},
{
"name": "Notes",
"slug": "notes",
"sections": []
},
]
table_fields: list = [
'team_name',
'modified',
'created',
]
def get_url( self, request = None ) -> str:
model_name = str(self._meta.verbose_name.lower()).replace(' ', '_')
if request:
return reverse(f"v2:_api_v2_organization_team-detail", request=request, kwargs = self.get_url_kwargs() )
return reverse(f"v2:_api_v2_organization_team-detail", kwargs = self.get_url_kwargs() )
def get_url_kwargs(self) -> dict:
"""Fetch the URL kwargs
Returns:
dict: kwargs required for generating the URL with `reverse`
"""
return {
'organization_id': self.organization.id,
'pk': self.id
}
@property
def parent_object(self):
@ -241,40 +433,67 @@ class Team(Group, TenancyObject):
return [permission_list, self.permissions.all()]
def __str__(self):
return self.team_name
class TeamUsers(SaveHistory):
class Meta:
# proxy = True
verbose_name_plural = "Team Users"
ordering = ['user']
verbose_name = "Team User"
verbose_name_plural = "Team Users"
id = models.AutoField(
blank=False,
help_text = 'ID of this Team User',
primary_key=True,
unique=True,
blank=False
verbose_name = 'ID'
)
team = models.ForeignKey(
Team,
blank = False,
help_text = 'Team user belongs to',
null = False,
on_delete=models.CASCADE,
related_name="team",
on_delete=models.CASCADE)
verbose_name = 'Team'
)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
blank = False,
help_text = 'User who will be added to the team',
null = False,
on_delete=models.CASCADE,
verbose_name = 'User'
)
manager = models.BooleanField(
verbose_name='manager',
blank=True,
default=False,
blank=True
help_text = 'Is this user to be a manager of this team',
verbose_name='manager',
)
created = AutoCreatedField()
modified = AutoLastModifiedField()
page_layout: list = []
table_fields: list = [
'user',
'manager'
]
def delete(self, using=None, keep_parents=False):
""" Delete Team
@ -296,6 +515,24 @@ class TeamUsers(SaveHistory):
return self.team.organization
def get_url( self, request = None ) -> str:
url_kwargs: dict = {
'organization_id': self.team.organization.id,
'team_id': self.team.id,
'pk': self.id
}
print(f'url kwargs are: {url_kwargs}')
if request:
return reverse(f"v2:_api_v2_organization_team_user-detail", request=request, kwargs = url_kwargs )
return reverse(f"v2:_api_v2_organization_team_user-detail", kwargs = url_kwargs )
def save(self, *args, **kwargs):
""" Save Team
@ -318,3 +555,6 @@ class TeamUsers(SaveHistory):
return self.team
def __str__(self):
return self.user.username

View File

@ -0,0 +1,89 @@
from rest_framework.reverse import reverse
from rest_framework import serializers
from access.models import Organization
from app.serializers.user import UserBaseSerializer
from core import fields as centurion_field
class OrganizationBaseSerializer(serializers.ModelSerializer):
display_name = serializers.SerializerMethodField('get_display_name')
def get_display_name(self, item) -> str:
return str( item )
url = serializers.HyperlinkedIdentityField(
view_name="v2:_api_v2_organization-detail", format="html"
)
class Meta:
model = Organization
fields = [
'id',
'display_name',
'name',
'url',
]
read_only_fields = [
'id',
'display_name',
'name',
'url',
]
class OrganizationModelSerializer(
OrganizationBaseSerializer
):
_urls = serializers.SerializerMethodField('get_url')
def get_url(self, item) -> dict:
return {
'_self': item.get_url( request = self._context['view'].request ),
'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:
model = Organization
fields = '__all__'
fields = [
'id',
'display_name',
'name',
'model_notes',
'manager',
'created',
'modified',
'_urls',
]
read_only_fields = [
'id',
'display_name',
'created',
'modified',
'_urls',
]
class OrganizationViewSerializer(OrganizationModelSerializer):
pass
manager = UserBaseSerializer(many=False, read_only = True)

View File

@ -0,0 +1,99 @@
from rest_framework.reverse import reverse
from rest_framework import serializers
from access.models import TeamUsers
from api.serializers import common
from app.serializers.user import UserBaseSerializer
class TeamUserBaseSerializer(serializers.ModelSerializer):
display_name = serializers.SerializerMethodField('get_display_name')
def get_display_name(self, item) -> str:
return str( item )
url = serializers.SerializerMethodField('get_url')
def get_url(self, item) -> str:
return item.get_url( request = self.context['view'].request )
class Meta:
model = TeamUsers
fields = [
'id',
'display_name',
'url',
]
read_only_fields = [
'id',
'display_name',
'url',
]
class TeamUserModelSerializer(
common.CommonModelSerializer,
TeamUserBaseSerializer
):
_urls = serializers.SerializerMethodField('get_url')
def get_url(self, item) -> dict:
return {
'_self': item.get_url( request = self._context['view'].request )
}
class Meta:
model = TeamUsers
fields = [
'id',
'display_name',
'manager',
'user',
'created',
'modified',
'_urls',
]
read_only_fields = [
'id',
'display_name',
'created',
'modified',
'_urls',
]
def is_valid(self, *, raise_exception=True) -> bool:
is_valid = False
is_valid = super().is_valid(raise_exception=raise_exception)
self.validated_data['team_id'] = int(self._context['view'].kwargs['team_id'])
return is_valid
class TeamUserViewSerializer(TeamUserModelSerializer):
user = UserBaseSerializer(read_only = True)

View File

@ -0,0 +1,128 @@
from rest_framework.reverse import reverse
from rest_framework import serializers
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 Permission, PermissionBaseSerializer
from core import fields as centurion_field
class TeamBaseSerializer(serializers.ModelSerializer):
display_name = serializers.SerializerMethodField('get_display_name')
def get_display_name(self, item) -> str:
return str( item )
url = serializers.SerializerMethodField('get_url')
def get_url(self, item) -> str:
return item.get_url( request = self.context['view'].request )
class Meta:
model = Team
fields = [
'id',
'display_name',
'team_name',
'url',
]
read_only_fields = [
'id',
'display_name',
'team_name',
'url',
]
class TeamModelSerializer(
common.CommonModelSerializer,
TeamBaseSerializer
):
_urls = serializers.SerializerMethodField('get_url')
def get_url(self, item) -> dict:
return {
'_self': item.get_url( request = self._context['view'].request ),
'users': reverse(
'v2:_api_v2_organization_team_user-list',
request=self.context['view'].request,
kwargs={
'organization_id': item.organization.id,
'team_id': item.pk
}
)
}
team_name = centurion_field.CharField( autolink = True )
permissions = serializers.PrimaryKeyRelatedField(many = True, queryset=permission_queryset(), required = False)
class Meta:
model = Team
fields = '__all__'
fields = [
'id',
'display_name',
'team_name',
'model_notes',
'permissions',
'organization',
'is_global',
'created',
'modified',
'_urls',
]
read_only_fields = [
'id',
'display_name',
'name',
'organization',
'created',
'modified',
'_urls',
]
def is_valid(self, *, raise_exception=True) -> bool:
is_valid = False
is_valid = super().is_valid(raise_exception=raise_exception)
self.validated_data['organization_id'] = int(self._context['view'].kwargs['organization_id'])
return is_valid
class TeamViewSerializer(TeamModelSerializer):
organization = OrganizationBaseSerializer(many=False, read_only=True)
permissions = PermissionBaseSerializer(many = True)

View File

@ -11,6 +11,19 @@ class TenancyObject:
model = None
""" Model to be tested """
should_model_history_be_saved: bool = True
""" Should model history be saved.
By default this should always be 'True', however in special
circumstances, this may not be desired.
"""
def test_history_save(self):
"""Confirm the desired intent for saving model history."""
assert self.model.save_model_history == self.should_model_history_be_saved
def test_has_attr_get_organization(self):
""" TenancyObject attribute check

View File

@ -0,0 +1,92 @@
import pytest
from django.contrib.auth.models import User
from django.test import TestCase
from rest_framework.exceptions import ValidationError
from access.serializers.organization import (
Organization,
OrganizationModelSerializer
)
class OrganizationValidationAPI(
TestCase,
):
model = Organization
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create an org
2. Create an item
"""
self.user = User.objects.create(username = 'org_user', password='random password')
self.valid_data = {
'name': 'valid_org_data',
'manager': self.user.id
}
self.item = self.model.objects.create(
name = 'random title',
)
def test_serializer_valid_data(self):
"""Serializer Validation Check
Ensure that if creating and no name is provided a validation error occurs
"""
serializer = OrganizationModelSerializer(
data = self.valid_data
)
assert serializer.is_valid(raise_exception = True)
def test_serializer_validation_no_name(self):
"""Serializer Validation Check
Ensure that if creating and no name is provided a validation error occurs
"""
data = self.valid_data.copy()
del data['name']
with pytest.raises(ValidationError) as err:
serializer = OrganizationModelSerializer(
data = data
)
serializer.is_valid(raise_exception = True)
assert err.value.get_codes()['name'][0] == 'required'
def test_serializer_validation_manager_optional(self):
"""Serializer Validation Check
Ensure that if creating and no name is provided a validation error occurs
"""
data = self.valid_data.copy()
del data['manager']
serializer = OrganizationModelSerializer(
data = data
)
assert serializer.is_valid(raise_exception = True)

View File

@ -0,0 +1,294 @@
import pytest
import unittest
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.shortcuts import reverse
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.api_serializer_viewset import SerializersTestCases
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional
class ViewSetBase:
model = Organization
app_namespace = 'v2'
url_name = '_api_v2_organization'
change_data = {'name': 'device'}
delete_data = {}
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create an organization for user and item
. create an organization that is different to item
2. Create a team
3. create teams with each permission: view, add, change, delete
4. create a user per team
"""
organization = Organization.objects.create(name='test_org')
self.organization = organization
self.item = organization
different_organization = Organization.objects.create(name='test_different_organization')
self.different_organization = different_organization
self.other_org_item = organization
view_permissions = Permission.objects.get(
codename = 'view_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
view_team = Team.objects.create(
team_name = 'view_team',
organization = organization,
)
view_team_b = Team.objects.create(
team_name = 'view_team',
organization = different_organization,
)
view_team.permissions.set([view_permissions])
view_team_b.permissions.set([view_permissions])
add_permissions = Permission.objects.get(
codename = 'add_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
add_team = Team.objects.create(
team_name = 'add_team',
organization = organization,
)
add_team.permissions.set([add_permissions])
change_permissions = Permission.objects.get(
codename = 'change_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
change_team = Team.objects.create(
team_name = 'change_team',
organization = organization,
)
change_team.permissions.set([change_permissions])
delete_permissions = Permission.objects.get(
codename = 'delete_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
delete_team = Team.objects.create(
team_name = 'delete_team',
organization = organization,
)
delete_team.permissions.set([delete_permissions])
self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password")
self.view_user = User.objects.create_user(username="test_user_view", password="password")
teamuser = TeamUsers.objects.create(
team = view_team,
user = self.view_user
)
self.view_user_b = User.objects.create_user(username="test_user_view_b", password="password")
teamuser = TeamUsers.objects.create(
team = view_team_b,
user = self.view_user_b
)
self.url_view_kwargs = { 'pk': self.item.id }
self.add_data = {
'name': 'team_post',
}
self.super_add_user = User.objects.create_user(username="test_user_add_super", password="password", is_superuser = True)
self.add_user = User.objects.create_user(username="test_user_add", password="password")
teamuser = TeamUsers.objects.create(
team = add_team,
user = self.add_user
)
self.change_user = User.objects.create_user(username="test_user_change", password="password")
teamuser = TeamUsers.objects.create(
team = change_team,
user = self.change_user
)
self.delete_user = User.objects.create_user(username="test_user_delete", password="password")
teamuser = TeamUsers.objects.create(
team = delete_team,
user = self.delete_user
)
self.different_organization_user = User.objects.create_user(username="test_different_organization_user", password="password")
different_organization_team = Team.objects.create(
team_name = 'different_organization_team',
organization = different_organization,
)
different_organization_team.permissions.set([
view_permissions,
add_permissions,
change_permissions,
delete_permissions,
])
TeamUsers.objects.create(
team = different_organization_team,
user = self.different_organization_user
)
class OrganizationPermissionsAPI(
ViewSetBase,
APIPermissions,
TestCase
):
def test_add_has_permission(self):
""" Check correct permission for add
Attempt to add as user with permission
"""
client = Client()
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' )
client.force_login( self.super_add_user )
response = client.post( url, data = self.add_data )
assert response.status_code == 201
def test_returned_results_only_user_orgs(self):
"""Returned results check
This test case is an override of a test of the same name.
organizations are not tenancy objects and therefor are supposed to
return all items when a user queries them.
Ensure that a query to the viewset endpoint does not return
items that are not part of the users organizations.
"""
# Ensure the other org item exists, without test not able to function
print('Check that the different organization item has been defined')
assert hasattr(self, 'other_org_item')
# ensure that the variables for the two orgs are different orgs
print('checking that the different and user oganizations are different')
assert self.different_organization.id != self.organization.id
client = Client()
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')
client.force_login(self.view_user)
response = client.get(url)
contains_different_org: bool = False
# for item in response.data['results']:
# if int(item['id']) != self.organization.id:
# contains_different_org = True
assert len(response.data['results']) == 2
class OrganizationViewSet(
ViewSetBase,
SerializersTestCases,
TestCase
):
pass
class OrganizationMetadata(
ViewSetBase,
MetadataAttributesFunctional,
MetaDataNavigationEntriesFunctional,
TestCase
):
menu_id = 'access'
menu_entry_id = 'organization'

View File

@ -0,0 +1,301 @@
import pytest
import unittest
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 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
class ViewSetBase:
model = Team
app_namespace = 'API'
url_name = '_api_v2_organization_team'
change_data = {'name': 'device'}
delete_data = {'device': 'device'}
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create an organization for user and item
. create an organization that is different to item
2. Create a team
3. create teams with each permission: view, add, change, delete
4. create a user per team
"""
organization = Organization.objects.create(name='test_org')
self.organization = organization
different_organization = Organization.objects.create(name='test_different_organization')
self.different_organization = different_organization
self.item = self.model.objects.create(
organization=organization,
name = 'teamone'
)
self.other_org_item = self.model.objects.create(
organization=different_organization,
name = 'teamtwo'
)
self.url_kwargs = {'organization_id': self.organization.id}
self.url_view_kwargs = {'organization_id': self.organization.id, 'pk': self.item.id}
self.add_data = {'team_name': 'team_post'}
view_permissions = Permission.objects.get(
codename = 'view_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
view_team = Team.objects.create(
team_name = 'view_team',
organization = organization,
)
view_team.permissions.set([view_permissions])
add_permissions = Permission.objects.get(
codename = 'add_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
add_team = Team.objects.create(
team_name = 'add_team',
organization = organization,
)
add_team.permissions.set([add_permissions])
change_permissions = Permission.objects.get(
codename = 'change_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
change_team = Team.objects.create(
team_name = 'change_team',
organization = organization,
)
change_team.permissions.set([change_permissions])
delete_permissions = Permission.objects.get(
codename = 'delete_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
delete_team = Team.objects.create(
team_name = 'delete_team',
organization = organization,
)
delete_team.permissions.set([delete_permissions])
self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password")
self.view_user = User.objects.create_user(username="test_user_view", password="password")
teamuser = TeamUsers.objects.create(
team = view_team,
user = self.view_user
)
self.add_user = User.objects.create_user(username="test_user_add", password="password")
teamuser = TeamUsers.objects.create(
team = add_team,
user = self.add_user
)
self.change_user = User.objects.create_user(username="test_user_change", password="password")
teamuser = TeamUsers.objects.create(
team = change_team,
user = self.change_user
)
self.delete_user = User.objects.create_user(username="test_user_delete", password="password")
teamuser = TeamUsers.objects.create(
team = delete_team,
user = self.delete_user
)
self.different_organization_user = User.objects.create_user(username="test_different_organization_user", password="password")
different_organization_team = Team.objects.create(
team_name = 'different_organization_team',
organization = different_organization,
)
different_organization_team.permissions.set([
view_permissions,
add_permissions,
change_permissions,
delete_permissions,
])
TeamUsers.objects.create(
team = different_organization_team,
user = self.different_organization_user
)
class TeamPermissionsAPI(
ViewSetBase,
APIPermissions,
TestCase,
):
pass
class TeamViewSet(
ViewSetBase,
SerializersTestCases,
TestCase,
):
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

@ -0,0 +1,180 @@
import pytest
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from rest_framework.exceptions import ValidationError
from access.models import Organization, Permission
from access.serializers.teams import (
Team,
TeamModelSerializer
)
class MockView:
action: str = None
kwargs: dict = {}
class MockRequest:
user = None
class TeamValidationAPI(
TestCase,
):
model = Team
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create an org
2. Create an item
"""
self.organization = Organization.objects.create(
name = 'team org serializer test'
)
self.user = User.objects.create(username = 'org_user', password='random password')
view_permissions = Permission.objects.get(
codename = 'view_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
self.valid_data = {
'organization': self.organization.id,
'team_name': 'valid_org_data',
'permissions': [
view_permissions.id,
]
}
self.item = self.model.objects.create(
organization = self.organization,
name = 'random team title',
)
def test_serializer_valid_data(self):
"""Serializer Validation Check
Ensure that if creating an item supplied valid data
creates an item.
"""
mock_view = MockView()
mock_view.action = 'create'
mock_view.kwargs: dict = {
'organization_id': self.organization.id
}
mock_request = MockRequest()
mock_request.user = self.user
mock_view.request = mock_request
serializer = TeamModelSerializer(
context = {
'request': mock_request,
'view': mock_view,
},
data = self.valid_data
)
assert serializer.is_valid(raise_exception = True)
def test_serializer_validation_no_name(self):
"""Serializer Validation Check
Ensure that if creating and no name is provided a validation error occurs
"""
mock_view = MockView()
mock_view.action = 'create'
mock_view.kwargs: dict = {
'organization_id': self.organization.id
}
mock_request = MockRequest()
mock_request.user = self.user
mock_view.request = mock_request
data = self.valid_data.copy()
del data['team_name']
with pytest.raises(ValidationError) as err:
serializer = TeamModelSerializer(
context = {
'request': mock_request,
'view': mock_view,
},
data = data
)
serializer.is_valid(raise_exception = True)
assert err.value.get_codes()['team_name'][0] == 'required'
def test_serializer_validation_permissions_optional(self):
"""Serializer Validation Check
Ensure that if creating and permissions are not supplied, the item is
still created.
"""
mock_view = MockView()
mock_view.action = 'create'
mock_view.kwargs: dict = {
'organization_id': self.organization.id
}
mock_request = MockRequest()
mock_request.user = self.user
mock_view.request = mock_request
data = self.valid_data.copy()
del data['permissions']
serializer = TeamModelSerializer(
context = {
'request': mock_request,
'view': mock_view,
},
data = data
)
assert serializer.is_valid(raise_exception = True)

View File

@ -0,0 +1,309 @@
import pytest
import unittest
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 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
class ViewSetBase:
model = TeamUsers
app_namespace = 'API'
url_name = '_api_v2_organization_team_user'
change_data = {'name': 'device'}
delete_data = {'device': 'device'}
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create an organization for user and item
2. Create a team
3. create teams with each permission: view, add, change, delete
4. create a user per team
"""
organization = Organization.objects.create(name='test_org')
self.organization = organization
different_organization = Organization.objects.create(name='test_different_organization')
self.different_organization = different_organization
view_permissions = Permission.objects.get(
codename = 'view_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
view_team = Team.objects.create(
team_name = 'view_team',
organization = organization,
)
view_team_b = Team.objects.create(
team_name = 'view_team',
organization = different_organization,
)
view_team.permissions.set([view_permissions])
view_team_b.permissions.set([view_permissions])
add_permissions = Permission.objects.get(
codename = 'add_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
add_team = Team.objects.create(
team_name = 'add_team',
organization = organization,
)
add_team.permissions.set([add_permissions])
change_permissions = Permission.objects.get(
codename = 'change_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
change_team = Team.objects.create(
team_name = 'change_team',
organization = organization,
)
change_team.permissions.set([change_permissions])
delete_permissions = Permission.objects.get(
codename = 'delete_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
delete_team = Team.objects.create(
team_name = 'delete_team',
organization = organization,
)
delete_team.permissions.set([delete_permissions])
self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password")
self.view_user = User.objects.create_user(username="test_user_view", password="password")
self.view_user_b = User.objects.create_user(username="test_user_view_b", password="password")
self.item = TeamUsers.objects.create(
team = view_team,
user = self.view_user
)
self.other_org_item = TeamUsers.objects.create(
team = view_team_b,
user = self.view_user_b
)
self.url_view_kwargs = {'organization_id': self.organization.id, 'team_id': view_team.id, 'pk': self.item.id}
self.url_kwargs = {'organization_id': self.organization.id, 'team_id': view_team.id}
random_user = User.objects.create_user(username="random_user", password="password")
self.add_data = {'user': random_user.id}
self.add_user = User.objects.create_user(username="test_user_add", password="password")
teamuser = TeamUsers.objects.create(
team = add_team,
user = self.add_user
)
self.change_user = User.objects.create_user(username="test_user_change", password="password")
teamuser = TeamUsers.objects.create(
team = change_team,
user = self.change_user
)
self.delete_user = User.objects.create_user(username="test_user_delete", password="password")
teamuser = TeamUsers.objects.create(
team = delete_team,
user = self.delete_user
)
self.different_organization_user = User.objects.create_user(username="test_different_organization_user", password="password")
different_organization_team = Team.objects.create(
team_name = 'different_organization_team',
organization = different_organization,
)
different_organization_team.permissions.set([
view_permissions,
add_permissions,
change_permissions,
delete_permissions,
])
TeamUsers.objects.create(
team = different_organization_team,
user = self.different_organization_user
)
class TeamUserPermissionsAPI(
ViewSetBase,
APIPermissions,
TestCase
):
def test_returned_results_only_user_orgs(self):
"""This test is not applicable for team_user as users are not tenancy objects
"""
pass
class TeamUserViewSet(
ViewSetBase,
SerializersTestCases,
TestCase
):
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

@ -0,0 +1,186 @@
import pytest
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from rest_framework.exceptions import ValidationError
from access.models import Organization, Permission, Team
from access.serializers.team_user import (
TeamUsers,
TeamUserModelSerializer
)
class MockView:
action: str = None
kwargs: dict = {}
class MockRequest:
user = None
class TeamValidationAPI(
TestCase,
):
model = TeamUsers
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create an org
2. Create an item
"""
self.organization = Organization.objects.create(
name = 'team org serializer test'
)
self.user = User.objects.create(username = 'org_user', password='random password')
view_permissions = Permission.objects.get(
codename = 'view_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
self.team = Team.objects.create(
organization = self.organization,
name = 'random team title',
)
self.valid_data = {
'team': self.team.id,
'user': self.user.id
}
self.item = self.model.objects.create(
team = self.team,
user = self.user,
)
def test_serializer_valid_data(self):
"""Serializer Validation Check
Ensure that if creating an item supplied valid data
creates an item.
"""
mock_view = MockView()
mock_view.action = 'create'
mock_view.kwargs: dict = {
'organization_id': self.organization.id,
'team_id': self.team.id
}
mock_request = MockRequest()
mock_request.user = self.user
mock_view.request = mock_request
serializer = TeamUserModelSerializer(
context = {
'request': mock_request,
'view': mock_view,
},
data = self.valid_data
)
assert serializer.is_valid(raise_exception = True)
def test_serializer_validation_no_team_creates(self):
"""Serializer Validation Check
Ensure that if creating and no team is provided no validation
error occurs as the team id is collected from the view
"""
mock_view = MockView()
mock_view.action = 'create'
mock_view.kwargs: dict = {
'organization_id': self.organization.id,
'team_id': self.team.id
}
mock_request = MockRequest()
mock_request.user = self.user
mock_view.request = mock_request
data = self.valid_data.copy()
del data['team']
serializer = TeamUserModelSerializer(
context = {
'request': mock_request,
'view': mock_view,
},
data = data
)
assert serializer.is_valid(raise_exception = True)
def test_serializer_validation_no_user(self):
"""Serializer Validation Check
Ensure that if creating and no user is provided a validation error occurs
"""
mock_view = MockView()
mock_view.action = 'create'
mock_view.kwargs: dict = {
'organization_id': self.organization.id,
'team_id': self.team.id
}
mock_request = MockRequest()
mock_request.user = self.user
mock_view.request = mock_request
data = self.valid_data.copy()
del data['user']
with pytest.raises(ValidationError) as err:
serializer = TeamUserModelSerializer(
context = {
'request': mock_request,
'view': mock_view,
},
data = data
)
serializer.is_valid(raise_exception = True)
assert err.value.get_codes()['user'][0] == 'required'

View File

@ -17,7 +17,7 @@ class OrganizationAPI(TestCase):
model = Organization
app_namespace = 'API'
app_namespace = 'v1'
url_name = '_api_organization'

View File

@ -0,0 +1,192 @@
import pytest
import unittest
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.shortcuts import reverse
from django.test import Client, TestCase
from rest_framework.relations import Hyperlink
from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_fields import APICommonFields
class OrganizationAPI(
TestCase,
APICommonFields
):
model = Organization
app_namespace = 'v2'
url_name = '_api_v2_organization'
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create the object
2. create view user
3. add user as org manager
4. make api request
"""
organization = Organization.objects.create(name='test_org', model_notes='random text')
self.organization = organization
self.item = organization
self.url_view_kwargs = {'pk': self.item.id}
view_permissions = Permission.objects.get(
codename = 'view_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
view_team = Team.objects.create(
team_name = 'view_team',
organization = organization,
)
view_team.permissions.set([view_permissions])
self.view_user = User.objects.create_user(username="test_user_view", password="password")
teamuser = TeamUsers.objects.create(
team = view_team,
user = self.view_user
)
organization.manager = self.view_user
organization.save()
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.view_user)
response = client.get(url)
self.api_data = response.data
def test_api_field_exists_name(self):
""" Test for existance of API Field
name field must exist
"""
assert 'name' in self.api_data
def test_api_field_type_name(self):
""" Test for type for API Field
name field must be str
"""
assert type(self.api_data['name']) is str
def test_api_field_exists_manager(self):
""" Test for existance of API Field
manager field must exist
"""
assert 'manager' in self.api_data
def test_api_field_type_manager(self):
""" Test for type for API Field
manager field must be dict
"""
assert type(self.api_data['manager']) is dict
def test_api_field_exists_manager_id(self):
""" Test for existance of API Field
manager.id field must exist
"""
assert 'id' in self.api_data['manager']
def test_api_field_type_manager_id(self):
""" Test for type for API Field
manager.id field must be int
"""
assert type(self.api_data['manager']['id']) is int
def test_api_field_exists_manager_display_name(self):
""" Test for existance of API Field
manager.display_name field must exist
"""
assert 'display_name' in self.api_data['manager']
def test_api_field_type_manager_display_name(self):
""" Test for type for API Field
manager.display_name field must be int
"""
assert type(self.api_data['manager']['display_name']) is str
def test_api_field_exists_manager_url(self):
""" Test for existance of API Field
manager.display_name field must exist
"""
assert 'url' in self.api_data['manager']
def test_api_field_type_manager_url(self):
""" Test for type for API Field
manager.url field must be Hyperlink
"""
assert type(self.api_data['manager']['url']) is Hyperlink
def test_api_field_exists_url_teams(self):
""" Test for existance of API Field
_urls.teams field must exist
"""
assert 'teams' in self.api_data['_urls']
def test_api_field_type_url_teams(self):
""" Test for type for API Field
_urls.teams field must be Hyperlink
"""
assert type(self.api_data['_urls']['teams']) is str

View File

@ -20,7 +20,7 @@ class OrganizationPermissionsAPI(TestCase, APIPermissionChange, APIPermissionVie
model_name = 'organization'
app_label = 'access'
app_namespace = 'API'
app_namespace = 'v1'
url_name = '_api_organization'

View File

@ -34,7 +34,7 @@ class OrganizationHistory(TestCase):
self.history_create = History.objects.get(
action = History.Actions.ADD[0],
action = int(History.Actions.ADD),
item_pk = self.item_create.pk,
item_class = self.model._meta.model_name,
)
@ -44,7 +44,7 @@ class OrganizationHistory(TestCase):
self.item_change.save()
self.history_change = History.objects.get(
action = History.Actions.UPDATE[0],
action = int(History.Actions.UPDATE),
item_pk = self.item_change.pk,
item_class = self.model._meta.model_name,
)
@ -72,7 +72,7 @@ class OrganizationHistory(TestCase):
history = self.history_create.__dict__
assert history['action'] == int(History.Actions.ADD[0])
assert history['action'] == int(History.Actions.ADD)
# assert type(history['action']) is int
@ -125,7 +125,7 @@ class OrganizationHistory(TestCase):
history = self.history_change.__dict__
assert history['action'] == int(History.Actions.UPDATE[0])
assert history['action'] == int(History.Actions.UPDATE)
# assert type(history['action']) is int

View File

@ -67,4 +67,74 @@ class TeamModel(
@pytest.mark.skip(reason="uses Django group manager")
def test_model_class_tenancy_manager_function_get_queryset_called(self):
pass
pass
def test_model_fields_parameter_not_empty_help_text(self):
"""Test Field called with Parameter
This is a custom test of a test derived of the samae name. It's required
as the team model extends the Group model.
During field creation, paramater `help_text` must not be `None` or empty ('')
"""
group_mode_fields_to_ignore: list = [
'id',
'name',
'group_ptr_id'
]
fields_have_test_value: bool = True
for field in self.model._meta.fields:
if field.attname in group_mode_fields_to_ignore:
continue
print(f'Checking field {field.attname} is not empty')
if (
field.help_text is None
or field.help_text == ''
):
print(f' Failure on field {field.attname}')
fields_have_test_value = False
assert fields_have_test_value
def test_model_fields_parameter_type_verbose_name(self):
"""Test Field called with Parameter
This is a custom test of a test derived of the samae name. It's required
as the team model extends the Group model.
During field creation, paramater `verbose_name` must be of type str
"""
group_mode_fields_to_ignore: list = [
'name',
]
fields_have_test_value: bool = True
for field in self.model._meta.fields:
if field.attname in group_mode_fields_to_ignore:
continue
print(f'Checking field {field.attname} is of type str')
if not type(field.verbose_name) is str:
print(f' Failure on field {field.attname}')
fields_have_test_value = False
assert fields_have_test_value

View File

@ -21,7 +21,7 @@ class TeamAPI(TestCase):
model = Team
app_namespace = 'API'
app_namespace = 'v1'
url_name = '_api_team'
@ -51,7 +51,8 @@ class TeamAPI(TestCase):
self.item = self.model.objects.create(
organization=organization,
team_name = 'teamone'
team_name = 'teamone',
model_notes = 'random note'
)
@ -130,6 +131,24 @@ class TeamAPI(TestCase):
assert type(self.api_data['team_name']) is str
def test_api_field_exists_model_notes(self):
""" Test for existance of API Field
model_notes field must exist
"""
assert 'model_notes' in self.api_data
def test_api_field_type_model_notes(self):
""" Test for type for API Field
model_notes field must be str
"""
assert type(self.api_data['model_notes']) is str
def test_api_field_exists_url(self):
""" Test for existance of API Field

View File

@ -0,0 +1,174 @@
import pytest
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import reverse
from django.test import Client, TestCase
from rest_framework.relations import Hyperlink
from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_fields import APITenancyObject
class TeamAPI(
TestCase,
APITenancyObject
):
model = Team
app_namespace = 'v2'
url_name = '_api_v2_organization_team'
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create the object
2. create view user
3. add user as org manager
4. make api request
"""
organization = Organization.objects.create(name='test_org')
self.organization = organization
different_organization = Organization.objects.create(name='test_different_organization')
self.item = self.model.objects.create(
organization=organization,
team_name = 'teamone',
model_notes = 'random note'
)
self.url_view_kwargs = {'organization_id': self.organization.id, 'pk': self.item.id}
view_permissions = Permission.objects.get(
codename = 'view_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
self.item.permissions.set([view_permissions])
self.view_user = User.objects.create_user(username="test_user_view", password="password")
teamuser = TeamUsers.objects.create(
team = self.item,
user = self.view_user
)
organization.manager = self.view_user
organization.save()
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.view_user)
response = client.get(url)
self.api_data = response.data
def test_api_field_exists_team_name(self):
""" Test for existance of API Field
team_name field must exist
"""
assert 'team_name' in self.api_data
def test_api_field_type_team_name(self):
""" Test for type for API Field
team_name field must be str
"""
assert type(self.api_data['team_name']) is str
def test_api_field_exists_permissions(self):
""" Test for existance of API Field
permissions field must exist
"""
assert 'permissions' in self.api_data
def test_api_field_type_permissions(self):
""" Test for type for API Field
url field must be list
"""
assert type(self.api_data['permissions']) is list
def test_api_field_exists_permissions_id(self):
""" Test for existance of API Field
permissions.id field must exist
"""
assert 'id' in self.api_data['permissions'][0]
def test_api_field_type_permissions_id(self):
""" Test for type for API Field
permissions.id field must be int
"""
assert type(self.api_data['permissions'][0]['id']) is int
def test_api_field_exists_permissions_display_name(self):
""" Test for existance of API Field
permissions.display_name field must exist
"""
assert 'display_name' in self.api_data['permissions'][0]
def test_api_field_type_permissions_display_name(self):
""" Test for type for API Field
permissions.display_name field must be str
"""
assert type(self.api_data['permissions'][0]['display_name']) is str
def test_api_field_exists_permissions_url(self):
""" Test for existance of API Field
permissions.url field must exist
"""
assert 'url' in self.api_data['permissions'][0]
def test_api_field_type_permissions_url(self):
""" Test for type for API Field
permissions.url field must be str
"""
assert type(self.api_data['permissions'][0]['url']) is Hyperlink

View File

@ -39,7 +39,7 @@ class TeamHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
self.history_create = History.objects.get(
action = History.Actions.ADD[0],
action = int(History.Actions.ADD),
item_pk = self.item_create.pk,
item_class = self.model._meta.model_name,
)
@ -51,7 +51,7 @@ class TeamHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
self.field_after_expected_value = '{"name": "test_org_' + self.item_change.team_name + '", "team_name": "' + self.item_change.team_name + '"}'
self.history_change = History.objects.get(
action = History.Actions.UPDATE[0],
action = int(History.Actions.UPDATE),
item_pk = self.item_change.pk,
item_class = self.model._meta.model_name,
)
@ -68,7 +68,7 @@ class TeamHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
self.item_delete.delete()
self.history_delete = History.objects.get(
action = History.Actions.DELETE[0],
action = int(History.Actions.DELETE),
item_pk = self.deleted_pk,
item_class = self.model._meta.model_name,
)

View File

@ -18,7 +18,7 @@ class TeamPermissionsAPI(TestCase, APIPermissions):
model = Team
app_namespace = 'API'
app_namespace = 'v1'
url_name = '_api_team'

View File

@ -6,9 +6,13 @@ from django.contrib.auth.models import User
from access.models import Organization, Team, TeamUsers, Permission
from app.tests.abstract.models import BaseModel
class TeamUsersModel(TestCase):
class TeamUsersModel(
TestCase,
BaseModel
):
model = TeamUsers

View File

@ -0,0 +1,214 @@
import pytest
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import reverse
from django.test import Client, TestCase
from rest_framework.relations import Hyperlink
from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_fields import APICommonFields
class TeamUserAPI(
TestCase,
APICommonFields
):
model = TeamUsers
app_namespace = 'v2'
url_name = '_api_v2_organization_team_user'
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create the object
2. create view user
3. add user as org manager
4. make api request
"""
organization = Organization.objects.create(name='test_org')
self.organization = organization
different_organization = Organization.objects.create(name='test_different_organization')
view_permissions = Permission.objects.get(
codename = 'view_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
view_team = Team.objects.create(
team_name = 'view_team',
organization = organization,
)
view_team.permissions.set([view_permissions])
self.view_user = User.objects.create_user(username="test_user_view", password="password")
self.item = TeamUsers.objects.create(
team = view_team,
user = self.view_user
)
self.url_view_kwargs = {'organization_id': self.organization.id, 'team_id': view_team.id, 'pk': self.item.id}
self.url_kwargs = {'organization_id': self.organization.id, 'team_id': view_team.id}
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.view_user)
response = client.get(url)
self.api_data = response.data
def test_api_field_exists_manager(self):
""" Test for existance of API Field
manager field must exist
"""
assert 'manager' in self.api_data
def test_api_field_type_manager(self):
""" Test for type for API Field
manager field must be bool
"""
assert type(self.api_data['manager']) is bool
def test_api_field_exists_created(self):
""" Test for existance of API Field
created field must exist
"""
assert 'created' in self.api_data
def test_api_field_type_created(self):
""" Test for type for API Field
created field must be str
"""
assert type(self.api_data['created']) is str
def test_api_field_exists_modified(self):
""" Test for existance of API Field
modified field must exist
"""
assert 'modified' in self.api_data
def test_api_field_type_modified(self):
""" Test for type for API Field
modified field must be str
"""
assert type(self.api_data['modified']) is str
# def test_api_field_exists_permissions(self):
# """ Test for existance of API Field
# permissions field must exist
# """
# assert 'permissions' in self.api_data
# def test_api_field_type_permissions(self):
# """ Test for type for API Field
# url field must be list
# """
# assert type(self.api_data['permissions']) is list
# def test_api_field_exists_permissions_id(self):
# """ Test for existance of API Field
# permissions.id field must exist
# """
# assert 'id' in self.api_data['permissions'][0]
# def test_api_field_type_permissions_id(self):
# """ Test for type for API Field
# permissions.id field must be int
# """
# assert type(self.api_data['permissions'][0]['id']) is int
# def test_api_field_exists_permissions_display_name(self):
# """ Test for existance of API Field
# permissions.display_name field must exist
# """
# assert 'display_name' in self.api_data['permissions'][0]
# def test_api_field_type_permissions_display_name(self):
# """ Test for type for API Field
# permissions.display_name field must be str
# """
# assert type(self.api_data['permissions'][0]['display_name']) is str
# def test_api_field_exists_permissions_url(self):
# """ Test for existance of API Field
# permissions.url field must exist
# """
# assert 'url' in self.api_data['permissions'][0]
# def test_api_field_type_permissions_url(self):
# """ Test for type for API Field
# permissions.url field must be str
# """
# assert type(self.api_data['permissions'][0]['url']) is Hyperlink

View File

@ -48,7 +48,7 @@ class TeamUsersHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
self.history_create = History.objects.get(
action = History.Actions.ADD[0],
action = int(History.Actions.ADD),
item_pk = self.item_create.pk,
item_class = self.model._meta.model_name,
)
@ -60,7 +60,7 @@ class TeamUsersHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
self.field_after_expected_value = '{"manager": true}'
self.history_change = History.objects.get(
action = History.Actions.UPDATE[0],
action = int(History.Actions.UPDATE),
item_pk = self.item_change.pk,
item_class = self.model._meta.model_name,
)
@ -81,7 +81,7 @@ class TeamUsersHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
self.item_delete.delete()
self.history_delete = History.objects.get(
action = History.Actions.DELETE[0],
action = int(History.Actions.DELETE),
item_pk = self.deleted_pk,
item_class = self.model._meta.model_name,
)

View File

@ -91,3 +91,13 @@ class TenancyObjectTests(TestCase):
"""
assert self.item.objects is not None
@pytest.mark.skip(reason="write test")
def test_field_not_none_organzation(self):
""" Ensure field is set
Field organization must be defined for all tenancy objects
"""
assert self.item.objects is not None

View File

@ -0,0 +1,42 @@
from django.contrib.auth.models import User
from django.shortcuts import reverse
from django.test import Client, TestCase
from access.models import Organization
from api.tests.abstract.viewsets import ViewSetCommon
from access.viewsets.index import Index
class AccessViewset(
TestCase,
ViewSetCommon
):
viewset = Index
route_name = 'API:_api_v2_access_home'
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create an organization for user
3. create super user
"""
organization = Organization.objects.create(name='test_org')
self.organization = organization
self.view_user = User.objects.create_user(username="test_user_add", password="password", is_superuser=True)
client = Client()
url = reverse(self.route_name + '-list')
client.force_login(self.view_user)
self.http_options_response_list = client.options(url)

View File

@ -0,0 +1,30 @@
from drf_spectacular.utils import extend_schema
from rest_framework.response import Response
from rest_framework.reverse import reverse
from api.viewsets.common import CommonViewSet
@extend_schema(exclude = True)
class Index(CommonViewSet):
allowed_methods: list = [
'GET',
'HEAD',
'OPTIONS'
]
view_description = "Access Module"
view_name = "Access"
def list(self, request, pk=None):
return Response(
{
"organization": reverse('v2:_api_v2_organization-list', request=request)
}
)

View File

@ -0,0 +1,89 @@
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse
from access.serializers.organization import (
Organization,
OrganizationModelSerializer,
OrganizationViewSerializer
)
from api.viewsets.common import ModelViewSet
# @extend_schema(tags=['access'])
@extend_schema_view(
create=extend_schema(
summary = 'Create an orgnaization',
description='',
responses = {
# 200: OpenApiResponse(description='Allready exists', response=OrganizationViewSerializer),
201: OpenApiResponse(description='Created', response=OrganizationViewSerializer),
# 400: OpenApiResponse(description='Validation failed.'),
403: OpenApiResponse(description='User is missing add permissions'),
}
),
destroy = extend_schema(
summary = 'Delete an orgnaization',
description = '',
responses = {
204: OpenApiResponse(description=''),
403: OpenApiResponse(description='User is missing delete permissions'),
}
),
list = extend_schema(
summary = 'Fetch all orgnaizations',
description='',
responses = {
200: OpenApiResponse(description='', response=OrganizationViewSerializer),
403: OpenApiResponse(description='User is missing view permissions'),
}
),
retrieve = extend_schema(
summary = 'Fetch a single orgnaization',
description='',
responses = {
200: OpenApiResponse(description='', response=OrganizationViewSerializer),
403: OpenApiResponse(description='User is missing view permissions'),
}
),
update = extend_schema(exclude = True),
partial_update = extend_schema(
summary = 'Update an orgnaization',
description = '',
responses = {
200: OpenApiResponse(description='', response=OrganizationViewSerializer),
# 201: OpenApiResponse(description='Created', response=OrganizationViewSerializer),
# # 400: OpenApiResponse(description='Validation failed.'),
403: OpenApiResponse(description='User is missing change permissions'),
}
),
)
class ViewSet( ModelViewSet ):
filterset_fields = [
'name',
'manager',
]
search_fields = [
'name',
]
model = Organization
documentation: str = ''
view_description = 'Centurion Organizations'
def get_serializer_class(self):
if (
self.action == 'list'
or self.action == 'retrieve'
):
return globals()[str( self.model._meta.verbose_name) + 'ViewSerializer']
return globals()[str( self.model._meta.verbose_name) + 'ModelSerializer']

186
app/access/viewsets/team.py Normal file
View File

@ -0,0 +1,186 @@
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiResponse
from access.models import Organization
from access.serializers.teams import (
Team,
TeamModelSerializer,
TeamViewSerializer
)
from api.viewsets.common import ModelViewSet
# @extend_schema(tags=['access'])
@extend_schema_view(
create=extend_schema(
summary = 'Create a team within this organization',
description='',
parameters = [
OpenApiParameter(
name = 'organization_id',
location = 'path',
type = int
),
],
responses = {
200: OpenApiResponse(description='Allready exists', response=TeamViewSerializer),
201: OpenApiResponse(description='Created', response=TeamViewSerializer),
# 400: OpenApiResponse(description='Validation failed.'),
403: OpenApiResponse(description='User is missing add permissions'),
}
),
destroy = extend_schema(
summary = 'Delete a team from this organization',
description = '',
parameters = [
OpenApiParameter(
name = 'id',
location = 'path',
type = int
),
OpenApiParameter(
name = 'organization_id',
location = 'path',
type = int
),
],
responses = {
204: OpenApiResponse(description=''),
403: OpenApiResponse(description='User is missing delete permissions'),
}
),
list = extend_schema(
summary = 'Fetch all teams from this organization',
description='',
parameters = [
OpenApiParameter(
name = 'organization_id',
location = 'path',
type = int
),
],
responses = {
200: OpenApiResponse(description='', response=TeamViewSerializer),
403: OpenApiResponse(description='User is missing view permissions'),
}
),
retrieve = extend_schema(
summary = 'Fetch a single team from this organization',
description='',
parameters = [
OpenApiParameter(
name = 'id',
location = 'path',
type = int
),
OpenApiParameter(
name = 'organization_id',
location = 'path',
type = int
),
],
responses = {
200: OpenApiResponse(description='', response=TeamViewSerializer),
403: OpenApiResponse(description='User is missing view permissions'),
}
),
update = extend_schema(exclude = True),
partial_update = extend_schema(
summary = 'Update a team within this organization',
description = '',
parameters = [
OpenApiParameter(
name = 'id',
location = 'path',
type = int
),
OpenApiParameter(
name = 'organization_id',
location = 'path',
type = int
),
],
responses = {
200: OpenApiResponse(description='', response=TeamViewSerializer),
# 201: OpenApiResponse(description='Created', response=OrganizationViewSerializer),
# # 400: OpenApiResponse(description='Validation failed.'),
403: OpenApiResponse(description='User is missing change permissions'),
}
),
)
class ViewSet( ModelViewSet ):
filterset_fields = [
'team_name',
]
search_fields = [
'team_name',
]
model = Team
documentation: str = ''
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()
queryset = queryset.filter(organization_id=self.kwargs['organization_id'])
self.queryset = queryset
return self.queryset
def get_serializer_class(self):
if (
self.action == 'list'
or self.action == 'retrieve'
):
return globals()[str( self.model._meta.verbose_name) + 'ViewSerializer']
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

@ -0,0 +1,212 @@
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,
TeamUserViewSerializer
)
from api.viewsets.common import ModelViewSet
@extend_schema_view(
create=extend_schema(
summary = 'Create a user within this team',
description='',
parameters = [
OpenApiParameter(
name = 'organization_id',
location = 'path',
type = int
),
OpenApiParameter(
name = 'team_id',
location = 'path',
type = int
),
],
responses = {
# 200: OpenApiResponse(description='Allready exists', response=TeamUserViewSerializer),
201: OpenApiResponse(description='Created', response=TeamUserViewSerializer),
# 400: OpenApiResponse(description='Validation failed.'),
403: OpenApiResponse(description='User is missing add permissions'),
}
),
destroy = extend_schema(
summary = 'Delete a user from this team',
description = '',
parameters = [
OpenApiParameter(
name = 'id',
location = 'path',
type = int
),
OpenApiParameter(
name = 'organization_id',
location = 'path',
type = int
),
OpenApiParameter(
name = 'team_id',
location = 'path',
type = int
),
],
responses = {
204: OpenApiResponse(description=''),
403: OpenApiResponse(description='User is missing delete permissions'),
}
),
list = extend_schema(
summary = 'Fetch all users from this team',
description='',
parameters = [
OpenApiParameter(
name = 'organization_id',
location = 'path',
type = int
),
OpenApiParameter(
name = 'team_id',
location = 'path',
type = int
),
],
responses = {
200: OpenApiResponse(description='', response=TeamUserViewSerializer),
403: OpenApiResponse(description='User is missing view permissions'),
}
),
retrieve = extend_schema(
summary = 'Fetch a single user from this team',
description='',
parameters = [
OpenApiParameter(
name = 'id',
location = 'path',
type = int
),
OpenApiParameter(
name = 'organization_id',
location = 'path',
type = int
),
OpenApiParameter(
name = 'team_id',
location = 'path',
type = int
),
],
responses = {
200: OpenApiResponse(description='', response=TeamUserViewSerializer),
403: OpenApiResponse(description='User is missing view permissions'),
}
),
update = extend_schema(exclude = True),
partial_update = extend_schema(
summary = 'Update a user within this team',
description = '',
parameters = [
OpenApiParameter(
name = 'id',
location = 'path',
type = int
),
OpenApiParameter(
name = 'organization_id',
location = 'path',
type = int
),
OpenApiParameter(
name = 'team_id',
location = 'path',
type = int
),
],
responses = {
200: OpenApiResponse(description='', response=TeamUserViewSerializer),
# 201: OpenApiResponse(description='Created', response=OrganizationViewSerializer),
# # 400: OpenApiResponse(description='Validation failed.'),
403: OpenApiResponse(description='User is missing change permissions'),
}
),
)
class ViewSet( ModelViewSet ):
filterset_fields = [
'manager',
'team__organization',
]
search_fields = []
model = TeamUsers
documentation: str = ''
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()
queryset = queryset.filter(
team_id = self.kwargs['team_id']
)
self.queryset = queryset
return self.queryset
def get_serializer_class(self):
if (
self.action == 'list'
or self.action == 'retrieve'
):
return globals()[str( self.model._meta.verbose_name).replace(' ', '') + 'ViewSerializer']
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

@ -5,6 +5,20 @@ from rest_framework.authentication import BaseAuthentication, get_authorization_
from api.models.tokens import AuthToken
# scheme.py
from drf_spectacular.extensions import OpenApiAuthenticationExtension
class TokenScheme(OpenApiAuthenticationExtension):
target_class = "api.auth.TokenAuthentication"
name = "TokenAuthentication"
def get_security_definition(self, auto_schema):
return {
"type": "apiKey",
"in": "header",
"name": "Token Authorization",
"description": "Token-based authentication with required prefix 'Token'",
}
class TokenAuthentication(BaseAuthentication):

8
app/api/exceptions.py Normal file
View File

@ -0,0 +1,8 @@
from rest_framework.exceptions import APIException
from rest_framework import status
class UnknownTicketType(APIException):
status_code = status.HTTP_400_BAD_REQUEST
default_detail = 'Unable to determin the ticket type.'
default_code = 'unknown_ticket_type'

View File

@ -0,0 +1,41 @@
# Generated by Django 5.1.2 on 2024-12-06 06:47
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('api', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterField(
model_name='authtoken',
name='expires',
field=models.DateTimeField(help_text='When this token expires', verbose_name='Expiry Date'),
),
migrations.AlterField(
model_name='authtoken',
name='id',
field=models.AutoField(help_text='ID of this token', primary_key=True, serialize=False, unique=True, verbose_name='ID'),
),
migrations.AlterField(
model_name='authtoken',
name='note',
field=models.CharField(blank=True, default=None, help_text='A note about this token', max_length=50, null=True, verbose_name='Note'),
),
migrations.AlterField(
model_name='authtoken',
name='token',
field=models.CharField(db_index=True, help_text='The authorization token', max_length=64, unique=True, verbose_name='Auth Token'),
),
migrations.AlterField(
model_name='authtoken',
name='user',
field=models.ForeignKey(help_text='User this token belongs to', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Owner'),
),
]

View File

@ -5,6 +5,8 @@ import string
from django.conf import settings
from django.contrib.auth.models import User
from django.db import models
from django.db.models import Field
from django.forms import ValidationError
from access.fields import *
from access.models import TenancyObject
@ -14,38 +16,77 @@ from access.models import TenancyObject
class AuthToken(models.Model):
def validate_note_no_token(self, note, token):
""" Ensure plaintext token cant be saved to notes field.
called from app.settings.views.user_settings.TokenAdd.form_valid()
Args:
note (Field): _Note field_
token (Field): _Token field_
Raises:
ValidationError: _Validation failed_
"""
validation: bool = True
if str(note) == str(token):
validation = False
if str(token)[:9] in str(note): # Allow user to use up to 8 chars so they can reference it.
validation = False
if not validation:
raise ValidationError('Token can not be placed in the notes field.')
id = models.AutoField(
blank=False,
help_text = 'ID of this token',
primary_key=True,
unique=True,
blank=False
verbose_name = 'ID'
)
note = models.CharField(
blank = True,
max_length = 50,
default = None,
help_text = 'A note about this token',
max_length = 50,
null= True,
verbose_name = 'Note'
)
token = models.CharField(
verbose_name = 'Auth Token',
blank = False,
db_index=True,
help_text = 'The authorization token',
max_length = 64,
null = False,
blank = False,
unique = True,
verbose_name = 'Auth Token',
)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
help_text = 'User this token belongs to',
on_delete=models.CASCADE,
verbose_name = 'Owner'
)
expires = models.DateTimeField(
verbose_name = 'Expiry Date',
blank = False,
help_text = 'When this token expires',
null = False,
blank = False
verbose_name = 'Expiry Date',
)

View File

@ -0,0 +1,505 @@
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
from rest_framework.reverse import reverse
from rest_framework.utils.field_mapping import ClassLookupDict
from rest_framework_json_api.utils import get_related_resource_type
from app.serializers.user import User, UserBaseSerializer
from core import fields as centurion_field
from core.fields.badge import BadgeField
from core.fields.icon import IconField
class OverRideJSONAPIMetadata(JSONAPIMetadata):
type_lookup = ClassLookupDict(
{
serializers.Field: "GenericField",
serializers.RelatedField: "Relationship",
serializers.BooleanField: "Boolean",
serializers.CharField: "String",
serializers.URLField: "URL",
serializers.EmailField: "Email",
serializers.RegexField: "Regex",
serializers.SlugField: "Slug",
serializers.IntegerField: "Integer",
serializers.FloatField: "Float",
serializers.DecimalField: "Decimal",
serializers.DateField: "Date",
serializers.DateTimeField: "DateTime",
serializers.TimeField: "Time",
serializers.ChoiceField: "Choice",
serializers.MultipleChoiceField: "MultipleChoice",
serializers.FileField: "File",
serializers.ImageField: "Image",
serializers.ListField: "List",
serializers.DictField: "Dict",
serializers.Serializer: "Serializer",
serializers.JSONField: "JSON", # New. Does not exist in base class
BadgeField: 'Badge',
IconField: 'Icon',
User: 'Relationship',
UserBaseSerializer: 'Relationship',
centurion_field.CharField: 'String',
centurion_field.MarkdownField: 'Markdown'
}
)
class ReactUIMetadata(OverRideJSONAPIMetadata):
def determine_metadata(self, request, view):
metadata = {}
metadata["name"] = view.get_view_name()
metadata["description"] = view.get_view_description()
metadata['urls']: dict = {}
url_self = None
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 )
elif view.kwargs:
url_self = reverse('v2:' + view.basename + '-list', request = view.request, kwargs = view.kwargs )
else:
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"] = [
renderer.media_type for renderer in view.renderer_classes
]
metadata["parses"] = [parser.media_type for parser in view.parser_classes]
metadata["allowed_methods"] = view.allowed_methods
if hasattr(view, 'get_serializer'):
serializer = view.get_serializer()
metadata['fields'] = self.get_serializer_info(serializer)
if view.suffix == 'Instance':
metadata['layout'] = view.get_page_layout()
if hasattr(view, 'get_model_documentation'):
if view.get_model_documentation():
metadata['documentation'] = view.get_model_documentation()
elif view.suffix == 'List':
if hasattr(view, 'table_fields'):
metadata['table_fields'] = view.get_table_fields()
if view.documentation:
metadata['documentation'] = view.documentation
if hasattr(view, 'page_layout'):
metadata['layout'] = view.get_page_layout()
build_repo: str = None
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
def get_field_info(self, field):
""" Custom from `rest_framewarok_json_api.metadata.py`
Require that read-only fields have their choices added to the
metadata.
Given an instance of a serializer field, return a dictionary
of metadata about it.
"""
field_info = {}
serializer = field.parent
if hasattr(field, 'textarea'):
if field.textarea:
field_info["multi_line"] = True
if isinstance(field, serializers.ManyRelatedField):
field_info["type"] = self.type_lookup[field.child_relation]
else:
field_info["type"] = self.type_lookup[field]
try:
serializer_model = serializer.Meta.model
field_info["relationship_type"] = self.relation_type_lookup[
getattr(serializer_model, field.field_name)
]
except KeyError:
pass
except AttributeError:
pass
else:
field_info["relationship_resource"] = get_related_resource_type(field)
if hasattr(field, 'autolink'):
if field.autolink:
field_info['autolink'] = field.autolink
field_info["required"] = getattr(field, "required", False)
if hasattr(field, 'style_class'):
field_info["style"]: dict = {
'class': field.style_class
}
attrs = [
"read_only",
"write_only",
"label",
"help_text",
"min_length",
"max_length",
"min_value",
"max_value",
"initial",
]
for attr in attrs:
value = getattr(field, attr, None)
if value is not None and value != "":
field_info[attr] = force_str(value, strings_only=True)
if getattr(field, "child", None):
field_info["child"] = self.get_field_info(field.child)
elif getattr(field, "fields", None):
field_info["children"] = self.get_serializer_info(field)
if (
hasattr(field, "choices")
):
field_info["choices"] = [
{
"value": choice_value,
"display_name": force_str(choice_name, strings_only=True),
}
for choice_value, choice_name in field.choices.items()
]
if (
hasattr(serializer, "included_serializers")
and "relationship_resource" in field_info
):
field_info["allows_include"] = (
field.field_name in serializer.included_serializers
)
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

@ -15,6 +15,7 @@ class TeamSerializerBase(serializers.ModelSerializer):
model = Team
fields = (
'team_name',
'model_notes',
'permissions',
'url',
)
@ -24,7 +25,7 @@ class TeamSerializerBase(serializers.ModelSerializer):
request = self.context.get('request')
return request.build_absolute_uri(reverse("API:_api_team", args=[obj.organization.id,obj.pk]))
return request.build_absolute_uri(reverse("v1:_api_team", args=[obj.organization.id,obj.pk]))
@ -47,7 +48,7 @@ class TeamSerializer(TeamSerializerBase):
team = Team.objects.get(pk=obj.id)
return request.build_absolute_uri(reverse('API:_api_team_permission', args=[team.organization_id,team.id]))
return request.build_absolute_uri(reverse('v1:_api_team_permission', args=[team.organization_id,team.id]))
def validate(self, data):
@ -66,7 +67,7 @@ class TeamSerializer(TeamSerializerBase):
request = self.context.get('request')
return request.build_absolute_uri(reverse('API:_api_team', args=[obj.organization_id,obj.id]))
return request.build_absolute_uri(reverse('v1:_api_team', args=[obj.organization_id,obj.id]))
class Meta:
@ -75,6 +76,7 @@ class TeamSerializer(TeamSerializerBase):
fields = (
"id",
"team_name",
'model_notes',
'permissions',
'permissions_url',
'url',
@ -91,7 +93,7 @@ class TeamSerializer(TeamSerializerBase):
class OrganizationListSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name="API:_api_organization", format="html"
view_name="v1:_api_organization", format="html"
)
@ -108,7 +110,7 @@ class OrganizationListSerializer(serializers.ModelSerializer):
class OrganizationSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name="API:_api_organization", format="html"
view_name="v1:_api_organization", format="html"
)
team_url = serializers.SerializerMethodField('get_url')
@ -119,11 +121,11 @@ class OrganizationSerializer(serializers.ModelSerializer):
team = Team.objects.filter(pk=obj.id)
return request.build_absolute_uri(reverse('API:_api_organization_teams', args=[obj.id]))
return request.build_absolute_uri(reverse('v1:_api_organization_teams', args=[obj.id]))
teams = TeamSerializer(source='team_set', many=True, read_only=False)
view_name="API:_api_organization"
view_name="v1:_api_organization"
class Meta:

View File

@ -0,0 +1,63 @@
from rest_framework.fields import empty
from api.serializers.core.ticket import TicketSerializer
from core.models.ticket.ticket import Ticket
class RequestTicketSerializer(
TicketSerializer,
):
class Meta:
model = Ticket
fields = [
'id',
'assigned_teams',
'assigned_users',
'category',
'created',
'modified',
'status',
'title',
'description',
'estimate',
'urgency',
'impact',
'priority',
'external_ref',
'external_system',
'ticket_type',
'is_deleted',
'date_closed',
# 'planned_start_date',
# 'planned_finish_date',
# 'real_start_date',
# 'real_finish_date',
'opened_by',
'organization',
'project',
'milestone',
'subscribed_teams',
'subscribed_users',
'ticket_comments',
'url',
]
read_only_fields = [
'id',
'ticket_type',
'url',
]
def __init__(self, instance=None, data=empty, **kwargs):
super().__init__(instance=instance, data=data, **kwargs)
self.fields.fields['category'].queryset = self.fields.fields['category'].queryset.filter(
request = True
)

View File

@ -0,0 +1,53 @@
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):
pass
class CommonModelSerializer(CommonBaseSerializer):
"""Common Model Serializer
_**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

@ -28,7 +28,7 @@ class ParentGroupSerializer(serializers.ModelSerializer):
request = self.context.get('request')
return request.build_absolute_uri(reverse("API:_api_config_group", args=[obj.pk]))
return request.build_absolute_uri(reverse("v1:_api_config_group", args=[obj.pk]))
@ -59,7 +59,7 @@ class ConfigGroupsSerializerBase(serializers.ModelSerializer):
request = self.context.get('request')
return request.build_absolute_uri(reverse("API:_api_config_group", args=[obj.pk]))
return request.build_absolute_uri(reverse("v1:_api_config_group", args=[obj.pk]))
@ -74,6 +74,7 @@ class ConfigGroupsSerializer(ConfigGroupsSerializerBase):
'parent',
'name',
'config',
'hosts',
'url',
]
read_only_fields = [

View File

@ -0,0 +1,196 @@
from django.urls import reverse
from rest_framework import serializers
from rest_framework.fields import empty
from api.serializers.core.ticket_comment import TicketCommentSerializer
from core import exceptions as centurion_exception
from core.forms.validate_ticket import TicketValidation
from core.models.ticket.ticket import Ticket
class TicketSerializer(
serializers.ModelSerializer,
TicketValidation,
):
url = serializers.SerializerMethodField('get_url_ticket')
def get_url_ticket(self, item):
request = self.context.get('request')
kwargs: dict = {
'pk': item.id
}
if item.ticket_type == self.Meta.model.TicketType.CHANGE.value:
view_name = '_api_itim_change'
elif item.ticket_type == self.Meta.model.TicketType.INCIDENT.value:
view_name = '_api_itim_incident'
elif item.ticket_type == self.Meta.model.TicketType.PROBLEM.value:
view_name = '_api_itim_problem'
elif item.ticket_type == self.Meta.model.TicketType.REQUEST.value:
view_name = '_api_assistance_request'
elif item.ticket_type == self.Meta.model.TicketType.PROJECT_TASK.value:
view_name = '_api_project_tasks'
kwargs.update({'project_id': item.project.id})
else:
raise ValueError('Serializer unable to obtain ticket type')
return request.build_absolute_uri(
reverse(
'v1:' + view_name + '-detail',
kwargs = kwargs
)
)
ticket_comments = serializers.SerializerMethodField('get_url_ticket_comments')
def get_url_ticket_comments(self, item):
request = self.context.get('request')
kwargs: dict = {
'ticket_id': item.id
}
if item.ticket_type == self.Meta.model.TicketType.CHANGE.value:
view_name = '_api_itim_change_ticket_comments'
elif item.ticket_type == self.Meta.model.TicketType.INCIDENT.value:
view_name = '_api_itim_incident_ticket_comments'
elif item.ticket_type == self.Meta.model.TicketType.PROBLEM.value:
view_name = '_api_itim_problem_ticket_comments'
elif item.ticket_type == self.Meta.model.TicketType.REQUEST.value:
view_name = '_api_assistance_request_ticket_comments'
elif item.ticket_type == self.Meta.model.TicketType.PROJECT_TASK.value:
view_name = '_api_project_tasks_comments'
kwargs.update({'project_id': item.project.id})
else:
raise ValueError('Serializer unable to obtain ticket type')
return request.build_absolute_uri(
reverse(
'v1:' + view_name + '-list',
kwargs = kwargs
)
)
class Meta:
model = Ticket
fields = [
'id',
'assigned_teams',
'assigned_users',
'category',
'created',
'modified',
'status',
'title',
'description',
'urgency',
'impact',
'priority',
'external_ref',
'external_system',
'ticket_type',
'is_deleted',
'date_closed',
'planned_start_date',
'planned_finish_date',
'real_start_date',
'real_finish_date',
'opened_by',
'organization',
'project',
'subscribed_teams',
'subscribed_users',
'ticket_comments',
'url',
]
read_only_fields = [
'id',
'url',
]
def __init__(self, instance=None, data=empty, **kwargs):
self.fields.fields['status'].initial = Ticket.TicketStatus.All.NEW
self.fields.fields['status'].default = Ticket.TicketStatus.All.NEW
self.ticket_type_fields = self.Meta.fields
super().__init__(instance=instance, data=data, **kwargs)
self.fields['organization'].required = True
def is_valid(self, *, raise_exception=True) -> bool:
is_valid = False
try:
self.request = self._context['request']
is_valid = super().is_valid(raise_exception=raise_exception)
self._ticket_type = str(self.fields['ticket_type'].choices[self._context['view']._ticket_type_value]).lower().replace(' ', '_')
self.validated_data['ticket_type'] = int(self._context['view']._ticket_type_value)
is_valid = self.validate_ticket()
if self.instance is None:
subscribed_users: list = []
if 'subscribed_users' in self.validated_data:
subscribed_users = self.validated_data['subscribed_users']
self.validated_data['subscribed_users'] = subscribed_users + [ self.validated_data['opened_by'] ]
except Exception as unhandled_exception:
centurion_exception.ParseError(
detail=f"Server encountered an error during validation, Traceback: {unhandled_exception.with_traceback}"
)
return is_valid

View File

@ -0,0 +1,44 @@
from django.urls import reverse
from rest_framework import serializers
from rest_framework.fields import empty
from api.serializers.core.ticket_comment import TicketCommentSerializer
from core.forms.validate_ticket import TicketValidation
from core.models.ticket.ticket_category import TicketCategory
class TicketCategorySerializer(
serializers.ModelSerializer,
):
url = serializers.HyperlinkedIdentityField(
view_name="v1:_api_ticket_category-detail", format="html"
)
class Meta:
model = TicketCategory
fields = '__all__'
read_only_fields = [
'id',
'url',
]
def __init__(self, instance=None, data=empty, **kwargs):
if instance is not None:
if hasattr(instance, 'id'):
self.fields.fields['parent'].queryset = self.fields.fields['parent'].queryset.exclude(
id=instance.id
)
super().__init__(instance=instance, data=data, **kwargs)

View File

@ -0,0 +1,74 @@
from django.urls import reverse
from rest_framework import serializers
from rest_framework.fields import empty
from core.models.ticket.ticket_comment import Ticket, TicketComment
class TicketCommentSerializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField('get_url_ticket_comment')
def get_url_ticket_comment(self, item):
request = self.context.get('request')
if item.ticket.ticket_type == item.ticket.__class__.TicketType.CHANGE:
view_name = '_api_itim_change_ticket_comments'
elif item.ticket.ticket_type == item.ticket.__class__.TicketType.INCIDENT:
view_name = '_api_itim_incident_ticket_comments'
elif item.ticket.ticket_type == item.ticket.__class__.TicketType.PROBLEM:
view_name = '_api_itim_problem_ticket_comments'
elif item.ticket.ticket_type == item.ticket.__class__.TicketType.REQUEST:
view_name = '_api_assistance_request_ticket_comments'
else:
raise ValueError('Serializer unable to obtain ticket type')
return request.build_absolute_uri(
reverse('v1:' + view_name + '-detail',
kwargs={
'ticket_id': item.ticket.id,
'pk': item.id
}
)
)
class Meta:
model = TicketComment
fields = '__all__'
def __init__(self, instance=None, data=empty, **kwargs):
if 'context' in self._kwargs:
if 'view' in self._kwargs['context']:
if 'ticket_id' in self._kwargs['context']['view'].kwargs:
ticket = Ticket.objects.get(pk=int(self._kwargs['context']['view'].kwargs['ticket_id']))
self.fields.fields['organization'].initial = ticket.organization.id
self.fields.fields['ticket'].initial = int(self._kwargs['context']['view'].kwargs['ticket_id'])
self.fields.fields['comment_type'].initial = TicketComment.CommentType.COMMENT
self.fields.fields['user'].initial = kwargs['context']['request']._user.id
super().__init__(instance=instance, data=data, **kwargs)

View File

@ -0,0 +1,42 @@
from django.urls import reverse
from rest_framework import serializers
from rest_framework.fields import empty
from core.models.ticket.ticket_comment_category import TicketCommentCategory
class TicketCommentCategorySerializer(
serializers.ModelSerializer,
):
url = serializers.HyperlinkedIdentityField(
view_name="v1:_api_ticket_comment_category-detail", format="html"
)
class Meta:
model = TicketCommentCategory
fields = '__all__'
read_only_fields = [
'id',
'url',
]
def __init__(self, instance=None, data=empty, **kwargs):
if instance is not None:
if hasattr(instance, 'id'):
self.fields.fields['parent'].queryset = self.fields.fields['parent'].queryset.exclude(
id=instance.id
)
super().__init__(instance=instance, data=data, **kwargs)

View File

@ -1,6 +1,9 @@
from django.core.exceptions import ValidationError
from django.utils.html import escape
from rest_framework.exceptions import ValidationError
class Inventory:
""" Inventory Object

View File

@ -4,7 +4,7 @@ from rest_framework import serializers
from api.serializers.config import ParentGroupSerializer
from config_management.models.groups import ConfigGroupHosts
from config_management.models.groups import ConfigGroups
from itam.models.device import Device
@ -12,15 +12,13 @@ from itam.models.device import Device
class DeviceConfigGroupsSerializer(serializers.ModelSerializer):
name = serializers.CharField(source='group.name', read_only=True)
url = serializers.HyperlinkedIdentityField(
view_name="API:_api_config_group", format="html"
view_name="v1:_api_config_group", format="html"
)
class Meta:
model = ConfigGroupHosts
model = ConfigGroups
fields = [
'id',
@ -38,22 +36,21 @@ class DeviceConfigGroupsSerializer(serializers.ModelSerializer):
class DeviceSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name="API:device-detail", format="html"
view_name="v1:device-detail", format="html"
)
config = serializers.SerializerMethodField('get_device_config')
groups = DeviceConfigGroupsSerializer(source='configgrouphosts_set', many=True, read_only=True)
groups = DeviceConfigGroupsSerializer(source='configgroups_set', many=True, read_only=True)
def get_device_config(self, device):
request = self.context.get('request')
return request.build_absolute_uri(reverse('API:_api_device_config', args=[device.slug]))
return request.build_absolute_uri(reverse('v1:_api_device_config', args=[device.slug]))
class Meta:
model = Device
depth = 1
fields = [
'id',
'is_global',

View File

@ -7,7 +7,7 @@ from itam.models.device import Software
class SoftwareSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name="API:software-detail", format="html"
view_name="v1:software-detail", format="html"
)
class Meta:

View File

@ -0,0 +1,63 @@
from rest_framework.fields import empty
from api.serializers.core.ticket import TicketSerializer
from core.models.ticket.ticket import Ticket
class ChangeTicketSerializer(
TicketSerializer,
):
class Meta:
model = Ticket
fields = [
'id',
'assigned_teams',
'assigned_users',
'category',
'created',
'modified',
'status',
'title',
'description',
'estimate',
'urgency',
'impact',
'priority',
'external_ref',
'external_system',
'ticket_type',
'is_deleted',
'date_closed',
# 'planned_start_date',
# 'planned_finish_date',
# 'real_start_date',
# 'real_finish_date',
'opened_by',
'organization',
'project',
'milestone',
'subscribed_teams',
'subscribed_users',
'ticket_comments',
'url',
]
read_only_fields = [
'id',
'ticket_type',
'url',
]
def __init__(self, instance=None, data=empty, **kwargs):
super().__init__(instance=instance, data=data, **kwargs)
self.fields.fields['category'].queryset = self.fields.fields['category'].queryset.filter(
project_task = True
)

View File

@ -0,0 +1,63 @@
from rest_framework.fields import empty
from api.serializers.core.ticket import TicketSerializer
from core.models.ticket.ticket import Ticket
class IncidentTicketSerializer(
TicketSerializer,
):
class Meta:
model = Ticket
fields = [
'id',
'assigned_teams',
'assigned_users',
'category',
'created',
'modified',
'status',
'title',
'description',
'estimate',
'urgency',
'impact',
'priority',
'external_ref',
'external_system',
'ticket_type',
'is_deleted',
'date_closed',
# 'planned_start_date',
# 'planned_finish_date',
# 'real_start_date',
# 'real_finish_date',
'opened_by',
'organization',
'project',
'milestone',
'subscribed_teams',
'subscribed_users',
'ticket_comments',
'url',
]
read_only_fields = [
'id',
'ticket_type',
'url',
]
def __init__(self, instance=None, data=empty, **kwargs):
super().__init__(instance=instance, data=data, **kwargs)
self.fields.fields['category'].queryset = self.fields.fields['category'].queryset.filter(
incident = True
)

View File

@ -0,0 +1,63 @@
from rest_framework.fields import empty
from api.serializers.core.ticket import TicketSerializer
from core.models.ticket.ticket import Ticket
class ProblemTicketSerializer(
TicketSerializer,
):
class Meta:
model = Ticket
fields = [
'id',
'assigned_teams',
'assigned_users',
'category',
'created',
'modified',
'status',
'title',
'description',
'estimate',
'urgency',
'impact',
'priority',
'external_ref',
'external_system',
'ticket_type',
'is_deleted',
'date_closed',
# 'planned_start_date',
# 'planned_finish_date',
# 'real_start_date',
# 'real_finish_date',
'opened_by',
'organization',
'project',
'milestone',
'subscribed_teams',
'subscribed_users',
'ticket_comments',
'url',
]
read_only_fields = [
'id',
'ticket_type',
'url',
]
def __init__(self, instance=None, data=empty, **kwargs):
super().__init__(instance=instance, data=data, **kwargs)
self.fields.fields['category'].queryset = self.fields.fields['category'].queryset.filter(
problem = True
)

View File

@ -0,0 +1,74 @@
from django.urls import reverse
from rest_framework import serializers
from rest_framework.fields import empty
from project_management.models.projects import Project
from project_management.models.project_milestone import ProjectMilestone
class ProjectMilestoneSerializer(
serializers.ModelSerializer,
):
url = serializers.SerializerMethodField('get_url_project_milestone')
def get_url_project_milestone(self, item):
request = self.context.get('request')
return request.build_absolute_uri(
reverse('v1:_api_project_milestone-detail',
kwargs={
'project_id': item.project.id,
'pk': item.id
}
)
)
class Meta:
model = ProjectMilestone
fields = [
'name',
'description',
'organization',
'project',
'start_date',
'finish_date',
'created',
'modified',
'url',
]
read_only_fields = [
'id',
'url',
]
def __init__(self, instance=None, data=empty, **kwargs):
self.fields.fields['organization'].read_only = True
self.fields.fields['project'].read_only = True
super().__init__(instance=instance, data=data, **kwargs)
def is_valid(self, *, raise_exception=False):
is_valid = super().is_valid(raise_exception=raise_exception)
project = Project.objects.get(
pk = int(self._kwargs['context']['view'].kwargs['project_id'])
)
self._validated_data.update({
'organization': project.organization,
'project': project
})
return is_valid

View File

@ -0,0 +1,33 @@
from django.urls import reverse
from rest_framework import serializers
from rest_framework.fields import empty
from project_management.models.project_states import ProjectState
class ProjectStateSerializer(
serializers.ModelSerializer,
):
url = serializers.HyperlinkedIdentityField(
view_name="v1:_api_project_state-detail", format="html"
)
class Meta:
model = ProjectState
fields = '__all__'
read_only_fields = [
'id',
'url',
]
def __init__(self, instance=None, data=empty, **kwargs):
super().__init__(instance=instance, data=data, **kwargs)

View File

@ -0,0 +1,63 @@
from rest_framework.fields import empty
from api.serializers.core.ticket import TicketSerializer
from core.models.ticket.ticket import Ticket
class ProjectTaskSerializer(
TicketSerializer,
):
class Meta:
model = Ticket
fields = [
'id',
'assigned_teams',
'assigned_users',
'category',
'created',
'modified',
'status',
'title',
'description',
'estimate',
'urgency',
'impact',
'priority',
'external_ref',
'external_system',
'ticket_type',
'is_deleted',
'date_closed',
'planned_start_date',
'planned_finish_date',
'real_start_date',
'real_finish_date',
'opened_by',
'organization',
'project',
'milestone',
'subscribed_teams',
'subscribed_users',
'ticket_comments',
'url',
]
read_only_fields = [
'id',
'ticket_type',
'url',
]
def __init__(self, instance=None, data=empty, **kwargs):
super().__init__(instance=instance, data=data, **kwargs)
self.fields.fields['category'].queryset = self.fields.fields['category'].queryset.filter(
project_task = True
)

View File

@ -0,0 +1,33 @@
from django.urls import reverse
from rest_framework import serializers
from rest_framework.fields import empty
from project_management.models.project_types import ProjectType
class ProjectTypeSerializer(
serializers.ModelSerializer,
):
url = serializers.HyperlinkedIdentityField(
view_name="v1:_api_project_state-detail", format="html"
)
class Meta:
model = ProjectType
fields = '__all__'
read_only_fields = [
'id',
'url',
]
def __init__(self, instance=None, data=empty, **kwargs):
super().__init__(instance=instance, data=data, **kwargs)

View File

@ -0,0 +1,134 @@
from django.urls import reverse
from rest_framework import serializers
from rest_framework.fields import empty
from project_management.models.projects import Project
class ProjectSerializer(
serializers.ModelSerializer,
):
percent_completed = serializers.CharField(
read_only = True,
)
url = serializers.SerializerMethodField('get_url')
def get_url(self, item):
request = self.context.get('request')
return request.build_absolute_uri(reverse("v1:_api_projects-detail", args=[item.pk]))
project_tasks_url = serializers.SerializerMethodField('get_url_project_tasks')
def get_url_project_tasks(self, item):
request = self.context.get('request')
return request.build_absolute_uri(
reverse(
'v1:_api_project_tasks-list',
kwargs={
'project_id': item.id
}
)
)
project_milestone_url = serializers.SerializerMethodField('get_url_project_milestone')
def get_url_project_milestone(self, item):
request = self.context.get('request')
return request.build_absolute_uri(
reverse(
'v1:_api_project_milestone-list',
kwargs={
'project_id': item.id
}
)
)
class Meta:
model = Project
fields = [
'id',
'organization',
'state',
'project_type',
'priority',
'name',
'description',
'code',
'planned_start_date',
'planned_finish_date',
'real_start_date',
'real_finish_date',
'manager_user',
'manager_team',
'team_members',
'project_tasks_url',
'project_milestone_url',
'percent_completed',
'created',
'modified',
'url',
]
read_only_fields = [
'id',
'url',
'created',
'modified',
]
class ProjectImportSerializer(ProjectSerializer):
class Meta:
model = Project
fields = [
'id',
'organization',
'state',
'project_type',
'priority',
'name',
'description',
'code',
'planned_start_date',
'planned_finish_date',
'real_start_date',
'real_finish_date',
'manager_user',
'manager_team',
'team_members',
'project_tasks_url',
'project_milestone_url',
'percent_completed',
'created',
'modified',
'external_ref',
'external_system',
'is_deleted',
'url',
]
read_only_fields = [
'id',
'url',
]

View File

@ -9,7 +9,7 @@ from celery import states
from access.models import Organization
from api.serializers.inventory import Inventory
from itam.serializers.inventory import InventorySerializer
from itam.models.device import Device, DeviceType, DeviceOperatingSystem, DeviceSoftware
from itam.models.operating_system import OperatingSystem, OperatingSystemVersion
@ -32,8 +32,15 @@ def process_inventory(self, data, organization: int):
logger.info('Begin Processing Inventory')
data = json.loads(data)
data = Inventory(data)
if type(data) is str:
data = json.loads(data)
data = InventorySerializer(
data = data
)
data.is_valid()
organization = Organization.objects.get(id=organization)
@ -42,13 +49,13 @@ def process_inventory(self, data, organization: int):
device_serial_number = None
device_uuid = None
if data.details.serial_number and str(data.details.serial_number).lower() != 'na':
if data.validated_data['details']['serial_number'] and str(data.validated_data['details']['serial_number']).lower() != 'na':
device_serial_number = str(data.details.serial_number)
device_serial_number = str(data.validated_data['details']['serial_number'])
if data.details.uuid and str(data.details.uuid).lower() != 'na':
if data.validated_data['details']['uuid'] and str(data.validated_data['details']['uuid']).lower() != 'na':
device_uuid = str(data.details.uuid)
device_uuid = str(data.validated_data['details']['uuid'])
if device_serial_number: # Search for device by serial number.
@ -88,13 +95,13 @@ def process_inventory(self, data, organization: int):
if not device: # Search for device by Name.
device = Device.objects.filter(
name__iexact=str(data.details.name).lower()
name__iexact=str(data.validated_data['details']['name']).lower()
)
if device.exists():
device = Device.objects.get(
name__iexact=str(data.details.name).lower()
name__iexact=str(data.validated_data['details']['name']).lower()
)
else:
@ -107,7 +114,7 @@ def process_inventory(self, data, organization: int):
if not device: # Create the device
device = Device.objects.create(
name = data.details.name,
name = data.validated_data['details']['name'],
device_type = None,
serial_number = device_serial_number,
uuid = device_uuid,
@ -131,14 +138,14 @@ def process_inventory(self, data, organization: int):
if not device.serial_number and device_serial_number:
device.serial_number = data.details.serial_number
device.serial_number = data.validated_data['details']['serial_number']
device_edited = True
if str(device.name).lower() != str(data.details.name).lower(): # Update device Name
if str(device.name).lower() != str(data.validated_data['details']['name']).lower(): # Update device Name
device.name = data.details.name
device.name = data.validated_data['details']['name']
device_edited = True
@ -149,14 +156,14 @@ def process_inventory(self, data, organization: int):
operating_system = OperatingSystem.objects.filter(
name=data.operating_system.name,
name = data.validated_data['os']['name'],
is_global = True
)
if operating_system.exists():
operating_system = OperatingSystem.objects.get(
name=data.operating_system.name,
name = data.validated_data['os']['name'],
is_global = True
)
@ -170,7 +177,7 @@ def process_inventory(self, data, organization: int):
if not operating_system:
operating_system = OperatingSystem.objects.filter(
name=data.operating_system.name,
name = data.validated_data['os']['name'],
organization = organization
)
@ -178,7 +185,7 @@ def process_inventory(self, data, organization: int):
if operating_system.exists():
operating_system = OperatingSystem.objects.get(
name=data.operating_system.name,
name = data.validated_data['os']['name'],
organization = organization
)
@ -190,22 +197,22 @@ def process_inventory(self, data, organization: int):
if not operating_system:
operating_system = OperatingSystem.objects.create(
name = data.operating_system.name,
name = data.validated_data['os']['name'],
organization = organization,
is_global = True
)
operating_system_version = OperatingSystemVersion.objects.filter(
name=data.operating_system.version_major,
operating_system=operating_system
name = data.validated_data['os']['version_major'],
operating_system = operating_system
)
if operating_system_version.exists():
operating_system_version = OperatingSystemVersion.objects.get(
name=data.operating_system.version_major,
operating_system=operating_system
name = data.validated_data['os']['version_major'],
operating_system = operating_system
)
else:
@ -218,7 +225,7 @@ def process_inventory(self, data, organization: int):
operating_system_version = OperatingSystemVersion.objects.create(
organization = organization,
is_global = True,
name = data.operating_system.version_major,
name = data.validated_data['os']['version_major'],
operating_system = operating_system,
)
@ -241,8 +248,8 @@ def process_inventory(self, data, organization: int):
device_operating_system = DeviceOperatingSystem.objects.create(
organization = organization,
device=device,
version = data.operating_system.version,
device = device,
version = data.validated_data['os']['version'],
operating_system_version = operating_system_version,
installdate = timezone.now()
)
@ -261,9 +268,9 @@ def process_inventory(self, data, organization: int):
device_operating_system.save()
if device_operating_system.version != data.operating_system.version:
if device_operating_system.version != data.validated_data['os']['version']:
device_operating_system.version = data.operating_system.version
device_operating_system.version = data.validated_data['os']['version']
device_operating_system.save()
@ -287,7 +294,7 @@ def process_inventory(self, data, organization: int):
inventoried_software: list = []
for inventory in list(data.software):
for inventory in list(data.validated_data['software']):
software = None
software_category = None
@ -295,13 +302,13 @@ def process_inventory(self, data, organization: int):
device_software = None
software_category = SoftwareCategory.objects.filter( name = inventory.category )
software_category = SoftwareCategory.objects.filter( name = inventory['category'] )
if software_category.exists():
software_category = SoftwareCategory.objects.get(
name = inventory.category
name = inventory['category']
)
else: # Create Software Category
@ -309,16 +316,16 @@ def process_inventory(self, data, organization: int):
software_category = SoftwareCategory.objects.create(
organization = software_category_organization,
is_global = True,
name = inventory.category,
name = inventory['category'],
)
if software_category.name == inventory.category:
if software_category.name == inventory['category']:
if Software.objects.filter( name = inventory.name ).exists():
if Software.objects.filter( name = inventory['name'] ).exists():
software = Software.objects.get(
name = inventory.name
name = inventory['name']
)
if not software.category:
@ -331,16 +338,16 @@ def process_inventory(self, data, organization: int):
software = Software.objects.create(
organization = software_organization,
is_global = True,
name = inventory.name,
name = inventory['name'],
category = software_category,
)
if software.name == inventory.name:
if software.name == inventory['name']:
pattern = r"^(\d+:)?(?P<semver>\d+\.\d+(\.\d+)?)"
semver = re.search(pattern, str(inventory.version), re.DOTALL)
semver = re.search(pattern, str(inventory['version']), re.DOTALL)
if semver:
@ -348,7 +355,7 @@ def process_inventory(self, data, organization: int):
semver = semver['semver']
else:
semver = inventory.version
semver = inventory['version']
if SoftwareVersion.objects.filter( name = semver, software = software ).exists():

View File

@ -0,0 +1,249 @@
from rest_framework.relations import Hyperlink
class APICommonFields:
"""Test Cases for fields common to All API responses
Must contain:
- id
- display_name
- _urls
- _urls._self
"""
api_data: object
""" API Response data """
def test_api_field_exists_id(self):
""" Test for existance of API Field
id field must exist
"""
assert 'id' in self.api_data
def test_api_field_type_id(self):
""" Test for type for API Field
id field must be int
"""
assert type(self.api_data['id']) is int
def test_api_field_exists_display_name(self):
""" Test for existance of API Field
display_name field must exist
"""
assert 'display_name' in self.api_data
def test_api_field_type_display_name(self):
""" Test for type for API Field
display_name field must be str
"""
assert type(self.api_data['display_name']) is str
def test_api_field_exists_urls(self):
""" Test for existance of API Field
_urls field must exist
"""
assert '_urls' in self.api_data
def test_api_field_type_urls(self):
""" Test for type for API Field
_urls field must be str
"""
assert type(self.api_data['_urls']) is dict
def test_api_field_exists_urls_self(self):
""" Test for existance of API Field
_urls._self field must exist
"""
assert '_self' in self.api_data['_urls']
def test_api_field_type_urls(self):
""" Test for type for API Field
_urls._self field must be str
"""
assert type(self.api_data['_urls']['_self']) is str
class APIModelFields(
APICommonFields
):
"""Test Cases for fields common to All API Model responses
Must contain:
- id
- display_name
- _urls
- _urls._self
"""
api_data: object
""" API Response data """
def test_api_field_exists_model_notes(self):
""" Test for existance of API Field
model_notes field must exist
"""
assert 'model_notes' in self.api_data
def test_api_field_type_model_notes(self):
""" Test for type for API Field
model_notes field must be str
"""
assert type(self.api_data['model_notes']) is str
def test_api_field_exists_created(self):
""" Test for existance of API Field
created field must exist
"""
assert 'created' in self.api_data
def test_api_field_type_created(self):
""" Test for type for API Field
created field must be str
"""
assert type(self.api_data['created']) is str
def test_api_field_exists_modified(self):
""" Test for existance of API Field
modified field must exist
"""
assert 'modified' in self.api_data
def test_api_field_type_modified(self):
""" Test for type for API Field
modified field must be str
"""
assert type(self.api_data['modified']) is str
class APITenancyObject(
APIModelFields
):
api_data: object
""" API Response data """
def test_api_field_exists_organization(self):
""" Test for existance of API Field
organization field must exist
"""
assert 'organization' in self.api_data
def test_api_field_type_organization(self):
""" Test for type for API Field
organization field must be dict
"""
assert type(self.api_data['organization']) is dict
def test_api_field_exists_organization_id(self):
""" Test for existance of API Field
organization.id field must exist
"""
assert 'id' in self.api_data['organization']
def test_api_field_type_organization_id(self):
""" Test for type for API Field
organization.id field must be dict
"""
assert type(self.api_data['organization']['id']) is int
def test_api_field_exists_organization_display_name(self):
""" Test for existance of API Field
organization.display_name field must exist
"""
assert 'display_name' in self.api_data['organization']
def test_api_field_type_organization_display_name(self):
""" Test for type for API Field
organization.display_name field must be str
"""
assert type(self.api_data['organization']['display_name']) is str
def test_api_field_exists_organization_url(self):
""" Test for existance of API Field
organization.url field must exist
"""
assert 'url' in self.api_data['organization']
def test_api_field_type_organization_url(self):
""" Test for type for API Field
organization.url field must be str
"""
assert type(self.api_data['organization']['url']) is Hyperlink

View File

@ -194,7 +194,7 @@ class APIPermissionAdd:
def test_add_has_permission(self):
""" Check correct permission for add
Attempt to add as user with no permission
Attempt to add as user with permission
"""
client = Client()

View File

@ -0,0 +1,513 @@
import pytest
import unittest
from django.shortcuts import reverse
from django.test import TestCase, Client
class APIPermissionView:
model: object
""" Item Model to test """
app_namespace: str = None
""" URL namespace """
url_name: str
""" URL name of the view to test """
url_view_kwargs: dict = None
""" URL kwargs of the item page """
def test_view_user_anon_denied(self):
""" Check correct permission for view
Attempt to view as anon user
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
response = client.get(url)
assert response.status_code == 401
def test_view_no_permission_denied(self):
""" Check correct permission for view
Attempt to view with user missing permission
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.no_permissions_user)
response = client.get(url)
assert response.status_code == 403
def test_view_different_organizaiton_denied(self):
""" Check correct permission for view
Attempt to view with user from different organization
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.different_organization_user)
response = client.get(url)
assert response.status_code == 403
def test_view_has_permission(self):
""" Check correct permission for view
Attempt to view as user with view permission
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.view_user)
response = client.get(url)
assert response.status_code == 200
def test_returned_results_only_user_orgs(self):
"""Returned results check
Ensure that a query to the viewset endpoint does not return
items that are not part of the users organizations.
"""
# Ensure the other org item exists, without test not able to function
print('Check that the different organization item has been defined')
assert hasattr(self, 'other_org_item')
# ensure that the variables for the two orgs are different orgs
print('checking that the different and user oganizations are different')
assert self.different_organization.id != self.organization.id
client = Client()
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')
client.force_login(self.view_user)
response = client.get(url)
contains_different_org: bool = False
for item in response.data['results']:
if int(item['organization']['id']) != self.organization.id:
contains_different_org = True
assert not contains_different_org
class APIPermissionAdd:
model: object
""" Item Model to test """
app_namespace: str = None
""" URL namespace """
url_list: str
""" URL view name of the item list page """
url_kwargs: dict = None
""" URL view kwargs for the item list page """
add_data: dict = None
def test_add_user_anon_denied(self):
""" Check correct permission for add
Attempt to add as anon user
"""
client = Client()
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.put(url, data=self.add_data)
assert response.status_code == 401
# @pytest.mark.skip(reason="ToDO: figure out why fails")
def test_add_no_permission_denied(self):
""" Check correct permission for add
Attempt to add as user with no permissions
"""
client = Client()
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')
client.force_login(self.no_permissions_user)
response = client.post(url, data=self.add_data)
assert response.status_code == 403
# @pytest.mark.skip(reason="ToDO: figure out why fails")
def test_add_different_organization_denied(self):
""" Check correct permission for add
attempt to add as user from different organization
"""
client = Client()
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')
client.force_login(self.different_organization_user)
response = client.post(url, data=self.add_data)
assert response.status_code == 403
def test_add_permission_view_denied(self):
""" Check correct permission for add
Attempt to add a user with view permission
"""
client = Client()
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')
client.force_login(self.view_user)
response = client.post(url, data=self.add_data)
assert response.status_code == 403
def test_add_has_permission(self):
""" Check correct permission for add
Attempt to add as user with permission
"""
client = Client()
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')
client.force_login(self.add_user)
response = client.post(url, data=self.add_data)
assert response.status_code == 201
class APIPermissionChange:
model: object
""" Item Model to test """
app_namespace: str = None
""" URL namespace """
url_name: str
""" URL name of the view to test """
url_view_kwargs: dict = None
""" URL kwargs of the item page """
change_data: dict = None
def test_change_user_anon_denied(self):
""" Check correct permission for change
Attempt to change as anon
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
response = client.patch(url, data=self.change_data, content_type='application/json')
assert response.status_code == 401
def test_change_no_permission_denied(self):
""" Ensure permission view cant make change
Attempt to make change as user without permissions
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.no_permissions_user)
response = client.patch(url, data=self.change_data, content_type='application/json')
assert response.status_code == 403
def test_change_different_organization_denied(self):
""" Ensure permission view cant make change
Attempt to make change as user from different organization
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.different_organization_user)
response = client.patch(url, data=self.change_data, content_type='application/json')
assert response.status_code == 403
def test_change_permission_view_denied(self):
""" Ensure permission view cant make change
Attempt to make change as user with view permission
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.view_user)
response = client.patch(url, data=self.change_data, content_type='application/json')
assert response.status_code == 403
def test_change_permission_add_denied(self):
""" Ensure permission view cant make change
Attempt to make change as user with add permission
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.add_user)
response = client.patch(url, data=self.change_data, content_type='application/json')
assert response.status_code == 403
def test_change_has_permission(self):
""" Check correct permission for change
Make change with user who has change permission
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.change_user)
response = client.patch(url, data=self.change_data, content_type='application/json')
assert response.status_code == 200
class APIPermissionDelete:
model: object
""" Item Model to test """
app_namespace: str = None
""" URL namespace """
url_name: str
""" URL name of the view to test """
url_view_kwargs: dict = None
""" URL kwargs of the item page """
delete_data: dict = None
def test_delete_user_anon_denied(self):
""" Check correct permission for delete
Attempt to delete item as anon user
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 401
def test_delete_no_permission_denied(self):
""" Check correct permission for delete
Attempt to delete as user with no permissons
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.no_permissions_user)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 403
def test_delete_different_organization_denied(self):
""" Check correct permission for delete
Attempt to delete as user from different organization
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.different_organization_user)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 403
def test_delete_permission_view_denied(self):
""" Check correct permission for delete
Attempt to delete as user with veiw permission only
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.view_user)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 403
def test_delete_permission_add_denied(self):
""" Check correct permission for delete
Attempt to delete as user with add permission only
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.add_user)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 403
def test_delete_permission_change_denied(self):
""" Check correct permission for delete
Attempt to delete as user with change permission only
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.change_user)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 403
def test_delete_has_permission(self):
""" Check correct permission for delete
Delete item as user with delete permission
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.delete_user)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 204
class APIPermissions(
APIPermissionAdd,
APIPermissionChange,
APIPermissionDelete,
APIPermissionView
):
""" Abstract class containing all API Permission test cases """
model: object
""" Item Model to test """

View File

@ -0,0 +1,163 @@
import pytest
import unittest
from django.shortcuts import reverse
from django.test import TestCase, Client
class SerializerView:
model: object
""" Item Model to test """
app_namespace: str = None
""" URL namespace """
url_name: str
""" URL name of the view to test """
url_view_kwargs: dict = None
""" URL kwargs of the item page """
def test_returned_serializer_user_view(self):
""" Check correct Serializer is returned
View action for view user must return `ViewSerializer`
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.view_user)
response = client.get(url)
assert str(response.renderer_context['view'].get_serializer().__class__.__name__).endswith('ViewSerializer')
class SerializerAdd:
model: object
""" Item Model to test """
app_namespace: str = None
""" URL namespace """
url_list: str
""" URL view name of the item list page """
url_kwargs: dict = None
""" URL view kwargs for the item list page """
add_data: dict = None
def test_returned_serializer_user_add(self):
""" Check correct Serializer is returned
Add action for add user must return `ModelSerializer`
"""
client = Client()
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')
client.force_login(self.add_user)
response = client.post(url, data=self.add_data)
assert str(response.renderer_context['view'].get_serializer().__class__.__name__).endswith('ModelSerializer')
class SerializerChange:
model: object
""" Item Model to test """
app_namespace: str = None
""" URL namespace """
url_name: str
""" URL name of the view to test """
url_view_kwargs: dict = None
""" URL kwargs of the item page """
change_data: dict = None
def test_returned_serializer_user_change(self):
""" Check correct Serializer is returned
Change action for change user must return `ModelSerializer`
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.change_user)
response = client.patch(url, data=self.change_data, content_type='application/json')
assert str(response.renderer_context['view'].get_serializer().__class__.__name__).endswith('ModelSerializer')
class SerializerDelete:
model: object
""" Item Model to test """
app_namespace: str = None
""" URL namespace """
url_name: str
""" URL name of the view to test """
url_view_kwargs: dict = None
""" URL kwargs of the item page """
delete_data: dict = None
def test_returned_serializer_user_delete(self):
""" Check correct Serializer is returned
Delete action for delete user must return `ModelSerializer`
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.delete_user)
response = client.delete(url)
assert str(response.renderer_context['view'].get_serializer().__class__.__name__).endswith('ModelSerializer')
class SerializersTestCases(
SerializerAdd,
SerializerChange,
SerializerDelete,
SerializerView
):
""" Abstract class containing all ViewSet test cases """
model: object
""" Item Model to test """

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

View File

@ -0,0 +1,586 @@
from api.react_ui_metadata import ReactUIMetadata
from api.views.mixin import OrganizationPermissionAPI
class AllViewSet:
"""Tests specific to the Viewset
**Dont include these tests directly, see below for correct class**
Tests are for ALL viewsets.
"""
viewset = None
"""ViewSet to Test"""
def test_view_attr_allowed_methods_exists(self):
"""Attribute Test
Attribute `allowed_methods` must exist
"""
assert hasattr(self.viewset, 'allowed_methods')
def test_view_attr_allowed_methods_not_empty(self):
"""Attribute Test
Attribute `allowed_methods` must return a value
"""
view_set = self.viewset()
assert view_set.allowed_methods is not None
def test_view_attr_allowed_methods_type(self):
"""Attribute Test
Attribute `allowed_methods` must be of type list
"""
view_set = self.viewset()
assert type(view_set.allowed_methods) is list
def test_view_attr_allowed_methods_values(self):
"""Attribute Test
Attribute `allowed_methods` only contains valid values
"""
# Values valid for index views
valid_values: list = [
'GET',
'HEAD',
'OPTIONS',
]
all_valid: bool = True
view_set = self.viewset()
for method in list(view_set.allowed_methods):
if method not in valid_values:
all_valid = False
assert all_valid
def test_view_attr_metadata_class_exists(self):
"""Attribute Test
Attribute `metadata_class` must exist
"""
assert hasattr(self.viewset, 'metadata_class')
def test_view_attr_metadata_class_not_empty(self):
"""Attribute Test
Attribute `metadata_class` must return a value
"""
view_set = self.viewset()
assert view_set.metadata_class is not None
def test_view_attr_metadata_class_type(self):
"""Attribute Test
Attribute `metadata_class` must be metadata class `ReactUIMetadata`
"""
view_set = self.viewset()
assert view_set.metadata_class is ReactUIMetadata
def test_view_attr_permission_classes_exists(self):
"""Attribute Test
Attribute `permission_classes` must exist
"""
assert hasattr(self.viewset, 'permission_classes')
def test_view_attr_permission_classes_not_empty(self):
"""Attribute Test
Attribute `permission_classes` must return a value
"""
view_set = self.viewset()
assert view_set.permission_classes is not None
def test_view_attr_permission_classes_type(self):
"""Attribute Test
Attribute `permission_classes` must be list
"""
view_set = self.viewset()
assert type(view_set.permission_classes) is list
def test_view_attr_permission_classes_value(self):
"""Attribute Test
Attribute `permission_classes` must be metadata class `ReactUIMetadata`
"""
view_set = self.viewset()
assert view_set.permission_classes[0] is OrganizationPermissionAPI
assert len(view_set.permission_classes) == 1
def test_view_attr_view_description_exists(self):
"""Attribute Test
Attribute `view_description` must exist
"""
assert hasattr(self.viewset, 'view_description')
def test_view_attr_view_description_not_empty(self):
"""Attribute Test
Attribute `view_description` must return a value
"""
assert self.viewset.view_description is not None
def test_view_attr_view_description_type(self):
"""Attribute Test
Attribute `view_description` must be of type str
"""
assert type(self.viewset.view_description) is str
def test_view_attr_view_name_exists(self):
"""Attribute Test
Attribute `view_name` must exist
"""
assert hasattr(self.viewset, 'view_name')
def test_view_attr_view_name_not_empty(self):
"""Attribute Test
Attribute `view_name` must return a value
"""
assert self.viewset.view_name is not None
def test_view_attr_view_name_type(self):
"""Attribute Test
Attribute `view_name` must be of type str
"""
view_set = self.viewset()
assert (
type(view_set.view_name) is str
)
class APIRenderViewSet:
"""Function ViewSet test
**Dont include these tests directly, see below for correct class**
These tests ensure that the data from the ViewSet is present for a
HTTP Request
"""
http_options_response_list: dict = None
"""The HTTP/Options Response for the ViewSet"""
def test_api_render_field_allowed_methods_exists(self):
"""Attribute Test
Attribute `allowed_methods` must exist
"""
assert 'allowed_methods' in self.http_options_response_list.data
def test_api_render_field_allowed_methods_not_empty(self):
"""Attribute Test
Attribute `allowed_methods` must return a value
"""
assert len(self.http_options_response_list.data['allowed_methods']) > 0
def test_api_render_field_allowed_methods_type(self):
"""Attribute Test
Attribute `allowed_methods` must be of type list
"""
assert type(self.http_options_response_list.data['allowed_methods']) is list
def test_api_render_field_allowed_methods_values(self):
"""Attribute Test
Attribute `allowed_methods` only contains valid values
"""
# Values valid for index views
valid_values: list = [
'GET',
'HEAD',
'OPTIONS',
]
all_valid: bool = True
for method in list(self.http_options_response_list.data['allowed_methods']):
if method not in valid_values:
all_valid = False
assert all_valid
def test_api_render_field_view_description_exists(self):
"""Attribute Test
Attribute `description` must exist
"""
assert 'description' in self.http_options_response_list.data
def test_api_render_field_view_description_not_empty(self):
"""Attribute Test
Attribute `view_description` must return a value
"""
assert self.http_options_response_list.data['description'] is not None
def test_api_render_field_view_description_type(self):
"""Attribute Test
Attribute `view_description` must be of type str
"""
assert type(self.http_options_response_list.data['description']) is str
def test_api_render_field_view_name_exists(self):
"""Attribute Test
Attribute `view_name` must exist
"""
assert 'name' in self.http_options_response_list.data
def test_api_render_field_view_name_not_empty(self):
"""Attribute Test
Attribute `view_name` must return a value
"""
assert self.http_options_response_list.data['name'] is not None
def test_api_render_field_view_name_type(self):
"""Attribute Test
Attribute `view_name` must be of type str
"""
assert type(self.http_options_response_list.data['name']) is str
class ModelViewSet(AllViewSet):
"""Tests for Model Viewsets
**Dont include these tests directly, see below for correct class**
"""
viewset = None
"""ViewSet to Test"""
def test_view_attr_documentation_exists(self):
"""Attribute Test
Attribute `documentation` must exist
"""
assert hasattr(self.viewset, 'documentation')
def test_view_attr_documentation_type(self):
"""Attribute Test
Attribute `documentation` must be of type str or None.
this attribute is optional.
"""
view_set = self.viewset()
assert (
type(view_set.documentation) is str
or type(view_set.documentation) is None
)
def test_view_attr_filterset_fields_exists(self):
"""Attribute Test
Attribute `filterset_fields` must exist
"""
assert hasattr(self.viewset, 'filterset_fields')
def test_view_attr_filterset_fields_not_empty(self):
"""Attribute Test
Attribute `filterset_fields` must return a value
"""
assert self.viewset.filterset_fields is not None
def test_view_attr_filterset_fields_type(self):
"""Attribute Test
Attribute `filterset_fields` must be of type list
"""
view_set = self.viewset()
assert (
type(view_set.filterset_fields) is list
)
def test_view_attr_allowed_methods_values(self):
"""Attribute Test
Attribute `allowed_methods` only contains valid values
"""
# Values valid for model views
valid_values: list = [
'DELETE',
'GET',
'HEAD',
'OPTIONS',
'PATCH',
'POST',
'PUT',
]
all_valid: bool = True
view_set = self.viewset()
for method in list(view_set.allowed_methods):
if method not in valid_values:
all_valid = False
assert all_valid
def test_view_attr_model_exists(self):
"""Attribute Test
Attribute `model` must exist
"""
assert hasattr(self.viewset, 'model')
def test_view_attr_model_not_empty(self):
"""Attribute Test
Attribute `model` must return a value
"""
view_set = self.viewset()
assert view_set.model is not None
def test_view_attr_search_fields_exists(self):
"""Attribute Test
Attribute `search_fields` must exist
"""
assert hasattr(self.viewset, 'search_fields')
def test_view_attr_search_fields_not_empty(self):
"""Attribute Test
Attribute `search_fields` must return a value
"""
assert self.viewset.search_fields is not None
def test_view_attr_search_fields_type(self):
"""Attribute Test
Attribute `search_fields` must be of type list
"""
view_set = self.viewset()
assert (
type(view_set.search_fields) is list
)
def test_view_attr_view_name_not_empty(self):
"""Attribute Test
Attribute `view_name` must return a value
"""
view_set = self.viewset()
assert (
view_set.view_name is not None
or view_set.get_view_name() is not None
)
def test_view_attr_view_name_type(self):
"""Attribute Test
Attribute `view_name` must be of type str
"""
view_set = self.viewset()
assert (
type(view_set.view_name) is str
or type(view_set.get_view_name()) is str
)
class APIRenderModelViewSet(APIRenderViewSet):
"""Tests for Model Viewsets
**Dont include these tests directly, see below for correct class**
"""
viewset = None
"""ViewSet to Test"""
def test_api_render_field_allowed_methods_values(self):
"""Attribute Test
Attribute `allowed_methods` only contains valid values
"""
# Values valid for model views
valid_values: list = [
'DELETE',
'GET',
'HEAD',
'OPTIONS',
'PATCH',
'POST',
'PUT',
]
all_valid: bool = True
for method in list(self.http_options_response_list.data['allowed_methods']):
if method not in valid_values:
all_valid = False
assert all_valid
class ViewSetCommon(
AllViewSet,
APIRenderViewSet
):
""" Tests for Non-Model Viewsets
**Include this class directly into Non-Model ViewSets**
Args:
AllViewSet (class): Tests for all Viewsets.
APIRenderViewSet (class): Tests to check API Rendering to ensure data present.
"""
pass
class ViewSetModel(
ModelViewSet,
APIRenderModelViewSet
):
"""Tests for model ViewSets
**Include this class directly into Model ViewSets**
Args:
ModelViewSet (class): Tests for Model Viewsets, includes `AllViewSet` tests.
APIRenderModelViewSet (class): Tests to check API rendering to ensure data is present, includes `APIRenderViewSet` tests.
"""
pass

View File

@ -160,7 +160,7 @@ class InventoryAPI(TestCase):
"""
client = Client()
url = reverse('API:_api_device_inventory')
url = reverse('v1:_api_device_inventory')
client.force_login(self.add_user)
response = client.post(url, data=self.inventory, content_type='application/json')
@ -182,7 +182,7 @@ class InventoryAPI(TestCase):
"""
client = Client()
url = reverse('API:_api_device_inventory')
url = reverse('v1:_api_device_inventory')
client.force_login(self.add_user)
response = client.post(url, data=self.inventory, content_type='application/json')
@ -201,7 +201,7 @@ class InventoryAPI(TestCase):
"""
client = Client()
url = reverse('API:_api_device_inventory')
url = reverse('v1:_api_device_inventory')
client.force_login(self.add_user)
response = client.post(url, data=self.inventory, content_type='application/json')
@ -220,7 +220,7 @@ class InventoryAPI(TestCase):
"""
client = Client()
url = reverse('API:_api_device_inventory')
url = reverse('v1:_api_device_inventory')
client.force_login(self.add_user)
response = client.post(url, data=self.inventory, content_type='application/json')
@ -239,7 +239,7 @@ class InventoryAPI(TestCase):
"""
client = Client()
url = reverse('API:_api_device_inventory')
url = reverse('v1:_api_device_inventory')
client.force_login(self.add_user)
response = client.post(url, data=self.inventory, content_type='application/json')
@ -395,7 +395,7 @@ class InventoryAPI(TestCase):
""" Successful inventory upload returns 200 for existing device"""
client = Client()
url = reverse('API:_api_device_inventory')
url = reverse('v1:_api_device_inventory')
client.force_login(self.add_user)
response = client.post(url, data=self.inventory, content_type='application/json')
@ -409,7 +409,7 @@ class InventoryAPI(TestCase):
""" Incorrectly formated inventory upload returns 400 """
client = Client()
url = reverse('API:_api_device_inventory')
url = reverse('v1:_api_device_inventory')
mod_inventory = self.inventory.copy()
@ -502,7 +502,8 @@ class InventoryAPIDifferentNameSerialNumberMatch(TestCase):
Device.objects.create(
name='random device name',
serial_number='serial_number_123'
serial_number='serial_number_123',
organization = organization,
)
add_permissions = Permission.objects.get(
@ -537,7 +538,7 @@ class InventoryAPIDifferentNameSerialNumberMatch(TestCase):
process_inventory(json.dumps(self.inventory), organization.id)
self.device = Device.objects.get(name=self.inventory['details']['name'])
self.device = Device.objects.get(name=self.inventory['details']['name'], organization = organization)
self.operating_system = OperatingSystem.objects.get(name=self.inventory['os']['name'])
@ -778,7 +779,8 @@ class InventoryAPIDifferentNameUUIDMatch(TestCase):
Device.objects.create(
name='random device name',
uuid='123-456-789'
uuid='123-456-789',
organization = organization,
)
add_permissions = Permission.objects.get(

View File

@ -201,7 +201,7 @@ class InventoryPermissionsAPI(TestCase):
"""
client = Client()
url = reverse('API:_api_device_inventory')
url = reverse('v1:_api_device_inventory')
response = client.put(url, data=self.inventory, content_type='application/json')
@ -218,7 +218,7 @@ class InventoryPermissionsAPI(TestCase):
"""
client = Client()
url = reverse('API:_api_device_inventory')
url = reverse('v1:_api_device_inventory')
client.force_login(self.no_permissions_user)
@ -236,7 +236,7 @@ class InventoryPermissionsAPI(TestCase):
"""
client = Client()
url = reverse('API:_api_device_inventory')
url = reverse('v1:_api_device_inventory')
client.force_login(self.different_organization_user)
@ -254,7 +254,7 @@ class InventoryPermissionsAPI(TestCase):
"""
client = Client()
url = reverse('API:_api_device_inventory')
url = reverse('v1:_api_device_inventory')
client.force_login(self.view_user)
@ -272,7 +272,7 @@ class InventoryPermissionsAPI(TestCase):
"""
client = Client()
url = reverse('API:_api_device_inventory')
url = reverse('v1:_api_device_inventory')
client.force_login(self.add_user)

View File

@ -0,0 +1,42 @@
from django.contrib.auth.models import User
from django.shortcuts import reverse
from django.test import Client, TestCase
from access.models import Organization
from api.tests.abstract.viewsets import ViewSetCommon
from api.viewsets.index import Index
class HomeViewset(
TestCase,
ViewSetCommon
):
viewset = Index
route_name = 'API:_api_v2_home'
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create an organization for user
3. create super user
"""
organization = Organization.objects.create(name='test_org')
self.organization = organization
self.view_user = User.objects.create_user(username="test_user_add", password="password", is_superuser=True)
client = Client()
url = reverse(self.route_name + '-list')
client.force_login(self.view_user)
self.http_options_response_list = client.options(url)

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,25 @@ from rest_framework.urlpatterns import format_suffix_patterns
from .views import access, config, index
from api.views.settings import permissions
from api.views.settings import index as settings
from api.views import assistance, itim, project_management
from api.views.assistance import request_ticket
from api.views.core import (
ticket_categories,
ticket_comment_categories,
ticket_comments as core_ticket_comments
)
from api.views.itim import change_ticket, incident_ticket, problem_ticket
from api.views.project_management import (
projects,
project_milestone,
project_state,
project_type,
project_task
)
from .views.itam import software, config as itam_config
from .views.itam.device import DeviceViewSet
from .views.itam import inventory
@ -13,15 +32,42 @@ from .views.itam import inventory
app_name = "API"
router = DefaultRouter()
router = DefaultRouter(trailing_slash=False)
router.register('', index.Index, basename='_api_home')
router.register('assistance/request', request_ticket.View, basename='_api_assistance_request')
router.register('assistance/request/(?P<ticket_id>[0-9]+)/comments', core_ticket_comments.View, basename='_api_assistance_request_ticket_comments')
router.register('device', DeviceViewSet, basename='device')
router.register('itim/change', change_ticket.View, basename='_api_itim_change')
router.register('itim/change/(?P<ticket_id>[0-9]+)/comments', core_ticket_comments.View, basename='_api_itim_change_ticket_comments')
router.register('itim/incident', incident_ticket.View, basename='_api_itim_incident')
router.register('itim/incident/(?P<ticket_id>[0-9]+)/comments', core_ticket_comments.View, basename='_api_itim_incident_ticket_comments')
router.register('itim/problem', problem_ticket.View, basename='_api_itim_problem')
router.register('itim/problem/(?P<ticket_id>[0-9]+)/comments', core_ticket_comments.View, basename='_api_itim_problem_ticket_comments')
router.register('project_management/projects', projects.View, basename='_api_projects')
router.register('project_management/projects/(?P<project_id>[0-9]+)/milestones', project_milestone.View, basename='_api_project_milestone')
router.register('project_management/projects/(?P<project_id>[0-9]+)/tasks', project_task.View, basename='_api_project_tasks')
router.register('project_management/projects/(?P<project_id>[0-9]+)/tasks/(?P<ticket_id>[0-9]+)/comments', core_ticket_comments.View, basename='_api_project_tasks_comments')
router.register('settings/ticket_categories', ticket_categories.View, basename='_api_ticket_category')
router.register('settings/project_state', project_state.View, basename='_api_project_state')
router.register('settings/project_type', project_type.View, basename='_api_project_type')
router.register('settings/ticket_comment_categories', ticket_comment_categories.View, basename='_api_ticket_comment_category')
router.register('software', software.SoftwareViewSet, basename='software')
urlpatterns = [
path("assistance", assistance.index.Index.as_view(), name="_api_assistance"),
path("config/<slug:slug>/", itam_config.View.as_view(), name="_api_device_config"),
path("configuration/", config.ConfigGroupsList.as_view(), name='_api_config_groups'),
@ -29,6 +75,8 @@ urlpatterns = [
path("device/inventory", inventory.Collect.as_view(), name="_api_device_inventory"),
path("itim", itim.index.Index.as_view(), name="_api_itim"),
path("organization/", access.OrganizationList.as_view(), name='_api_orgs'),
path("organization/<int:pk>/", access.OrganizationDetail.as_view(), name='_api_organization'),
path("organization/<int:organization_id>/team", access.TeamList.as_view(), name='_api_organization_teams'),
@ -36,6 +84,11 @@ urlpatterns = [
path("organization/<int:organization_id>/team/<int:group_ptr_id>/permissions", access.TeamPermissionDetail.as_view(), name='_api_team_permission'),
path("organization/team/", access.TeamList.as_view(), name='_api_teams'),
path("project_management", project_management.index.Index.as_view(), name="_api_project_management"),
path("settings", settings.View.as_view(), name='_settings'),
path("settings/permissions", permissions.View.as_view(), name='_settings_permissions'),
]
urlpatterns = format_suffix_patterns(urlpatterns)

196
app/api/urls_v2.py Normal file
View File

@ -0,0 +1,196 @@
from django.urls import path
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
from rest_framework.routers import DefaultRouter
from api.viewsets import (
index as v2
)
from app.viewsets.base import (
index as base_index_v2,
content_type as content_type_v2,
permisson as permission_v2,
user as user_v2
)
from access.viewsets import (
index as access_v2,
organization as organization_v2,
team as team_v2,
team_user as team_user_v2
)
from assistance.viewsets import (
index as assistance_index_v2,
knowledge_base as knowledge_base_v2,
knowledge_base_category as knowledge_base_category_v2,
request as request_ticket_v2,
)
from config_management.viewsets import (
index as config_management_v2,
config_group as config_group_v2,
config_group_software as config_group_software_v2
)
from core.viewsets import (
celery_log as celery_log_v2,
history as history_v2,
manufacturer as manufacturer_v2,
notes as notes_v2,
ticket_category,
ticket_comment,
ticket_comment_category,
ticket_linked_item,
related_ticket,
)
from itam.viewsets import (
index as itam_index_v2,
device as device_v2,
device_model as device_model_v2,
device_type as device_type_v2,
device_software as device_software_v2,
device_operating_system,
inventory,
operating_system as operating_system_v2,
operating_system_version as operating_system_version_v2,
software as software_v2,
software_category as software_category_v2,
software_version as software_version_v2,
)
from itim.viewsets import (
index as itim_v2,
change,
cluster as cluster_v2,
cluster_type as cluster_type_v2,
incident,
port as port_v2,
problem,
service as service_v2,
service_device as service_device_v2
)
from project_management.viewsets import (
index as project_management_v2,
project as project_v2,
project_milestone as project_milestone_v2,
project_state as project_state_v2,
project_task,
project_type as project_type_v2,
)
from settings.viewsets import (
app_settings as app_settings_v2,
external_link as external_link_v2,
index as settings_index_v2,
user_settings as user_settings_v2
)
app_name = "API"
router = DefaultRouter(trailing_slash=False)
router.register('', v2.Index, basename='_api_v2_home')
router.register('access', access_v2.Index, basename='_api_v2_access_home')
router.register('access/organization', organization_v2.ViewSet, basename='_api_v2_organization')
router.register('access/organization/(?P<organization_id>[0-9]+)/team', team_v2.ViewSet, basename='_api_v2_organization_team')
router.register('access/organization/(?P<organization_id>[0-9]+)/team/(?P<team_id>[0-9]+)/user', team_user_v2.ViewSet, basename='_api_v2_organization_team_user')
router.register('assistance', assistance_index_v2.Index, basename='_api_v2_assistance_home')
router.register('assistance/knowledge_base', knowledge_base_v2.ViewSet, basename='_api_v2_knowledge_base')
router.register('assistance/ticket/request', request_ticket_v2.ViewSet, basename='_api_v2_ticket_request')
router.register('base', base_index_v2.Index, basename='_api_v2_base_home')
router.register('base/content_type', content_type_v2.ViewSet, basename='_api_v2_content_type')
router.register('base/permission', permission_v2.ViewSet, basename='_api_v2_permission')
router.register('base/user', user_v2.ViewSet, basename='_api_v2_user')
router.register('config_management', config_management_v2.Index, basename='_api_v2_config_management_home')
router.register('config_management/group', config_group_v2.ViewSet, basename='_api_v2_config_group')
router.register('config_management/group/(?P<parent_group>[0-9]+)/child_group', config_group_v2.ViewSet, basename='_api_v2_config_group_child')
router.register('config_management/group/(?P<config_group_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_config_group_notes')
router.register('config_management/group/(?P<config_group_id>[0-9]+)/software', config_group_software_v2.ViewSet, basename='_api_v2_config_group_software')
router.register('core/(?P<model_class>.+)/(?P<model_id>[0-9]+)/history', history_v2.ViewSet, basename='_api_v2_model_history')
router.register('core/ticket/(?P<ticket_id>[0-9]+)/comments', ticket_comment.ViewSet, basename='_api_v2_ticket_comment')
router.register('core/ticket/(?P<ticket_id>[0-9]+)/comments/(?P<parent_id>[0-9]+)/threads', ticket_comment.ViewSet, basename='_api_v2_ticket_comment_threads')
router.register('core/ticket/(?P<ticket_id>[0-9]+)/linked_item', ticket_linked_item.ViewSet, basename='_api_v2_ticket_linked_item')
router.register('core/ticket/(?P<ticket_id>[0-9]+)/related_ticket', related_ticket.ViewSet, basename='_api_v2_ticket_related')
router.register('core/(?P<item_class>[a-z_]+)/(?P<item_id>[0-9]+)/item_ticket', ticket_linked_item.ViewSet, basename='_api_v2_item_tickets')
router.register('itam', itam_index_v2.Index, basename='_api_v2_itam_home')
router.register('itam/device', device_v2.ViewSet, basename='_api_v2_device')
router.register('itam/device/(?P<device_id>[0-9]+)/operating_system', device_operating_system.ViewSet, basename='_api_v2_device_operating_system')
router.register('itam/device/(?P<device_id>[0-9]+)/software', device_software_v2.ViewSet, basename='_api_v2_device_software')
router.register('itam/device/(?P<device_id>[0-9]+)/service', service_device_v2.ViewSet, basename='_api_v2_service_device')
router.register('itam/device/(?P<device_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_device_notes')
router.register('itam/inventory', inventory.ViewSet, basename='_api_v2_inventory')
router.register('itam/operating_system', operating_system_v2.ViewSet, basename='_api_v2_operating_system')
router.register('itam/operating_system/(?P<operating_system_id>[0-9]+)/installs', device_operating_system.ViewSet, basename='_api_v2_operating_system_installs')
router.register('itam/operating_system/(?P<operating_system_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_operating_system_notes')
router.register('itam/operating_system/(?P<operating_system_id>[0-9]+)/version', operating_system_version_v2.ViewSet, basename='_api_v2_operating_system_version')
router.register('itam/software', software_v2.ViewSet, basename='_api_v2_software')
router.register('itam/software/(?P<software_id>[0-9]+)/installs', device_software_v2.ViewSet, basename='_api_v2_software_installs')
router.register('itam/software/(?P<software_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_software_notes')
router.register('itam/software/(?P<software_id>[0-9]+)/version', software_version_v2.ViewSet, basename='_api_v2_software_version')
router.register('itim', itim_v2.Index, basename='_api_v2_itim_home')
router.register('itim/ticket/change', change.ViewSet, basename='_api_v2_ticket_change')
router.register('itim/cluster', cluster_v2.ViewSet, basename='_api_v2_cluster')
router.register('itim/cluster/(?P<cluster_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_cluster_notes')
router.register('itim/ticket/incident', incident.ViewSet, basename='_api_v2_ticket_incident')
router.register('itim/ticket/problem', problem.ViewSet, basename='_api_v2_ticket_problem')
router.register('itim/service', service_v2.ViewSet, basename='_api_v2_service')
router.register('itim/service/(?P<service_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_service_notes')
router.register('project_management', project_management_v2.Index, basename='_api_v2_project_management_home')
router.register('project_management/project', project_v2.ViewSet, basename='_api_v2_project')
router.register('project_management/project/(?P<project_id>[0-9]+)/milestone', project_milestone_v2.ViewSet, basename='_api_v2_project_milestone')
router.register('project_management/project/(?P<project_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_project_notes')
router.register('project_management/project/(?P<project_id>[0-9]+)/project_task', project_task.ViewSet, basename='_api_v2_ticket_project_task')
router.register('settings', settings_index_v2.Index, basename='_api_v2_settings_home')
router.register('settings/app_settings', app_settings_v2.ViewSet, basename='_api_v2_app_settings')
router.register('settings/celery_log', celery_log_v2.ViewSet, basename='_api_v2_celery_log')
router.register('settings/cluster_type', cluster_type_v2.ViewSet, basename='_api_v2_cluster_type')
router.register('settings/cluster_type/(?P<cluster_type_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_cluster_type_notes')
router.register('settings/device_model', device_model_v2.ViewSet, basename='_api_v2_device_model')
router.register('settings/device_type', device_type_v2.ViewSet, basename='_api_v2_device_type')
router.register('settings/external_link', external_link_v2.ViewSet, basename='_api_v2_external_link')
router.register('settings/knowledge_base_category', knowledge_base_category_v2.ViewSet, basename='_api_v2_knowledge_base_category')
router.register('settings/manufacturer', manufacturer_v2.ViewSet, basename='_api_v2_manufacturer')
router.register('settings/manufacturer/(?P<manufacturer_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_manufacturer_notes')
router.register('settings/port', port_v2.ViewSet, basename='_api_v2_port')
router.register('settings/port/(?P<port_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_port_notes')
router.register('settings/project_state', project_state_v2.ViewSet, basename='_api_v2_project_state')
router.register('settings/project_type', project_type_v2.ViewSet, basename='_api_v2_project_type')
router.register('settings/software_category', software_category_v2.ViewSet, basename='_api_v2_software_category')
router.register('settings/ticket_category', ticket_category.ViewSet, basename='_api_v2_ticket_category')
router.register('settings/ticket_comment_category', ticket_comment_category.ViewSet, basename='_api_v2_ticket_comment_category')
router.register('settings/user_settings', user_settings_v2.ViewSet, basename='_api_v2_user_settings')
urlpatterns = [
path('schema', SpectacularAPIView.as_view(api_version='v2'), name='schema-v2',),
path('docs', SpectacularSwaggerView.as_view(url_name='schema-v2'), name='_api_v2_docs'),
]
urlpatterns += router.urls

View File

@ -0,0 +1,47 @@
from django.test import TestCase
class ViewSetAttributesUnit:
""" Unit Tests For View Set attributes.
These tests ensure that View sets contian the required attributesthat are
used by the API .
"""
def test_attribute_exists_page_layout(self):
"""Attrribute Test, Exists
Ensure attribute `page_layout` exists
"""
pass
def test_attribute_type_page_layout(self):
"""Attrribute Test, Type
Ensure attribute `page_layout` is of type `list`
"""
pass
def test_attribute_not_callable_page_layout(self):
"""Attrribute Test, Not Callable
Attribute must be a property
Ensure attribute `page_layout` is not callable.
"""
pass
# other tests required
# - filterset_fields
# - metadata_class
# - search_fields
# - documentation
# - model_documentation or is in `model.documentation`

View File

@ -12,7 +12,7 @@ from access.models import Organization, Team
from api.serializers.access import OrganizationSerializer, OrganizationListSerializer, TeamSerializer, TeamPermissionSerializer
from api.views.mixin import OrganizationPermissionAPI
@extend_schema(deprecated=True)
@extend_schema_view(
get=extend_schema(
summary = "Fetch Organizations",
@ -34,7 +34,7 @@ class OrganizationList(generics.ListAPIView):
return "Organizations"
@extend_schema(deprecated=True)
@extend_schema_view(
get=extend_schema(
summary = "Get An Organization",
@ -61,7 +61,7 @@ class OrganizationDetail(generics.RetrieveUpdateAPIView):
return "Organization"
@extend_schema(deprecated=True)
@extend_schema_view(
post=extend_schema(
summary = "Create a Team",
@ -97,7 +97,7 @@ class TeamList(generics.ListCreateAPIView):
return "Organization Teams"
@extend_schema(deprecated=True)
@extend_schema_view(
get=extend_schema(
summary = "Fetch a Team",
@ -149,7 +149,7 @@ class TeamDetail(generics.RetrieveUpdateDestroyAPIView):
lookup_field = 'group_ptr_id'
@extend_schema(deprecated=True)
@extend_schema_view(
get=extend_schema(
summary = "Fetch a teams permissions",

View File

@ -0,0 +1 @@
from .index import *

View File

@ -0,0 +1,37 @@
from django.utils.safestring import mark_safe
from drf_spectacular.utils import extend_schema
from rest_framework import generics, permissions, routers, views
# from rest_framework.decorators import api_view
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.reverse import reverse
@extend_schema(deprecated=True)
class Index(views.APIView):
permission_classes = [
IsAuthenticated,
]
def get_view_name(self):
return "Assistance"
def get_view_description(self, html=False) -> str:
text = "Assistance Module"
if html:
return mark_safe(f"<p>{text}</p>")
else:
return text
def get(self, request, *args, **kwargs):
body: dict = {
'requests': reverse('v1:_api_assistance_request-list', request=request)
}
return Response(body)

View File

@ -0,0 +1,78 @@
from drf_spectacular.utils import extend_schema, OpenApiResponse
from api.serializers.assistance.request import RequestTicketSerializer
from api.views.core.tickets import View
@extend_schema(deprecated=True)
class View(View):
_ticket_type:str = 'request'
@extend_schema(
summary='Create a ticket',
description = """This model includes all of the ticket types.
Due to this not all fields will be available and what fields are available
depends upon the comment type. see
[administration docs](https://nofusscomputing.com/projects/centurion_erp/administration/core/ticketing/index.html) for more info.
""",
request = RequestTicketSerializer,
responses = {
201: OpenApiResponse(
response = RequestTicketSerializer,
),
}
)
def create(self, request, *args, **kwargs):
return super().create(request, *args, **kwargs)
@extend_schema(
summary='Fetch all tickets',
description = """This model includes all of the ticket comment types.
Due to this not all fields will be available and what fields are available
depends upon the comment type. see
[administration docs](https://nofusscomputing.com/projects/centurion_erp/administration/core/ticketing/index.html) for more info.
""",
methods=["GET"],
responses = {
200: OpenApiResponse(
description='Success',
response = RequestTicketSerializer
)
}
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
@extend_schema(
summary='Fetch the selected ticket',
description = """This model includes all of the ticket comment types.
Due to this not all fields will be available and what fields are available
depends upon the comment type. see
[administration docs](https://nofusscomputing.com/projects/centurion_erp/administration/core/ticketing/index.html) for more info.
""",
methods=["GET"],
responses = {
200: OpenApiResponse(
description='Success',
response = RequestTicketSerializer
)
}
)
def retrieve(self, request, *args, **kwargs):
return super().retrieve(request, *args, **kwargs)
def get_view_name(self):
if self.detail:
return "Request Ticket"
return 'Request Tickets'

View File

@ -8,7 +8,7 @@ from api.views.mixin import OrganizationPermissionAPI
from config_management.models.groups import ConfigGroups
@extend_schema( deprecated = True )
@extend_schema_view(
get=extend_schema(
summary = "Fetch Config groups",
@ -31,6 +31,7 @@ class ConfigGroupsList(generics.ListAPIView):
@extend_schema( deprecated = True )
@extend_schema_view(
get=extend_schema(
summary = "Get A Config Group",

View File

@ -0,0 +1,79 @@
from django.shortcuts import get_object_or_404
from drf_spectacular.utils import extend_schema, OpenApiResponse
from rest_framework import generics, viewsets
from access.mixin import OrganizationMixin
from api.serializers.core.ticket_category import TicketCategory, TicketCategorySerializer
from api.views.mixin import OrganizationPermissionAPI
@extend_schema(deprecated=True)
class View(OrganizationMixin, viewsets.ModelViewSet):
permission_classes = [
OrganizationPermissionAPI
]
queryset = TicketCategory.objects.all()
serializer_class = TicketCategorySerializer
@extend_schema(
summary='Create a ticket category',
request = TicketCategorySerializer,
responses = {
201: OpenApiResponse(description='Ticket category created', response=TicketCategorySerializer),
403: OpenApiResponse(description='User tried to edit field they dont have access to'),
}
)
def create(self, request, *args, **kwargs):
return super().create(request, *args, **kwargs)
@extend_schema(
summary='Fetch all of a tickets category',
methods=["GET"],
responses = {
200: OpenApiResponse(description='Success', response=TicketCategorySerializer),
}
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
@extend_schema(
summary='Fetch the selected ticket category',
methods=["GET"],
responses = {
200: OpenApiResponse(description='Success', response=TicketCategorySerializer),
}
)
def retrieve(self, request, *args, **kwargs):
return super().retrieve(request, *args, **kwargs)
@extend_schema(
summary='Update a ticket category',
methods=["PUT"],
responses = {
200: OpenApiResponse(description='Ticket comment updated', response=TicketCategorySerializer),
403: OpenApiResponse(description='User tried to edit field they dont have access to'),
}
)
def update(self, request, *args, **kwargs):
return super().update(request, *args, **kwargs)
def get_view_name(self):
if self.detail:
return "Ticket Category"
return 'Ticket Categories'

View File

@ -0,0 +1,79 @@
from django.shortcuts import get_object_or_404
from drf_spectacular.utils import extend_schema, OpenApiResponse
from rest_framework import generics, viewsets
from access.mixin import OrganizationMixin
from api.serializers.core.ticket_comment_category import TicketCommentCategory, TicketCommentCategorySerializer
from api.views.mixin import OrganizationPermissionAPI
@extend_schema(deprecated=True)
class View(OrganizationMixin, viewsets.ModelViewSet):
permission_classes = [
OrganizationPermissionAPI
]
queryset = TicketCommentCategory.objects.all()
serializer_class = TicketCommentCategorySerializer
@extend_schema(
summary='Create a ticket comment category',
request = TicketCommentCategorySerializer,
responses = {
201: OpenApiResponse(description='Ticket category created', response=TicketCommentCategorySerializer),
403: OpenApiResponse(description='User tried to edit field they dont have access to'),
}
)
def create(self, request, *args, **kwargs):
return super().create(request, *args, **kwargs)
@extend_schema(
summary='Fetch all of the ticket comment categories',
methods=["GET"],
responses = {
200: OpenApiResponse(description='Success', response=TicketCommentCategorySerializer),
}
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
@extend_schema(
summary='Fetch the selected ticket comment category',
methods=["GET"],
responses = {
200: OpenApiResponse(description='Success', response=TicketCommentCategorySerializer),
}
)
def retrieve(self, request, *args, **kwargs):
return super().retrieve(request, *args, **kwargs)
@extend_schema(
summary='Update a ticket comment category',
methods=["PUT"],
responses = {
200: OpenApiResponse(description='Ticket comment updated', response=TicketCommentCategorySerializer),
403: OpenApiResponse(description='User tried to edit field they dont have access to'),
}
)
def update(self, request, *args, **kwargs):
return super().update(request, *args, **kwargs)
def get_view_name(self):
if self.detail:
return "Ticket Comment Category"
return 'Ticket Comment Categories'

View File

@ -0,0 +1,102 @@
from django.shortcuts import get_object_or_404
from drf_spectacular.utils import extend_schema, OpenApiResponse
from rest_framework import generics, viewsets
from access.mixin import OrganizationMixin
from api.serializers.core.ticket_comment import TicketCommentSerializer
from api.views.mixin import OrganizationPermissionAPI
from core.models.ticket.ticket_comment import TicketComment
@extend_schema(deprecated=True)
class View(OrganizationMixin, viewsets.ModelViewSet):
permission_classes = [
OrganizationPermissionAPI
]
queryset = TicketComment.objects.all()
serializer_class = TicketCommentSerializer
@extend_schema(
summary='Create a ticket comment',
description = """This model includes all of the ticket comment types.
Due to this not all fields will be available and what fields are available
depends upon the comment type.
""",
request = TicketCommentSerializer,
responses = {
201: OpenApiResponse(description='Ticket comment created', response=TicketCommentSerializer),
403: OpenApiResponse(description='User tried to edit field they dont have access to'),
}
)
def create(self, request, *args, **kwargs):
return super().create(request, *args, **kwargs)
@extend_schema(
summary='Fetch all of a tickets comments',
methods=["GET"],
responses = {
200: OpenApiResponse(description='Success', response=TicketCommentSerializer),
}
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
@extend_schema(
summary='Fetch the selected ticket Comment',
methods=["GET"],
responses = {
200: OpenApiResponse(description='Success', response=TicketCommentSerializer),
}
)
def retrieve(self, request, *args, **kwargs):
return super().retrieve(request, *args, **kwargs)
@extend_schema(
summary='Update a ticket Comment',
description = """This model includes all of the ticket comment types.
Due to this not all fields will be available and what fields are available
depends upon the comment type.
""",
methods=["PUT"],
responses = {
200: OpenApiResponse(description='Ticket comment updated', response=TicketCommentSerializer),
403: OpenApiResponse(description='User tried to edit field they dont have access to'),
}
)
def update(self, request, *args, **kwargs):
return super().update(request, *args, **kwargs)
def get_queryset(self):
if 'ticket_id' in self.kwargs:
self.queryset = self.queryset.filter(ticket=self.kwargs['ticket_id']).order_by('created')
if 'pk' in self.kwargs:
self.queryset = self.queryset.filter(pk = self.kwargs['pk'])
return self.queryset
def get_view_name(self):
if self.detail:
return "Ticket Comment"
return 'Ticket Comments'

View File

@ -0,0 +1,164 @@
from django.db.models import Q
from rest_framework import generics, viewsets
from access.mixin import OrganizationMixin
from api.serializers.assistance.request import RequestTicketSerializer
from api.serializers.itim.change import ChangeTicketSerializer
from api.serializers.itim.incident import IncidentTicketSerializer
from api.serializers.itim.problem import ProblemTicketSerializer
from api.serializers.project_management.project_task import ProjectTaskSerializer
from api.views.mixin import OrganizationPermissionAPI
from core.models.ticket.ticket import Ticket
class View(OrganizationMixin, viewsets.ModelViewSet):
filterset_fields = [
'external_system',
'external_ref',
]
search_fields = [
'title',
'description',
]
permission_classes = [
OrganizationPermissionAPI
]
def get_dynamic_permissions(self):
if self.action == 'create':
action_keyword = 'add'
elif self.action == 'destroy':
action_keyword = 'delete'
elif self.action == 'list':
action_keyword = 'view'
elif self.action == 'partial_update':
action_keyword = 'change'
elif self.action == 'retrieve':
action_keyword = 'view'
elif self.action == 'update':
action_keyword = 'change'
elif self.action is None:
action_keyword = 'view'
else:
raise ValueError('unable to determin the action_keyword')
self.permission_required = [
'core.' + action_keyword + '_ticket_' + self._ticket_type,
]
return super().get_permission_required()
# queryset = Ticket.objects.all()
queryset = None
model = Ticket
def get_serializer(self, *args, **kwargs):
if self._ticket_type == 'change':
self.serializer_class = ChangeTicketSerializer
self._ticket_type_value = Ticket.TicketType.CHANGE.value
elif self._ticket_type == 'incident':
self.serializer_class = IncidentTicketSerializer
self._ticket_type_value = Ticket.TicketType.INCIDENT.value
elif self._ticket_type == 'problem':
self.serializer_class = ProblemTicketSerializer
self._ticket_type_value = Ticket.TicketType.PROBLEM.value
elif self._ticket_type == 'request':
self.serializer_class = RequestTicketSerializer
self._ticket_type_value = Ticket.TicketType.REQUEST.value
elif self._ticket_type == 'project_task':
self.serializer_class = ProjectTaskSerializer
self._ticket_type_value = Ticket.TicketType.PROJECT_TASK.value
else:
raise ValueError('unable to determin the serializer_class')
return super().get_serializer(*args, **kwargs)
def get_queryset(self):
if self._ticket_type == 'change':
ticket_type = self.model.TicketType.CHANGE.value
elif self._ticket_type == 'incident':
ticket_type = self.model.TicketType.INCIDENT.value
elif self._ticket_type == 'problem':
ticket_type = self.model.TicketType.PROBLEM.value
elif self._ticket_type == 'request':
ticket_type = self.model.TicketType.REQUEST.value
elif self._ticket_type == 'project_task':
ticket_type = self.model.TicketType.REQUEST.value
# return self.queryset.filter(
# project = self.kwargs['project_id']
# )
else:
raise ValueError('Unknown ticket type. kwarg `ticket_type` must be set')
if not self.queryset:
queryset = Ticket.objects.all()
queryset = queryset.filter(
ticket_type = ticket_type
)
if self._ticket_type == 'project_task':
queryset = queryset.filter(
project = self.kwargs['project_id']
)
self.queryset = queryset
return self.queryset

View File

@ -1,21 +1,27 @@
# from django.contrib.auth.mixins import PermissionRequiredMixin, LoginRequiredMixin
from django.contrib.auth.models import User
from django.conf import settings as django_settings
from django.utils.safestring import mark_safe
from rest_framework import generics, permissions, routers, viewsets
from rest_framework.decorators import api_view
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.reverse import reverse
class Index(viewsets.ViewSet):
# permission_required = 'access.view_organization'
permission_classes = [
IsAuthenticated,
]
def get_view_name(self):
return "API Index"
return "API"
def get_view_description(self, html=False) -> str:
text = "My REST API"
text = "Centurion ERP Rest API"
if html:
return mark_safe(f"<p>{text}</p>")
else:
@ -23,12 +29,18 @@ class Index(viewsets.ViewSet):
def list(self, request, pk=None):
return Response(
{
API: dict = {
# "teams": reverse("_api_teams", request=request),
"devices": reverse("API:device-list", request=request),
"config_groups": reverse("API:_api_config_groups", request=request),
"organizations": reverse("API:_api_orgs", request=request),
"software": reverse("API:software-list", request=request),
'assistance': reverse("v1:_api_assistance", request=request),
"devices": reverse("v1:device-list", request=request),
"config_groups": reverse("v1:_api_config_groups", request=request),
'itim': reverse("v1:_api_itim", request=request),
"organizations": reverse("v1:_api_orgs", request=request),
'project_management': reverse("v1:_api_project_management", request=request),
"settings": reverse('v1:_settings', request=request),
"software": reverse("v1:software-list", request=request),
'v2': reverse("v2:_api_v2_home-list", request=request)
}
)
return Response( API )

View File

@ -1,11 +1,14 @@
from django.contrib.auth.mixins import PermissionRequiredMixin, LoginRequiredMixin
from drf_spectacular.utils import extend_schema
from itam.models.device import Device
from rest_framework import views
from rest_framework.response import Response
@extend_schema( deprecated = True )
class View(views.APIView):
def get(self, request, slug):

View File

@ -1,9 +1,10 @@
from django.db.models import Q
from django.shortcuts import get_object_or_404
from drf_spectacular.utils import extend_schema
from drf_spectacular.utils import extend_schema, OpenApiResponse
from rest_framework import generics, viewsets
from rest_framework.response import Response
from access.mixin import OrganizationMixin
@ -13,7 +14,7 @@ from api.views.mixin import OrganizationPermissionAPI
from itam.models.device import Device
@extend_schema( deprecated = True )
class DeviceViewSet(OrganizationMixin, viewsets.ModelViewSet):
permission_classes = [
@ -24,6 +25,46 @@ class DeviceViewSet(OrganizationMixin, viewsets.ModelViewSet):
serializer_class = DeviceSerializer
@extend_schema(
summary = 'Create a device',
description="""Add a new device to the ITAM database.
If you attempt to create a device and a device with a matching name and uuid or name and serial number
is found within the database, it will not re-create it. The device will be returned within the message body.
""",
methods=["POST"],
responses = {
200: OpenApiResponse(description='Device allready exists', response=DeviceSerializer),
201: OpenApiResponse(description='Device created', response=DeviceSerializer),
400: OpenApiResponse(description='Validation failed.'),
403: OpenApiResponse(description='User is missing create permissions'),
}
)
def create(self, request, *args, **kwargs):
current_device = []
if 'uuid' in self.request.POST:
current_device = self.serializer_class.Meta.model.objects.filter(
organization = int(self.request.POST['organization']),
uuid = str(self.request.POST['uuid'])
)
if 'serial_number' in self.request.POST and len(current_device) == 0:
current_device = self.serializer_class.Meta.model.objects.filter(
organization = int(self.request.POST['organization']),
serial_number = str(self.request.POST['serial_number'])
)
if len(current_device) == 1:
instance = current_device.get()
serializer = self.get_serializer(instance)
return Response(serializer.data)
return super().create(request, *args, **kwargs)
@extend_schema( description='Fetch devices that are from the users assigned organization(s)', methods=["GET"])
def list(self, request):

View File

@ -1,11 +1,10 @@
import json
import re
from django.core.exceptions import ValidationError, PermissionDenied
from drf_spectacular.utils import extend_schema, OpenApiResponse
from rest_framework import generics, views
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.response import Response
from api.views.mixin import OrganizationPermissionAPI
@ -34,6 +33,7 @@ class InventoryPermissions(OrganizationPermissionAPI):
@extend_schema( deprecated = True )
class Collect(OrganizationPermissionAPI, views.APIView):
queryset = Device.objects.all()
@ -91,12 +91,13 @@ this setting populated, no device will be created and the endpoint will return H
if not self.permission_check(request=request, view=self, obj=device):
raise Http404
raise PermissionDenied()
task = process_inventory.delay(request.body, self.default_organization.id)
response_data: dict = {"task_id": f"{task.id}"}
except PermissionDenied as e:
status = Http.Status.FORBIDDEN
@ -105,7 +106,7 @@ this setting populated, no device will be created and the endpoint will return H
except ValidationError as e:
status = Http.Status.BAD_REQUEST
response_data = e.message
response_data = e.detail
except Exception as e:

View File

@ -1,6 +1,8 @@
from django.db.models import Q
from django.shortcuts import get_object_or_404
from drf_spectacular.utils import extend_schema
from rest_framework import generics, viewsets
from access.mixin import OrganizationMixin
@ -11,7 +13,7 @@ from api.views.mixin import OrganizationPermissionAPI
from itam.models.software import Software
@extend_schema(deprecated = True)
class SoftwareViewSet(OrganizationMixin, viewsets.ModelViewSet):
permission_classes = [

View File

@ -0,0 +1 @@
from .index import *

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