Compare commits
233 Commits
Author | SHA1 | Date | |
---|---|---|---|
04780767c0 | |||
2d8275d2af | |||
db25eabfdd | |||
71bfb5b9e4 | |||
3146417001 | |||
32d97932d1 | |||
32251d0a08 | |||
b694df0330 | |||
52ffb58276 | |||
9ebc65f3b3 | |||
bdc4066a17 | |||
ee018802a9 | |||
d4221913d2 | |||
ab4ebdab24 | |||
a18d9c2789 | |||
f2b13e5a0c | |||
3763eb8727 | |||
b99b38f623 | |||
7aae162453 | |||
aad1407647 | |||
79efc2258f | |||
8b32faa736 | |||
3b04b0c9a4 | |||
4518449232 | |||
6d708d0c00 | |||
5bf89e1ca6 | |||
23213c148c | |||
dd4175a6ba | |||
1d9ce9d310 | |||
813470f084 | |||
bb27bcc280 | |||
1b53faf9ce | |||
33d20a4c0a | |||
fe7e2b9d22 | |||
c563c07345 | |||
3b72df61a4 | |||
c3da9503d2 | |||
c42037cef6 | |||
192f46022c | |||
81860cac84 | |||
b66feadb5a | |||
b01a2a9a47 | |||
f7d3cdd463 | |||
3bb63b2a5b | |||
05ec53331a | |||
6aebde7845 | |||
4de24a7d88 | |||
322b7a1c41 | |||
825683e162 | |||
6bb5a47dd3 | |||
47381c7bf7 | |||
8b08d03d95 | |||
360bf60578 | |||
c8c2fcabd2 | |||
1830b86309 | |||
9e712d3624 | |||
a2a79be7c1 | |||
d5a2adc3a9 | |||
779335458e | |||
8bc0b3338c | |||
9d8bcff2a0 | |||
8a8023f510 | |||
7289758ed9 | |||
d7405a500d | |||
d63c249120 | |||
e920a66a47 | |||
cb4a12f2a0 | |||
d8263b36f7 | |||
74482956a4 | |||
1bc199493c | |||
5cccca865e | |||
03d258ca57 | |||
dfea1fdba0 | |||
edff3eb889 | |||
bdd55a4df6 | |||
ca0bb96808 | |||
5b0d9d8d81 | |||
dd264920a6 | |||
050b2d7602 | |||
ea7c359cc8 | |||
b3391c7e3e | |||
22e369aa3e | |||
d0f710d9f8 | |||
bd58d1d9cf | |||
184820780a | |||
6941668709 | |||
f2cdd403e3 | |||
cb43c56efb | |||
9bbfbbba4b | |||
296188b202 | |||
de26f4b4e9 | |||
600a12177f | |||
167f4140ba | |||
2d6c347859 | |||
2998f48e33 | |||
03a9582703 | |||
3417e37317 | |||
aba9f44552 | |||
569a256dd5 | |||
22785a095d | |||
209e67e0b1 | |||
b50b0bbd8b | |||
bbbf7cb38a | |||
3624885f1e | |||
1f817109ae | |||
711c546e69 | |||
11e32435a2 | |||
8c0b9bf182 | |||
44ec81c3ae | |||
24132a0b1c | |||
b887bb6169 | |||
93ce31d2cf | |||
7f4ff50ceb | |||
534dab2ca6 | |||
705a775ddd | |||
7e79385558 | |||
798cfbe975 | |||
bfefbae686 | |||
8363cd379f | |||
9496ae6aaf | |||
5208340370 | |||
9b58e5913f | |||
748dbea515 | |||
b7880de54d | |||
69b727a06c | |||
26cdd7495a | |||
e2182fe37e | |||
e8b30796ab | |||
36f314fc6f | |||
1d1c76e033 | |||
e8bc98c315 | |||
853906e9ee | |||
403b6be252 | |||
ac78032cca | |||
08fd187692 | |||
8bd90df582 | |||
85a2779563 | |||
9cc5db7869 | |||
e65b2531ed | |||
3a9198f63c | |||
4d8fc508d4 | |||
67b0187a58 | |||
12ef8918ba | |||
5f3990e15a | |||
491e0dba64 | |||
50cb54ab0c | |||
7638fa39da | |||
b0df5713b2 | |||
57c5947c55 | |||
bfd54c112b | |||
e2ca5b8587 | |||
f406e7bf3b | |||
1e127d7180 | |||
a0dd0384bf | |||
60cc64ba19 | |||
2e9470be83 | |||
ade836911f | |||
84f2e8d8c3 | |||
64b677eaa9 | |||
4bd5a890db | |||
48a7a206d2 | |||
b837338140 | |||
0ad80a6f9a | |||
a30cad25bc | |||
668a64bb79 | |||
9d67624e9d | |||
ca2e4e00fa | |||
57cd4851a8 | |||
e6f576ef1a | |||
b34c76afde | |||
f0171fcfda | |||
04b5b4dc24 | |||
77e42db3c9 | |||
25e0f6d950 | |||
e725efb9b7 | |||
f6bf6df31c | |||
2994cfd783 | |||
d006da803f | |||
b1f80cb1b2 | |||
9485d4fce7 | |||
f5e8dd95db | |||
83d937ce7a | |||
ee4ff23618 | |||
2b19f466f2 | |||
8caa8646b4 | |||
dc9d1d283f | |||
0c82fd2bb1 | |||
5483c1878d | |||
bd22604d9d | |||
e8c246a949 | |||
69c631d59b | |||
561e175723 | |||
4c6c27a4bd | |||
cb95cb506a | |||
2b3070c5c2 | |||
9229036b72 | |||
b5b8030c81 | |||
8d4aad7745 | |||
c5e33c4e3d | |||
23773a8776 | |||
ef0b024a12 | |||
3cdf0c7324 | |||
051995efca | |||
717bd1e221 | |||
e158be3cb2 | |||
7a7281ecfc | |||
88d1abaef7 | |||
5804abc367 | |||
96f8949be2 | |||
cce802ea9e | |||
c767a528eb | |||
895cfb8b22 | |||
8dfaec8df7 | |||
69cabda78e | |||
8abced87de | |||
983311dda5 | |||
2becbeef87 | |||
9583631473 | |||
44e1461c6b | |||
105e6509b0 | |||
20d979963a | |||
928dee74b5 | |||
bde2b2e758 | |||
54b97b43ed | |||
405538fa35 | |||
7a9680d988 | |||
d7d85bd01d | |||
becb1eef26 | |||
bf973d3765 | |||
7912a67ab7 | |||
6272eef45f | |||
176537d583 | |||
2491ab611b |
2
.cz.yaml
2
.cz.yaml
@ -17,5 +17,5 @@ commitizen:
|
||||
prerelease_offset: 1
|
||||
tag_format: $version
|
||||
update_changelog_on_bump: false
|
||||
version: 1.13.0
|
||||
version: 1.15.1
|
||||
version_scheme: semver
|
||||
|
9
.github/ISSUE_TEMPLATE/new_model.md
vendored
9
.github/ISSUE_TEMPLATE/new_model.md
vendored
@ -25,7 +25,7 @@ Describe in detail the following:
|
||||
-->
|
||||
|
||||
|
||||
### 🚧 Tasks
|
||||
## 🚧 Tasks
|
||||
|
||||
<!-- Don't remove tasks strike them out. use `~~` before and after the item. i.e. `- ~~[ ] Model Created~~` note: don't include the list dash-->
|
||||
|
||||
@ -36,7 +36,6 @@ Describe in detail the following:
|
||||
- [ ] 🏷️ Model tag added to `app/core/lib/slash_commands/linked_model.CommandLinkedModel.get_model()` function
|
||||
|
||||
- [ ] 📘 Tag updated in the [docs](https://nofusscomputing.com/projects/centurion_erp/user/core/markdown/#model-reference)
|
||||
- [ ] tag added to `app/core/models/ticket/ticket_linked_items.TicketLinkedItem.__str__()`
|
||||
- [ ] tag added to `app/core/lib/slash_commands/linked_model.CommandLinkedModel.get_model()`
|
||||
- [ ] ⚒️ Migration _Ticket Linked Item item_type choices update_
|
||||
|
||||
@ -51,7 +50,7 @@ Describe in detail the following:
|
||||
- [ ] 🆕 Model Created
|
||||
- [ ] 🛠️ Migrations added
|
||||
- [ ] Add `app_label` to KB Models `app/assistance/models/model_knowledge_base_article.all_models().model_apps`
|
||||
- [ ] _(Notes not used/required) - _ Add `model_name` to KB Models `app/assistance/models/model_knowledge_base_article.all_models().excluded_models`
|
||||
- [ ] _(Notes not used/required) -_ Add `model_name` to KB Models `app/assistance/models/model_knowledge_base_article.all_models().excluded_models`
|
||||
- [ ] 🧪 [Unit tested](https://nofusscomputing.com/projects/centurion_erp/development/core/model_notes/#testing)
|
||||
- [ ] 🧪 [Functional tested](https://nofusscomputing.com/projects/centurion_erp/development/core/model_notes/#testing)
|
||||
|
||||
@ -65,7 +64,7 @@ Describe in detail the following:
|
||||
|
||||
|
||||
|
||||
#### 🧪 Tests
|
||||
### 🧪 Tests
|
||||
|
||||
- [ ] Unit Test Model
|
||||
- [ ] Unit Test Tenancy Object
|
||||
@ -79,7 +78,7 @@ Describe in detail the following:
|
||||
- [ ] Function Test History API Render (fields)
|
||||
|
||||
|
||||
### ✅ Requirements
|
||||
## ✅ Requirements
|
||||
|
||||
A Requirement is a must have. In addition will also be tested.
|
||||
|
||||
|
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@ -20,7 +20,7 @@
|
||||
|
||||
<!-- dont remove tasks below strike through including the checkbox by enclosing in double tidle '~~' -->
|
||||
|
||||
- [ ] **Feature Release ONLY** :red_square: Squash migration files :red_square:
|
||||
- [ ] **Feature Release ONLY** :red_square: [Squash migration files](https://docs.djangoproject.com/en/5.2/topics/migrations/#squashing-migrations) :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)?
|
||||
|
243
CHANGELOG.md
243
CHANGELOG.md
@ -1,3 +1,246 @@
|
||||
## 1.15.1 (2025-04-10)
|
||||
|
||||
### Fixes
|
||||
|
||||
- **python**: Downgrade django 5.2 -> 5.1.8
|
||||
|
||||
## 1.15.0 (2025-04-10)
|
||||
|
||||
### feat
|
||||
|
||||
- **settings**: Move Ticket Comment Category from settings to ITOps menu
|
||||
- **settings**: Move Ticket Category from settings to ITOps menu
|
||||
- **access**: place roles nav behind feature flag 2025-00003
|
||||
- **access**: place directory nav behind feature flag 2025-00002
|
||||
- **accounting**: add new module
|
||||
- **access**: Ensure that the same person cant be created more than once
|
||||
- **access**: Place Roles Model behind feature flag `2025-00003`
|
||||
- **access**: When querying permissions, select related field `content_type`
|
||||
- **core**: Model tag for Access/Role
|
||||
- **access**: Model Role notes endpoint
|
||||
- **access**: Add navigation entry for roles
|
||||
- **access**: Model Role History migrations
|
||||
- **access**: Add model Role History
|
||||
- **access**: Role Notes model viewset
|
||||
- **access**: Role Notes model serializer
|
||||
- **access**: Model Role Notes migrations
|
||||
- **access**: Add model Role Notes
|
||||
- **access**: Role model viewset
|
||||
- **access**: Role model serializer
|
||||
- **access**: Model Role migrations
|
||||
- **access**: Add model Role
|
||||
- **python**: Upgrade Django 5.1.7 -> 5.2
|
||||
- **access**: Place Entity URLs behind feature flag `2025-00002`
|
||||
- **access**: Add detail page layout for contact model
|
||||
- **access**: Add Menu entry for corporate directory
|
||||
- **access**: Add back_url to Entity metadata
|
||||
- **core**: Add Entity model tag
|
||||
- **access**: Update Entity field `entity_type` if it does not match the entity type
|
||||
- **access**: All Entity models to use the entity history endpoint
|
||||
- **access**: Enable specifying the history model to use for audit history for a model
|
||||
- **access**: Enable specifying the kb model to use for linking kb article to a model
|
||||
- **access**: All Entity models to use the entity notes endpoint
|
||||
- **access**: Enable specifying the notes `basename` for a model
|
||||
- **access**: ViewSet for Entity Notes model
|
||||
- **access**: Serializer for Entity Notes model
|
||||
- **access**: new model Entity Notes
|
||||
- **access**: New model Entity History
|
||||
- **access**: Add Entity URL routes
|
||||
- **access**: new serializer Contact
|
||||
- **access**: new model Contact
|
||||
- **access**: new serializer Person
|
||||
- **access**: new model Person
|
||||
- **access**: new ViewSet for for Entity and sub-entities
|
||||
- **access**: new serializer Entity
|
||||
- **access**: new model Entity
|
||||
- **human_resources**: Add navigation menu entry for Human Resources (HR)
|
||||
- **human_resources**: Add module Human Resources (HR) to API Urls
|
||||
- **base**: Add module Human Resources (HR) to installed apps
|
||||
- Add module Human Resources (HR)
|
||||
|
||||
### Fixes
|
||||
|
||||
- **api**: Correct documentation link to use models verbose name
|
||||
- **feature_flag**: cater for settings flag overrides
|
||||
- **access**: Add missing field directory to contact model
|
||||
- **settings**: Add Application Settings to Admin page
|
||||
- **access**: Remove app_namespace from Entity
|
||||
- **access**: add missing tenancy object fields to non-tenancy object models
|
||||
- **core**: Dont attempt to fetch history related objects if no history exists
|
||||
- **api**: Dont attempt to access kwargs if not exists within common serializer
|
||||
|
||||
### Refactoring
|
||||
|
||||
- **core**: When saving history, ensure field `_prefetched_objects_cache` is not included
|
||||
|
||||
### Tests
|
||||
|
||||
- **settings**: Correct nav menu entry for Ticket Category and Ticket Comment Category
|
||||
- **access**: Ensure Model Contacts inherits from Person Model
|
||||
- **access**: Functional Test Suite for Contact API Metadata, API Permissions and ViewSet
|
||||
- **access**: Functional Test Suite for Contact serializer
|
||||
- **access**: Functional Test Suite for Contact history
|
||||
- **access**: Correct Entity and person functional Test Suite so sub-model testing works
|
||||
- **access**: Correct table_fields test case to cater for dynamic field
|
||||
- **access**: Unit Test for Contact ViewSet
|
||||
- **access**: Unit Test for Contact model
|
||||
- **access**: Unit Test for Contact history API field checks
|
||||
- **access**: Unit Test for Contact API field checks
|
||||
- **access**: Unit Test for Person Tenancy Object
|
||||
- **access**: Correct Entity and person unit Test Suite so sub-model testing works
|
||||
- **access**: Entity Function Serializer test cases
|
||||
- **access**: Person Model field test cases
|
||||
- **access**: Functional Test for Person ViewSet, Permissions and Metadata
|
||||
- **access**: Functional Test for Person History
|
||||
- **access**: Correct Entity Function Test Suite so sub-model testing works
|
||||
- **access**: Unit Test for Person ViewSet
|
||||
- **access**: Unit Test for Person Model
|
||||
- **access**: Unit Test for Person History API fields
|
||||
- **access**: Unit Test for Person API fields
|
||||
- **access**: Unit Test for Person Tenancy Object
|
||||
- **access**: Correct Entity Test Suite so sub-model testing works
|
||||
- **app**: exclude any field check that ends in `_ptr_id`
|
||||
- **access**: Remove teardown from Function Test cases for Role serializer
|
||||
- **access**: Test cases for Role serializer
|
||||
- **access**: Function Test cases for Role SPI Permissions, ViewSet and Metadata
|
||||
- **access**: Function Test cases for Role History
|
||||
- **access**: Unit Test case to ensure Role is by organization
|
||||
- **access**: Unit Test case to ensure Role cant be set as global object
|
||||
- **access**: Unit Test cases for Role ViewSet
|
||||
- **access**: Unit Test cases for Role model
|
||||
- **access**: Unit Test cases for Role History API v2
|
||||
- **access**: Unit Test cases for Role API v2
|
||||
- **access**: Unit Test cases for Role Tenancy Object
|
||||
- During testing add debug_feature_flags so object behind can be tested
|
||||
- **access**: Notes ViewSet Functional Tests for Entity Model
|
||||
- **access**: Notes API Field Functional Tests for Entity Model
|
||||
- **access**: Correct functional ViewSet test suite for Entity model
|
||||
- **access**: History functional Tests for Entity model
|
||||
- **access**: PermissionsAPI, ViewSet and Metadata Tests for Entity model
|
||||
- **access**: Model test cases for Entity
|
||||
- **access**: API Rendering test cases for Entity model
|
||||
- **api**: Ensure that when mocking the request the viewset is instantiated
|
||||
- **access**: History tests for Entity model
|
||||
- **access**: ViewSet tests for Entity model
|
||||
- **access**: Tenancy object test for Entity model
|
||||
|
||||
## 1.14.0 (2025-03-29)
|
||||
|
||||
### feat
|
||||
|
||||
- **itops**: Add navigation menu
|
||||
- New Module ITOps
|
||||
- **devops**: Ensure GitHub Groups can't be nested
|
||||
- **devops**: Models Git Repository must use organization from `git_group` as must group if parent set
|
||||
- **devops**: Add git provider badge to git_group table fields
|
||||
- **devops**: Add git provider badge to git_repository table fields
|
||||
- **devops**: Add Git GRoup to navigation
|
||||
- **itam**: Add `back_url` to Software Version ViewSet
|
||||
- **itam**: Add `back_url` to Operating System ViewSet
|
||||
- **devops**: Add `page_layout` to Git Group model
|
||||
- **devops**: Add `page_layout` to GitLab repository model
|
||||
- **devops**: Add `page_layout` to GitHub repository model
|
||||
- **devops**: git_repository ViewSet updated to fetch queryset based off of repository provider
|
||||
- **devops**: Add ti git_repository ViewSet return and back urls
|
||||
- **devops**: Make fields `provider` and `provider_id` unique_together for git_repository model
|
||||
- **devops**: Add fields to ALL git_repository serializers
|
||||
- **devops**: Add fetching of URL to base git_repository model
|
||||
- **api**: Enable fetching of app_namespace from model
|
||||
- **access**: Add function get_page_layout
|
||||
- **feature_flag**: Provide user with ability to override feature flags
|
||||
- **base**: Add middleware feature_flag
|
||||
- **devops**: Disable notes for GIT Repository Base Model
|
||||
- **devops**: Add git_repository model tag migration
|
||||
- **devops**: Add git_repository as a model that can be linked to a ticket
|
||||
- **devops**: Git Group Notes Migration
|
||||
- **devops**: Git Group Notes ViewSet
|
||||
- **devops**: Git Group Notes Serializer
|
||||
- **devops**: Git Group Notes Model
|
||||
- **devops**: GitHub and GitLab Repository Notes Migrations
|
||||
- **devops**: GitLab Repository Notes Viewset
|
||||
- **devops**: GitHub Repository Notes Viewset
|
||||
- **devops**: GitLab Repository Notes Serializer
|
||||
- **devops**: GitHub Repository Notes Serializer
|
||||
- **devops**: GitLab Repository Notes Model
|
||||
- **devops**: GitHub Repository Notes Model
|
||||
- **devops**: Git Group History Migrations
|
||||
- **devops**: Git Group History
|
||||
- **devops**: GitLab and GitHub Repository History Migrations
|
||||
- **devops**: GitLab Repository History
|
||||
- **devops**: GitHub Repository History
|
||||
- **devops**: [2025-00001] Git Group and Repositories URLs
|
||||
- **devops**: Git Group and Repositories Migrations
|
||||
- **devops**: GIT Group ViewSet
|
||||
- **devops**: GIT Group Serializer
|
||||
- **devops**: GIT Group Model
|
||||
- **devops**: GIT Repositories Viewset
|
||||
- **devops**: GitLab Serializer for git repositories
|
||||
- **devops**: GitHub Serializer for git repositories
|
||||
- **devops**: Base Serializer for git repositories
|
||||
- **devops**: GitLab Repository Model
|
||||
- **devops**: GitHub Repository Model
|
||||
- **devops**: Base model for git repositories
|
||||
- **core**: Enable slash command related ticket to have multiple ticket references
|
||||
- **core**: Enable slash command linked model to have multiple models
|
||||
- **core**: process ticket slash commands by line
|
||||
- **core**: Migrations for new slash commands
|
||||
- **project_management**: Add project_state slash command
|
||||
- **core**: Add ticket_comment_category slash command
|
||||
- **core**: Add ticket_category slash command
|
||||
- **itam**: when displaying software version, add prefix with software name
|
||||
- **itam**: Add markdown tag $software_version
|
||||
- **itam**: Enable ticket tab on software version page
|
||||
|
||||
### Fixes
|
||||
|
||||
- **devops**: Correct git_group serializer parameter name
|
||||
- **devops**: Correct field path to no be unique for git_repository
|
||||
- **feature_flag**: if over_rides not set ensure val set to empty dict
|
||||
- **devops**: git_group serializers must define fields
|
||||
- **devops**: git_group serializers must return urls
|
||||
- **devops**: Correct git_repository notes urls
|
||||
- **devops**: Correct git_repository url regex
|
||||
- **devops**: Correct ViewSerializer for GitLab Repository
|
||||
- **devops**: Correct ViewSerializer for GitHib Repository
|
||||
- **devops**: Correct model git_group modified field name part 2
|
||||
- **devops**: Correct model git_group modified field name
|
||||
- **api**: Fetching of serializer_class must be dynamic
|
||||
- **core**: Don't create an empty ticket comment if the body is empty when slash commands removed
|
||||
- **core**: when processing slash commands trim each line prior to processing
|
||||
- **core**: slash command NL char is `\r\n` not `\n`, however support both
|
||||
- **core**: When processing slash commands trim whitespace on return
|
||||
- **core**: Ensure linked ticket models are unique
|
||||
- **itam**: Add back url to software_version model
|
||||
|
||||
### Refactoring
|
||||
|
||||
- **devops**: remove model unique_together constraint for git group and repository
|
||||
- **devops**: Field `provider_id` must not be user editable for git group or repository
|
||||
- **api**: mv _nav property to function get_nav_items
|
||||
|
||||
### Tests
|
||||
|
||||
- **api**: Correct test cases for view_name and view_description
|
||||
- Refactor all ViewSet Unit Test cases to use new test cases class
|
||||
- **api**: Common ViewSet classes Tests and Test cases for classes that inherit them
|
||||
- **api**: correct nav menu setup to use mock request
|
||||
- **core**: un-mark tests as skipped so that multiple linked items per ticket can be tested
|
||||
- **core**: correct ticket linked item to prevent duplicate creation
|
||||
|
||||
## 1.13.1 (2025-03-17)
|
||||
|
||||
### Fixes
|
||||
|
||||
- **devops**: After fetching feature flags dont attempt to access results unless status=200
|
||||
- **docker**: only download feature flags when not a worker
|
||||
- **devops**: Use correct stderr function when using feature_flag management command
|
||||
- **devops**: Cater for connection timeout when fetching feature flags
|
||||
- when building feature flag version, use first 8 chars of build hash
|
||||
|
||||
### Refactoring
|
||||
|
||||
- **docker**: Use crontabs not cron.d
|
||||
|
||||
## 1.13.0 (2025-03-16)
|
||||
|
||||
### feat
|
||||
|
@ -1,3 +1,17 @@
|
||||
## Version 1.15.0
|
||||
|
||||
- Entities model added behind feature flag `2025-00002` and will remain behind this flag until production ready.
|
||||
|
||||
- Roles model added behind feature flag `2025-00003` and will remain behind this flag until production ready.
|
||||
|
||||
- Accounting Module added behind feature flag `2025-00004` and will remain behind this flag until production ready.
|
||||
|
||||
|
||||
## Version 1.14.0
|
||||
|
||||
- Git Repository and Git Group Models added behind feature flag `2025-00001`. They will remain behind this feature flag until the Git features are fully developed and ready for use.
|
||||
|
||||
|
||||
## Version 1.13.0
|
||||
|
||||
- DevOps Module added.
|
||||
|
@ -47,7 +47,7 @@ def permission_queryset():
|
||||
'view_history',
|
||||
]
|
||||
|
||||
return Permission.objects.filter(
|
||||
return Permission.objects.select_related('content_type').filter(
|
||||
content_type__app_label__in=apps,
|
||||
).exclude(
|
||||
content_type__model__in=exclude_models
|
||||
|
@ -0,0 +1,140 @@
|
||||
# Generated by Django 5.2 on 2025-04-10 02:34
|
||||
|
||||
import access.fields
|
||||
import access.models.tenancy
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0004_organizationhistory_teamhistory'),
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
('core', '0021_alter_ticketlinkeditem_item_type'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Entity',
|
||||
fields=[
|
||||
('is_global', models.BooleanField(default=False, help_text='Is this a global object?', verbose_name='Global Object')),
|
||||
('model_notes', models.TextField(blank=True, default=None, help_text='Tid bits of information', null=True, verbose_name='Notes')),
|
||||
('id', models.AutoField(help_text='Primary key of the entry', primary_key=True, serialize=False, unique=True, verbose_name='ID')),
|
||||
('entity_type', models.CharField(default='entity', help_text='Type this entity is', max_length=30, verbose_name='Entity Type')),
|
||||
('created', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, help_text='Date and time of creation', verbose_name='Created')),
|
||||
('modified', access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, help_text='Date and time of last modification', verbose_name='Modified')),
|
||||
('organization', models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='+', to='access.organization', validators=[access.models.tenancy.TenancyObject.validatate_organization_exists], verbose_name='Organization')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Entity',
|
||||
'verbose_name_plural': 'Entities',
|
||||
'ordering': ['created', 'modified', 'organization'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Person',
|
||||
fields=[
|
||||
('entity_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='access.entity')),
|
||||
('f_name', models.CharField(help_text='The persons first name', max_length=64, verbose_name='First Name')),
|
||||
('m_name', models.CharField(blank=True, default=None, help_text='The persons middle name(s)', max_length=100, null=True, verbose_name='Middle Name(s)')),
|
||||
('l_name', models.CharField(help_text='The persons Last name', max_length=64, verbose_name='Last Name')),
|
||||
('dob', models.DateField(blank=True, default=None, help_text='The Persons Date of Birth (DOB)', null=True, verbose_name='DOB')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Person',
|
||||
'verbose_name_plural': 'People',
|
||||
'ordering': ['l_name', 'm_name', 'f_name', 'dob'],
|
||||
},
|
||||
bases=('access.entity',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EntityHistory',
|
||||
fields=[
|
||||
('modelhistory_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='core.modelhistory')),
|
||||
('model', models.ForeignKey(help_text='Model this note belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='history', to='access.entity', verbose_name='Model')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Entity History',
|
||||
'verbose_name_plural': 'Entity History',
|
||||
'db_table': 'access_entity_history',
|
||||
'ordering': ['-created'],
|
||||
},
|
||||
bases=('core.modelhistory',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EntityNotes',
|
||||
fields=[
|
||||
('modelnotes_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='core.modelnotes')),
|
||||
('model', models.ForeignKey(help_text='Model this note belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='access.entity', verbose_name='Model')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Entity Note',
|
||||
'verbose_name_plural': 'Entity Notes',
|
||||
'db_table': 'access_entity_notes',
|
||||
'ordering': ['-created'],
|
||||
},
|
||||
bases=('core.modelnotes',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Role',
|
||||
fields=[
|
||||
('model_notes', models.TextField(blank=True, default=None, help_text='Tid bits of information', null=True, verbose_name='Notes')),
|
||||
('id', models.AutoField(help_text='Primary key of the entry', primary_key=True, serialize=False, unique=True, verbose_name='ID')),
|
||||
('name', models.CharField(help_text='Name of this role', max_length=30, verbose_name='Name')),
|
||||
('created', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, help_text='Date and time of creation', verbose_name='Created')),
|
||||
('modified', access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, help_text='Date and time of last modification', verbose_name='Modified')),
|
||||
('organization', models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='+', to='access.organization', validators=[access.models.tenancy.TenancyObject.validatate_organization_exists], verbose_name='Organization')),
|
||||
('permissions', models.ManyToManyField(blank=True, help_text='Permissions part of this role', related_name='roles', to='auth.permission', verbose_name='Permissions')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Role',
|
||||
'verbose_name_plural': 'Roles',
|
||||
'ordering': ['organization', 'name'],
|
||||
'unique_together': {('organization', 'name')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RoleHistory',
|
||||
fields=[
|
||||
('modelhistory_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='core.modelhistory')),
|
||||
('model', models.ForeignKey(help_text='Model this note belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='history', to='access.role', verbose_name='Model')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Role History',
|
||||
'verbose_name_plural': 'Role History',
|
||||
'db_table': 'access_role_history',
|
||||
'ordering': ['-created'],
|
||||
},
|
||||
bases=('core.modelhistory',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RoleNotes',
|
||||
fields=[
|
||||
('modelnotes_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='core.modelnotes')),
|
||||
('model', models.ForeignKey(help_text='Model this note belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='access.role', verbose_name='Model')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Role Note',
|
||||
'verbose_name_plural': 'Role Notes',
|
||||
'db_table': 'access_role_notes',
|
||||
'ordering': ['-created'],
|
||||
},
|
||||
bases=('core.modelnotes',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Contact',
|
||||
fields=[
|
||||
('person_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='access.person')),
|
||||
('directory', models.BooleanField(blank=True, default=True, help_text='Show contact details in directory', verbose_name='Show in Directory')),
|
||||
('email', models.EmailField(help_text='E-mail address for this person', max_length=254, unique=True, verbose_name='E-Mail')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Contact',
|
||||
'verbose_name_plural': 'Contacts',
|
||||
'ordering': ['email'],
|
||||
},
|
||||
bases=('access.person',),
|
||||
),
|
||||
]
|
@ -0,0 +1,2 @@
|
||||
from . import contact
|
||||
from . import person
|
||||
|
115
app/access/models/contact.py
Normal file
115
app/access/models/contact.py
Normal file
@ -0,0 +1,115 @@
|
||||
from django.db import models
|
||||
|
||||
from access.models.person import Person
|
||||
|
||||
|
||||
|
||||
class Contact(
|
||||
Person
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
ordering = [
|
||||
'email',
|
||||
]
|
||||
|
||||
verbose_name = 'Contact'
|
||||
|
||||
verbose_name_plural = 'Contacts'
|
||||
|
||||
|
||||
directory = models.BooleanField(
|
||||
blank = True,
|
||||
default = True,
|
||||
help_text = 'Show contact details in directory',
|
||||
null = False,
|
||||
verbose_name = 'Show in Directory',
|
||||
)
|
||||
|
||||
email = models.EmailField(
|
||||
blank = False,
|
||||
help_text = 'E-mail address for this person',
|
||||
unique = True,
|
||||
verbose_name = 'E-Mail',
|
||||
)
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
return self.f_name + ' ' + self.l_name
|
||||
|
||||
documentation = ''
|
||||
|
||||
page_layout: list = [
|
||||
{
|
||||
"name": "Details",
|
||||
"slug": "details",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "double",
|
||||
"left": [
|
||||
'organization',
|
||||
'created',
|
||||
'modified',
|
||||
],
|
||||
"right": [
|
||||
'model_notes',
|
||||
'directory',
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Personal Details",
|
||||
"layout": "double",
|
||||
"left": [
|
||||
'display_name',
|
||||
'dob',
|
||||
],
|
||||
"right": [
|
||||
'f_name',
|
||||
'm_name',
|
||||
'l_name',
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"layout": "double",
|
||||
"left": [
|
||||
'email',
|
||||
],
|
||||
"right": [
|
||||
'',
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Knowledge Base",
|
||||
"slug": "kb_articles",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "knowledge_base",
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Notes",
|
||||
"slug": "notes",
|
||||
"sections": []
|
||||
},
|
||||
]
|
||||
|
||||
table_fields: list = [
|
||||
{
|
||||
"field": "display_name",
|
||||
"type": "link",
|
||||
"key": "_self"
|
||||
},
|
||||
'f_name',
|
||||
'l_name',
|
||||
'email',
|
||||
'organization',
|
||||
'created',
|
||||
]
|
241
app/access/models/entity.py
Normal file
241
app/access/models/entity.py
Normal file
@ -0,0 +1,241 @@
|
||||
from django.db import models
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from access.fields import AutoCreatedField, AutoLastModifiedField
|
||||
from access.models.tenancy import TenancyObject
|
||||
|
||||
from core.lib.feature_not_used import FeatureNotUsed
|
||||
|
||||
|
||||
|
||||
class Entity(
|
||||
TenancyObject
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
ordering = [
|
||||
'created',
|
||||
'modified',
|
||||
'organization',
|
||||
]
|
||||
|
||||
verbose_name = 'Entity'
|
||||
|
||||
verbose_name_plural = 'Entities'
|
||||
|
||||
|
||||
id = models.AutoField(
|
||||
blank=False,
|
||||
help_text = 'Primary key of the entry',
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
verbose_name = 'ID'
|
||||
)
|
||||
|
||||
entity_type = models.CharField(
|
||||
blank = False,
|
||||
default = Meta.verbose_name.lower(),
|
||||
help_text = 'Type this entity is',
|
||||
max_length = 30,
|
||||
unique = False,
|
||||
verbose_name = 'Entity Type'
|
||||
)
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
related_model = self.get_related_model()
|
||||
|
||||
if related_model is None:
|
||||
|
||||
return f'{self.entity_type} {self.pk}'
|
||||
|
||||
|
||||
return str( related_model )
|
||||
|
||||
|
||||
|
||||
# app_namespace = 'access'
|
||||
|
||||
history_app_label = 'access'
|
||||
|
||||
history_model_name = 'entity'
|
||||
|
||||
kb_model_name = 'entity'
|
||||
|
||||
note_basename = '_api_v2_entity_note'
|
||||
|
||||
documentation = ''
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
|
||||
table_fields: list = [
|
||||
'organization',
|
||||
'entity_type',
|
||||
'display_name',
|
||||
'created',
|
||||
'modified',
|
||||
]
|
||||
|
||||
|
||||
def get_related_field_name(self) -> str:
|
||||
|
||||
meta = getattr(self, '_meta')
|
||||
|
||||
for related_object in getattr(meta, 'related_objects', []):
|
||||
|
||||
if getattr(self, related_object.name, None):
|
||||
|
||||
if(
|
||||
not str(related_object.name).endswith('history')
|
||||
and not str(related_object.name).endswith('notes')
|
||||
):
|
||||
|
||||
return related_object.name
|
||||
break
|
||||
|
||||
|
||||
return ''
|
||||
|
||||
|
||||
def get_related_model(self):
|
||||
"""Recursive model Fetch
|
||||
|
||||
Returns the lowest model found in a chain of inherited models.
|
||||
|
||||
Args:
|
||||
model (models.Model, optional): Model to fetch the child model from. Defaults to None.
|
||||
|
||||
Returns:
|
||||
models.Model: Lowset model found in inherited model chain
|
||||
"""
|
||||
|
||||
related_model_name = self.get_related_field_name()
|
||||
|
||||
related_model = getattr(self, related_model_name, None)
|
||||
|
||||
if related_model_name == '':
|
||||
|
||||
return None
|
||||
|
||||
elif related_model is None:
|
||||
|
||||
return self
|
||||
|
||||
elif related_model.get_related_field_name() != '':
|
||||
|
||||
related_model = related_model.get_related_model()
|
||||
|
||||
|
||||
return related_model
|
||||
|
||||
|
||||
def get_url_kwargs(self) -> dict:
|
||||
|
||||
model = self.get_related_model()
|
||||
|
||||
if len(self._meta.parents) == 0 and model is None:
|
||||
|
||||
return {
|
||||
'pk': self.id
|
||||
}
|
||||
|
||||
if model is None:
|
||||
|
||||
model = self
|
||||
|
||||
kwargs = {
|
||||
'entity_model': str(model._meta.verbose_name).lower().replace(' ', '_'),
|
||||
}
|
||||
|
||||
if model.pk:
|
||||
|
||||
kwargs.update({
|
||||
'pk': model.id
|
||||
})
|
||||
|
||||
return kwargs
|
||||
|
||||
|
||||
|
||||
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 = None
|
||||
|
||||
if getattr(self, 'get_related_model', None):
|
||||
|
||||
model = self.get_related_model()
|
||||
|
||||
|
||||
|
||||
if model is None:
|
||||
|
||||
model = self
|
||||
|
||||
|
||||
sub_entity = ''
|
||||
if model._meta.model_name != 'entity':
|
||||
|
||||
sub_entity = '_sub'
|
||||
|
||||
|
||||
kwargs = self.get_url_kwargs()
|
||||
|
||||
view = 'list'
|
||||
if 'pk' in kwargs:
|
||||
|
||||
view = 'detail'
|
||||
|
||||
if request:
|
||||
|
||||
return reverse(f"v2:" + model.get_app_namespace() + f"_api_v2_entity" + sub_entity + "-" + view, request=request, kwargs = kwargs )
|
||||
|
||||
return reverse(f"v2:" + model.get_app_namespace() + f"_api_v2_entity" + sub_entity + "-" + view, kwargs = kwargs )
|
||||
|
||||
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
|
||||
related_model = self.get_related_model()
|
||||
|
||||
if related_model is None:
|
||||
|
||||
related_model = self
|
||||
|
||||
if self.entity_type != str(related_model._meta.verbose_name).lower().replace(' ', '_'):
|
||||
|
||||
self.entity_type = str(related_model._meta.verbose_name).lower().replace(' ', '_')
|
||||
|
||||
super().save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
|
||||
|
||||
|
||||
def save_history(self, before: dict, after: dict) -> bool:
|
||||
|
||||
from access.models.entity_history import EntityHistory
|
||||
|
||||
history = super().save_history(
|
||||
before = before,
|
||||
after = after,
|
||||
history_model = EntityHistory
|
||||
)
|
||||
|
||||
return history
|
55
app/access/models/entity_history.py
Normal file
55
app/access/models/entity_history.py
Normal file
@ -0,0 +1,55 @@
|
||||
from django.db import models
|
||||
|
||||
from access.models.entity import Entity
|
||||
|
||||
from core.models.model_history import ModelHistory
|
||||
|
||||
from devops.models.feature_flag import FeatureFlag
|
||||
|
||||
|
||||
|
||||
class EntityHistory(
|
||||
ModelHistory
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
db_table = 'access_entity_history'
|
||||
|
||||
ordering = ModelHistory._meta.ordering
|
||||
|
||||
verbose_name = 'Entity History'
|
||||
|
||||
verbose_name_plural = 'Entity History'
|
||||
|
||||
|
||||
model = models.ForeignKey(
|
||||
Entity,
|
||||
blank = False,
|
||||
help_text = 'Model this note belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
related_name = 'history',
|
||||
verbose_name = 'Model',
|
||||
)
|
||||
|
||||
table_fields: list = []
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
|
||||
def get_object(self):
|
||||
|
||||
return self
|
||||
|
||||
|
||||
def get_serialized_model(self, serializer_context):
|
||||
|
||||
model = None
|
||||
|
||||
from access.serializers.entity import BaseSerializer
|
||||
|
||||
model = BaseSerializer(self.model, context = serializer_context)
|
||||
|
||||
return model
|
45
app/access/models/entity_notes.py
Normal file
45
app/access/models/entity_notes.py
Normal file
@ -0,0 +1,45 @@
|
||||
from django.db import models
|
||||
|
||||
from access.models.entity import Entity
|
||||
|
||||
from core.models.model_notes import ModelNotes
|
||||
|
||||
|
||||
|
||||
class EntityNotes(
|
||||
ModelNotes
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
db_table = 'access_entity_notes'
|
||||
|
||||
ordering = ModelNotes._meta.ordering
|
||||
|
||||
verbose_name = 'Entity Note'
|
||||
|
||||
verbose_name_plural = 'Entity Notes'
|
||||
|
||||
|
||||
model = models.ForeignKey(
|
||||
Entity,
|
||||
blank = False,
|
||||
help_text = 'Model this note belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
related_name = 'notes',
|
||||
verbose_name = 'Model',
|
||||
)
|
||||
|
||||
table_fields: list = []
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
|
||||
def get_url_kwargs(self) -> dict:
|
||||
|
||||
return {
|
||||
'model_id': self.model.pk,
|
||||
'pk': self.pk
|
||||
}
|
117
app/access/models/person.py
Normal file
117
app/access/models/person.py
Normal file
@ -0,0 +1,117 @@
|
||||
from django.db import models
|
||||
|
||||
from core.exceptions import ValidationError
|
||||
|
||||
from access.models.entity import Entity
|
||||
|
||||
|
||||
|
||||
class Person(
|
||||
Entity
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
ordering = [
|
||||
'l_name',
|
||||
'm_name',
|
||||
'f_name',
|
||||
'dob',
|
||||
]
|
||||
|
||||
verbose_name = 'Person'
|
||||
|
||||
verbose_name_plural = 'People'
|
||||
|
||||
f_name = models.CharField(
|
||||
blank = False,
|
||||
help_text = 'The persons first name',
|
||||
max_length = 64,
|
||||
unique = False,
|
||||
verbose_name = 'First Name'
|
||||
)
|
||||
|
||||
m_name = models.CharField(
|
||||
blank = True,
|
||||
default = None,
|
||||
help_text = 'The persons middle name(s)',
|
||||
max_length = 100,
|
||||
null = True,
|
||||
unique = False,
|
||||
verbose_name = 'Middle Name(s)'
|
||||
)
|
||||
|
||||
l_name = models.CharField(
|
||||
blank = False,
|
||||
help_text = 'The persons Last name',
|
||||
max_length = 64,
|
||||
unique = False,
|
||||
verbose_name = 'Last Name'
|
||||
)
|
||||
|
||||
dob = models.DateField(
|
||||
blank = True,
|
||||
default = None,
|
||||
help_text = 'The Persons Date of Birth (DOB)',
|
||||
null = True,
|
||||
unique = False,
|
||||
verbose_name = 'DOB',
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
return self.f_name + ' ' + self.l_name + f' (DOB: {self.dob})'
|
||||
|
||||
documentation = ''
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
table_fields: list = [
|
||||
'organization',
|
||||
'f_name',
|
||||
'l_name',
|
||||
'dob',
|
||||
'created',
|
||||
]
|
||||
|
||||
|
||||
def clean(self):
|
||||
|
||||
super().clean()
|
||||
|
||||
if self.dob is not None:
|
||||
|
||||
if self.pk:
|
||||
|
||||
duplicate_entry = Person.objects.filter(
|
||||
f_name = self.f_name,
|
||||
l_name = self.l_name,
|
||||
).exclude(
|
||||
pk = self.pk
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
duplicate_entry = Person.objects.filter(
|
||||
f_name = self.f_name,
|
||||
l_name = self.l_name,
|
||||
)
|
||||
|
||||
|
||||
for entry in duplicate_entry:
|
||||
|
||||
if(
|
||||
entry.f_name == self.f_name
|
||||
and entry.m_name == self.m_name
|
||||
and entry.l_name == self.l_name
|
||||
and entry.dob == self.dob
|
||||
):
|
||||
|
||||
raise ValidationError(
|
||||
detail = {
|
||||
'dob': f'Person {self.f_name} {self.l_name} already exists with this birthday {entry.dob}'
|
||||
},
|
||||
code = 'duplicate_person_on_dob'
|
||||
)
|
||||
|
143
app/access/models/role.py
Normal file
143
app/access/models/role.py
Normal file
@ -0,0 +1,143 @@
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.db import models
|
||||
|
||||
from access.fields import AutoCreatedField, AutoLastModifiedField
|
||||
from access.models.tenancy import TenancyObject
|
||||
|
||||
|
||||
|
||||
class Role(
|
||||
TenancyObject
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
ordering = [
|
||||
'organization',
|
||||
'name',
|
||||
]
|
||||
|
||||
unique_together = [
|
||||
'organization',
|
||||
'name'
|
||||
]
|
||||
|
||||
verbose_name = 'Role'
|
||||
|
||||
verbose_name_plural = 'Roles'
|
||||
|
||||
|
||||
id = models.AutoField(
|
||||
blank=False,
|
||||
help_text = 'Primary key of the entry',
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
verbose_name = 'ID'
|
||||
)
|
||||
|
||||
name = models.CharField(
|
||||
blank = False,
|
||||
help_text = 'Name of this role',
|
||||
max_length = 30,
|
||||
unique = False,
|
||||
verbose_name = 'Name'
|
||||
)
|
||||
|
||||
permissions = models.ManyToManyField(
|
||||
Permission,
|
||||
blank = True,
|
||||
help_text = 'Permissions part of this role',
|
||||
related_name = 'roles',
|
||||
symmetrical = False,
|
||||
verbose_name = 'Permissions'
|
||||
)
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
is_global = None
|
||||
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
return str( self.organization ) + ' / ' + self.name
|
||||
|
||||
|
||||
documentation = ''
|
||||
|
||||
page_layout: dict = [
|
||||
{
|
||||
"name": "Details",
|
||||
"slug": "details",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "double",
|
||||
"left": [
|
||||
'organization',
|
||||
'name',
|
||||
'created',
|
||||
'modified',
|
||||
],
|
||||
"right": [
|
||||
'model_notes',
|
||||
]
|
||||
},
|
||||
{
|
||||
"layout": "single",
|
||||
"name": "Permissions",
|
||||
"fields": [
|
||||
"permissions",
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Knowledge Base",
|
||||
"slug": "kb_articles",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "knowledge_base",
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Tickets",
|
||||
"slug": "tickets",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "tickets",
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "Notes",
|
||||
"slug": "notes",
|
||||
"sections": []
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
table_fields: list = [
|
||||
'organization',
|
||||
'name',
|
||||
'created',
|
||||
'modified',
|
||||
]
|
||||
|
||||
|
||||
def save_history(self, before: dict, after: dict) -> bool:
|
||||
|
||||
from access.models.role_history import RoleHistory
|
||||
|
||||
history = super().save_history(
|
||||
before = before,
|
||||
after = after,
|
||||
history_model = RoleHistory
|
||||
)
|
||||
|
||||
return history
|
53
app/access/models/role_history.py
Normal file
53
app/access/models/role_history.py
Normal file
@ -0,0 +1,53 @@
|
||||
from django.db import models
|
||||
|
||||
from core.models.model_history import ModelHistory
|
||||
|
||||
from access.models.role import Role
|
||||
|
||||
|
||||
|
||||
class RoleHistory(
|
||||
ModelHistory
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
db_table = 'access_role_history'
|
||||
|
||||
ordering = ModelHistory._meta.ordering
|
||||
|
||||
verbose_name = 'Role History'
|
||||
|
||||
verbose_name_plural = 'Role History'
|
||||
|
||||
|
||||
model = models.ForeignKey(
|
||||
Role,
|
||||
blank = False,
|
||||
help_text = 'Model this note belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
related_name = 'history',
|
||||
verbose_name = 'Model',
|
||||
)
|
||||
|
||||
table_fields: list = []
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
|
||||
def get_object(self):
|
||||
|
||||
return self
|
||||
|
||||
|
||||
def get_serialized_model(self, serializer_context):
|
||||
|
||||
model = None
|
||||
|
||||
from access.serializers.role import BaseSerializer
|
||||
|
||||
model = BaseSerializer(self.model, context = serializer_context)
|
||||
|
||||
return model
|
45
app/access/models/role_notes.py
Normal file
45
app/access/models/role_notes.py
Normal file
@ -0,0 +1,45 @@
|
||||
from django.db import models
|
||||
|
||||
from access.models.role import Role
|
||||
|
||||
from core.models.model_notes import ModelNotes
|
||||
|
||||
|
||||
|
||||
class RoleNotes(
|
||||
ModelNotes
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
db_table = 'access_role_notes'
|
||||
|
||||
ordering = ModelNotes._meta.ordering
|
||||
|
||||
verbose_name = 'Role Note'
|
||||
|
||||
verbose_name_plural = 'Role Notes'
|
||||
|
||||
|
||||
model = models.ForeignKey(
|
||||
Role,
|
||||
blank = False,
|
||||
help_text = 'Model this note belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
related_name = 'notes',
|
||||
verbose_name = 'Model',
|
||||
)
|
||||
|
||||
table_fields: list = []
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
|
||||
def get_url_kwargs(self) -> dict:
|
||||
|
||||
return {
|
||||
'model_id': self.model.pk,
|
||||
'pk': self.pk
|
||||
}
|
@ -73,6 +73,11 @@ class TeamUsers(SaveHistory):
|
||||
'manager'
|
||||
]
|
||||
|
||||
history_app_label: str = None
|
||||
history_model_name: str = None
|
||||
kb_model_name: str = None
|
||||
note_basename: str = None
|
||||
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
""" Delete Team
|
||||
|
@ -172,6 +172,40 @@ class TenancyObject(SaveHistory):
|
||||
the API version, i.e. `v2:devops`.
|
||||
"""
|
||||
|
||||
history_app_label: str = None
|
||||
"""History Model Application Label
|
||||
|
||||
This value is derived from `<model>._meta.app_label`. This value should
|
||||
only be used when there is model inheritence.
|
||||
"""
|
||||
|
||||
history_model_name: str = None
|
||||
"""History Model Model Name
|
||||
|
||||
This value is derived from `<model>._meta.model_name`. This value should
|
||||
only be used when there is model inheritence.
|
||||
"""
|
||||
|
||||
kb_model_name: str = None
|
||||
"""Model name to use for KB article linking
|
||||
|
||||
This value is derived from `<model>._meta.model_name`. This value should
|
||||
only be used when there is model inheritence.
|
||||
"""
|
||||
|
||||
note_basename: str = None
|
||||
"""URL BaseName for the notes endpoint.
|
||||
|
||||
Don't specify the `app_namespace`, use property `app_namespace` above.
|
||||
"""
|
||||
|
||||
|
||||
def get_page_layout(self):
|
||||
""" FEtch the page layout"""
|
||||
|
||||
return self.page_layout
|
||||
|
||||
|
||||
def get_app_namespace(self) -> str:
|
||||
"""Fetch the Application namespace if specified.
|
||||
|
||||
|
0
app/access/serializers/__init__.py
Normal file
0
app/access/serializers/__init__.py
Normal file
75
app/access/serializers/contact.py
Normal file
75
app/access/serializers/contact.py
Normal file
@ -0,0 +1,75 @@
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.models.contact import Contact
|
||||
|
||||
from access.serializers.person import (
|
||||
BaseSerializer as BaseBaseSerializer,
|
||||
ModelSerializer as BaseModelSerializer,
|
||||
)
|
||||
from access.serializers.organization import OrganizationBaseSerializer
|
||||
|
||||
|
||||
|
||||
class BaseSerializer(
|
||||
BaseBaseSerializer,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'ContactEntityModelSerializer')
|
||||
class ModelSerializer(
|
||||
BaseSerializer,
|
||||
BaseModelSerializer,
|
||||
):
|
||||
"""Contact Model
|
||||
|
||||
This model first inherits from Person then inherits from the Entity Base model.
|
||||
"""
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Contact
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'person_ptr_id',
|
||||
'organization',
|
||||
'entity_type',
|
||||
'display_name',
|
||||
'f_name',
|
||||
'm_name',
|
||||
'l_name',
|
||||
'dob',
|
||||
'email',
|
||||
'directory',
|
||||
'model_notes',
|
||||
'is_global',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'entity_type',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'ContactEntityViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
):
|
||||
"""Contact View Model
|
||||
|
||||
This model inherits from the Person model.
|
||||
"""
|
||||
|
||||
organization = OrganizationBaseSerializer(many=False, read_only=True)
|
90
app/access/serializers/entity.py
Normal file
90
app/access/serializers/entity.py
Normal file
@ -0,0 +1,90 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.models.entity import Entity
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from access.serializers.organization import OrganizationBaseSerializer
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'EntityBaseBaseSerializer')
|
||||
class BaseSerializer(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 = Entity
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'url',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'EntityBaseModelSerializer')
|
||||
class ModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
BaseSerializer
|
||||
):
|
||||
"""Entity Base Model"""
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Entity
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'entity_type',
|
||||
'display_name',
|
||||
'model_notes',
|
||||
'is_global',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'entity_type',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'EntityBaseViewSerializer')
|
||||
class ViewSerializer(ModelSerializer):
|
||||
"""Entity Base View Model"""
|
||||
|
||||
organization = OrganizationBaseSerializer(many=False, read_only=True)
|
41
app/access/serializers/entity_notes.py
Normal file
41
app/access/serializers/entity_notes.py
Normal file
@ -0,0 +1,41 @@
|
||||
from core.serializers.model_notes import (
|
||||
ModelNoteBaseSerializer,
|
||||
ModelNoteModelSerializer,
|
||||
ModelNoteViewSerializer
|
||||
)
|
||||
|
||||
from access.models.entity_notes import EntityNotes
|
||||
|
||||
|
||||
|
||||
class EntityNoteBaseSerializer(ModelNoteBaseSerializer):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class EntityNoteModelSerializer(
|
||||
ModelNoteModelSerializer
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = EntityNotes
|
||||
|
||||
fields = ModelNoteModelSerializer.Meta.fields + [
|
||||
'model',
|
||||
]
|
||||
|
||||
read_only_fields = ModelNoteModelSerializer.Meta.read_only_fields + [
|
||||
'model',
|
||||
'content_type',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class EntityNoteViewSerializer(
|
||||
ModelNoteViewSerializer,
|
||||
EntityNoteModelSerializer,
|
||||
):
|
||||
|
||||
pass
|
73
app/access/serializers/person.py
Normal file
73
app/access/serializers/person.py
Normal file
@ -0,0 +1,73 @@
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.models.person import Person
|
||||
|
||||
from access.serializers.entity import (
|
||||
BaseSerializer as BaseBaseSerializer,
|
||||
ModelSerializer as BaseModelSerializer,
|
||||
)
|
||||
from access.serializers.organization import OrganizationBaseSerializer
|
||||
|
||||
|
||||
|
||||
class BaseSerializer(
|
||||
BaseBaseSerializer,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'PersonEntityModelSerializer')
|
||||
class ModelSerializer(
|
||||
BaseSerializer,
|
||||
BaseModelSerializer,
|
||||
):
|
||||
"""Person Model
|
||||
|
||||
This model inherits from the Entity base model.
|
||||
"""
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Person
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'entity_ptr_id',
|
||||
'organization',
|
||||
'entity_type',
|
||||
'display_name',
|
||||
'f_name',
|
||||
'm_name',
|
||||
'l_name',
|
||||
'dob',
|
||||
'model_notes',
|
||||
'is_global',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'entity_type',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'PersonEntityViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
):
|
||||
"""Person View Model
|
||||
|
||||
This model inherits from the Entity base model.
|
||||
"""
|
||||
|
||||
organization = OrganizationBaseSerializer(many=False, read_only=True)
|
114
app/access/serializers/role.py
Normal file
114
app/access/serializers/role.py
Normal file
@ -0,0 +1,114 @@
|
||||
from rest_framework import serializers
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.functions.permissions import permission_queryset
|
||||
from access.models.role import Role
|
||||
from access.serializers.organization import OrganizationBaseSerializer
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from app.serializers.permission import PermissionBaseSerializer
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'RoleBaseSerializer')
|
||||
class BaseSerializer(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 = Role
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'url',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'RoleModelSerializer')
|
||||
class ModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
BaseSerializer
|
||||
):
|
||||
"""Role Base Model"""
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> dict:
|
||||
|
||||
get_url = super().get_url( item = item )
|
||||
|
||||
get_url.update({
|
||||
'tickets': reverse(
|
||||
"v2:_api_v2_item_tickets-list",
|
||||
request=self._context['view'].request,
|
||||
kwargs={
|
||||
'item_class': self.Meta.model._meta.model_name,
|
||||
'item_id': item.pk
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return get_url
|
||||
|
||||
|
||||
permissions = serializers.PrimaryKeyRelatedField(many = True, queryset=permission_queryset(), required = False)
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Role
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'name',
|
||||
'permissions',
|
||||
'model_notes',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'RoleViewSerializer')
|
||||
class ViewSerializer(ModelSerializer):
|
||||
"""Role Base View Model"""
|
||||
|
||||
organization = OrganizationBaseSerializer( many=False, read_only=True )
|
||||
|
||||
permissions = PermissionBaseSerializer( many=True, read_only=True )
|
48
app/access/serializers/role_notes.py
Normal file
48
app/access/serializers/role_notes.py
Normal file
@ -0,0 +1,48 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from access.models.role_notes import RoleNotes
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from app.serializers.user import UserBaseSerializer
|
||||
|
||||
from core.serializers.model_notes import (
|
||||
ModelNotes,
|
||||
ModelNoteBaseSerializer,
|
||||
ModelNoteModelSerializer,
|
||||
ModelNoteViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
class RoleNoteBaseSerializer(ModelNoteBaseSerializer):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class RoleNoteModelSerializer(
|
||||
ModelNoteModelSerializer
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = RoleNotes
|
||||
|
||||
fields = ModelNoteModelSerializer.Meta.fields + [
|
||||
'model',
|
||||
]
|
||||
|
||||
read_only_fields = ModelNoteModelSerializer.Meta.read_only_fields + [
|
||||
'model',
|
||||
'content_type',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class RoleNoteViewSerializer(
|
||||
ModelNoteViewSerializer,
|
||||
RoleNoteModelSerializer,
|
||||
):
|
||||
|
||||
pass
|
@ -0,0 +1,60 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.contact import Contact
|
||||
from access.tests.functional.person.test_functional_person_history import (
|
||||
PersonHistoryInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ContactTestCases(
|
||||
PersonHistoryInheritedCases,
|
||||
):
|
||||
|
||||
field_name = 'model_notes'
|
||||
|
||||
kwargs_create_obj: dict = {
|
||||
'email': 'ipfunny@unit.test',
|
||||
}
|
||||
|
||||
kwargs_delete_obj: dict = {
|
||||
'email': 'ipweird@unit.test',
|
||||
}
|
||||
|
||||
model = Contact
|
||||
|
||||
|
||||
|
||||
class ContactHistoryInheritedCases(
|
||||
ContactTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
"""Entity model to test"""
|
||||
|
||||
kwargs_create_obj: dict = None
|
||||
|
||||
kwargs_delete_obj: dict = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_obj.update(
|
||||
super().kwargs_create_obj
|
||||
)
|
||||
|
||||
self.kwargs_delete_obj.update(
|
||||
super().kwargs_delete_obj
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class ContactHistoryTest(
|
||||
ContactTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
@ -0,0 +1,129 @@
|
||||
import pytest
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from access.serializers.contact import (
|
||||
Contact,
|
||||
ModelSerializer
|
||||
)
|
||||
from access.tests.functional.person.test_functional_person_serializer import (
|
||||
PersonSerializerInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class SerializerTestCases(
|
||||
PersonSerializerInheritedCases,
|
||||
):
|
||||
|
||||
duplicate_f_name_l_name_dob = {
|
||||
'email': 'contactentityduplicateone@unit.test',
|
||||
}
|
||||
|
||||
kwargs_create_item: dict = {
|
||||
'email': 'ipfunny@unit.test',
|
||||
}
|
||||
|
||||
kwargs_create_item_duplicate_f_name_l_name_dob = {
|
||||
'email': 'contactentityduplicatetwo@unit.test',
|
||||
}
|
||||
|
||||
model = Contact
|
||||
"""Model to test"""
|
||||
|
||||
create_model_serializer = ModelSerializer
|
||||
"""Serializer to test"""
|
||||
|
||||
valid_data: dict = {
|
||||
'email': 'ipweird@unit.test',
|
||||
}
|
||||
|
||||
|
||||
|
||||
def test_serializer_validation_no_email_exception(self):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that when creating with valid data and field email is missing
|
||||
a validation error occurs.
|
||||
"""
|
||||
|
||||
data = self.valid_data.copy()
|
||||
|
||||
del data['email']
|
||||
|
||||
with pytest.raises(ValidationError) as err:
|
||||
|
||||
serializer = self.create_model_serializer(
|
||||
data = data
|
||||
)
|
||||
|
||||
serializer.is_valid(raise_exception = True)
|
||||
|
||||
assert err.value.get_codes()['email'][0] == 'required'
|
||||
|
||||
|
||||
|
||||
class ContactSerializerInheritedCases(
|
||||
SerializerTestCases,
|
||||
):
|
||||
|
||||
create_model_serializer = None
|
||||
"""Serializer to test"""
|
||||
|
||||
duplicate_f_name_l_name_dob: dict = None
|
||||
""" Duplicate model serializer dict
|
||||
|
||||
used for testing for duplicate f_name, l_name and dob fields.
|
||||
"""
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
""" Model kwargs to create item"""
|
||||
|
||||
kwargs_create_item_duplicate_f_name_l_name_dob: dict = None
|
||||
"""model kwargs to create object
|
||||
|
||||
**None:** Ensure that the fields of sub-model to person do not match
|
||||
`self.duplicate_f_name_l_name_dob`. if they do the wrong exception will be thrown.
|
||||
|
||||
used for testing for duplicate f_name, l_name and dob fields.
|
||||
"""
|
||||
|
||||
model = None
|
||||
"""Model to test"""
|
||||
|
||||
valid_data: dict = None
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test"""
|
||||
|
||||
self.duplicate_f_name_l_name_dob.update(
|
||||
super().duplicate_f_name_l_name_dob
|
||||
)
|
||||
|
||||
self.kwargs_create_item_duplicate_f_name_l_name_dob.update(
|
||||
super().kwargs_create_item_duplicate_f_name_l_name_dob
|
||||
)
|
||||
|
||||
self.kwargs_create_item.update(
|
||||
super().kwargs_create_item
|
||||
)
|
||||
|
||||
self.valid_data.update(
|
||||
super().valid_data
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class ContactSerializerTest(
|
||||
SerializerTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
@ -0,0 +1,169 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.contact import Contact
|
||||
from access.tests.functional.person.test_functional_person_viewset import (
|
||||
PersonMetadataInheritedCases,
|
||||
PersonPermissionsAPIInheritedCases,
|
||||
PersonViewSetInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ViewSetBase:
|
||||
|
||||
add_data = {
|
||||
'email': 'ipfunny@unit.test',
|
||||
}
|
||||
|
||||
kwargs_create_item_diff_org = {
|
||||
'email': 'ipstrange@unit.test',
|
||||
}
|
||||
|
||||
kwargs_create_item = {
|
||||
'email': 'ipweird@unit.test',
|
||||
}
|
||||
|
||||
model = Contact
|
||||
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
|
||||
|
||||
class PermissionsAPITestCases(
|
||||
ViewSetBase,
|
||||
PersonPermissionsAPIInheritedCases,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class ContactPermissionsAPIInheritedCases(
|
||||
PermissionsAPITestCases,
|
||||
):
|
||||
|
||||
add_data: dict = None
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.add_data.update(
|
||||
super().add_data
|
||||
)
|
||||
|
||||
self.kwargs_create_item.update(
|
||||
super().kwargs_create_item
|
||||
)
|
||||
|
||||
self.kwargs_create_item_diff_org.update(
|
||||
super().kwargs_create_item_diff_org
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class ContactPermissionsAPITest(
|
||||
PermissionsAPITestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class ViewSetTestCases(
|
||||
ViewSetBase,
|
||||
PersonViewSetInheritedCases,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class ContactViewSetInheritedCases(
|
||||
ViewSetTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_item.update(
|
||||
super().kwargs_create_item
|
||||
)
|
||||
|
||||
self.kwargs_create_item_diff_org.update(
|
||||
super().kwargs_create_item_diff_org
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class ContactViewSetTest(
|
||||
ViewSetTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class MetadataTestCases(
|
||||
ViewSetBase,
|
||||
PersonMetadataInheritedCases,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class ContactMetadataInheritedCases(
|
||||
MetadataTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_item.update(
|
||||
super().kwargs_create_item
|
||||
)
|
||||
|
||||
self.kwargs_create_item_diff_org.update(
|
||||
super().kwargs_create_item_diff_org
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class ContactMetadataTest(
|
||||
MetadataTestCases,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
|
||||
pass
|
@ -0,0 +1,78 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.entity_history import Entity, EntityHistory
|
||||
|
||||
from core.tests.abstract.test_functional_history import HistoryEntriesCommon
|
||||
|
||||
|
||||
|
||||
class HistoryTestCases(
|
||||
HistoryEntriesCommon,
|
||||
):
|
||||
|
||||
field_name = 'model_notes'
|
||||
|
||||
history_model = EntityHistory
|
||||
|
||||
kwargs_create_obj: dict = {}
|
||||
|
||||
kwargs_delete_obj: dict = {}
|
||||
|
||||
model = Entity
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
self.obj = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = self.field_value_original,
|
||||
**self.kwargs_create_obj,
|
||||
)
|
||||
|
||||
self.obj_delete = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = 'another note',
|
||||
**self.kwargs_delete_obj,
|
||||
)
|
||||
|
||||
self.call_the_banners()
|
||||
|
||||
|
||||
|
||||
class EntityHistoryInheritedCases(
|
||||
HistoryTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
"""Entity model to test"""
|
||||
|
||||
kwargs_create_obj: dict = None
|
||||
|
||||
kwargs_delete_obj: dict = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_obj.update(
|
||||
super().kwargs_create_obj
|
||||
)
|
||||
|
||||
self.kwargs_delete_obj.update(
|
||||
super().kwargs_delete_obj
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class EntityHistoryTest(
|
||||
HistoryTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
@ -0,0 +1,106 @@
|
||||
import pytest
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.serializers.entity import (
|
||||
Entity,
|
||||
ModelSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
class SerializerTestCases:
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
""" Model kwargs to create item"""
|
||||
|
||||
model = Entity
|
||||
"""Model to test"""
|
||||
|
||||
create_model_serializer = ModelSerializer
|
||||
"""Serializer to test"""
|
||||
|
||||
valid_data: dict = {}
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test"""
|
||||
|
||||
self.organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.kwargs_create_item.update({
|
||||
'model_notes': 'model notes field'
|
||||
})
|
||||
|
||||
self.valid_data.update({
|
||||
'organization': self.organization.pk,
|
||||
'model_notes': 'model notes field'
|
||||
})
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
**self.kwargs_create_item,
|
||||
)
|
||||
|
||||
|
||||
|
||||
def test_serializer_valid_data(self):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that when creating an object with valid data, no validation
|
||||
error occurs.
|
||||
"""
|
||||
|
||||
serializer = self.create_model_serializer(
|
||||
data = self.valid_data
|
||||
)
|
||||
|
||||
assert serializer.is_valid(raise_exception = True)
|
||||
|
||||
|
||||
def test_serializer_validation_no_model_notes(self):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that if creating and no model_notes is provided no validation
|
||||
error occurs
|
||||
"""
|
||||
|
||||
data = self.valid_data.copy()
|
||||
|
||||
del data['model_notes']
|
||||
|
||||
serializer = self.create_model_serializer(
|
||||
data = data
|
||||
)
|
||||
|
||||
assert serializer.is_valid(raise_exception = True)
|
||||
|
||||
|
||||
|
||||
class EntitySerializerInheritedCases(
|
||||
SerializerTestCases,
|
||||
):
|
||||
|
||||
create_model_serializer = None
|
||||
"""Serializer to test"""
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
""" Model kwargs to create item"""
|
||||
|
||||
model = None
|
||||
"""Model to test"""
|
||||
|
||||
valid_data: dict = None
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
|
||||
class EntitySerializerTest(
|
||||
SerializerTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
@ -0,0 +1,504 @@
|
||||
from django.contrib.auth.models import Permission, User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.entity import Entity
|
||||
from access.models.organization import Organization
|
||||
from access.models.team import Team
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
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:
|
||||
|
||||
add_data: dict = None
|
||||
|
||||
app_namespace = 'v2'
|
||||
|
||||
change_data = None
|
||||
|
||||
delete_data = {}
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
model = None
|
||||
|
||||
url_kwargs: dict = None
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
|
||||
url_name = None
|
||||
|
||||
|
||||
@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.different_organization = Organization.objects.create(name='test_different_organization')
|
||||
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = organization,
|
||||
model_notes = 'some notes',
|
||||
**self.kwargs_create_item
|
||||
)
|
||||
|
||||
self.other_org_item = self.model.objects.create(
|
||||
organization = self.different_organization,
|
||||
model_notes = 'some more notes',
|
||||
**self.kwargs_create_item_diff_org
|
||||
)
|
||||
|
||||
|
||||
self.url_view_kwargs.update({ 'pk': self.item.id })
|
||||
|
||||
if self.add_data is not None:
|
||||
|
||||
self.add_data.update({'organization': self.organization.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])
|
||||
|
||||
|
||||
|
||||
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")
|
||||
TeamUsers.objects.create(
|
||||
team = view_team,
|
||||
user = self.view_user
|
||||
)
|
||||
|
||||
self.add_user = User.objects.create_user(username="test_user_add", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = add_team,
|
||||
user = self.add_user
|
||||
)
|
||||
|
||||
self.change_user = User.objects.create_user(username="test_user_change", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = change_team,
|
||||
user = self.change_user
|
||||
)
|
||||
|
||||
self.delete_user = User.objects.create_user(username="test_user_delete", password="password")
|
||||
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 = self.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 PermissionsAPITestCases(
|
||||
ViewSetBase,
|
||||
APIPermissions,
|
||||
):
|
||||
|
||||
|
||||
add_data: dict = {}
|
||||
|
||||
change_data = {'model_notes': 'device'}
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
url_kwargs: dict = None
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
|
||||
url_name = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.add_data.update({ 'model_note': 'added model note' })
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
def test_returned_data_from_user_and_global_organizations_only(self):
|
||||
"""Check items returned
|
||||
|
||||
This test case is a over-ride of a test case with the same name.
|
||||
This model is not a tenancy model making this test not-applicable.
|
||||
|
||||
Items returned from the query Must be from the users organization and
|
||||
global ONLY!
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class EntityPermissionsAPIInheritedCases(
|
||||
PermissionsAPITestCases,
|
||||
):
|
||||
|
||||
add_data: dict = None
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
url_name = '_api_v2_entity_sub'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.url_kwargs = {
|
||||
'entity_model': self.model._meta.model_name
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'entity_model': self.model._meta.model_name
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class EntityPermissionsAPITest(
|
||||
PermissionsAPITestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
model = Entity
|
||||
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
url_name = '_api_v2_entity'
|
||||
|
||||
|
||||
|
||||
|
||||
class ViewSetTestCases(
|
||||
ViewSetBase,
|
||||
SerializersTestCases,
|
||||
):
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
model = None
|
||||
|
||||
url_kwargs: dict = None
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
|
||||
url_name = None
|
||||
|
||||
|
||||
|
||||
class EntityViewSetInheritedCases(
|
||||
ViewSetTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
url_name = '_api_v2_entity_sub'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.url_kwargs = {
|
||||
'entity_model': self.model._meta.model_name
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'entity_model': self.model._meta.model_name
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class EntityViewSetTest(
|
||||
ViewSetTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
model = Entity
|
||||
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
url_name = '_api_v2_entity'
|
||||
|
||||
|
||||
|
||||
class MetadataTestCases(
|
||||
ViewSetBase,
|
||||
MetadataAttributesFunctional,
|
||||
):
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
model = None
|
||||
|
||||
url_kwargs: dict = None
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
|
||||
url_name = None
|
||||
|
||||
|
||||
|
||||
class EntityMetadataInheritedCases(
|
||||
MetadataTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
url_name = '_api_v2_entity_sub'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.url_kwargs = {
|
||||
'entity_model': self.model._meta.model_name
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'entity_model': self.model._meta.model_name
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class EntityMetadataTest(
|
||||
MetadataTestCases,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
model = Entity
|
||||
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
url_name = '_api_v2_entity'
|
||||
|
||||
|
||||
# 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
|
||||
|
||||
|
@ -0,0 +1,81 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.entity_notes import Entity, EntityNotes
|
||||
|
||||
from core.tests.abstract.model_notes_api_fields import ModelNotesNotesAPIFields
|
||||
|
||||
|
||||
|
||||
class NotesAPITestCases(
|
||||
ModelNotesNotesAPIFields,
|
||||
):
|
||||
|
||||
entity_model = None
|
||||
|
||||
model = EntityNotes
|
||||
|
||||
kwargs_model_create: dict = None
|
||||
|
||||
# url_view_kwargs: dict = None
|
||||
|
||||
view_name: str = '_api_v2_entity_note'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Call parent setup
|
||||
2. Create a model note
|
||||
3. add url kwargs
|
||||
4. make the API request
|
||||
|
||||
"""
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
content = 'a random comment',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = self.entity_model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = 'text',
|
||||
**self.kwargs_model_create
|
||||
),
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'model_id': self.item.model.pk,
|
||||
'pk': self.item.pk
|
||||
}
|
||||
|
||||
self.make_request()
|
||||
|
||||
|
||||
|
||||
class EntityNotesAPIInheritedCases(
|
||||
NotesAPITestCases,
|
||||
):
|
||||
|
||||
entity_model = None
|
||||
|
||||
kwargs_model_create = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesAPITest(
|
||||
NotesAPITestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
entity_model = Entity
|
||||
|
||||
kwargs_model_create = {}
|
@ -0,0 +1,162 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.viewsets.entity_notes import ViewSet
|
||||
|
||||
from core.tests.abstract.test_functional_notes_viewset import (
|
||||
ModelNotesViewSetBase,
|
||||
ModelNotesMetadata,
|
||||
ModelNotesPermissionsAPI,
|
||||
ModelNotesSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ViewSetBase(
|
||||
ModelNotesViewSetBase
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
kwargs_create_model_item: dict = {}
|
||||
|
||||
kwargs_create_model_item_other_org: dict = {}
|
||||
|
||||
url_name = '_api_v2_entity_note'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
self.item = self.viewset.model.objects.create(
|
||||
organization = self.organization,
|
||||
content = 'a random comment',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = self.viewset.model.model.field.related_model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = 'text',
|
||||
**self.kwargs_create_model_item
|
||||
),
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
self.other_org_item = self.viewset.model.objects.create(
|
||||
organization = self.different_organization,
|
||||
content = 'a random comment',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = self.viewset.model.model.field.related_model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = 'text',
|
||||
**self.kwargs_create_model_item_other_org
|
||||
),
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
self.url_kwargs = {
|
||||
'model_id': self.item.model.pk,
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'model_id': self.item.model.pk,
|
||||
'pk': self.item.id
|
||||
}
|
||||
|
||||
|
||||
|
||||
class NotesPermissionsAPITestCases(
|
||||
ViewSetBase,
|
||||
ModelNotesPermissionsAPI,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
def test_returned_data_from_user_and_global_organizations_only(self):
|
||||
"""Check items returned
|
||||
|
||||
This test case is a over-ride of a test case with the same name.
|
||||
This model is not a global model making this test not-applicable.
|
||||
|
||||
Items returned from the query Must be from the users organization and
|
||||
global ONLY!
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class EntityNotesPermissionsAPIInheritedCases(
|
||||
NotesPermissionsAPITestCases,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesPermissionsAPITest(
|
||||
NotesPermissionsAPITestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
|
||||
|
||||
class NotesSerializerTestCases(
|
||||
ViewSetBase,
|
||||
ModelNotesSerializer,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesSerializerInheritedCases(
|
||||
NotesSerializerTestCases,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesSerializerTest(
|
||||
NotesSerializerTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
|
||||
|
||||
class NotesMetadataTestCases(
|
||||
ViewSetBase,
|
||||
ModelNotesMetadata,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesMetadataInheritedCases(
|
||||
NotesMetadataTestCases,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesMetadataTest(
|
||||
NotesMetadataTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
@ -0,0 +1,65 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.person import Person
|
||||
from access.tests.functional.entity.test_functional_entity_history import (
|
||||
EntityHistoryInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class PersonTestCases(
|
||||
EntityHistoryInheritedCases,
|
||||
):
|
||||
|
||||
field_name = 'model_notes'
|
||||
|
||||
kwargs_create_obj: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Funny',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
kwargs_delete_obj: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Weird',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
model = Person
|
||||
|
||||
|
||||
class PersonHistoryInheritedCases(
|
||||
PersonTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
"""Entity model to test"""
|
||||
|
||||
kwargs_create_obj: dict = None
|
||||
|
||||
kwargs_delete_obj: dict = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_obj.update(
|
||||
super().kwargs_create_obj
|
||||
)
|
||||
|
||||
self.kwargs_delete_obj.update(
|
||||
super().kwargs_delete_obj
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class PersonHistoryTest(
|
||||
PersonTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
@ -0,0 +1,230 @@
|
||||
import pytest
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from access.serializers.person import (
|
||||
Person,
|
||||
ModelSerializer
|
||||
)
|
||||
from access.tests.functional.entity.test_functional_entity_serializer import (
|
||||
EntitySerializerInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class SerializerTestCases(
|
||||
EntitySerializerInheritedCases,
|
||||
):
|
||||
|
||||
create_model_serializer = ModelSerializer
|
||||
"""Serializer to test"""
|
||||
|
||||
duplicate_f_name_l_name_dob: dict = {
|
||||
'f_name': 'fred',
|
||||
'm_name': 'D',
|
||||
'l_name': 'Flinstone',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
kwargs_create_item_duplicate_f_name_l_name_dob: dict = {
|
||||
'f_name': 'fred',
|
||||
'm_name': 'D',
|
||||
'l_name': 'Flinstone',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
kwargs_create_item: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Funny',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
model = Person
|
||||
"""Model to test"""
|
||||
|
||||
valid_data: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Strange',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
|
||||
|
||||
def test_serializer_validation_no_f_name_exception(self):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that when creating with valid data and field f_name is missing
|
||||
a validation error occurs.
|
||||
"""
|
||||
|
||||
data = self.valid_data.copy()
|
||||
|
||||
del data['f_name']
|
||||
|
||||
with pytest.raises(ValidationError) as err:
|
||||
|
||||
serializer = self.create_model_serializer(
|
||||
data = data
|
||||
)
|
||||
|
||||
serializer.is_valid(raise_exception = True)
|
||||
|
||||
assert err.value.get_codes()['f_name'][0] == 'required'
|
||||
|
||||
|
||||
|
||||
def test_serializer_validation_no_m_name(self):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that when creating with valid data and field f_name is missing
|
||||
no validation error occurs.
|
||||
"""
|
||||
|
||||
data = self.valid_data.copy()
|
||||
|
||||
del data['m_name']
|
||||
|
||||
serializer = self.create_model_serializer(
|
||||
data = data
|
||||
)
|
||||
|
||||
assert serializer.is_valid(raise_exception = True)
|
||||
|
||||
|
||||
|
||||
def test_serializer_validation_no_l_name_exception(self):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that when creating with valid data and field l_name is missing
|
||||
a validation error occurs.
|
||||
"""
|
||||
|
||||
data = self.valid_data.copy()
|
||||
|
||||
del data['l_name']
|
||||
|
||||
with pytest.raises(ValidationError) as err:
|
||||
|
||||
serializer = self.create_model_serializer(
|
||||
data = data
|
||||
)
|
||||
|
||||
serializer.is_valid(raise_exception = True)
|
||||
|
||||
assert err.value.get_codes()['l_name'][0] == 'required'
|
||||
|
||||
|
||||
|
||||
def test_serializer_validation_no_dob(self):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that when creating with valid data and field dob is missing
|
||||
no validation error occurs.
|
||||
"""
|
||||
|
||||
data = self.valid_data.copy()
|
||||
|
||||
del data['dob']
|
||||
|
||||
serializer = self.create_model_serializer(
|
||||
data = data
|
||||
)
|
||||
|
||||
assert serializer.is_valid(raise_exception = True)
|
||||
|
||||
|
||||
|
||||
def test_serializer_validation_duplicate_f_name_l_name_dob(self):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that when creating with valid data and fields f_name, l_name and
|
||||
dob already exists in the db a validation error occurs.
|
||||
"""
|
||||
|
||||
self.model.objects.create(
|
||||
organization = self.organization,
|
||||
**self.kwargs_create_item_duplicate_f_name_l_name_dob
|
||||
)
|
||||
|
||||
data = self.duplicate_f_name_l_name_dob.copy()
|
||||
|
||||
with pytest.raises(ValidationError) as err:
|
||||
|
||||
serializer = self.create_model_serializer(
|
||||
data = data
|
||||
)
|
||||
|
||||
serializer.is_valid(raise_exception = True)
|
||||
|
||||
serializer.save()
|
||||
|
||||
assert err.value.get_codes()['dob'] == 'duplicate_person_on_dob'
|
||||
|
||||
|
||||
|
||||
class PersonSerializerInheritedCases(
|
||||
SerializerTestCases,
|
||||
):
|
||||
|
||||
create_model_serializer = None
|
||||
"""Serializer to test"""
|
||||
|
||||
duplicate_f_name_l_name_dob: dict = None
|
||||
""" Duplicate model serializer dict
|
||||
|
||||
used for testing for duplicate f_name, l_name and dob fields.
|
||||
"""
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
""" Model kwargs to create item"""
|
||||
|
||||
kwargs_create_item_duplicate_f_name_l_name_dob: dict = None
|
||||
"""model kwargs to create object
|
||||
|
||||
**None:** Ensure that the fields of sub-model to person do not match
|
||||
`self.duplicate_f_name_l_name_dob`. if they do the wrong exception will be thrown.
|
||||
|
||||
used for testing for duplicate f_name, l_name and dob fields.
|
||||
"""
|
||||
|
||||
model = None
|
||||
"""Model to test"""
|
||||
|
||||
valid_data: dict = None
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test"""
|
||||
|
||||
self.duplicate_f_name_l_name_dob.update(
|
||||
super().duplicate_f_name_l_name_dob
|
||||
)
|
||||
|
||||
self.kwargs_create_item_duplicate_f_name_l_name_dob.update(
|
||||
super().kwargs_create_item_duplicate_f_name_l_name_dob
|
||||
)
|
||||
|
||||
self.kwargs_create_item.update(
|
||||
super().kwargs_create_item
|
||||
)
|
||||
|
||||
self.valid_data.update(
|
||||
super().valid_data
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class PersonSerializerTest(
|
||||
SerializerTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
@ -0,0 +1,178 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.person import Person
|
||||
from access.tests.functional.entity.test_functional_entity_viewset import (
|
||||
EntityMetadataInheritedCases,
|
||||
EntityPermissionsAPIInheritedCases,
|
||||
EntityViewSetInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ViewSetBase:
|
||||
|
||||
add_data = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Strange',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
kwargs_create_item_diff_org = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Funny',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
kwargs_create_item = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Weird',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
model = Person
|
||||
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
|
||||
|
||||
class PermissionsAPITestCases(
|
||||
ViewSetBase,
|
||||
EntityPermissionsAPIInheritedCases,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class PersonPermissionsAPIInheritedCases(
|
||||
PermissionsAPITestCases,
|
||||
):
|
||||
|
||||
add_data: dict = None
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.add_data.update(
|
||||
super().add_data
|
||||
)
|
||||
|
||||
self.kwargs_create_item.update(
|
||||
super().kwargs_create_item
|
||||
)
|
||||
|
||||
self.kwargs_create_item_diff_org.update(
|
||||
super().kwargs_create_item_diff_org
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class PersonPermissionsAPITest(
|
||||
PermissionsAPITestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class ViewSetTestCases(
|
||||
ViewSetBase,
|
||||
EntityViewSetInheritedCases,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class PersonViewSetInheritedCases(
|
||||
ViewSetTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_item.update(
|
||||
super().kwargs_create_item
|
||||
)
|
||||
|
||||
self.kwargs_create_item_diff_org.update(
|
||||
super().kwargs_create_item_diff_org
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class PersonViewSetTest(
|
||||
ViewSetTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class MetadataTestCases(
|
||||
ViewSetBase,
|
||||
EntityMetadataInheritedCases,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class PersonMetadataInheritedCases(
|
||||
MetadataTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_item.update(
|
||||
super().kwargs_create_item
|
||||
)
|
||||
|
||||
self.kwargs_create_item_diff_org.update(
|
||||
super().kwargs_create_item_diff_org
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class PersonMetadataTest(
|
||||
MetadataTestCases,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
|
||||
pass
|
@ -0,0 +1,55 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.role_history import Role, RoleHistory
|
||||
|
||||
from core.tests.abstract.test_functional_history import HistoryEntriesCommon
|
||||
|
||||
|
||||
|
||||
class HistoryTestCases(
|
||||
HistoryEntriesCommon,
|
||||
):
|
||||
|
||||
history_model = RoleHistory
|
||||
|
||||
kwargs_create_obj: dict = {}
|
||||
|
||||
kwargs_delete_obj: dict = {}
|
||||
|
||||
model = Role
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
self.obj = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = self.field_value_original,
|
||||
**self.kwargs_create_obj,
|
||||
)
|
||||
|
||||
self.obj_delete = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = 'another note',
|
||||
**self.kwargs_delete_obj,
|
||||
)
|
||||
|
||||
self.call_the_banners()
|
||||
|
||||
|
||||
|
||||
class RoleHistoryTest(
|
||||
HistoryTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
kwargs_create_obj: dict = {
|
||||
'name': 'original_name'
|
||||
}
|
||||
|
||||
kwargs_delete_obj: dict = {
|
||||
'name': 'delete obj'
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
import pytest
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.serializers.role import Role, ModelSerializer
|
||||
|
||||
|
||||
|
||||
class ValidationSerializer(
|
||||
TestCase,
|
||||
):
|
||||
|
||||
model = Role
|
||||
|
||||
serializer = ModelSerializer
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an org
|
||||
2. Create an item
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.diff_organization = Organization.objects.create(name='test_org_diff_org')
|
||||
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
name = 'one',
|
||||
)
|
||||
|
||||
self.valid_data = {
|
||||
'organization': self.organization.id,
|
||||
'name': 'two',
|
||||
'model_notes': 'dfsdfsd',
|
||||
}
|
||||
|
||||
|
||||
|
||||
def test_serializer_validation_valid_data(self):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that if creating and no name is provided a validation error occurs
|
||||
"""
|
||||
|
||||
serializer = self.serializer(
|
||||
data = self.valid_data
|
||||
)
|
||||
|
||||
|
||||
assert serializer.is_valid( raise_exception = True )
|
||||
|
||||
|
||||
def test_serializer_validation_no_name_exception(self):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that when creating and field name is not provided a
|
||||
validation error occurs
|
||||
"""
|
||||
|
||||
valid_data = self.valid_data.copy()
|
||||
|
||||
del valid_data['name']
|
||||
|
||||
with pytest.raises(ValidationError) as err:
|
||||
|
||||
serializer = self.serializer(
|
||||
data = valid_data
|
||||
)
|
||||
|
||||
serializer.is_valid(raise_exception = True)
|
||||
|
||||
assert err.value.get_codes()['name'][0] == 'required'
|
283
app/access/tests/functional/role/test_functional_role_viewset.py
Normal file
283
app/access/tests/functional/role/test_functional_role_viewset.py
Normal file
@ -0,0 +1,283 @@
|
||||
from django.contrib.auth.models import Permission, User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from access.models.role import Role
|
||||
from access.models.organization import Organization
|
||||
from access.models.team import Team
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
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
|
||||
|
||||
from settings.models.app_settings import AppSettings
|
||||
|
||||
|
||||
|
||||
class ViewSetBase:
|
||||
|
||||
add_data: dict = None
|
||||
|
||||
app_namespace = 'v2'
|
||||
|
||||
change_data = { 'name': 'changed name' }
|
||||
|
||||
delete_data = {}
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
kwargs_create_item_global_org_org: dict = {}
|
||||
|
||||
model = None
|
||||
|
||||
url_kwargs: dict = None
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
|
||||
url_name = None
|
||||
|
||||
|
||||
@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.different_organization = Organization.objects.create(name='test_different_organization')
|
||||
|
||||
self.global_organization = Organization.objects.create(name='test_global_organization')
|
||||
|
||||
app_settings = AppSettings.objects.get(
|
||||
owner_organization = None
|
||||
)
|
||||
|
||||
app_settings.global_organization = self.global_organization
|
||||
|
||||
app_settings.save()
|
||||
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = organization,
|
||||
model_notes = 'some notes',
|
||||
**self.kwargs_create_item
|
||||
)
|
||||
|
||||
self.other_org_item = self.model.objects.create(
|
||||
organization = self.different_organization,
|
||||
model_notes = 'some more notes',
|
||||
**self.kwargs_create_item_diff_org
|
||||
)
|
||||
|
||||
self.global_org_item = self.model.objects.create(
|
||||
organization = self.global_organization,
|
||||
model_notes = 'some more notes',
|
||||
**self.kwargs_create_item_global_org_org
|
||||
)
|
||||
|
||||
|
||||
# self.url_kwargs = {'organization_id': self.organization.id}
|
||||
|
||||
self.url_view_kwargs.update({ 'pk': self.item.id })
|
||||
|
||||
if self.add_data is not None:
|
||||
|
||||
self.add_data.update({'organization': self.organization.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])
|
||||
|
||||
|
||||
|
||||
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")
|
||||
TeamUsers.objects.create(
|
||||
team = view_team,
|
||||
user = self.view_user
|
||||
)
|
||||
|
||||
self.add_user = User.objects.create_user(username="test_user_add", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = add_team,
|
||||
user = self.add_user
|
||||
)
|
||||
|
||||
self.change_user = User.objects.create_user(username="test_user_change", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = change_team,
|
||||
user = self.change_user
|
||||
)
|
||||
|
||||
self.delete_user = User.objects.create_user(username="test_user_delete", password="password")
|
||||
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 = self.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 RolePermissionsAPITest(
|
||||
ViewSetBase,
|
||||
APIPermissions,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
add_data: dict = { 'name': 'added model note' }
|
||||
|
||||
kwargs_create_item: dict = { 'name': 'create item' }
|
||||
|
||||
kwargs_create_item_diff_org: dict = { 'name': 'diff org create' }
|
||||
|
||||
kwargs_create_item_global_org_org: dict = { 'name': 'global org create' }
|
||||
|
||||
model = Role
|
||||
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
url_name = '_api_v2_role'
|
||||
|
||||
|
||||
|
||||
class RoleViewSetTest(
|
||||
ViewSetBase,
|
||||
SerializersTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
kwargs_create_item: dict = { 'name': 'create item' }
|
||||
|
||||
kwargs_create_item_diff_org: dict = { 'name': 'diff org create' }
|
||||
|
||||
kwargs_create_item_global_org_org: dict = { 'name': 'global org create' }
|
||||
|
||||
model = Role
|
||||
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
url_name = '_api_v2_role'
|
||||
|
||||
|
||||
|
||||
class RoleMetadataTest(
|
||||
ViewSetBase,
|
||||
MetadataAttributesFunctional,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
|
||||
kwargs_create_item: dict = { 'name': 'create item' }
|
||||
|
||||
kwargs_create_item_diff_org: dict = { 'name': 'diff org create' }
|
||||
|
||||
kwargs_create_item_global_org_org: dict = { 'name': 'global org create' }
|
||||
|
||||
model = Role
|
||||
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
url_name = '_api_v2_role'
|
@ -0,0 +1,32 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.contact import Contact
|
||||
from access.tests.unit.person.test_unit_person_access_tenancy_object import (
|
||||
PersonTenancyObjectInheritedCases,
|
||||
)
|
||||
|
||||
class TenancyObjectTestCases(
|
||||
PersonTenancyObjectInheritedCases,
|
||||
):
|
||||
|
||||
model = Contact
|
||||
|
||||
|
||||
class ContactTenancyObjectInheritedCases(
|
||||
TenancyObjectTestCases,
|
||||
):
|
||||
"""Sub-Entity Test Cases
|
||||
|
||||
Test Cases for Entity models that inherit from model Contact
|
||||
"""
|
||||
|
||||
model = None
|
||||
|
||||
|
||||
|
||||
class ContactTenancyObjectTest(
|
||||
TenancyObjectTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
90
app/access/tests/unit/contact/test_unit_contact_api_v2.py
Normal file
90
app/access/tests/unit/contact/test_unit_contact_api_v2.py
Normal file
@ -0,0 +1,90 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.contact import Contact
|
||||
|
||||
from access.tests.unit.person.test_unit_person_api_v2 import (
|
||||
PersonAPIInheritedCases,
|
||||
)
|
||||
|
||||
|
||||
|
||||
class APITestCases(
|
||||
PersonAPIInheritedCases,
|
||||
):
|
||||
|
||||
model = Contact
|
||||
|
||||
kwargs_item_create: dict = {
|
||||
'email': 'ipfunny@unit.test',
|
||||
}
|
||||
|
||||
url_ns_name = '_api_v2_entity_sub'
|
||||
|
||||
|
||||
def test_api_field_exists_email(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
email field must exist
|
||||
"""
|
||||
|
||||
assert 'email' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_email(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
email field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['email']) is str
|
||||
|
||||
|
||||
def test_api_field_exists_directory(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
directory field must exist
|
||||
"""
|
||||
|
||||
assert 'directory' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_directory(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
directory field must be bool
|
||||
"""
|
||||
|
||||
assert type(self.api_data['directory']) is bool
|
||||
|
||||
|
||||
|
||||
class ContactAPIInheritedCases(
|
||||
APITestCases,
|
||||
):
|
||||
"""Sub-Entity Test Cases
|
||||
|
||||
Test Cases for Entity models that inherit from model Contact
|
||||
"""
|
||||
|
||||
kwargs_item_create: dict = None
|
||||
|
||||
model = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_item_create.update(
|
||||
super().kwargs_item_create
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class ContactAPITest(
|
||||
APITestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
@ -0,0 +1,56 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.contact import Contact
|
||||
from access.tests.unit.person.test_unit_person_history_api_v2 import (
|
||||
PersonHistoryAPIInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ContactModelHistoryAPITestCases(
|
||||
PersonHistoryAPIInheritedCases,
|
||||
):
|
||||
""" Model Histoy Test Cases
|
||||
|
||||
Test must be setup by creating object `kwargs_create_audit_object` with the
|
||||
attributes required to create the object.
|
||||
"""
|
||||
|
||||
audit_model = Contact
|
||||
|
||||
kwargs_create_audit_object: dict = {
|
||||
'email': 'ipfunny@unit.test',
|
||||
}
|
||||
|
||||
|
||||
|
||||
class ContactHistoryAPIInheritedCases(
|
||||
ContactModelHistoryAPITestCases,
|
||||
):
|
||||
"""Sub-Entity Test Cases
|
||||
|
||||
Test Cases for Entity models that inherit from model Contact
|
||||
"""
|
||||
|
||||
audit_model = None
|
||||
|
||||
kwargs_create_audit_object: dict = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_audit_object.update(
|
||||
super().kwargs_create_audit_object
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class ContactModelHistoryAPITest(
|
||||
ContactModelHistoryAPITestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
100
app/access/tests/unit/contact/test_unit_contact_model.py
Normal file
100
app/access/tests/unit/contact/test_unit_contact_model.py
Normal file
@ -0,0 +1,100 @@
|
||||
from django.db.models.fields import NOT_PROVIDED
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.contact import Contact
|
||||
from access.tests.unit.person.test_unit_person_model import (
|
||||
Person,
|
||||
PersonModelInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ModelTestCases(
|
||||
PersonModelInheritedCases,
|
||||
):
|
||||
|
||||
model = Contact
|
||||
|
||||
kwargs_item_create: dict = {
|
||||
'email': 'ipweird@unit.test',
|
||||
}
|
||||
|
||||
|
||||
|
||||
def test_model_field_directory_optional(self):
|
||||
"""Test Field
|
||||
|
||||
Field `dob` must be an optional field
|
||||
"""
|
||||
|
||||
assert self.model._meta.get_field('directory').blank
|
||||
|
||||
|
||||
def test_model_field_directory_optional_default(self):
|
||||
"""Test Field
|
||||
|
||||
Field `directory` default value is `True`
|
||||
"""
|
||||
|
||||
assert (
|
||||
self.model._meta.get_field('directory').default is True
|
||||
and self.model._meta.get_field('directory').null is False
|
||||
)
|
||||
|
||||
|
||||
def test_model_field_email_mandatory(self):
|
||||
"""Test Field
|
||||
|
||||
Field `email` must be a mandatory field
|
||||
"""
|
||||
|
||||
assert(
|
||||
not (
|
||||
self.model._meta.get_field('email').blank
|
||||
and self.model._meta.get_field('email').null
|
||||
)
|
||||
and self.model._meta.get_field('email').default is NOT_PROVIDED
|
||||
)
|
||||
|
||||
|
||||
def test_model_inherits_person(self):
|
||||
"""Test model inheritence
|
||||
|
||||
model must inherit from Entity sub-model `Person`
|
||||
"""
|
||||
|
||||
assert issubclass(self.model, Person)
|
||||
|
||||
|
||||
|
||||
|
||||
class ContactModelInheritedCases(
|
||||
ModelTestCases,
|
||||
):
|
||||
"""Sub-Entity Test Cases
|
||||
|
||||
Test Cases for Entity models that inherit from model Contact
|
||||
"""
|
||||
|
||||
kwargs_item_create: dict = None
|
||||
|
||||
model = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_item_create.update(
|
||||
super().kwargs_item_create
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class ContactModelTest(
|
||||
ModelTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
36
app/access/tests/unit/contact/test_unit_contact_viewset.py
Normal file
36
app/access/tests/unit/contact/test_unit_contact_viewset.py
Normal file
@ -0,0 +1,36 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.contact import Contact
|
||||
from access.tests.unit.entity.test_unit_entity_viewset import (
|
||||
EntityViewsetInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ViewsetTestCases(
|
||||
EntityViewsetInheritedCases,
|
||||
):
|
||||
|
||||
model: str = Contact
|
||||
|
||||
|
||||
|
||||
class ContactViewsetInheritedCases(
|
||||
ViewsetTestCases,
|
||||
):
|
||||
"""Sub-Entity Test Cases
|
||||
|
||||
Test Cases for Entity models that inherit from model Contact
|
||||
"""
|
||||
|
||||
model: str = None
|
||||
"""name of the model to test"""
|
||||
|
||||
|
||||
|
||||
class ContactViewsetTest(
|
||||
ViewsetTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
@ -0,0 +1,29 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.entity import Entity
|
||||
from access.tests.abstract.tenancy_object import TenancyObject
|
||||
|
||||
|
||||
|
||||
class TenancyObjectTestCases(
|
||||
TenancyObject,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
|
||||
|
||||
class EntityTenancyObjectInheritedCases(
|
||||
TenancyObjectTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
|
||||
|
||||
class EntityTenancyObjectTest(
|
||||
TenancyObjectTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
model = Entity
|
215
app/access/tests/unit/entity/test_unit_entity_api_v2.py
Normal file
215
app/access/tests/unit/entity/test_unit_entity_api_v2.py
Normal file
@ -0,0 +1,215 @@
|
||||
from django.contrib.auth.models import Permission, 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.entity import Entity
|
||||
from access.models.organization import Organization
|
||||
from access.models.team import Team
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
from api.tests.abstract.api_fields import APITenancyObject
|
||||
|
||||
|
||||
|
||||
class APITestCases(
|
||||
APITenancyObject,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_item_create: dict = None
|
||||
|
||||
url_ns_name = None
|
||||
"""Url namespace (optional, if not required) and url name"""
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization for user and item
|
||||
2. Create an item
|
||||
|
||||
"""
|
||||
|
||||
self.organization = Organization.objects.create(name='test_org')
|
||||
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = 'random notes',
|
||||
**self.kwargs_item_create
|
||||
)
|
||||
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'pk': self.item.id
|
||||
}
|
||||
|
||||
if self.model._meta.model_name != 'entity':
|
||||
|
||||
self.url_view_kwargs.update({
|
||||
'entity_model': self.item.entity_type,
|
||||
})
|
||||
|
||||
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 = self.organization,
|
||||
)
|
||||
|
||||
view_team.permissions.set([view_permissions])
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_user_view", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = view_team,
|
||||
user = self.view_user
|
||||
)
|
||||
|
||||
client = Client()
|
||||
url = reverse('v2:' + self.url_ns_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_entity_type(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
entity_type field must exist
|
||||
"""
|
||||
|
||||
assert 'entity_type' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_entity_type(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
entity_type field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['entity_type']) is str
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_url_history(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
_urls.history field must exist
|
||||
"""
|
||||
|
||||
assert 'history' in self.api_data['_urls']
|
||||
|
||||
|
||||
def test_api_field_type_url_history(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
_urls.history field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['_urls']['history']) is str
|
||||
|
||||
|
||||
def test_api_field_type_url_history_value(self):
|
||||
""" Test for url value
|
||||
|
||||
_urls.history field must use the endpoint for entity model
|
||||
"""
|
||||
|
||||
assert str(self.api_data['_urls']['history']).endswith('/access/entity/' + str(self.item.pk) + '/history')
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_url_knowledge_base(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
_urls.knowledge_base field must exist
|
||||
"""
|
||||
|
||||
assert 'knowledge_base' in self.api_data['_urls']
|
||||
|
||||
|
||||
def test_api_field_type_url_knowledge_base(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
_urls.knowledge_base field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['_urls']['knowledge_base']) is str
|
||||
|
||||
|
||||
def test_api_field_type_url_knowledge_base_value(self):
|
||||
""" Test for url value
|
||||
|
||||
_urls.knowledge_base field must use the endpoint for entity model
|
||||
"""
|
||||
|
||||
assert str(self.api_data['_urls']['knowledge_base']).endswith('/assistance/entity/' + str(self.item.pk) + '/knowledge_base')
|
||||
|
||||
|
||||
|
||||
class EntityAPIInheritedCases(
|
||||
APITestCases,
|
||||
):
|
||||
|
||||
kwargs_item_create: dict = None
|
||||
|
||||
model = None
|
||||
|
||||
url_ns_name = '_api_v2_entity_sub'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_item_create.update({
|
||||
'entity_type': self.model._meta.model_name
|
||||
})
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
def test_api_field_exists_entity_value(self):
|
||||
""" Test for value of API Field
|
||||
|
||||
entity_type field must match model name
|
||||
"""
|
||||
|
||||
assert self.api_data['entity_type'] == self.model._meta.model_name
|
||||
|
||||
|
||||
|
||||
class EntityAPITest(
|
||||
APITestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
kwargs_item_create: dict = None
|
||||
|
||||
model = Entity
|
||||
|
||||
url_ns_name = '_api_v2_entity'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_item_create = {
|
||||
'entity_type': 'entity'
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
@ -0,0 +1,82 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.entity_history import Entity, EntityHistory
|
||||
|
||||
from core.tests.abstract.test_unit_model_history_api_v2 import PrimaryModelHistoryAPI
|
||||
|
||||
|
||||
|
||||
class ModelHistoryAPITestCases(
|
||||
PrimaryModelHistoryAPI,
|
||||
):
|
||||
""" Model Histoy Test Cases
|
||||
|
||||
Test must be setup by creating object `kwargs_create_audit_object` with the
|
||||
attributes required to create the object.
|
||||
"""
|
||||
|
||||
audit_model = None
|
||||
|
||||
kwargs_create_audit_object: dict = {}
|
||||
|
||||
model = EntityHistory
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
self.audit_object = self.audit_model.objects.create(
|
||||
organization = self.organization,
|
||||
entity_type = self.audit_model._meta.model_name,
|
||||
**self.kwargs_create_audit_object
|
||||
)
|
||||
|
||||
|
||||
self.history_entry = self.model.objects.create(
|
||||
organization = self.audit_object.organization,
|
||||
action = self.model.Actions.ADD,
|
||||
user = self.view_user,
|
||||
before = {},
|
||||
after = {},
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.audit_object._meta.app_label,
|
||||
model = self.audit_object._meta.model_name,
|
||||
),
|
||||
model = self.audit_object,
|
||||
)
|
||||
|
||||
|
||||
self.make_request()
|
||||
|
||||
|
||||
|
||||
class EntityModelHistoryAPIInheritedCases(
|
||||
ModelHistoryAPITestCases,
|
||||
):
|
||||
|
||||
audit_model = None
|
||||
|
||||
kwargs_create_audit_object: dict = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_audit_object.update(
|
||||
super().kwargs_create_audit_object
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class EntityModelHistoryAPITest(
|
||||
ModelHistoryAPITestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
audit_model = Entity
|
||||
|
||||
kwargs_create_audit_object: dict = {}
|
64
app/access/tests/unit/entity/test_unit_entity_model.py
Normal file
64
app/access/tests/unit/entity/test_unit_entity_model.py
Normal file
@ -0,0 +1,64 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.entity import Entity
|
||||
from access.models.organization import Organization
|
||||
|
||||
from app.tests.abstract.models import TenancyModel
|
||||
|
||||
|
||||
|
||||
class ModelTestCases(
|
||||
TenancyModel,
|
||||
):
|
||||
|
||||
model = Entity
|
||||
|
||||
kwargs_item_create: dict = {}
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test"""
|
||||
|
||||
self.organization = Organization.objects.create(name='test_org')
|
||||
|
||||
different_organization = Organization.objects.create(name='test_different_organization')
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = 'notes',
|
||||
entity_type = self.model._meta.model_name,
|
||||
**self.kwargs_item_create,
|
||||
)
|
||||
|
||||
|
||||
|
||||
class EntityModelInheritedCases(
|
||||
ModelTestCases,
|
||||
):
|
||||
"""Sub-Entity Test Cases
|
||||
|
||||
Test Cases for Entity models that inherit from model Entity
|
||||
"""
|
||||
|
||||
kwargs_item_create: dict = None
|
||||
|
||||
model = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_item_create.update(
|
||||
super().kwargs_item_create
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class EntityModelTest(
|
||||
ModelTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
82
app/access/tests/unit/entity/test_unit_entity_viewset.py
Normal file
82
app/access/tests/unit/entity/test_unit_entity_viewset.py
Normal file
@ -0,0 +1,82 @@
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
|
||||
from access.viewsets.entity import (
|
||||
NoDocsViewSet,
|
||||
ViewSet,
|
||||
)
|
||||
|
||||
from api.tests.unit.test_unit_common_viewset import ModelViewSetInheritedCases
|
||||
|
||||
|
||||
|
||||
class ViewsetTestCases(
|
||||
ModelViewSetInheritedCases,
|
||||
):
|
||||
|
||||
kwargs = None
|
||||
|
||||
viewset = None
|
||||
|
||||
route_name = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. make list request
|
||||
"""
|
||||
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
client = Client()
|
||||
|
||||
url = reverse(
|
||||
self.route_name + '-list',
|
||||
kwargs = self.kwargs
|
||||
)
|
||||
|
||||
client.force_login(self.view_user)
|
||||
|
||||
self.http_options_response_list = client.options(url)
|
||||
|
||||
|
||||
|
||||
class EntityViewsetInheritedCases(
|
||||
ViewsetTestCases,
|
||||
):
|
||||
|
||||
model: str = None
|
||||
"""name of the model to test"""
|
||||
|
||||
route_name = 'API:_api_v2_entity_sub'
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs = {
|
||||
'entity_model': self.model._meta.model_name
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class EntityViewsetTest(
|
||||
ViewsetTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
kwargs = {}
|
||||
|
||||
route_name = 'API:_api_v2_entity'
|
||||
|
||||
viewset = NoDocsViewSet
|
@ -1,48 +1,22 @@
|
||||
import pytest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.viewsets.organization import ViewSet
|
||||
|
||||
from api.tests.abstract.viewsets import ViewSetModel
|
||||
from api.tests.unit.test_unit_common_viewset import ModelViewSetInheritedCases
|
||||
|
||||
|
||||
|
||||
class ViewsetCommon(
|
||||
ViewSetModel,
|
||||
class OrganizationViewsetList(
|
||||
ModelViewSetInheritedCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
route_name = 'API:_api_v2_organization'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization
|
||||
3. create super user
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_view_user", password="password", is_superuser=True)
|
||||
|
||||
self.kwargs = {}
|
||||
|
||||
|
||||
class OrganizationViewsetList(
|
||||
ViewsetCommon,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
@ -1,47 +1,22 @@
|
||||
import pytest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.viewsets.team_notes import ViewSet
|
||||
|
||||
from api.tests.abstract.viewsets import ViewSetModel
|
||||
from api.tests.unit.test_unit_common_viewset import ModelViewSetInheritedCases
|
||||
|
||||
|
||||
|
||||
class ViewsetCommon(
|
||||
ViewSetModel,
|
||||
class OrganizationNotesViewsetList(
|
||||
ModelViewSetInheritedCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
route_name = 'v2:_api_v2_organization_note'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization
|
||||
3. create super user
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_view_user", password="password", is_superuser=True)
|
||||
|
||||
|
||||
|
||||
|
||||
class OrganizationNotesViewsetList(
|
||||
ViewsetCommon,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
@ -0,0 +1,32 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.person import Person
|
||||
from access.tests.unit.entity.test_unit_entity_access_tenancy_object import (
|
||||
EntityTenancyObjectInheritedCases,
|
||||
)
|
||||
|
||||
class TenancyObjectTestCases(
|
||||
EntityTenancyObjectInheritedCases,
|
||||
):
|
||||
|
||||
model = Person
|
||||
|
||||
|
||||
class PersonTenancyObjectInheritedCases(
|
||||
TenancyObjectTestCases,
|
||||
):
|
||||
"""Sub-Entity Test Cases
|
||||
|
||||
Test Cases for Entity models that inherit from model Person
|
||||
"""
|
||||
|
||||
model = None
|
||||
|
||||
|
||||
|
||||
class PersonTenancyObjectTest(
|
||||
TenancyObjectTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
127
app/access/tests/unit/person/test_unit_person_api_v2.py
Normal file
127
app/access/tests/unit/person/test_unit_person_api_v2.py
Normal file
@ -0,0 +1,127 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.person import Person
|
||||
|
||||
from access.tests.unit.entity.test_unit_entity_api_v2 import (
|
||||
EntityAPIInheritedCases,
|
||||
)
|
||||
|
||||
|
||||
|
||||
class APITestCases(
|
||||
EntityAPIInheritedCases,
|
||||
):
|
||||
|
||||
model = Person
|
||||
|
||||
kwargs_item_create: dict = {}
|
||||
|
||||
url_ns_name = '_api_v2_entity_sub'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_item_create.update({
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Funny',
|
||||
'dob': '2025-04-08',
|
||||
})
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_f_name(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
f_name field must exist
|
||||
"""
|
||||
|
||||
assert 'f_name' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_f_name(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
f_name field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['f_name']) is str
|
||||
|
||||
|
||||
def test_api_field_exists_m_name(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
m_name field must exist
|
||||
"""
|
||||
|
||||
assert 'm_name' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_f_name(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
m_name field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['m_name']) is str
|
||||
|
||||
|
||||
def test_api_field_exists_l_name(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
l_name field must exist
|
||||
"""
|
||||
|
||||
assert 'l_name' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_f_name(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
l_name field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['l_name']) is str
|
||||
|
||||
|
||||
def test_api_field_exists_dob(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
dob field must exist
|
||||
"""
|
||||
|
||||
assert 'dob' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_dob(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
dob field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['dob']) is str
|
||||
|
||||
|
||||
|
||||
class PersonAPIInheritedCases(
|
||||
APITestCases,
|
||||
):
|
||||
"""Sub-Entity Test Cases
|
||||
|
||||
Test Cases for Entity models that inherit from model Person
|
||||
"""
|
||||
|
||||
kwargs_item_create: dict = None
|
||||
|
||||
model = None
|
||||
|
||||
|
||||
class PersonAPITest(
|
||||
APITestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
@ -0,0 +1,66 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.person import Person
|
||||
from access.tests.unit.entity.test_unit_entity_history_api_v2 import (
|
||||
EntityModelHistoryAPIInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class PersonModelHistoryAPITestCases(
|
||||
EntityModelHistoryAPIInheritedCases,
|
||||
):
|
||||
""" Model Histoy Test Cases
|
||||
|
||||
Test must be setup by creating object `kwargs_create_audit_object` with the
|
||||
attributes required to create the object.
|
||||
"""
|
||||
|
||||
audit_model = Person
|
||||
|
||||
kwargs_create_audit_object: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Funny',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
|
||||
|
||||
class PersonHistoryAPIInheritedCases(
|
||||
PersonModelHistoryAPITestCases,
|
||||
):
|
||||
"""Sub-Entity Test Cases
|
||||
|
||||
Test Cases for Entity models that inherit from model Person
|
||||
"""
|
||||
|
||||
audit_model = None
|
||||
|
||||
kwargs_create_audit_object: dict = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_audit_object.update(
|
||||
super().kwargs_create_audit_object
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class PersonModelHistoryAPITest(
|
||||
PersonModelHistoryAPITestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
audit_model = Person
|
||||
|
||||
kwargs_create_audit_object: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Funny',
|
||||
'dob': '2025-04-08',
|
||||
}
|
95
app/access/tests/unit/person/test_unit_person_model.py
Normal file
95
app/access/tests/unit/person/test_unit_person_model.py
Normal file
@ -0,0 +1,95 @@
|
||||
from django.db.models.fields import NOT_PROVIDED
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.person import Person
|
||||
from access.tests.unit.entity.test_unit_entity_model import (
|
||||
EntityModelInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ModelTestCases(
|
||||
EntityModelInheritedCases,
|
||||
):
|
||||
|
||||
model = Person
|
||||
|
||||
kwargs_item_create: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Funny',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
|
||||
|
||||
def test_model_field_dob_optional(self):
|
||||
"""Test Field
|
||||
|
||||
Field `dob` must be an optional field
|
||||
"""
|
||||
|
||||
assert self.model._meta.get_field('dob').blank
|
||||
|
||||
|
||||
def test_model_field_f_name_mandatory(self):
|
||||
"""Test Field
|
||||
|
||||
Field `f_name` must be a mandatory field
|
||||
"""
|
||||
|
||||
assert(
|
||||
not (
|
||||
self.model._meta.get_field('f_name').blank
|
||||
and self.model._meta.get_field('f_name').null
|
||||
)
|
||||
and self.model._meta.get_field('f_name').default is NOT_PROVIDED
|
||||
)
|
||||
|
||||
|
||||
def test_model_field_l_name_mandatory(self):
|
||||
"""Test Field
|
||||
|
||||
Field `l_name` must be a mandatory field
|
||||
"""
|
||||
|
||||
assert (
|
||||
not (
|
||||
self.model._meta.get_field('l_name').blank
|
||||
and self.model._meta.get_field('l_name').null
|
||||
)
|
||||
and self.model._meta.get_field('l_name').default is NOT_PROVIDED
|
||||
)
|
||||
|
||||
|
||||
|
||||
class PersonModelInheritedCases(
|
||||
ModelTestCases,
|
||||
):
|
||||
"""Sub-Entity Test Cases
|
||||
|
||||
Test Cases for Entity models that inherit from model Person
|
||||
"""
|
||||
|
||||
kwargs_item_create: dict = None
|
||||
|
||||
model = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_item_create.update(
|
||||
super().kwargs_item_create
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class PersonModelTest(
|
||||
ModelTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
36
app/access/tests/unit/person/test_unit_person_viewset.py
Normal file
36
app/access/tests/unit/person/test_unit_person_viewset.py
Normal file
@ -0,0 +1,36 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.person import Person
|
||||
from access.tests.unit.entity.test_unit_entity_viewset import (
|
||||
EntityViewsetInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ViewsetTestCases(
|
||||
EntityViewsetInheritedCases,
|
||||
):
|
||||
|
||||
model: str = Person
|
||||
|
||||
|
||||
|
||||
class PersonViewsetInheritedCases(
|
||||
ViewsetTestCases,
|
||||
):
|
||||
"""Sub-Entity Test Cases
|
||||
|
||||
Test Cases for Entity models that inherit from model Person
|
||||
"""
|
||||
|
||||
model: str = None
|
||||
"""name of the model to test"""
|
||||
|
||||
|
||||
|
||||
class PersonViewsetTest(
|
||||
ViewsetTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
@ -0,0 +1,21 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.role import Role
|
||||
from access.tests.abstract.tenancy_object import TenancyObject
|
||||
|
||||
|
||||
|
||||
class TenancyObjectTestCases(
|
||||
TenancyObject,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
|
||||
|
||||
class RoleTenancyObjectTest(
|
||||
TenancyObjectTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
model = Role
|
172
app/access/tests/unit/role/test_unit_role_api_v2.py
Normal file
172
app/access/tests/unit/role/test_unit_role_api_v2.py
Normal file
@ -0,0 +1,172 @@
|
||||
from django.contrib.auth.models import Permission, 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.role import Role
|
||||
from access.models.organization import Organization
|
||||
from access.models.team import Team
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
from api.tests.abstract.api_fields import APITenancyObject
|
||||
|
||||
|
||||
|
||||
class APITestCases(
|
||||
APITenancyObject,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_item_create: dict = None
|
||||
|
||||
url_ns_name = None
|
||||
"""Url namespace (optional, if not required) and url name"""
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization for user and item
|
||||
2. Create an item
|
||||
|
||||
"""
|
||||
|
||||
self.organization = Organization.objects.create(name='test_org')
|
||||
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = 'random notes',
|
||||
**self.kwargs_item_create
|
||||
)
|
||||
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'pk': self.item.id
|
||||
}
|
||||
|
||||
# if self.model._meta.model_name != 'entity':
|
||||
|
||||
# self.url_view_kwargs.update({
|
||||
# 'entity_model': self.item.entity_type,
|
||||
# })
|
||||
|
||||
|
||||
# if self.model._meta.model_name != 'entity':
|
||||
|
||||
# self.url_view_kwargs.update({
|
||||
# 'entity_type': self.model._meta.model_name
|
||||
# })
|
||||
|
||||
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 = self.organization,
|
||||
)
|
||||
|
||||
view_team.permissions.set([view_permissions])
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_user_view", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = view_team,
|
||||
user = self.view_user
|
||||
)
|
||||
|
||||
client = Client()
|
||||
url = reverse('v2:' + self.url_ns_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_url_history(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
_urls.history field must exist
|
||||
"""
|
||||
|
||||
assert 'history' in self.api_data['_urls']
|
||||
|
||||
|
||||
def test_api_field_type_url_history(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
_urls.history field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['_urls']['history']) is str
|
||||
|
||||
|
||||
def test_api_field_type_url_history_value(self):
|
||||
""" Test for url value
|
||||
|
||||
_urls.history field must use the endpoint for entity model
|
||||
"""
|
||||
|
||||
assert str(self.api_data['_urls']['history']).endswith('/access/role/' + str(self.item.pk) + '/history')
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_url_knowledge_base(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
_urls.knowledge_base field must exist
|
||||
"""
|
||||
|
||||
assert 'knowledge_base' in self.api_data['_urls']
|
||||
|
||||
|
||||
def test_api_field_type_url_knowledge_base(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
_urls.knowledge_base field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['_urls']['knowledge_base']) is str
|
||||
|
||||
|
||||
def test_api_field_type_url_knowledge_base_value(self):
|
||||
""" Test for url value
|
||||
|
||||
_urls.knowledge_base field must use the endpoint for role model
|
||||
"""
|
||||
|
||||
assert str(self.api_data['_urls']['knowledge_base']).endswith('/assistance/role/' + str(self.item.pk) + '/knowledge_base')
|
||||
|
||||
|
||||
|
||||
class RoleAPITest(
|
||||
APITestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
kwargs_item_create: dict = None
|
||||
|
||||
model = Role
|
||||
|
||||
url_ns_name = '_api_v2_role'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_item_create = {
|
||||
'name': 'a role'
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
72
app/access/tests/unit/role/test_unit_role_history_api_v2.py
Normal file
72
app/access/tests/unit/role/test_unit_role_history_api_v2.py
Normal file
@ -0,0 +1,72 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.role_history import Role, RoleHistory
|
||||
|
||||
from core.tests.abstract.test_unit_model_history_api_v2 import PrimaryModelHistoryAPI
|
||||
|
||||
|
||||
|
||||
class ModelHistoryAPITestCases(
|
||||
PrimaryModelHistoryAPI,
|
||||
):
|
||||
""" Model Histoy Test Cases
|
||||
|
||||
Test must be setup by creating object `kwargs_create_audit_object` with the
|
||||
attributes required to create the object.
|
||||
"""
|
||||
|
||||
audit_model = None
|
||||
|
||||
kwargs_create_audit_object: dict = None
|
||||
|
||||
model = None
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
self.audit_object = self.audit_model.objects.create(
|
||||
organization = self.organization,
|
||||
**self.kwargs_create_audit_object
|
||||
)
|
||||
|
||||
|
||||
self.history_entry = self.model.objects.create(
|
||||
organization = self.audit_object.organization,
|
||||
action = self.model.Actions.ADD,
|
||||
user = self.view_user,
|
||||
before = {},
|
||||
after = {},
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.audit_object._meta.app_label,
|
||||
model = self.audit_object._meta.model_name,
|
||||
),
|
||||
model = self.audit_object,
|
||||
)
|
||||
|
||||
|
||||
self.make_request()
|
||||
|
||||
|
||||
|
||||
class RoleHistoryAPITest(
|
||||
ModelHistoryAPITestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
audit_model = Role
|
||||
|
||||
kwargs_create_audit_object: dict = {}
|
||||
|
||||
model = RoleHistory
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_audit_object = {
|
||||
'name': 'a role'
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
66
app/access/tests/unit/role/test_unit_role_model.py
Normal file
66
app/access/tests/unit/role/test_unit_role_model.py
Normal file
@ -0,0 +1,66 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.role import Role
|
||||
from access.models.organization import Organization
|
||||
from access.models.tenancy import TenancyObject
|
||||
|
||||
from app.tests.abstract.models import TenancyModel
|
||||
|
||||
|
||||
|
||||
class ModelTestCases(
|
||||
TenancyModel,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_item_create: dict = None
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test"""
|
||||
|
||||
self.organization = Organization.objects.create(name='test_org')
|
||||
|
||||
different_organization = Organization.objects.create(name='test_different_organization')
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = 'notes',
|
||||
**self.kwargs_item_create,
|
||||
)
|
||||
|
||||
|
||||
|
||||
def test_field_not_exists_is_global(self):
|
||||
"""Test model field not used
|
||||
|
||||
object must not be settable as a global object
|
||||
|
||||
Attribute `is_global` must be defined as None
|
||||
"""
|
||||
|
||||
assert self.model.is_global is None
|
||||
|
||||
|
||||
|
||||
def test_model_must_be_by_organization(self):
|
||||
"""Test model must be by organization
|
||||
|
||||
This model **must** be by organization.
|
||||
"""
|
||||
|
||||
assert issubclass(self.model, TenancyObject)
|
||||
|
||||
|
||||
|
||||
class RoleModelTest(
|
||||
ModelTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
model = Role
|
||||
|
||||
kwargs_item_create: dict = {
|
||||
'name': 'a role'
|
||||
}
|
62
app/access/tests/unit/role/test_unit_role_serializer.py
Normal file
62
app/access/tests/unit/role/test_unit_role_serializer.py
Normal file
@ -0,0 +1,62 @@
|
||||
import pytest
|
||||
|
||||
# from pytest import MonkeyPatch
|
||||
|
||||
# from unittest.mock import patch
|
||||
|
||||
# from access.functions import permissions
|
||||
|
||||
# from access.serializers import role
|
||||
|
||||
# def mock_func(**kwargs):
|
||||
# return 'is_called'
|
||||
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# This test works when run alone, however not when all unit tests are run
|
||||
# need to figure out how to correctly isolate the test.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
|
||||
@pytest.mark.skip( reason = 'figure out how to isolate so entirety of unit tests can run without this test failing' )
|
||||
# @pytest.mark.forked
|
||||
# @pytest.mark.django_db
|
||||
# @patch("access.functions.permissions.permission_queryset", return_value='no_called', side_effect=mock_func)
|
||||
# @patch.object(role, "permission_queryset", return_value='no_called', side_effect=mock_func)
|
||||
# @patch.object(permissions, "permission_queryset", return_value='no_called', side_effect=mock_func)
|
||||
# @patch.object(role, "permission_queryset", side_effect=mock_func)
|
||||
# @patch.object(globals()['role'], "permission_queryset", return_value='no_called', side_effect=mock_func)
|
||||
# @patch.object(globals()['role'], "permission_queryset", side_effect=mock_func)
|
||||
# @pytest.mark.forked # from `pip install pytest-forked`
|
||||
# def test_serializer_field_permission_uses_permissions_selector(mocked_obj):
|
||||
def test_serializer_field_permission_uses_permissions_selector(mocker):
|
||||
# def test_serializer_field_permission_uses_permissions_selector(monkeypatch):
|
||||
"""Field Permission Check
|
||||
|
||||
field `permission` must be called with `queryset=access.functions.permissions.permission_queryset()`
|
||||
so that ONLY the designated permissions are visible
|
||||
"""
|
||||
|
||||
def mock_func(**kwargs):
|
||||
return 'is_called'
|
||||
|
||||
mocker.patch("access.functions.permissions.permission_queryset", return_value='no_called', side_effect=mock_func)
|
||||
|
||||
from access.serializers import role
|
||||
# from access.serializers.role import permission_queryset, ModelSerializer
|
||||
|
||||
# monkey = MonkeyPatch().setattr(role, 'permission_queryset', mock_func)
|
||||
|
||||
# monkeypatch.setattr(role, 'permission_queryset', mock_func)
|
||||
# monkey = MonkeyPatch.setattr('access.functions.permissions.permission_queryset', mock_func)
|
||||
# monkeypatch.setattr(permissions, 'permission_queryset', mock_func)
|
||||
|
||||
serializer = role.ModelSerializer()
|
||||
|
||||
# if `return_value` exists, the function was not called
|
||||
assert getattr(serializer.fields.fields['permissions'].child_relation.queryset, 'return_value', None) is None
|
||||
|
||||
#if `queryset == is_called` the function was called
|
||||
assert serializer.fields.fields['permissions'].child_relation.queryset == 'is_called'
|
56
app/access/tests/unit/role/test_unit_role_viewset.py
Normal file
56
app/access/tests/unit/role/test_unit_role_viewset.py
Normal file
@ -0,0 +1,56 @@
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
|
||||
from access.viewsets.role import ViewSet
|
||||
|
||||
from api.tests.unit.test_unit_common_viewset import ModelViewSetInheritedCases
|
||||
|
||||
|
||||
|
||||
class ViewsetTestCases(
|
||||
ModelViewSetInheritedCases,
|
||||
):
|
||||
|
||||
kwargs = None
|
||||
|
||||
viewset = None
|
||||
|
||||
route_name = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. make list request
|
||||
"""
|
||||
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
client = Client()
|
||||
|
||||
url = reverse(
|
||||
self.route_name + '-list',
|
||||
kwargs = self.kwargs
|
||||
)
|
||||
|
||||
client.force_login(self.view_user)
|
||||
|
||||
self.http_options_response_list = client.options(url)
|
||||
|
||||
|
||||
|
||||
class RoleViewsetTest(
|
||||
ViewsetTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
kwargs = {}
|
||||
|
||||
route_name = 'v2:_api_v2_role'
|
||||
|
||||
viewset = ViewSet
|
@ -1,48 +1,23 @@
|
||||
import pytest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from access.models.organization import Organization
|
||||
|
||||
from access.viewsets.team import ViewSet
|
||||
|
||||
from api.tests.abstract.viewsets import ViewSetModel
|
||||
from api.tests.unit.test_unit_common_viewset import ModelViewSetInheritedCases
|
||||
|
||||
|
||||
|
||||
class ViewsetCommon(
|
||||
ViewSetModel,
|
||||
class TeamViewsetList(
|
||||
ModelViewSetInheritedCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
route_name = 'API:_api_v2_organization_team'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization
|
||||
3. create super user
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_view_user", password="password", is_superuser=True)
|
||||
|
||||
self.kwargs = { 'organization_id': self.organization.id }
|
||||
|
||||
|
||||
|
||||
class TeamViewsetList(
|
||||
ViewsetCommon,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
@ -54,6 +29,7 @@ class TeamViewsetList(
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
self.kwargs = { 'organization_id': self.organization.id }
|
||||
|
||||
client = Client()
|
||||
|
||||
|
@ -1,47 +1,22 @@
|
||||
import pytest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.viewsets.team_notes import ViewSet
|
||||
|
||||
from api.tests.abstract.viewsets import ViewSetModel
|
||||
from api.tests.unit.test_unit_common_viewset import ModelViewSetInheritedCases
|
||||
|
||||
|
||||
|
||||
class ViewsetCommon(
|
||||
ViewSetModel,
|
||||
class TeamNotesViewsetList(
|
||||
ModelViewSetInheritedCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
route_name = 'v2:_api_v2_team_note'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization
|
||||
3. create super user
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_view_user", password="password", is_superuser=True)
|
||||
|
||||
|
||||
|
||||
|
||||
class TeamNotesViewsetList(
|
||||
ViewsetCommon,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
@ -1,57 +1,23 @@
|
||||
import pytest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from access.models.organization import Organization
|
||||
from access.models.team import Team
|
||||
from access.viewsets.team_user import ViewSet
|
||||
|
||||
from api.tests.abstract.viewsets import ViewSetModel
|
||||
from api.tests.unit.test_unit_common_viewset import ModelViewSetInheritedCases
|
||||
|
||||
|
||||
|
||||
class ViewsetCommon(
|
||||
ViewSetModel,
|
||||
class TeamUserViewsetList(
|
||||
ModelViewSetInheritedCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
route_name = 'API:_api_v2_organization_team_user'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization
|
||||
3. create super user
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.team = Team.objects.create(
|
||||
organization = self.organization,
|
||||
name = 'team'
|
||||
)
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_view_user", password="password", is_superuser=True)
|
||||
|
||||
self.kwargs = {
|
||||
'organization_id': self.organization.id,
|
||||
'team_id': self.team.id
|
||||
}
|
||||
|
||||
|
||||
|
||||
class TeamUserViewsetList(
|
||||
ViewsetCommon,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
@ -63,6 +29,15 @@ class TeamUserViewsetList(
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
self.team = Team.objects.create(
|
||||
organization = self.organization,
|
||||
name = 'team'
|
||||
)
|
||||
|
||||
self.kwargs = {
|
||||
'organization_id': self.organization.id,
|
||||
'team_id': self.team.id
|
||||
}
|
||||
|
||||
client = Client()
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
@ -8,11 +7,9 @@ from access.models.tenancy import TenancyObject
|
||||
|
||||
from core.mixin.history_save import SaveHistory
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
|
||||
class TenancyManagerTests(TestCase):
|
||||
class TenancyManagerTest(TestCase):
|
||||
|
||||
item = TenancyManager
|
||||
|
||||
@ -30,7 +27,7 @@ class TenancyManagerTests(TestCase):
|
||||
|
||||
|
||||
|
||||
class TenancyObjectTests(TestCase):
|
||||
class TenancyObjectTestCases:
|
||||
|
||||
item = TenancyObject
|
||||
|
||||
@ -44,6 +41,24 @@ class TenancyObjectTests(TestCase):
|
||||
assert issubclass(TenancyObject, SaveHistory)
|
||||
|
||||
|
||||
def test_has_attribute_history_app_label(self):
|
||||
""" Attribute history_app_name exists """
|
||||
|
||||
assert hasattr(self.item, 'history_app_label')
|
||||
|
||||
|
||||
def test_has_attribute_history_model_name(self):
|
||||
""" Attribute history_model_name exists """
|
||||
|
||||
assert hasattr(self.item, 'history_model_name')
|
||||
|
||||
|
||||
def test_has_attribute_kb_model_name(self):
|
||||
"""Attribute _kb_model_name exists """
|
||||
|
||||
assert hasattr(self.item, 'kb_model_name')
|
||||
|
||||
|
||||
def test_has_attribute_organization(self):
|
||||
""" Field organization exists """
|
||||
|
||||
@ -62,6 +77,12 @@ class TenancyObjectTests(TestCase):
|
||||
assert hasattr(self.item, 'model_notes')
|
||||
|
||||
|
||||
def test_has_attribute_note_basename(self):
|
||||
""" Attribute note_basename exists """
|
||||
|
||||
assert hasattr(self.item, 'note_basename')
|
||||
|
||||
|
||||
def test_has_attribute_get_organization(self):
|
||||
""" Function 'get_organization' Exists """
|
||||
|
||||
@ -102,3 +123,12 @@ class TenancyObjectTests(TestCase):
|
||||
"""
|
||||
|
||||
assert self.item.objects is not None
|
||||
|
||||
|
||||
|
||||
class TenancyObjectTest(
|
||||
TenancyObjectTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
@ -1,19 +1,15 @@
|
||||
from django.contrib.auth.models import User
|
||||
from django.shortcuts import reverse
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from access.models.organization import Organization
|
||||
|
||||
from api.tests.abstract.viewsets import ViewSetCommon
|
||||
from api.tests.unit.test_unit_common_viewset import IndexViewsetInheritedCases
|
||||
|
||||
from access.viewsets.index import Index
|
||||
|
||||
|
||||
|
||||
class AccessViewset(
|
||||
IndexViewsetInheritedCases,
|
||||
TestCase,
|
||||
ViewSetCommon
|
||||
):
|
||||
|
||||
viewset = Index
|
||||
@ -29,11 +25,7 @@ class AccessViewset(
|
||||
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)
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
client = Client()
|
||||
@ -42,19 +34,3 @@ class AccessViewset(
|
||||
|
||||
client.force_login(self.view_user)
|
||||
self.http_options_response_list = client.options(url)
|
||||
|
||||
self.kwargs = {}
|
||||
|
||||
|
||||
|
||||
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 IsAuthenticated
|
||||
|
||||
assert len(view_set.permission_classes) == 1
|
||||
|
344
app/access/viewsets/entity.py
Normal file
344
app/access/viewsets/entity.py
Normal file
@ -0,0 +1,344 @@
|
||||
import importlib
|
||||
|
||||
from django.apps import apps
|
||||
|
||||
from drf_spectacular.utils import (
|
||||
extend_schema,
|
||||
extend_schema_view,
|
||||
OpenApiParameter,
|
||||
OpenApiResponse,
|
||||
PolymorphicProxySerializer
|
||||
)
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
# THis import only exists so that the migrations can be created
|
||||
from access.models.entity_history import EntityHistory # pylint: disable=W0611:unused-import
|
||||
from access.models.entity import (
|
||||
Entity,
|
||||
)
|
||||
|
||||
from api.viewsets.common import ModelViewSet
|
||||
|
||||
|
||||
|
||||
def spectacular_request_serializers( serializer_type = 'Model'):
|
||||
|
||||
serializers: dict = {}
|
||||
|
||||
|
||||
for model in apps.get_models():
|
||||
|
||||
if issubclass(model, Entity):
|
||||
|
||||
serializer_module = importlib.import_module(
|
||||
model._meta.app_label + '.serializers.' + str(
|
||||
model._meta.verbose_name
|
||||
).lower().replace(' ', '_')
|
||||
)
|
||||
|
||||
serializers.update({
|
||||
str(model._meta.verbose_name).lower().replace(' ', '_'): getattr(serializer_module, serializer_type + 'Serializer')
|
||||
})
|
||||
|
||||
return serializers
|
||||
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
create=extend_schema(
|
||||
summary = 'Create an entity',
|
||||
description='.',
|
||||
parameters = [
|
||||
OpenApiParameter(
|
||||
name = 'entity_model',
|
||||
description = 'Enter the entity type. This is the name of the Entity sub-model.',
|
||||
location = OpenApiParameter.PATH,
|
||||
type = str,
|
||||
required = False,
|
||||
allow_blank = True,
|
||||
),
|
||||
],
|
||||
request = PolymorphicProxySerializer(
|
||||
component_name = 'Entities',
|
||||
serializers = spectacular_request_serializers(),
|
||||
resource_type_field_name = None,
|
||||
many = False,
|
||||
),
|
||||
responses = {
|
||||
200: OpenApiResponse(
|
||||
description='Already exists',
|
||||
response = PolymorphicProxySerializer(
|
||||
component_name = 'Entities (View)',
|
||||
serializers = spectacular_request_serializers( 'View' ),
|
||||
resource_type_field_name = None,
|
||||
many = False,
|
||||
)
|
||||
),
|
||||
201: OpenApiResponse(
|
||||
description = 'Created',
|
||||
response = PolymorphicProxySerializer(
|
||||
component_name = 'Entities (View)',
|
||||
serializers = spectacular_request_serializers( 'View' ),
|
||||
resource_type_field_name = None,
|
||||
many = False,
|
||||
)
|
||||
),
|
||||
403: OpenApiResponse(description='User is missing add permissions'),
|
||||
}
|
||||
),
|
||||
destroy = extend_schema(
|
||||
summary = 'Delete an entity',
|
||||
description = '.',
|
||||
parameters =[
|
||||
OpenApiParameter(
|
||||
name = 'entity_model',
|
||||
description = 'Enter the entity type. This is the name of the Entity sub-model.',
|
||||
location = OpenApiParameter.PATH,
|
||||
type = str,
|
||||
required = False,
|
||||
allow_blank = True,
|
||||
),
|
||||
],
|
||||
request = PolymorphicProxySerializer(
|
||||
component_name = 'Entities',
|
||||
serializers = spectacular_request_serializers(),
|
||||
resource_type_field_name = None,
|
||||
many = False,
|
||||
),
|
||||
responses = {
|
||||
204: OpenApiResponse(description='Object deleted'),
|
||||
403: OpenApiResponse(description='User is missing delete permissions'),
|
||||
}
|
||||
),
|
||||
list = extend_schema(
|
||||
summary = 'Fetch all entities',
|
||||
description='.',
|
||||
parameters = [
|
||||
OpenApiParameter(
|
||||
name = 'entity_model',
|
||||
description = 'Enter the entity type. This is the name of the Entity sub-model.',
|
||||
location = OpenApiParameter.PATH,
|
||||
type = str,
|
||||
required = False,
|
||||
allow_blank = True,
|
||||
),
|
||||
],
|
||||
request = PolymorphicProxySerializer(
|
||||
component_name = 'Entities',
|
||||
serializers = spectacular_request_serializers(),
|
||||
resource_type_field_name = None,
|
||||
many = False,
|
||||
),
|
||||
responses = {
|
||||
200: OpenApiResponse(
|
||||
description='',
|
||||
response = PolymorphicProxySerializer(
|
||||
component_name = 'Entities (View)',
|
||||
serializers = spectacular_request_serializers( 'View' ),
|
||||
resource_type_field_name = None,
|
||||
many = False,
|
||||
)
|
||||
),
|
||||
403: OpenApiResponse(description='User is missing view permissions'),
|
||||
}
|
||||
),
|
||||
retrieve = extend_schema(
|
||||
summary = 'Fetch a single entity',
|
||||
description='.',
|
||||
parameters = [
|
||||
OpenApiParameter(
|
||||
name = 'entity_model',
|
||||
description = 'Enter the entity type. This is the name of the Entity sub-model.',
|
||||
location = OpenApiParameter.PATH,
|
||||
type = str,
|
||||
required = False,
|
||||
allow_blank = True,
|
||||
),
|
||||
],
|
||||
request = PolymorphicProxySerializer(
|
||||
component_name = 'Entities',
|
||||
serializers = spectacular_request_serializers(),
|
||||
resource_type_field_name = None,
|
||||
many = False,
|
||||
),
|
||||
responses = {
|
||||
200: OpenApiResponse(
|
||||
description='',
|
||||
response = PolymorphicProxySerializer(
|
||||
component_name = 'Entities (View)',
|
||||
serializers = spectacular_request_serializers( 'View' ),
|
||||
resource_type_field_name = None,
|
||||
many = False,
|
||||
)
|
||||
),
|
||||
403: OpenApiResponse(description='User is missing view permissions'),
|
||||
}
|
||||
),
|
||||
update = extend_schema(exclude = True),
|
||||
partial_update = extend_schema(
|
||||
summary = 'Update an entity',
|
||||
description = '.',
|
||||
parameters = [
|
||||
OpenApiParameter(
|
||||
name = 'entity_model',
|
||||
description = 'Enter the entity type. This is the name of the Entity sub-model.',
|
||||
location = OpenApiParameter.PATH,
|
||||
type = str,
|
||||
required = False,
|
||||
allow_blank = True,
|
||||
),
|
||||
],
|
||||
request = PolymorphicProxySerializer(
|
||||
component_name = 'Entities',
|
||||
serializers = spectacular_request_serializers(),
|
||||
resource_type_field_name = None,
|
||||
many = False,
|
||||
),
|
||||
responses = {
|
||||
200: OpenApiResponse(
|
||||
description='',
|
||||
response = PolymorphicProxySerializer(
|
||||
component_name = 'Entities (View)',
|
||||
serializers = spectacular_request_serializers( 'View' ),
|
||||
resource_type_field_name = None,
|
||||
many = False,
|
||||
)
|
||||
),
|
||||
403: OpenApiResponse(description='User is missing change permissions'),
|
||||
}
|
||||
),
|
||||
)
|
||||
class ViewSet( ModelViewSet ):
|
||||
|
||||
filterset_fields = [
|
||||
'organization',
|
||||
'is_global'
|
||||
]
|
||||
|
||||
search_fields = [
|
||||
'model_notes',
|
||||
]
|
||||
|
||||
def related_objects(self, model, model_kwarg):
|
||||
"""Recursive relate_objects fetch
|
||||
|
||||
Fetch the model that is lowest in the chain of inherited models
|
||||
|
||||
Args:
|
||||
model (django.db.models.Model): The model to obtain the
|
||||
related_model from.
|
||||
model_kwarg (str): The URL Kwarg of the model.
|
||||
|
||||
Returns:
|
||||
_type_: _description_
|
||||
"""
|
||||
|
||||
related_model = None
|
||||
|
||||
if model_kwarg:
|
||||
|
||||
for related_object in model._meta.related_objects:
|
||||
|
||||
related_objects = getattr(related_object.related_model._meta, 'related_objects', [])
|
||||
|
||||
if(
|
||||
str(
|
||||
related_object.related_model._meta.verbose_name
|
||||
).lower().replace(' ', '_') == model_kwarg
|
||||
):
|
||||
|
||||
related_model = related_object.related_model
|
||||
break
|
||||
|
||||
elif related_objects:
|
||||
|
||||
related_model = self.related_objects(model = related_object.related_model, model_kwarg = model_kwarg)
|
||||
|
||||
break
|
||||
|
||||
|
||||
|
||||
return related_model
|
||||
|
||||
|
||||
@property
|
||||
def model(self):
|
||||
|
||||
|
||||
if getattr(self, '_model', None) is not None:
|
||||
|
||||
return self._model
|
||||
|
||||
model_kwarg = None
|
||||
|
||||
if hasattr(self, 'kwargs'):
|
||||
|
||||
model_kwarg = self.kwargs.get('entity_model', None)
|
||||
|
||||
if model_kwarg:
|
||||
|
||||
self._model = self.related_objects(Entity, model_kwarg)
|
||||
|
||||
else:
|
||||
|
||||
self._model = Entity
|
||||
|
||||
return self._model
|
||||
|
||||
view_description = 'All entities'
|
||||
|
||||
|
||||
def get_back_url(self) -> str:
|
||||
|
||||
if(
|
||||
self.back_url is None
|
||||
and self.kwargs.get('entity_model', None) is not None
|
||||
):
|
||||
|
||||
self.back_url = reverse(
|
||||
viewname = '_api_v2_entity_sub-list',
|
||||
request = self.request,
|
||||
kwargs = {
|
||||
'entity_model': self.kwargs['entity_model'],
|
||||
}
|
||||
)
|
||||
|
||||
return self.back_url
|
||||
|
||||
|
||||
def get_serializer_class(self):
|
||||
|
||||
serializer_module = importlib.import_module(
|
||||
self.model._meta.app_label + '.serializers.' + str(
|
||||
self.model._meta.verbose_name
|
||||
).lower().replace(' ', '_')
|
||||
)
|
||||
|
||||
if (
|
||||
self.action == 'list'
|
||||
or self.action == 'retrieve'
|
||||
):
|
||||
|
||||
self.serializer_class = getattr(serializer_module, 'ViewSerializer')
|
||||
|
||||
|
||||
else:
|
||||
|
||||
self.serializer_class = getattr(serializer_module, 'ModelSerializer')
|
||||
|
||||
|
||||
return self.serializer_class
|
||||
|
||||
|
||||
|
||||
@extend_schema_view( # prevent duplicate documentation of both /access/entity endpoints
|
||||
create = extend_schema(exclude = True),
|
||||
destroy = extend_schema(exclude = True),
|
||||
list = extend_schema(exclude = True),
|
||||
retrieve = extend_schema(exclude = True),
|
||||
update = extend_schema(exclude = True),
|
||||
partial_update = extend_schema(exclude = True),
|
||||
)
|
||||
class NoDocsViewSet( ViewSet ):
|
||||
pass
|
60
app/access/viewsets/entity_notes.py
Normal file
60
app/access/viewsets/entity_notes.py
Normal file
@ -0,0 +1,60 @@
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse
|
||||
|
||||
from access.serializers.entity_notes import (
|
||||
EntityNotes,
|
||||
EntityNoteModelSerializer,
|
||||
EntityNoteViewSerializer
|
||||
)
|
||||
|
||||
from core.viewsets.model_notes import ModelNoteViewSet
|
||||
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
create=extend_schema(
|
||||
summary = 'Add a note to an Entity',
|
||||
description = '',
|
||||
responses = {
|
||||
201: OpenApiResponse(description='created', response=EntityNoteViewSerializer),
|
||||
400: OpenApiResponse(description='Validation failed.'),
|
||||
403: OpenApiResponse(description='User is missing create permissions'),
|
||||
}
|
||||
),
|
||||
destroy = extend_schema(
|
||||
summary = 'Delete a Entity note',
|
||||
description = ''
|
||||
),
|
||||
list = extend_schema(
|
||||
summary = 'Fetch all Entity notes',
|
||||
description='',
|
||||
),
|
||||
retrieve = extend_schema(
|
||||
summary = 'Fetch a single Entity note',
|
||||
description='',
|
||||
),
|
||||
update = extend_schema(exclude = True),
|
||||
partial_update = extend_schema(
|
||||
summary = 'Update a Entity note',
|
||||
description = ''
|
||||
),
|
||||
)
|
||||
class ViewSet(ModelNoteViewSet):
|
||||
|
||||
model = EntityNotes
|
||||
|
||||
|
||||
def get_serializer_class(self):
|
||||
|
||||
if (
|
||||
self.action == 'list'
|
||||
or self.action == 'retrieve'
|
||||
):
|
||||
|
||||
self.serializer_class = EntityNoteViewSerializer
|
||||
|
||||
|
||||
else:
|
||||
|
||||
self.serializer_class = EntityNoteModelSerializer
|
||||
|
||||
return self.serializer_class
|
@ -23,8 +23,15 @@ class Index(IndexViewset):
|
||||
|
||||
def list(self, request, pk=None):
|
||||
|
||||
return Response(
|
||||
{
|
||||
"organization": reverse('v2:_api_v2_organization-list', request=request)
|
||||
response = {
|
||||
"organization": reverse('v2:_api_v2_organization-list', request=request),
|
||||
}
|
||||
)
|
||||
|
||||
if self.request.feature_flag['2025-00003']:
|
||||
|
||||
response.update({
|
||||
"role": reverse( 'v2:_api_v2_role-list', request=request ),
|
||||
})
|
||||
|
||||
|
||||
return Response(response)
|
||||
|
103
app/access/viewsets/role.py
Normal file
103
app/access/viewsets/role.py
Normal file
@ -0,0 +1,103 @@
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse
|
||||
|
||||
# THis import only exists so that the migrations can be created
|
||||
from access.models.role_history import RoleHistory # pylint: disable=W0611:unused-import
|
||||
from access.serializers.role import (
|
||||
Role,
|
||||
ModelSerializer,
|
||||
ViewSerializer,
|
||||
)
|
||||
|
||||
from api.viewsets.common import ModelViewSet
|
||||
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
create=extend_schema(
|
||||
summary = 'Create a Role',
|
||||
description='',
|
||||
responses = {
|
||||
201: OpenApiResponse(description='Created', response=ViewSerializer),
|
||||
403: OpenApiResponse(description='User is missing add permissions'),
|
||||
}
|
||||
),
|
||||
destroy = extend_schema(
|
||||
summary = 'Delete a Role',
|
||||
description = '',
|
||||
responses = {
|
||||
204: OpenApiResponse(description=''),
|
||||
403: OpenApiResponse(description='User is missing delete permissions'),
|
||||
}
|
||||
),
|
||||
list = extend_schema(
|
||||
summary = 'Fetch all Role',
|
||||
description='',
|
||||
responses = {
|
||||
200: OpenApiResponse(description='', response=ViewSerializer),
|
||||
403: OpenApiResponse(description='User is missing view permissions'),
|
||||
}
|
||||
),
|
||||
retrieve = extend_schema(
|
||||
summary = 'Fetch a single Role',
|
||||
description='',
|
||||
responses = {
|
||||
200: OpenApiResponse(description='', response=ViewSerializer),
|
||||
403: OpenApiResponse(description='User is missing view permissions'),
|
||||
}
|
||||
),
|
||||
update = extend_schema(exclude = True),
|
||||
partial_update = extend_schema(
|
||||
summary = 'Update a Role',
|
||||
description = '',
|
||||
responses = {
|
||||
200: OpenApiResponse(description='', response=ViewSerializer),
|
||||
403: OpenApiResponse(description='User is missing change permissions'),
|
||||
}
|
||||
),
|
||||
)
|
||||
class ViewSet(ModelViewSet):
|
||||
|
||||
filterset_fields = [
|
||||
'organization',
|
||||
'permissions',
|
||||
]
|
||||
|
||||
search_fields = [
|
||||
'model_notes',
|
||||
'name',
|
||||
]
|
||||
|
||||
model = Role
|
||||
|
||||
view_description: str = 'Available Roles'
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
if self.queryset is None:
|
||||
|
||||
self.queryset = self.model.objects.prefetch_related('permissions','permissions__content_type')
|
||||
|
||||
if 'pk' in getattr(self, 'kwargs', {}):
|
||||
|
||||
self.queryset = self.queryset.filter( pk = int( self.kwargs['pk'] ) )
|
||||
|
||||
|
||||
return self.queryset
|
||||
|
||||
|
||||
def get_serializer_class(self):
|
||||
|
||||
if (
|
||||
self.action == 'list'
|
||||
or self.action == 'retrieve'
|
||||
):
|
||||
|
||||
self.serializer_class = ViewSerializer
|
||||
|
||||
else:
|
||||
|
||||
self.serializer_class = ModelSerializer
|
||||
|
||||
|
||||
return self.serializer_class
|
60
app/access/viewsets/role_notes.py
Normal file
60
app/access/viewsets/role_notes.py
Normal file
@ -0,0 +1,60 @@
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse
|
||||
|
||||
from access.serializers.role_notes import (
|
||||
RoleNotes,
|
||||
RoleNoteModelSerializer,
|
||||
RoleNoteViewSerializer
|
||||
)
|
||||
|
||||
from core.viewsets.model_notes import ModelNoteViewSet
|
||||
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
create=extend_schema(
|
||||
summary = 'Add a note to a Team',
|
||||
description = '',
|
||||
responses = {
|
||||
201: OpenApiResponse(description='created', response=RoleNoteViewSerializer),
|
||||
400: OpenApiResponse(description='Validation failed.'),
|
||||
403: OpenApiResponse(description='User is missing create permissions'),
|
||||
}
|
||||
),
|
||||
destroy = extend_schema(
|
||||
summary = 'Delete a team note',
|
||||
description = ''
|
||||
),
|
||||
list = extend_schema(
|
||||
summary = 'Fetch all team notes',
|
||||
description='',
|
||||
),
|
||||
retrieve = extend_schema(
|
||||
summary = 'Fetch a single team note',
|
||||
description='',
|
||||
),
|
||||
update = extend_schema(exclude = True),
|
||||
partial_update = extend_schema(
|
||||
summary = 'Update a team note',
|
||||
description = ''
|
||||
),
|
||||
)
|
||||
class ViewSet(ModelNoteViewSet):
|
||||
|
||||
model = RoleNotes
|
||||
|
||||
|
||||
def get_serializer_class(self):
|
||||
|
||||
if (
|
||||
self.action == 'list'
|
||||
or self.action == 'retrieve'
|
||||
):
|
||||
|
||||
self.serializer_class = RoleNoteViewSerializer
|
||||
|
||||
|
||||
else:
|
||||
|
||||
self.serializer_class = RoleNoteModelSerializer
|
||||
|
||||
return self.serializer_class
|
0
app/accounting/__init__.py
Normal file
0
app/accounting/__init__.py
Normal file
6
app/accounting/apps.py
Normal file
6
app/accounting/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AccountingConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'accounting'
|
0
app/accounting/migrations/__init__.py
Normal file
0
app/accounting/migrations/__init__.py
Normal file
8
app/accounting/urls.py
Normal file
8
app/accounting/urls.py
Normal file
@ -0,0 +1,8 @@
|
||||
from centurion_feature_flag.urls.routers import DefaultRouter
|
||||
|
||||
|
||||
app_name = "accounting"
|
||||
|
||||
router = DefaultRouter(trailing_slash=False)
|
||||
|
||||
urlpatterns = router.urls
|
@ -113,6 +113,11 @@ class AuthToken(models.Model):
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
|
||||
history_app_label: str = None
|
||||
history_model_name: str = None
|
||||
kb_model_name: str = None
|
||||
note_basename: str = None
|
||||
|
||||
@property
|
||||
def generate(self) -> str:
|
||||
|
||||
|
@ -176,7 +176,7 @@ class ReactUIMetadata(OverRideJSONAPIMetadata):
|
||||
}
|
||||
|
||||
|
||||
metadata['navigation'] = self.get_navigation(request.user)
|
||||
metadata['navigation'] = self.get_navigation(request)
|
||||
|
||||
return metadata
|
||||
|
||||
@ -373,154 +373,238 @@ class ReactUIMetadata(OverRideJSONAPIMetadata):
|
||||
return field_info
|
||||
|
||||
|
||||
_nav = {
|
||||
'access': {
|
||||
"display_name": "Access",
|
||||
"name": "access",
|
||||
"pages": {
|
||||
'view_organization': {
|
||||
"display_name": "Organization",
|
||||
"name": "organization",
|
||||
"link": "/access/organization"
|
||||
def get_nav_items(self, request) -> dict:
|
||||
|
||||
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"
|
||||
},
|
||||
'accounting': {
|
||||
"display_name": "Accounting",
|
||||
"name": "accounting",
|
||||
"pages": {}
|
||||
},
|
||||
'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"
|
||||
},
|
||||
'human_resources': {
|
||||
"display_name": "Human Resources (HR)",
|
||||
"name": "human_resources",
|
||||
"pages": {
|
||||
# 'view_employees': {
|
||||
# "display_name": "Employees",
|
||||
# "name": "employees",
|
||||
# "icon": "employees",
|
||||
# "link": "/human_resources/employees"
|
||||
# }
|
||||
}
|
||||
}
|
||||
},
|
||||
'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"
|
||||
},
|
||||
}
|
||||
},
|
||||
'devops': {
|
||||
"display_name": "DevOPs",
|
||||
"name": "devops",
|
||||
"icon": "devops",
|
||||
"pages": {
|
||||
'view_featureflag': {
|
||||
"display_name": "Feature Flags",
|
||||
"name": "feature_flag",
|
||||
"icon": 'feature_flag',
|
||||
"link": "/devops/feature_flag"
|
||||
},
|
||||
'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"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'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"
|
||||
},
|
||||
'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"
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
'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"
|
||||
},
|
||||
'itops': {
|
||||
"display_name": "ITOps",
|
||||
"name": "itops",
|
||||
"icon": "itops",
|
||||
"pages": {
|
||||
'core.view_ticketcategory': {
|
||||
"display_name": "Ticket Category",
|
||||
"name": "ticketcategory",
|
||||
"icon": 'ticketcategory',
|
||||
"link": "/settings/ticket_category"
|
||||
},
|
||||
'core.view_ticketcommentcategory': {
|
||||
"display_name": "Ticket Comment Category",
|
||||
"name": "ticketcommentcategory",
|
||||
"icon": 'ticketcommentcategory',
|
||||
"link": "/settings/ticket_comment_category"
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
'devops': {
|
||||
"display_name": "DevOPs",
|
||||
"name": "devops",
|
||||
"icon": "devops",
|
||||
"pages": {
|
||||
'view_featureflag': {
|
||||
"display_name": "Feature Flags",
|
||||
"name": "feature_flag",
|
||||
"icon": 'feature_flag',
|
||||
"link": "/devops/feature_flag"
|
||||
}
|
||||
}
|
||||
},
|
||||
'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"
|
||||
'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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if getattr(request, 'feature_flag', None):
|
||||
|
||||
if request.feature_flag['2025-00001']:
|
||||
|
||||
nav['devops']['pages'].update({
|
||||
|
||||
'view_gitgroup': {
|
||||
"display_name": "Git Group",
|
||||
"name": "git_group",
|
||||
"icon": 'git_group',
|
||||
"link": "/devops/git_group"
|
||||
},
|
||||
'view_gitrepository': {
|
||||
"display_name": "Git Repositories",
|
||||
"name": "git_repository",
|
||||
"icon": 'git',
|
||||
"link": "/devops/git_repository"
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
def get_navigation(self, user) -> list(dict()):
|
||||
if request.feature_flag['2025-00002']:
|
||||
|
||||
nav['access']['pages'].update({
|
||||
'view_contact': {
|
||||
"display_name": "Directory",
|
||||
"name": "directory",
|
||||
"link": "/access/entity/contact"
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
if request.feature_flag['2025-00003']:
|
||||
|
||||
nav['access']['pages'].update({
|
||||
'view_role': {
|
||||
"display_name": "Roles",
|
||||
"name": "roles",
|
||||
"icon": 'roles',
|
||||
"link": "/access/role"
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
return nav
|
||||
|
||||
|
||||
def get_navigation(self, request) -> list(dict()):
|
||||
"""Render the navigation menu
|
||||
|
||||
Check the users permissions agains `_nav`. if they have the permission, add the
|
||||
Check the users permissions against `get_nav_items()`. 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.
|
||||
@ -536,7 +620,7 @@ class ReactUIMetadata(OverRideJSONAPIMetadata):
|
||||
|
||||
processed_permissions: dict = {}
|
||||
|
||||
for group in user.groups.all():
|
||||
for group in request.user.groups.all():
|
||||
|
||||
for permission in group.permissions.all():
|
||||
|
||||
@ -554,8 +638,6 @@ class ReactUIMetadata(OverRideJSONAPIMetadata):
|
||||
view_settings: list = [
|
||||
'assistance.view_knowledgebasecategory',
|
||||
'core.view_manufacturer',
|
||||
'core.view_ticketcategory',
|
||||
'core.view_ticketcommentcategory',
|
||||
'itam.view_devicemodel',
|
||||
'itam.view_devicetype',
|
||||
'itam.view_softwarecategory',
|
||||
@ -569,11 +651,11 @@ class ReactUIMetadata(OverRideJSONAPIMetadata):
|
||||
# user = view.request.user
|
||||
|
||||
user_orgainzations = Organization.objects.filter(
|
||||
manager = user
|
||||
manager = request.user
|
||||
)
|
||||
|
||||
|
||||
for app, entry in self._nav.items():
|
||||
for app, entry in self.get_nav_items(request).items():
|
||||
|
||||
new_menu_entry: dict = {}
|
||||
|
||||
|
@ -60,24 +60,43 @@ class CommonModelSerializer(CommonBaseSerializer):
|
||||
|
||||
get_url = {
|
||||
'_self': item.get_url( request = self._context['view'].request ),
|
||||
'knowledge_base': reverse(
|
||||
"v2:_api_v2_model_kb-list",
|
||||
request=self._context['view'].request,
|
||||
kwargs={
|
||||
'model': self.Meta.model._meta.model_name,
|
||||
'model_pk': item.pk
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
kb_model_name = self.Meta.model._meta.model_name
|
||||
if getattr(item, 'kb_model_name'):
|
||||
|
||||
kb_model_name = item.kb_model_name
|
||||
|
||||
|
||||
get_url['knowledge_base'] = reverse(
|
||||
'v2:_api_v2_model_kb-list',
|
||||
request=self._context['view'].request,
|
||||
kwargs={
|
||||
'model': kb_model_name,
|
||||
'model_pk': item.pk
|
||||
}
|
||||
)
|
||||
|
||||
if getattr(self.Meta.model, 'save_model_history', True):
|
||||
|
||||
history_app_label = self.Meta.model._meta.app_label
|
||||
if getattr(item, 'history_app_label'):
|
||||
|
||||
history_app_label = item.history_app_label
|
||||
|
||||
|
||||
history_model_name = self.Meta.model._meta.model_name
|
||||
if getattr(item, 'history_model_name'):
|
||||
|
||||
history_model_name = item.history_model_name
|
||||
|
||||
|
||||
get_url['history'] = reverse(
|
||||
"v2:_api_v2_model_history-list",
|
||||
request = self._context['view'].request,
|
||||
kwargs = {
|
||||
'app_label': self.Meta.model._meta.app_label,
|
||||
'model_name': self.Meta.model._meta.model_name,
|
||||
'app_label': history_app_label,
|
||||
'model_name': history_model_name,
|
||||
'model_id': item.pk
|
||||
}
|
||||
)
|
||||
@ -94,7 +113,17 @@ class CommonModelSerializer(CommonBaseSerializer):
|
||||
and obj is not FeatureNotUsed
|
||||
):
|
||||
|
||||
note_basename = '_api_v2_' + str(item._meta.verbose_name).lower().replace(' ', '_') + '_note'
|
||||
app_namespace = ''
|
||||
|
||||
if getattr(item, 'app_namespace', None):
|
||||
|
||||
app_namespace = str(item.app_namespace) + ':'
|
||||
|
||||
note_basename = app_namespace + '_api_v2_' + str(item._meta.verbose_name).lower().replace(' ', '_') + '_note'
|
||||
|
||||
if getattr(item, 'note_basename'):
|
||||
|
||||
note_basename = app_namespace + item.note_basename
|
||||
|
||||
if getattr(self.Meta, 'note_basename', None):
|
||||
|
||||
|
@ -398,7 +398,16 @@ class MetadataAttributesFunctionalTable:
|
||||
|
||||
for item in response.data['table_fields']:
|
||||
|
||||
if type(item) is not str:
|
||||
if(
|
||||
type(item) is not str
|
||||
and not (
|
||||
type(item) is dict
|
||||
and 'field' in item
|
||||
and 'type' in item
|
||||
and item['type'] == 'link'
|
||||
and 'key' in item
|
||||
)
|
||||
):
|
||||
|
||||
all_string = False
|
||||
|
||||
|
@ -1,740 +0,0 @@
|
||||
from django.contrib.auth.models import ContentType, Permission, User
|
||||
|
||||
from unittest.mock import patch, PropertyMock
|
||||
|
||||
from access.mixins.permissions import OrganizationPermissionMixin
|
||||
|
||||
from api.react_ui_metadata import ReactUIMetadata
|
||||
|
||||
from access.middleware.request import Tenancy
|
||||
from access.models.organization import Organization
|
||||
from access.models.team import Team
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
from settings.models.app_settings import AppSettings
|
||||
|
||||
|
||||
|
||||
class MockRequest:
|
||||
"""Fake Request
|
||||
|
||||
contains the user and tenancy object for permission checks
|
||||
|
||||
Some ViewSets rely upon the request object for obtaining the user and
|
||||
fetching the tenacy object for permission checking.
|
||||
"""
|
||||
|
||||
data = {}
|
||||
|
||||
kwargs = {}
|
||||
|
||||
tenancy: Tenancy = None
|
||||
|
||||
user: User = None
|
||||
|
||||
def __init__(self, user: User, organization: Organization, viewset):
|
||||
|
||||
self.user = user
|
||||
|
||||
view_permission = Permission.objects.get(
|
||||
codename = 'view_' + viewset.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = viewset.model._meta.app_label,
|
||||
model = viewset.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
view_team = Team.objects.create(
|
||||
team_name = 'view_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
view_team.permissions.set([view_permission])
|
||||
|
||||
|
||||
teamuser = TeamUsers.objects.create(
|
||||
team = view_team,
|
||||
user = user
|
||||
)
|
||||
|
||||
|
||||
self.app_settings = AppSettings.objects.select_related('global_organization').get(
|
||||
owner_organization = None
|
||||
)
|
||||
|
||||
self.tenancy = Tenancy(
|
||||
user = user,
|
||||
app_settings = self.app_settings
|
||||
)
|
||||
|
||||
|
||||
|
||||
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()
|
||||
|
||||
view_set.kwargs = self.kwargs
|
||||
|
||||
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()
|
||||
|
||||
view_set.kwargs = self.kwargs
|
||||
|
||||
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 OrganizationPermissionMixin
|
||||
|
||||
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 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()
|
||||
|
||||
view_set.kwargs = self.kwargs
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
|
||||
def test_view_func_get_queryset_cache_result(self):
|
||||
"""Viewset Test
|
||||
|
||||
Ensure that the `get_queryset` function caches the result under
|
||||
attribute `<viewset>.queryset`
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
view_set.request = MockRequest(
|
||||
user = self.view_user,
|
||||
organization = self.organization,
|
||||
viewset = self.viewset
|
||||
)
|
||||
|
||||
view_set.request.headers = {}
|
||||
|
||||
view_set.kwargs = self.kwargs
|
||||
|
||||
view_set.action = 'list'
|
||||
|
||||
view_set.detail = False
|
||||
|
||||
assert view_set.queryset is None # Must be empty before init
|
||||
|
||||
q = view_set.get_queryset()
|
||||
|
||||
assert view_set.queryset is not None # Must not be empty after init
|
||||
|
||||
assert q == view_set.queryset
|
||||
|
||||
|
||||
def test_view_func_get_queryset_cache_result_used(self):
|
||||
"""Viewset Test
|
||||
|
||||
Ensure that the `get_queryset` function caches the result under
|
||||
attribute `<viewset>.queryset`
|
||||
"""
|
||||
|
||||
view_set = self.viewset()
|
||||
|
||||
view_set.request = MockRequest(
|
||||
user = self.view_user,
|
||||
organization = self.organization,
|
||||
viewset = self.viewset
|
||||
)
|
||||
|
||||
view_set.request.headers = {}
|
||||
view_set.kwargs = self.kwargs
|
||||
view_set.action = 'list'
|
||||
view_set.detail = False
|
||||
|
||||
mock_return = view_set.get_queryset() # Real item to be used as mock return Some
|
||||
# functions use `Queryset` for additional filtering
|
||||
|
||||
setter_not_called = True
|
||||
|
||||
|
||||
with patch.object(self.viewset, 'queryset', new_callable=PropertyMock) as qs:
|
||||
|
||||
qs.return_value = mock_return
|
||||
|
||||
mocked_view_set = self.viewset()
|
||||
|
||||
mocked_view_set.kwargs = self.kwargs
|
||||
mocked_view_set.action = 'list'
|
||||
mocked_view_set.detail = False
|
||||
|
||||
qs.reset_mock() # Just in case
|
||||
|
||||
mocked_setup = mocked_view_set.get_queryset() # should only add two calls, if exists and the return
|
||||
|
||||
|
||||
for mock_call in list(qs.mock_calls): # mock_calls with args means setter was called
|
||||
|
||||
if len(mock_call.args) > 0:
|
||||
|
||||
setter_not_called = False
|
||||
|
||||
|
||||
assert setter_not_called
|
||||
assert qs.call_count == 2
|
@ -2,18 +2,16 @@ from django.contrib.auth.models import User
|
||||
from django.shortcuts import reverse
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from access.models.organization import Organization
|
||||
|
||||
from api.tests.abstract.viewsets import ViewSetCommon
|
||||
from api.tests.unit.test_unit_common_viewset import IndexViewsetInheritedCases
|
||||
|
||||
from api.viewsets.index import Index
|
||||
|
||||
|
||||
class HomeViewset(
|
||||
TestCase,
|
||||
ViewSetCommon
|
||||
IndexViewsetInheritedCases
|
||||
):
|
||||
|
||||
viewset = Index
|
||||
@ -45,17 +43,3 @@ class HomeViewset(
|
||||
|
||||
self.kwargs = {}
|
||||
|
||||
|
||||
|
||||
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 IsAuthenticated
|
||||
|
||||
assert len(view_set.permission_classes) == 1
|
||||
|
||||
|
@ -8,6 +8,13 @@ from access.models.team_user import TeamUsers
|
||||
|
||||
from api.react_ui_metadata import ReactUIMetadata
|
||||
|
||||
class MockRequst:
|
||||
|
||||
user = None
|
||||
|
||||
def __init__(self, user ):
|
||||
|
||||
self.user = user
|
||||
|
||||
class NavigationMenu(
|
||||
TestCase
|
||||
@ -150,7 +157,7 @@ class NavigationMenu(
|
||||
|
||||
for model_name in model_names:
|
||||
|
||||
setattr(self, app_label + "_" + model_name['permission_model'], User.objects.create_user(username= app_label + "_" + model_name['permission_model'], password="password"))
|
||||
setattr(self, app_label + "_" + model_name['permission_model'], MockRequst( user = User.objects.create_user(username= app_label + "_" + model_name['permission_model'], password="password")))
|
||||
|
||||
team = Team.objects.create(
|
||||
team_name = app_label + "_" + model_name['permission_model'],
|
||||
@ -169,7 +176,7 @@ class NavigationMenu(
|
||||
|
||||
team_user = TeamUsers.objects.create(
|
||||
team = team,
|
||||
user = getattr(self, app_label + "_" + model_name['permission_model'])
|
||||
user = getattr(self, app_label + "_" + model_name['permission_model']).user
|
||||
)
|
||||
|
||||
self.metadata = ReactUIMetadata()
|
||||
@ -1459,9 +1466,9 @@ class NavigationMenu(
|
||||
|
||||
nav_menu = self.metadata.get_navigation(self.core_ticketcategory)
|
||||
|
||||
menu_name = 'settings'
|
||||
menu_name = 'itops'
|
||||
|
||||
page_name = 'setting'
|
||||
page_name = 'ticketcategory'
|
||||
|
||||
menu_page_exists: bool = False
|
||||
|
||||
@ -1514,9 +1521,9 @@ class NavigationMenu(
|
||||
|
||||
nav_menu = self.metadata.get_navigation(self.core_ticketcommentcategory)
|
||||
|
||||
menu_name = 'settings'
|
||||
menu_name = 'itops'
|
||||
|
||||
page_name = 'setting'
|
||||
page_name = 'ticketcommentcategory'
|
||||
|
||||
menu_page_exists: bool = False
|
||||
|
||||
|
2397
app/api/tests/unit/test_unit_common_viewset.py
Normal file
2397
app/api/tests/unit/test_unit_common_viewset.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,51 +1,27 @@
|
||||
import pytest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from access.models.organization import Organization
|
||||
from api.tests.unit.test_unit_common_viewset import (
|
||||
ModelCreateViewSetInheritedCases,
|
||||
ModelListRetrieveDeleteViewSetInheritedCases,
|
||||
)
|
||||
from api.viewsets.auth_token import ViewSet
|
||||
|
||||
from api.tests.abstract.viewsets import ViewSetModel
|
||||
|
||||
from settings.viewsets.user_settings import ViewSet
|
||||
# from settings.viewsets.user_settings import ViewSet
|
||||
|
||||
|
||||
|
||||
class ViewsetCommon(
|
||||
ViewSetModel,
|
||||
class ViewsetList(
|
||||
ModelCreateViewSetInheritedCases,
|
||||
ModelListRetrieveDeleteViewSetInheritedCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
route_name = 'v2:_api_v2_user_settings_token'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization
|
||||
3. create super user
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_view_user", password="password", is_superuser=True)
|
||||
|
||||
self.kwargs = {
|
||||
'model_id': self.view_user.id
|
||||
}
|
||||
|
||||
|
||||
|
||||
class ViewsetList(
|
||||
ViewsetCommon,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
@ -54,9 +30,12 @@ class ViewsetList(
|
||||
1. make list request
|
||||
"""
|
||||
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
self.kwargs = {
|
||||
'model_id': self.view_user.id
|
||||
}
|
||||
|
||||
|
||||
client = Client()
|
||||
|
||||
|
@ -2,7 +2,7 @@ from django.urls import include, path
|
||||
|
||||
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
|
||||
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from centurion_feature_flag.urls.routers import DefaultRouter
|
||||
|
||||
from api.viewsets import (
|
||||
auth_token,
|
||||
@ -17,9 +17,13 @@ from app.viewsets.base import (
|
||||
)
|
||||
|
||||
from access.viewsets import (
|
||||
entity,
|
||||
entity_notes,
|
||||
index as access_v2,
|
||||
organization as organization_v2,
|
||||
organization_notes,
|
||||
role,
|
||||
role_notes,
|
||||
team as team_v2,
|
||||
team_notes,
|
||||
team_user as team_user_v2
|
||||
@ -131,12 +135,18 @@ 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/entity/(?P<entity_model>[a-z]+)?', entity.ViewSet, feature_flag = '2025-00002', basename='_api_v2_entity_sub')
|
||||
router.register('access/entity', entity.NoDocsViewSet, feature_flag = '2025-00002', basename='_api_v2_entity')
|
||||
router.register('access/entity/(?P<model_id>[0-9]+)/notes', entity_notes.ViewSet, feature_flag = '2025-00002', basename='_api_v2_entity_note')
|
||||
|
||||
router.register('access/organization', organization_v2.ViewSet, basename='_api_v2_organization')
|
||||
router.register('access/organization/(?P<model_id>[0-9]+)/notes', organization_notes.ViewSet, basename='_api_v2_organization_note')
|
||||
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<model_id>[0-9]+)/notes', team_notes.ViewSet, basename='_api_v2_team_note')
|
||||
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('access/role', role.ViewSet, feature_flag = '2025-00003', basename='_api_v2_role')
|
||||
router.register('access/role/(?P<model_id>[0-9]+)/notes', role_notes.ViewSet, feature_flag = '2025-00003', basename='_api_v2_role_note')
|
||||
|
||||
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')
|
||||
@ -247,6 +257,8 @@ urlpatterns = [
|
||||
urlpatterns += router.urls
|
||||
|
||||
urlpatterns += [
|
||||
path("accounting/", include("accounting.urls")),
|
||||
path("devops/", include("devops.urls")),
|
||||
path("hr/", include('human_resources.urls')),
|
||||
path('public/', include('api.urls_public')),
|
||||
]
|
||||
|
@ -44,21 +44,31 @@ class Create(
|
||||
response = super().create(request = request, *args, **kwargs)
|
||||
|
||||
# Always return using the ViewSerializer
|
||||
serializer_module = importlib.import_module(self.serializer_class.__module__)
|
||||
serializer_module = importlib.import_module(self.get_serializer_class().__module__)
|
||||
|
||||
view_serializer = getattr(serializer_module, self.get_view_serializer_name())
|
||||
|
||||
serializer = view_serializer(
|
||||
self.get_queryset().get( pk = int(response.data['id']) ),
|
||||
context = {
|
||||
'request': request,
|
||||
'view': self,
|
||||
},
|
||||
)
|
||||
if response.data['id'] is not None:
|
||||
|
||||
serializer = view_serializer(
|
||||
self.get_queryset().get( pk = int(response.data['id']) ),
|
||||
context = {
|
||||
'request': request,
|
||||
'view': self,
|
||||
},
|
||||
)
|
||||
|
||||
serializer_data = serializer.data
|
||||
|
||||
else:
|
||||
|
||||
|
||||
serializer_data = {}
|
||||
|
||||
|
||||
# Mimic ALL details from DRF response except serializer
|
||||
response = Response(
|
||||
data = serializer.data,
|
||||
data = serializer_data,
|
||||
status = response.status_code,
|
||||
template_name = response.template_name,
|
||||
headers = response.headers,
|
||||
@ -272,7 +282,7 @@ class Update(
|
||||
response = super().partial_update(request = request, *args, **kwargs)
|
||||
|
||||
# Always return using the ViewSerializer
|
||||
serializer_module = importlib.import_module(self.serializer_class.__module__)
|
||||
serializer_module = importlib.import_module(self.get_serializer_class().__module__)
|
||||
|
||||
view_serializer = getattr(serializer_module, self.get_view_serializer_name())
|
||||
|
||||
@ -339,7 +349,7 @@ class Update(
|
||||
response = super().update(request = request, *args, **kwargs)
|
||||
|
||||
# Always return using the ViewSerializer
|
||||
serializer_module = importlib.import_module(self.serializer_class.__module__)
|
||||
serializer_module = importlib.import_module(self.get_serializer_class().__module__)
|
||||
|
||||
view_serializer = getattr(serializer_module, self.get_view_serializer_name())
|
||||
|
||||
@ -515,7 +525,8 @@ class CommonViewSet(
|
||||
|
||||
elif getattr(self.model, '_meta', None):
|
||||
|
||||
self._model_documentation = self.model._meta.app_label + '/' + self.model._meta.model_name
|
||||
self._model_documentation = self.model._meta.app_label + '/' + str(
|
||||
self.model._meta.verbose_name).lower().replace(' ', '_')
|
||||
|
||||
|
||||
return self._model_documentation
|
||||
@ -604,7 +615,7 @@ class CommonViewSet(
|
||||
self.view_name = str(self.model._meta.verbose_name)
|
||||
|
||||
else:
|
||||
|
||||
|
||||
self.view_name = str(self.model._meta.verbose_name_plural)
|
||||
|
||||
return self.view_name
|
||||
@ -658,11 +669,9 @@ class ModelViewSetBase(
|
||||
|
||||
self.queryset = self.model.objects.all()
|
||||
|
||||
if 'pk' in self.kwargs:
|
||||
if 'pk' in getattr(self, 'kwargs', {}):
|
||||
|
||||
if self.kwargs['pk']:
|
||||
|
||||
self.queryset = self.queryset.filter( pk = int( self.kwargs['pk'] ) )
|
||||
self.queryset = self.queryset.filter( pk = int( self.kwargs['pk'] ) )
|
||||
|
||||
|
||||
return self.queryset
|
||||
@ -698,7 +707,7 @@ class ModelViewSetBase(
|
||||
|
||||
if self.view_serializer_name is None:
|
||||
|
||||
self.view_serializer_name = self.serializer_class.__name__.replace('ModelSerializer', 'ViewSerializer')
|
||||
self.view_serializer_name = self.get_serializer_class().__name__.replace('ModelSerializer', 'ViewSerializer')
|
||||
|
||||
return self.view_serializer_name
|
||||
|
||||
|
@ -69,6 +69,7 @@ CELERY_TASK_SEND_SENT_EVENT = True
|
||||
CELERY_WORKER_SEND_TASK_EVENTS = True # worker_send_task_events
|
||||
|
||||
FEATURE_FLAGGING_ENABLED = True # Turn Feature Flagging on/off
|
||||
FEATURE_FLAG_OVERRIDES = None # Feature Flags to override fetched feature flags
|
||||
|
||||
# PROMETHEUS_METRICS_EXPORT_PORT_RANGE = range(8010, 8010)
|
||||
# PROMETHEUS_METRICS_EXPORT_PORT = 8010
|
||||
@ -137,7 +138,10 @@ INSTALLED_APPS = [
|
||||
'config_management.apps.ConfigManagementConfig',
|
||||
'project_management.apps.ProjectManagementConfig',
|
||||
'devops.apps.DevOpsConfig',
|
||||
'centurion_feature_flag',
|
||||
'centurion_feature_flag.apps.CenturionFeatureFlagConfig',
|
||||
'human_resources.apps.HumanResourcesConfig',
|
||||
'itops.apps.ItOpsConfig',
|
||||
'accounting.apps.AccountingConfig',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
@ -152,7 +156,7 @@ MIDDLEWARE = [
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'core.middleware.get_request.RequestMiddleware',
|
||||
'app.middleware.timezone.TimezoneMiddleware',
|
||||
# 'centurion_feature_flag.middleware.feature_flag.FeatureFlagMiddleware',
|
||||
'centurion_feature_flag.middleware.feature_flag.FeatureFlagMiddleware',
|
||||
]
|
||||
|
||||
|
||||
@ -437,7 +441,7 @@ if SSO_ENABLED:
|
||||
|
||||
if BUILD_VERSION:
|
||||
|
||||
feature_flag_version = str(BUILD_VERSION) + '+' + str(BUILD_SHA)[8:]
|
||||
feature_flag_version = str(BUILD_VERSION) + '+' + str(BUILD_SHA)[:8]
|
||||
|
||||
else:
|
||||
|
||||
@ -488,5 +492,59 @@ if FEATURE_FLAGGING_ENABLED:
|
||||
'cache_dir': str(BASE_DIR) + '/',
|
||||
'disable_downloading': False,
|
||||
'unique_id': unique_id,
|
||||
'version': feature_flag_version
|
||||
'version': feature_flag_version,
|
||||
}
|
||||
|
||||
if FEATURE_FLAG_OVERRIDES:
|
||||
|
||||
feature_flag.update({
|
||||
'over_rides': FEATURE_FLAG_OVERRIDES
|
||||
})
|
||||
|
||||
|
||||
if DEBUG or RUNNING_TESTS:
|
||||
|
||||
feature_flag.update({ 'disable_downloading': True, })
|
||||
|
||||
debug_feature_flags = [
|
||||
{
|
||||
"2025-00001": {
|
||||
"name": "DevOps/Git Repositories",
|
||||
"description": "Disables Git Repositories and Git Groups. see https://github.com/nofusscomputing/centurion_erp/issues/515",
|
||||
"enabled": True,
|
||||
"created": "",
|
||||
"modified": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"2025-00002": {
|
||||
"name": "Entities",
|
||||
"description": "Entities see https://github.com/nofusscomputing/centurion_erp/issues/704",
|
||||
"enabled": True,
|
||||
"created": "",
|
||||
"modified": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"2025-00003": {
|
||||
"name": "Role Based Access Control (RBAC)",
|
||||
"description": "Refactor of authentication and authorization to be RBAC based. see https://github.com/nofusscomputing/centurion_erp/issues/551",
|
||||
"enabled": True,
|
||||
"created": "",
|
||||
"modified": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"2025-00004": {
|
||||
"name": "Accounting Module",
|
||||
"description": "Accounting related functions. see https://github.com/nofusscomputing/centurion_erp/issues/88",
|
||||
"enabled": True,
|
||||
"created": "",
|
||||
"modified": ""
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
feature_flag.update({
|
||||
'over_rides': debug_feature_flags
|
||||
})
|
||||
|
@ -172,8 +172,11 @@ class BaseModel:
|
||||
print(f'Checking field {field.attname} is not empty')
|
||||
|
||||
if (
|
||||
field.help_text is None
|
||||
or field.help_text == ''
|
||||
(
|
||||
field.help_text is None
|
||||
or field.help_text == ''
|
||||
)
|
||||
and not str(field.attname).endswith('_ptr_id')
|
||||
):
|
||||
|
||||
print(f' Failure on field {field.attname}')
|
||||
|
@ -1,60 +0,0 @@
|
||||
from django.contrib.auth.models import User
|
||||
from django.shortcuts import reverse
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from access.models.organization import Organization
|
||||
|
||||
from api.tests.abstract.viewsets import ViewSetCommon
|
||||
|
||||
from assistance.viewsets.index import Index
|
||||
|
||||
|
||||
class AssistanceViewset(
|
||||
TestCase,
|
||||
ViewSetCommon
|
||||
):
|
||||
|
||||
viewset = Index
|
||||
|
||||
route_name = 'v2:_api_v2_assistance_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)
|
||||
|
||||
self.kwargs = {}
|
||||
|
||||
|
||||
|
||||
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 IsAuthenticated
|
||||
|
||||
assert len(view_set.permission_classes) == 1
|
@ -1,49 +1,22 @@
|
||||
import pytest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from access.models.organization import Organization
|
||||
|
||||
from api.tests.abstract.viewsets import ViewSetModel
|
||||
from api.tests.unit.test_unit_common_viewset import ModelViewSetInheritedCases
|
||||
|
||||
from assistance.viewsets.knowledge_base import ViewSet
|
||||
|
||||
|
||||
|
||||
class ViewsetCommon(
|
||||
ViewSetModel,
|
||||
class KnowledgeBaseViewsetList(
|
||||
ModelViewSetInheritedCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
route_name = 'v2:_api_v2_knowledge_base'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization
|
||||
3. create super user
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_view_user", password="password", is_superuser=True)
|
||||
|
||||
self.kwargs = {}
|
||||
|
||||
|
||||
|
||||
class KnowledgeBaseViewsetList(
|
||||
ViewsetCommon,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
@ -1,49 +1,22 @@
|
||||
import pytest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from access.models.organization import Organization
|
||||
|
||||
from api.tests.abstract.viewsets import ViewSetModel
|
||||
from api.tests.unit.test_unit_common_viewset import ModelViewSetInheritedCases
|
||||
|
||||
from assistance.viewsets.knowledge_base_category import ViewSet
|
||||
|
||||
|
||||
|
||||
class ViewsetCommon(
|
||||
ViewSetModel,
|
||||
class KnowledgeBaseViewsetList(
|
||||
ModelViewSetInheritedCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
route_name = 'v2:_api_v2_knowledge_base_category'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization
|
||||
3. create super user
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_view_user", password="password", is_superuser=True)
|
||||
|
||||
self.kwargs = {}
|
||||
|
||||
|
||||
|
||||
class KnowledgeBaseViewsetList(
|
||||
ViewsetCommon,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
@ -1,47 +1,22 @@
|
||||
import pytest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from access.models.organization import Organization
|
||||
|
||||
from api.tests.abstract.viewsets import ViewSetModel
|
||||
from api.tests.unit.test_unit_common_viewset import ModelViewSetInheritedCases
|
||||
|
||||
from assistance.viewsets.knowledge_base_category_notes import ViewSet
|
||||
|
||||
|
||||
class ViewsetCommon(
|
||||
ViewSetModel,
|
||||
|
||||
class KnowledgeBaseCategoryNotesViewsetList(
|
||||
ModelViewSetInheritedCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
route_name = 'v2:_api_v2_knowledge_base_category_note'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization
|
||||
3. create super user
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_view_user", password="password", is_superuser=True)
|
||||
|
||||
|
||||
|
||||
|
||||
class KnowledgeBaseCategoryNotesViewsetList(
|
||||
ViewsetCommon,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
@ -1,47 +1,22 @@
|
||||
import pytest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from access.models.organization import Organization
|
||||
|
||||
from api.tests.abstract.viewsets import ViewSetModel
|
||||
from api.tests.unit.test_unit_common_viewset import ModelViewSetInheritedCases
|
||||
|
||||
from assistance.viewsets.knowledge_base_notes import ViewSet
|
||||
|
||||
|
||||
class ViewsetCommon(
|
||||
ViewSetModel,
|
||||
|
||||
class KnowledgeBaseNotesViewsetList(
|
||||
ModelViewSetInheritedCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
route_name = 'v2:_api_v2_knowledge_base_note'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization
|
||||
3. create super user
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_view_user", password="password", is_superuser=True)
|
||||
|
||||
|
||||
|
||||
|
||||
class KnowledgeBaseNotesViewsetList(
|
||||
ViewsetCommon,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
@ -1,59 +1,23 @@
|
||||
import pytest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from access.models.organization import Organization
|
||||
|
||||
from api.tests.abstract.viewsets import ViewSetModel
|
||||
from api.tests.unit.test_unit_common_viewset import ModelViewSetInheritedCases
|
||||
|
||||
from assistance.viewsets.model_knowledge_base_article import ViewSet
|
||||
|
||||
from itam.models.device import Device
|
||||
|
||||
|
||||
|
||||
class ViewsetCommon(
|
||||
ViewSetModel,
|
||||
class ModelKnowledgeBaseArticleViewsetList(
|
||||
ModelViewSetInheritedCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
route_name = 'v2:_api_v2_model_kb'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization
|
||||
3. create super user
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_view_user", password="password", is_superuser=True)
|
||||
|
||||
device = Device.objects.create(
|
||||
organization = self.organization,
|
||||
name = 'device'
|
||||
)
|
||||
|
||||
self.kwargs = {
|
||||
'model': 'itam.device',
|
||||
'model_pk': device.id,
|
||||
}
|
||||
|
||||
|
||||
|
||||
class ModelKnowledgeBaseArticleViewsetList(
|
||||
ViewsetCommon,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
@ -65,6 +29,16 @@ class ModelKnowledgeBaseArticleViewsetList(
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
device = Device.objects.create(
|
||||
organization = self.organization,
|
||||
name = 'device'
|
||||
)
|
||||
|
||||
self.kwargs = {
|
||||
'model': 'itam.device',
|
||||
'model_pk': device.id,
|
||||
}
|
||||
|
||||
|
||||
client = Client()
|
||||
|
||||
|
37
app/assistance/tests/unit/test_assistance_viewset.py
Normal file
37
app/assistance/tests/unit/test_assistance_viewset.py
Normal file
@ -0,0 +1,37 @@
|
||||
from django.shortcuts import reverse
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from api.tests.unit.test_unit_common_viewset import IndexViewsetInheritedCases
|
||||
|
||||
from assistance.viewsets.index import Index
|
||||
|
||||
|
||||
class AssistanceViewset(
|
||||
IndexViewsetInheritedCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = Index
|
||||
|
||||
route_name = 'v2:_api_v2_assistance_home'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization for user
|
||||
3. create super user
|
||||
"""
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.route_name + '-list')
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
self.http_options_response_list = client.options(url)
|
||||
|
||||
self.kwargs = {}
|
@ -1,50 +1,22 @@
|
||||
import pytest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from access.models.organization import Organization
|
||||
|
||||
from api.tests.abstract.viewsets import ViewSetModel
|
||||
from api.tests.unit.test_unit_common_viewset import ModelViewSetInheritedCases
|
||||
|
||||
from assistance.viewsets.request import ViewSet
|
||||
|
||||
|
||||
|
||||
|
||||
class ViewsetCommon(
|
||||
ViewSetModel,
|
||||
class RequestViewsetList(
|
||||
ModelViewSetInheritedCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
route_name = 'v2:_api_v2_ticket_request'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization
|
||||
3. create super user
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_view_user", password="password", is_superuser=True)
|
||||
|
||||
self.kwargs = {}
|
||||
|
||||
|
||||
|
||||
class RequestViewsetList(
|
||||
ViewsetCommon,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
@ -14,6 +14,7 @@ feature_flag = {
|
||||
'disable_downloading': False # Prevent downloading feature flags
|
||||
'unique_id': 'unique ID for application', # Unique ID for this instance of your Django application
|
||||
'version': '1.0.0', # The Version of Your Django Application
|
||||
'over_rides': [] # list(dict). Feature Flag over rides. use same format as API endpoint.
|
||||
} # Note: All key values are strings
|
||||
|
||||
```
|
||||
|
@ -76,6 +76,9 @@ class CenturionFeatureFlagging:
|
||||
_last_modified: datetime = None
|
||||
""" Last modified date/time of the feature flags"""
|
||||
|
||||
_over_rides: dict = None
|
||||
"""Feature Flag Over rides."""
|
||||
|
||||
_response: requests.Response = None
|
||||
"""Cached response from fetched feature flags"""
|
||||
|
||||
@ -95,6 +98,7 @@ class CenturionFeatureFlagging:
|
||||
disable_downloading: bool = False,
|
||||
unique_id: str = None,
|
||||
version: str = None,
|
||||
over_rides: dict = None,
|
||||
):
|
||||
|
||||
if not str(cache_dir).endswith('/'):
|
||||
@ -108,6 +112,25 @@ class CenturionFeatureFlagging:
|
||||
|
||||
self._disable_downloading = disable_downloading
|
||||
|
||||
if self._disable_downloading:
|
||||
|
||||
self._feature_flags = {}
|
||||
|
||||
_over_rides: dict = {}
|
||||
|
||||
if over_rides:
|
||||
|
||||
for entry in over_rides:
|
||||
|
||||
[*key], [*flag] = zip(*entry.items())
|
||||
|
||||
_over_rides.update({
|
||||
key[0]: FeatureFlag(key[0], flag[0])
|
||||
})
|
||||
|
||||
|
||||
self._over_rides = _over_rides
|
||||
|
||||
|
||||
if version is None:
|
||||
|
||||
@ -142,6 +165,9 @@ class CenturionFeatureFlagging:
|
||||
self._feature_flags is not None
|
||||
and self._last_modified is not None
|
||||
)
|
||||
or (
|
||||
self._over_rides is not None
|
||||
)
|
||||
):
|
||||
|
||||
return True
|
||||
@ -164,7 +190,10 @@ class CenturionFeatureFlagging:
|
||||
Returns:
|
||||
dict: A complete Feature Flag.
|
||||
"""
|
||||
if self._feature_flags is None:
|
||||
if(
|
||||
self._feature_flags is None
|
||||
and self._over_rides.get(key, None) is None
|
||||
):
|
||||
|
||||
print('Feature Flagging has not been completly initialized.')
|
||||
print(' please ensure that the feature flags have been downloaded.')
|
||||
@ -174,6 +203,7 @@ class CenturionFeatureFlagging:
|
||||
|
||||
if(
|
||||
self._feature_flags.get(key, None) is None
|
||||
and self._over_rides.get(key, None) is None
|
||||
and raise_exceptions
|
||||
):
|
||||
|
||||
@ -182,10 +212,19 @@ class CenturionFeatureFlagging:
|
||||
elif(
|
||||
not raise_exceptions
|
||||
and self._feature_flags.get(key, None) is None
|
||||
and self._over_rides.get(key, None) is None
|
||||
):
|
||||
|
||||
return False
|
||||
|
||||
elif(
|
||||
not raise_exceptions
|
||||
and self._over_rides.get(key, None) is not None
|
||||
):
|
||||
|
||||
return self._over_rides[key]
|
||||
|
||||
|
||||
return self._feature_flags[key]
|
||||
|
||||
|
||||
@ -258,18 +297,31 @@ class CenturionFeatureFlagging:
|
||||
|
||||
self._response = response
|
||||
|
||||
fetched_flags += resp.json()['results']
|
||||
|
||||
if resp.status_code == 304: # Nothing has changed, exit the loop
|
||||
|
||||
url = None
|
||||
|
||||
else: # Fetch next page of results
|
||||
elif resp.ok: # Fetch next page of results
|
||||
|
||||
fetched_flags += resp.json()['results']
|
||||
|
||||
url = resp.json()['next']
|
||||
|
||||
else:
|
||||
|
||||
url = None
|
||||
|
||||
except requests.exceptions.ConnectionError as err:
|
||||
|
||||
print(f'Error Connecting to {url}')
|
||||
|
||||
url = None
|
||||
|
||||
except requests.exceptions.ReadTimeout as err:
|
||||
|
||||
print(f'Connection Timed Out connecting to {url}')
|
||||
|
||||
url = None
|
||||
|
||||
|
||||
|
@ -27,6 +27,7 @@ class Command(BaseCommand):
|
||||
disable_downloading = settings.feature_flag.get('disable_downloading', False),
|
||||
unique_id = settings.feature_flag.get('unique_id', None),
|
||||
version = settings.feature_flag.get('version', None),
|
||||
over_rides = settings.feature_flag.get('over_rides', None),
|
||||
)
|
||||
|
||||
self.stdout.write('Fetching Feature Flags.....')
|
||||
@ -39,7 +40,7 @@ class Command(BaseCommand):
|
||||
|
||||
else:
|
||||
|
||||
self.stdout.stderr('Error. Something went wrong.')
|
||||
self.stderr.write('Error. Something went wrong.')
|
||||
|
||||
if kwargs['reload']:
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user