Compare commits

..

70 Commits

Author SHA1 Message Date
aaaf902759 build: bump version 1.18.0 -> 1.19.0 2025-08-15 07:08:06 +00:00
Jon
c6f382bb0f ci: update to current gitlab-ci HEAD again again
ref: #950
2025-08-15 16:36:54 +09:30
Jon
ea58248be2 ci: update to current gitlab-ci HEAD again
ref: #950
2025-08-15 15:46:47 +09:30
Jon
e89b4f39b2 ci: update to current gitlab-ci HEAD
ref: #950
2025-08-15 15:26:34 +09:30
Jon
59a79519c0 Merge pull request #764 from nofusscomputing/feature-next-release 2025-08-15 14:40:25 +09:30
Jon
9581791fd6 docs: correct release notes version
ref: #764 #738
2025-08-15 14:26:34 +09:30
Jon
48adbd7021 chore: squash migrations
ref: #764 #738
2025-08-15 13:51:44 +09:30
Jon
fcb2c39e4d Merge pull request #948 from nofusscomputing/2025-08-14 2025-08-14 18:08:46 +09:30
Jon
b391bcbb9f feat(core): add to migration signal system user and use for inventory objects
ref: #948 closes #949
2025-08-14 17:20:37 +09:30
Jon
1b004bee2d test(core): Notes Meta Models API Permissions Test cases for All Notes Models
ref: #948 closes #778
2025-08-14 17:11:47 +09:30
Jon
594e77fc77 Merge pull request #947 from nofusscomputing/initial-integration-tests 2025-08-12 17:05:47 +09:30
Jon
89970dc4e2 docs: Add tested version badges
ref: #947 #152
2025-08-12 16:35:02 +09:30
Jon
190e4b4a98 ci(integration): Increase wait time after gunicorn restart to 60secs
ref: #947 #152
2025-08-12 14:23:26 +09:30
Jon
8d6d1d258d ci(python): add app versions to inputs
ref: #947 #152
2025-08-12 13:57:14 +09:30
Jon
b32d7f302e chore(tests): conduct propper'er cleanup of project objs
ref: #947
2025-08-12 13:57:04 +09:30
Jon
089480620e test: Add initial integration tests
ref: #947 #152 closes #774
2025-08-12 13:15:15 +09:30
Jon
bc9d6b74fd feat(docker): Adjust gunicorn works=4 100reqs/max and preload app
ref: #947 #152 #774
2025-08-12 13:03:45 +09:30
Jon
525da2fbe0 refactor(docker): update healthcheck interval=10s and start-period=30s
ref: #947 #152 #774
2025-08-12 13:02:49 +09:30
Jon
ecc16e6cbf test(docker): Add compose setup for integration testing
ref: #947 #152 #774
2025-08-12 13:01:58 +09:30
Jon
e1f8ba0c2b chore(docker): Add optional coverage to gunicorn
ref: #947 #152
2025-08-12 12:52:44 +09:30
Jon
037fbabeae refactor(docker): when l;aunching gunicorn create a pid file
ref: #947
2025-08-12 12:51:16 +09:30
Jon
6732315b96 fix: remove trailing slant from URLs
ref: #947
2025-08-12 12:50:27 +09:30
Jon
3131258491 fix(access): When creating permission QuerySet prevent app crash if db not setup
ref: #947 #152
2025-08-12 12:48:08 +09:30
Jon
6ff4779cfb chore: exclude app/artifacts dir from repo
ref: #947 #774 #152
2025-08-10 16:38:55 +09:30
Jon
1c83547aaa Merge pull request #946 from nofusscomputing/refactor-tests 2025-08-07 16:33:27 +09:30
Jon
40f8e4d8b1 refactor(devops): API Fields render Functional Test Suite re-written to Pytest for model SoftwareEnableFeatureFlag Again
ref: #946 #945
2025-08-07 14:16:16 +09:30
Jon
7b009f7378 chore(api): Remove commented code
ref: #946 closes #895
2025-08-07 13:58:45 +09:30
Jon
de3934a761 feat(api): Ensure that serializer converts Django exceptions to rest_framework exceptions
ref: #946
2025-08-07 13:45:03 +09:30
Jon
1b7d108a29 refactor(devops): Remove old test suites no longer required model SoftwareEnableFeatureFlag
ref: #946 closes #945
2025-08-07 13:44:07 +09:30
Jon
ca7a9baab8 refactor(devops): ViewSet Unit Test Suite re-written to Pytest for model SoftwareEnableFeatureFlag
ref: #946 #945
2025-08-07 13:43:46 +09:30
Jon
274c32c673 refactor(devops): Serializer Unit Test Suite re-written to Pytest for model SoftwareEnableFeatureFlag
ref: #946 #945 closes #874
2025-08-07 13:42:49 +09:30
Jon
e806a5652e refactor(devops): API Fields render Functional Test Suite re-written to Pytest for model SoftwareEnableFeatureFlag
ref: #946 #945 closes #730
2025-08-07 13:42:19 +09:30
Jon
703b6a67b2 refactor(devops): Model Functional Test Suite re-written to Pytest for model SoftwareEnableFeatureFlag
ref: #946 #945
2025-08-07 13:41:53 +09:30
Jon
0159fd6ed8 refactor(devops): API Metadata Functional Test Suite re-written to Pytest for model SoftwareEnableFeatureFlag
ref: #946 #945
2025-08-07 13:41:17 +09:30
Jon
2b8513abf0 refactor(devops): Remove old test suites no longer required model FeatureFlag
ref: #946 closes #944
2025-08-07 12:50:04 +09:30
Jon
146dd508d6 refactor(devops): ViewSet Unit Test Suite re-written to Pytest for model FeatureFlag
ref: #946 #944
2025-08-07 12:49:23 +09:30
Jon
20f5c3b5d7 refactor(devops): Serializer Unit Test Suite re-written to Pytest for model FeatureFlag
ref: #946 #944
2025-08-07 12:49:06 +09:30
Jon
d340fb3375 refactor(devops): API Fields render Functional Test Suite re-written to Pytest for model FeatureFlag
ref: #946 #944
2025-08-07 12:48:51 +09:30
Jon
bc3f1e8a68 refactor(devops): Model Functional Test Suite re-written to Pytest for model FeatureFlag
ref: #946 #944
2025-08-07 12:48:39 +09:30
Jon
44adc6c8ab refactor(devops): API Metadata Functional Test Suite re-written to Pytest for model FeatureFlag
ref: #946 #944
2025-08-07 12:48:24 +09:30
Jon
6cb66db46f refactor(api): Remove old test suites no longer required model AuthToken
ref: #946 closes #943
2025-08-07 12:10:26 +09:30
Jon
a08ea057cc refactor(api): ViewSet Unit Test Suite re-written to Pytest for model AuthToken
ref: #946 #943
2025-08-07 12:09:18 +09:30
Jon
30605d7998 refactor(api): Serializer Unit Test Suite re-written to Pytest for model AuthToken
ref: #946 #943
2025-08-07 12:08:38 +09:30
Jon
121be79e03 refactor(api): API Fields render Functional Test Suite re-written to Pytest for model AuthToken
ref: #946 #943
2025-08-07 12:08:03 +09:30
Jon
c4ca06fba4 refactor(api): Model Functional Test Suite re-written to Pytest for model AuthToken
ref: #946 #943
2025-08-07 12:07:38 +09:30
Jon
9271702a62 refactor(api): API Metadata Functional Test Suite re-written to Pytest for model AuthToken
ref: #946 #943
2025-08-07 12:06:31 +09:30
Jon
861bef0ce1 Merge pull request #942 from nofusscomputing/settings-refactor-tests 2025-08-05 15:09:20 +09:30
Jon
3b86ab0e88 refactor(access): Remove old test suites no longer required model Tenant
ref: #942 closes #899
2025-08-05 14:41:08 +09:30
Jon
9f62e2a458 refactor(access): Serializer Unit Test Suite re-written to Pytest for model Tenant
ref: #942 #899
2025-08-05 14:39:07 +09:30
Jon
bd393e3dd4 refactor(access): API Fields render Functional Test Suite re-written to Pytest for model Tenant
ref: #942 #899
2025-08-05 14:38:38 +09:30
Jon
9c73885f1b refactor(access): Model Functional Test Suite re-written to Pytest for model Tenant
ref: #942 #899
2025-08-05 14:38:23 +09:30
Jon
dbac9326b3 refactor(access): API Metadata Functional Test Suite re-written to Pytest for model Tenant
ref: #942 #899
2025-08-05 14:38:03 +09:30
Jon
dd0a6a01b9 refactor(settings): Remove old test suites no longer required model UserSettings
ref: #942 closes #941
2025-08-05 13:56:07 +09:30
Jon
c294f9c8f5 refactor(settings): ViewSet Unit Test Suite re-written to Pytest for model UserSettings
ref: #942 #941
2025-08-05 13:55:58 +09:30
Jon
71a510170d refactor(settings): Serializer Unit Test Suite re-written to Pytest for model UserSettings
ref: #942 #941
2025-08-05 13:55:49 +09:30
Jon
d577b12a33 refactor(settings): API Fields render Functional Test Suite re-written to Pytest for model UserSettings
ref: #942 #941
2025-08-05 13:43:49 +09:30
Jon
fb090b6f63 refactor(settings): Model Functional Test Suite re-written to Pytest for model UserSettings
ref: #942 #941
2025-08-05 13:43:13 +09:30
Jon
75cda05579 refactor(settings): API Metadata Functional Test Suite re-written to Pytest for model UserSettings
ref: #942 #941
2025-08-05 13:43:01 +09:30
Jon
97e63f1daa refactor(settings): Remove old test suites no longer required model ExternalLink
ref: #942 closes #940
2025-08-05 13:18:07 +09:30
Jon
dce169109c refactor(settings): ViewSet Unit Test Suite re-written to Pytest for model ExternalLink
ref: #942 #940
2025-08-05 13:17:33 +09:30
Jon
4c6473a7b0 refactor(settings): Serializer Unit Test Suite re-written to Pytest for model ExternalLink
ref: #942 #940
2025-08-05 13:17:23 +09:30
Jon
204a20b793 refactor(settings): API Fields render Functional Test Suite re-written to Pytest for model ExternalLink
ref: #942 #940
2025-08-05 13:17:08 +09:30
Jon
bf1a60439f refactor(settings): Model Functional Test Suite re-written to Pytest for model ExternalLink
ref: #942 #940
2025-08-05 13:16:56 +09:30
Jon
006d7ab0c2 refactor(settings): API Metadata Functional Test Suite re-written to Pytest for model ExternalLink
ref: #942 #940
2025-08-05 13:16:36 +09:30
Jon
cca693d02d refactor(settings): Remove old test suites no longer required model AppSettings
ref: #942 closes #939
2025-08-05 12:51:24 +09:30
Jon
bb88a7025d refactor(settings): ViewSet Unit Test Suite re-written to Pytest for model AppSettings
ref: #942 #939
2025-08-05 12:50:50 +09:30
Jon
8da10a147b refactor(settings): Serializer Unit Test Suite re-written to Pytest for model AppSettings
ref: #942 #939
2025-08-05 12:50:30 +09:30
Jon
653e29ffe5 refactor(settings): API Fields render Functional Test Suite re-written to Pytest for model AppSettings
ref: #942 #939
2025-08-05 12:50:10 +09:30
Jon
b13610ef0e refactor(settings): Model Functional Test Suite re-written to Pytest for model AppSettings
ref: #942 #939
2025-08-05 12:49:53 +09:30
Jon
8739eba655 refactor(settings): API Metadata Functional Test Suite re-written to Pytest for model AppSettings
ref: #942 #939
2025-08-05 12:49:16 +09:30
198 changed files with 9334 additions and 8885 deletions

View File

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

View File

@ -46,6 +46,19 @@ jobs:
WORKFLOW_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
integration-test:
name: 'Integration Test'
uses: nofusscomputing/action_python/.github/workflows/python-integration.yaml@development
needs:
- docker
with:
POSTGRES_VERSIONS: '[ "13", "14", "15", "16", "17" ]'
PYTHON_VERSION: '3.11'
RABBITMQ_VERSIONS: '[ "3.12", "3.13", "4.0", "4.1" ]'
secrets:
WORKFLOW_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
gitlab-mirror:
if: ${{ github.repository == 'nofusscomputing/centurion_erp' }}
runs-on: ubuntu-latest

5
.gitignore vendored
View File

@ -21,3 +21,8 @@ feature_flags.json
coverage_*.json
*-coverage.xml
log/
# Integration testing
app/artifacts/
app/pyproject.toml
app/histogram_**
app/counter_**

View File

@ -1,3 +1,698 @@
## 1.19.0 (2025-08-15)
### feat
- **core**: add to migration signal system user and use for inventory objects
- **docker**: Adjust gunicorn works=4 100reqs/max and preload app
- **api**: Ensure that serializer converts Django exceptions to rest_framework exceptions
- **access**: Filter history permissions
- **access**: Add Audit and notes tables for model Role
- **access**: Add AuditHistory Serializer for Role model
- **access**: Add Notes Serializer for Role model
- **itam**: Add AuditHistory Serializer for ITAMAssetBase model
- **itam**: Add Notes Serializer for ITAMAssetBase model
- **accounting**: Add AuditHistory Serializer for AssetBase model
- **accounting**: Add Notes Serializer for AssetBase model
- When attempting to create and objetc must be unique and alrready exists, dont return error return existing object
- **access**: History + Notes model migrations for Company Model
- **api**: map notfound and perm denied django -> drf exceptions
- **human_resources**: Add model tag for Employee model
- **human_resources**: Add AuditHistory Serializer for Employee model
- **human_resources**: Add Notes Serializer for Employee model
- **human_resources**: Change model to inherit from `CenturionModel` for Employee model
- **access**: Add model tag for Person model
- **access**: Add AuditHistory Serializer for Person model
- **access**: Add Notes Serializer for Person model
- **access**: Change model to inherit from `CenturionModel` for Person model
- **access**: Add model tag for Contact model
- **access**: Add AuditHistory Serializer for Contact model
- **access**: Add Notes Serializer for Contact model
- **access**: Change model to inherit from `CenturionModel` for Contact model
- **access**: Add AuditHistory Serializer for Company model
- **access**: Add Notes Serializer for Entity model
- **access**: Change model to inherit from `CenturionModel` for Company model
- **access**: Change model to inherit from `CenturionModel` for Entity model
- **access**: Add AuditHistory Serializer for Entity model
- **access**: Add Notes Serializer for Entity model
- **access**: Change model to inherit from `CenturionModel` for Entity model
- **settings**: Add model tag for ExtrnalLink model
- **settings**: Add AuditHistory Serializer for UserSettings model
- **settings**: Add Notes Serializer for UserSettings model
- **settings**: Change model to inherit from `CenturionModel` for UserSettings model
- **settings**: Add model ExternalLink to migrate for history and notes
- **settings**: Add AuditHistory Serializer for ExternalLink model
- **settings**: Add Notes Serializer for ExternalLink model
- **settings**: Change model to inherit from `CenturionModel` for ExternalSettings model
- **settings**: Add model AppSettings to migrate for history and notes
- **settings**: Add AuditHistory Serializer for AppSettings model
- **settings**: Add Notes Serializer for AppSettings model
- **settings**: Change model to inherit from `CenturionModel` for AppSettings model
- **project_management**: Add model ProjectType to migrate for history and notes
- **project_management**: Add AuditHistory Serializer for ProjectTYpe model
- **project_management**: Add Notes Serializer for ProjectType model
- **project_management**: Change model to inherit from `CenturionModel` for ProjectType model
- **project_management**: Add model ProjectState to migrate for history and notes
- **project_management**: Add AuditHistory Serializer for ProjectState model
- **project_management**: Add Notes Serializer for ProjectState model
- **project_management**: Change model to inherit from `CenturionModel` for ProjectState model
- **project_management**: Add model ProjectMilestone to migrate for history and notes
- **project_management**: Add AuditHistory Serializer for ProjectMilestone model
- **project_management**: Add Notes Serializer for ProjectMilestone model
- **project_management**: Change model to inherit from `CenturionModel` for ProjectManagement model
- **project_management**: Add model Project to migrate for history and notes
- **project_management**: Add AuditHistory Serializer for Project model
- **project_management**: Add Notes Serializer for Project model
- **project_management**: Change model to inherit from `CenturionModel` for Project model
- **itim**: Add model Service to migrate for history and notes
- **itim**: Add AuditHistory Serializer for Service model
- **itim**: Add Notes Serializer for Service model
- **itim**: Change model to inherit from `CenturionModel` for Service model
- **itim**: Add model Port to migrate for history and notes
- **itim**: Add AuditHistory Serializer for Port model
- **itim**: Add Notes Serializer for Port model
- **itim**: Change model to inherit from `CenturionModel` for Port model
- **itim**: Add AuditHistory Serializer for ClusterType model
- **itim**: Add Notes Serializer for ClusterType model
- **itim**: Change model to inherit from `CenturionModel` for ClusterType model
- **itim**: Add model Cluster to migrate for history and notes
- **itim**: Add Notes Serializer for Cluster model
- **itim**: Add AuditHistory Serializer for Cluster model
- **itim**: Change model to inherit from `CenturionModel` for Cluster model
- **itam**: Add model SoftwareVersion to migrate for history and notes
- **itam**: Add Notes Serializer for SoftwareVersiony model
- **itam**: Add AuditHistory Serializer for SoftwareVersion model
- **itam**: Change model to inherit from `CenturionModel` for SoftwareVersion model
- **itam**: Add model SoftwareCategory to migrate for history and notes
- **itam**: Add Notes Serializer for SoftwareCategory model
- **itam**: Add AuditHistory Serializer for SoftwareCategory model
- **itam**: Change model to inherit from `CenturionModel` for SoftwareCategory model
- **itam**: Add model Software to migrate for history and notes
- **itam**: Add Notes Serializer for Software model
- **itam**: Add AuditHistory Serializer for Software model
- **itam**: Change model to inherit from `CenturionModel` for Software model
- **itam**: Add model OperatingSystemVersion to migrate for history and notes
- **itam**: Add Notes Serializer for OperatingSystemVersion model
- **itam**: Add AuditHistory Serializer for OperatingSystemVersion model
- **itam**: Change model to inherit from `CenturionModel` for OperatingSystemVersion model
- **itam**: Add model OperatingSystem to migrate for history and notes
- **itam**: Add Note Serializer for DeviceSoftware model
- **itam**: Add AuditHistory Serializer for DeviceSoftware model
- **itam**: Change model to inherit from `CenturionModel` for DeviceSoftware model
- **itam**: Change model to inherit from `Centurion` for DeviceSoftware model
- **itam**: Add model_tag to DeviceType model
- **itam**: Add DeviceType for history and notes data migration
- **itam**: Add DeviceModel for history and notes data migration
- **itam**: Add DEvice for history and notes data migration
- **devops**: Switch SoftwareEnabledFeatureFlag model to inherit from CenturionModel
- **devops**: Update checkin model fixture so it creates the feature flag
- **devops**: Add methods get_url and get_url_kwargs to CheckIn model
- **devops**: Add migration to signal
- **devops**: Add migration to signal
- **devops**: Add migration to signal
- **devops**: Add migration to signal
- **devops**: Update URL route basename
- **devops**: Migrations for switching GitLabRepository model to inherit from `CenturionModel`
- **devops**: Migrations for switching GitRepository model to inherit from `CenturionModel`
- **devops**: Migrations for switching GitRepository model to inherit from `CenturionModel`
- **devops**: Serializers for GitRepository models notes and history
- **devops**: Serializers for GitHubGitRepository models notes and history
- **devops**: Serializers for GitLabGitRepository models notes and history
- **devops**: Switch GitLabGitRepository model to inherit from `CenturionModel`
- **devops**: Switch GitHubGitRepository model to inherit from `CenturionModel`
- **devops**: Switch GitRepository model to inherit from `CenturionModel`
- **devops**: Update Checkin model url route basename
- **devops**: Add app_namespace Checkin model
- **devops**: Add Checkin to migrate model history/notes
- **devops**: Migrations for switching Checkin model to inherit from `CenturionModel`
- **devops**: Switch Checkin model to inherit from `CenturionModel`
- **core**: add TicketCommentCategory to history/notes migration
- **core**: add model tag to ticket comment category
- **core**: Migrations for TicketCategory
- **core**: add TicketCategory to history/notes migration
- **core**: add model tag to ticket category
- **core**: add Manufacturer to history/notes migration
- **core**: add model tag to manufacturer
- **config_management**: add ConfigGroups to history/notes migration
- **config_management**: add ConfigGroupSoftware to history/notes migration
- **config_management**: add ConfigGroupHosts to history/notes migration
- **access**: add tenant to history/notes migration
- **access**: Migration for switching model inheritence to `CenturionModel`
- **itam**: Update model methods
- **access**: Migration for switching model inheritence to `CenturionMixin`
- **access**: Switch model inheritence to `CenturionMixin`
- **itam**: Update url basename
- **itam**: Update url basename
- **base**: add support for manytomany for model unit tests
- **itam**: Update url basename
- **core**: Update url basename
- **core**: Update url basename
- **core**: Update url basename
- **core**: Update url basename
- **core**: Update url basename
- **core**: Update url basename
- **access**: TeamUsers do not require notes
- **config_management**: ConfigGroupHosts and ConfigGroupSoftware do not require notes
- **config_management**: Add url_kwargs to ConfigGroupSoftware model
- **access**: Add url_kwargs to Team model
- **access**: Add url_kwargs to TeamUser model
- **access**: Update TeamUser API basename
- **access**: Update Team API basename
- **itam**: switch model Device to inheirt from CenturionModel
- **itam**: switch model DeviceType to inheirt from CenturionModel
- **itam**: switch model DeviceModel to inheirt from CenturionModel
- **core**: switch model TicketCategory to inheirt from CenturionModel
- **core**: switch model TicktetCommentCategory to inheirt from CenturionModel
- **core**: switch model Manufacturer to inheirt from CenturionModel
- **config_management**: switch model ConfigGroupHosts to inheirt from CenturionModel
- **config_management**: switch model ConfigGroupSoftware to inheirt from CenturionModel
- **config_management**: switch model ConfigGroups to inheirt from CenturionModel
- **access**: switch model TeamUsers to inheirt from CenturionModel
- **access**: switch model Team to inheirt from CenturionModel
- **core**: If user context not supplied, dont create audithistory for model
- **access**: Add init to tenancy model to clear state
- **core**: Ensure that model has user context
- **core**: Add supprt to model_instance fixture for manytomany field
- **core**: Add supprt to model create test for manytomany field
- **assistance**: migrations for new history and notes models for KnowledgeBaseCategory model
- **assistance**: migrations for new history and notes models for KnowledgeBase model
- **assistance**: Model inheritance migrations
- **core**: Migrate Centurion Model history and notes within a post_migrate signal
- **core**: Add ability to CenturionModel `get_url` to be either detail/list
- **core**: New Management command to list models
- **devops**: Switch model FeatureFlag inheritance to CenturionModel
- **core**: Disable Notes for model CenturionModelNote
- **devops**: Enable Model notes for GitGroup
- **core**: add Swagger docs for CenturionModelNotes ViewSet
- **core**: Meta Model for CenturionModelNotes
- **core**: Finalize Serializer for CenturionModelNotes
- **api**: Add to common serializer meta notes model for notes url
- **core**: Interim Meta model CenturionNotes
- **core**: Interim ViewSet for model CenturionNotes
- **core**: URL Route for model CenturionNotes
- **core**: Serializer for model CenturionNotes
- **core**: Migration for model CenturionNotes
- **core**: Add model CenturionNotes
- **devops**: dont allow deleting a git group if it has children
- **devops**: Add model tag attribute to model
- **core**: Add to Centurion Model an attribute to set the models tag
- **core**: Add Context to model when ViewSet loads
- **devops**: Add AuditHistory Serializer for GitGroup
- **core**: Add AuditHistory Serializer
- **core**: Add AuditHistory ViewSet
- **core**: Add URL route for AuditHistory
- **core**: Add audithistory URL to serializer for models with `_audit_enabled=True`
- **core**: Models url kwarg helper
- **core**: Support setting custom model name for url basename
- **api**: Add sub-model filter to `get_queryset` method
- **core**: Disable models audit history on model delete
- **core**: Use Previous TenancyManager until UserModel rewrite done
- **core**: Process a models history within AuditHistory
- **core**: Enable AuditHistory signal to start when apps are ready
- **core**: Add model instance to history object during history creation
- **core**: Update Meta AuditModel `db_name` to be suffixed `_audithistory`
- **core**: remove unnessecary method `clean_fields` from audit model
- **core**: remove un-needed field `model_notes` from audit models
- **core**: Run meta models create on Core module ready
- **core**: New model core.CenturionAudit
- **core**: cause sub-audit models to chuck a wobbler if clean_fields not re-implementated
- **access**: remove mill-seconds from datetime auto fields
- **core**: Centurion model Base
- **core**: Centurion Audit model
- **core**: permissions getter for role model
- **core**: Audit History Signal for Delete/Save
- **core**: Dynamic History model creation
### Fixes
- remove trailing slant from URLs
- **access**: When creating permission QuerySet prevent app crash if db not setup
- **itim**: Ensure during testing, fixture vals are copied for Model Service
- **base**: on fixture cleanup, only clean if obj exists
- **core**: required field must be null for logical chek to function
- **itam**: field slug no longer avail, use str
- **core**: Include model so content type is created
- **settings**: AppSettings requires super user perms
- **api**: Convert Django Exceptions to DRF API Exception equivilent
- **api**: Ensure if exception DRF, message returned is from that exception
- **devops**: git repository is sub-model ViewSet must inherit from SubModel
- **access**: entity field `entity_type` is an auto field
- **access**: Ensure that if method not allowed, exception is thrown first before perms check
- **itam**: Model software must be related linked to organization model
- **access**: if user has no orgs, dont filter by for query
- **devops**: Ensure mandatory fields are writeable for model GitRepository
- **access**: add property organization to Tenant model
- **itam**: Add missing import `now`
- **core**: notes meta model must add `model_kwargs` fixture
- **core**: clean_fields for created_by field belongs in model that contains field
- **core**: audit meta model must add `model_kwargs` fixture
- model fixture names must match model_name
- clean up mock model from django apps
- **core**: When obtaining model fields ensure it exists first
- **access**: use getattr instead as attribute may exist as None
- **assistance**: make kb article field longer for model name
- **assistance**: Add missing field `model_notes` to KB serializer
- **core**: Before attempting to get model audit data confirm fields dont already exist
- **api**: check if model has notes enabled before adding url to body
- **api**: Only return View Serialized data if status code is HTTP/2xx
- **core**: Conduct kwargs check fr ticket comment serializer during init
- **core**: Enable CenturionAudit model to get model history for item being deleted
- **core**: When creating the AuditHistory entry for a model, use the user from context
- **core**: When collecting AuditHistory cater for models being created
- **api**: remove surerflous feature for fetching app_namespace for models metadata
- **core**: Correct attribute names for referencing a Centurion Model from an AuditModel
- **core**: Correct before lookup for current models audit history
- **core**: When deleting a model check if sub-model within delete method
- **access**: Tenancy Manager should not attempt to get org as related field if it does not exist
- **api**: ensure val returns at least none
### Refactoring
- **docker**: update healthcheck interval=10s and start-period=30s
- **docker**: when l;aunching gunicorn create a pid file
- **devops**: API Fields render Functional Test Suite re-written to Pytest for model SoftwareEnableFeatureFlag Again
- **devops**: Remove old test suites no longer required model SoftwareEnableFeatureFlag
- **devops**: ViewSet Unit Test Suite re-written to Pytest for model SoftwareEnableFeatureFlag
- **devops**: Serializer Unit Test Suite re-written to Pytest for model SoftwareEnableFeatureFlag
- **devops**: API Fields render Functional Test Suite re-written to Pytest for model SoftwareEnableFeatureFlag
- **devops**: Model Functional Test Suite re-written to Pytest for model SoftwareEnableFeatureFlag
- **devops**: API Metadata Functional Test Suite re-written to Pytest for model SoftwareEnableFeatureFlag
- **devops**: Remove old test suites no longer required model FeatureFlag
- **devops**: ViewSet Unit Test Suite re-written to Pytest for model FeatureFlag
- **devops**: Serializer Unit Test Suite re-written to Pytest for model FeatureFlag
- **devops**: API Fields render Functional Test Suite re-written to Pytest for model FeatureFlag
- **devops**: Model Functional Test Suite re-written to Pytest for model FeatureFlag
- **devops**: API Metadata Functional Test Suite re-written to Pytest for model FeatureFlag
- **api**: Remove old test suites no longer required model AuthToken
- **api**: ViewSet Unit Test Suite re-written to Pytest for model AuthToken
- **api**: Serializer Unit Test Suite re-written to Pytest for model AuthToken
- **api**: API Fields render Functional Test Suite re-written to Pytest for model AuthToken
- **api**: Model Functional Test Suite re-written to Pytest for model AuthToken
- **api**: API Metadata Functional Test Suite re-written to Pytest for model AuthToken
- **access**: Remove old test suites no longer required model Tenant
- **access**: Serializer Unit Test Suite re-written to Pytest for model Tenant
- **access**: API Fields render Functional Test Suite re-written to Pytest for model Tenant
- **access**: Model Functional Test Suite re-written to Pytest for model Tenant
- **access**: API Metadata Functional Test Suite re-written to Pytest for model Tenant
- **settings**: Remove old test suites no longer required model UserSettings
- **settings**: ViewSet Unit Test Suite re-written to Pytest for model UserSettings
- **settings**: Serializer Unit Test Suite re-written to Pytest for model UserSettings
- **settings**: API Fields render Functional Test Suite re-written to Pytest for model UserSettings
- **settings**: Model Functional Test Suite re-written to Pytest for model UserSettings
- **settings**: API Metadata Functional Test Suite re-written to Pytest for model UserSettings
- **settings**: Remove old test suites no longer required model ExternalLink
- **settings**: ViewSet Unit Test Suite re-written to Pytest for model ExternalLink
- **settings**: Serializer Unit Test Suite re-written to Pytest for model ExternalLink
- **settings**: API Fields render Functional Test Suite re-written to Pytest for model ExternalLink
- **settings**: Model Functional Test Suite re-written to Pytest for model ExternalLink
- **settings**: API Metadata Functional Test Suite re-written to Pytest for model ExternalLink
- **settings**: Remove old test suites no longer required model AppSettings
- **settings**: ViewSet Unit Test Suite re-written to Pytest for model AppSettings
- **settings**: Serializer Unit Test Suite re-written to Pytest for model AppSettings
- **settings**: API Fields render Functional Test Suite re-written to Pytest for model AppSettings
- **settings**: Model Functional Test Suite re-written to Pytest for model AppSettings
- **settings**: API Metadata Functional Test Suite re-written to Pytest for model AppSettings
- **test**: remove xfail during `pytest_generate_tests` before parameterizing
- **project_management**: ensure within fixtur kwargs are copied
- **project_management**: Remove old test suites no longer required model ProjectType
- **project_management**: ViewSet Unit Test Suite re-written to Pytest for model ProjectType
- **project_management**: Serializer Unit Test Suite re-written to Pytest for model ProjectType
- **project_management**: API Fields render Functional Test Suite re-written to Pytest for model ProjectType
- **project_management**: Model Functional Test Suite re-written to Pytest for model ProjectType
- **project_management**: API Metadata Functional Test Suite re-written to Pytest for model ProjectType
- **project_management**: Remove old test suites no longer required model ProjectState
- **project_management**: ViewSet Unit Test Suite re-written to Pytest for model ProjectState
- **project_management**: Serializer Unit Test Suite re-written to Pytest for model ProjectState
- **project_management**: API Fields render Functional Test Suite re-written to Pytest for model ProjectState
- **project_management**: Model Functional Test Suite re-written to Pytest for model ProjectState
- **project_management**: API Metadata Functional Test Suite re-written to Pytest for model ProjectState
- **project_management**: Remove old test suites no longer required model ProjectMilestone
- **project_management**: ViewSet Unit Test Suite re-written to Pytest for model ProjectMilestone
- **project_management**: Serializer Unit Test Suite re-written to Pytest for model ProjectMilestone
- **project_management**: API Fields render Functional Test Suite re-written to Pytest for model ProjectMilestone
- **project_management**: Model Functional Test Suite re-written to Pytest for model ProjectMilestone
- **project_management**: API Metadata Functional Test Suite re-written to Pytest for model ProjectMilestone
- **project_management**: Remove old test suites no longer required model Project
- **project_management**: ViewSet Unit Test Suite re-written to Pytest for model Project
- **project_management**: Serializer Unit Test Suite re-written to Pytest for model Project
- **project_management**: API Fields render Functional Test Suite re-written to Pytest for model Project
- **project_management**: Model Functional Test Suite re-written to Pytest for model Project
- **project_management**: API Metadata Functional Test Suite re-written to Pytest for model Project
- **itim**: Remove old test suites no longer required model Service
- **itim**: ViewSet Unit Test Suite re-written to Pytest for model Service
- **itim**: Serializer Unit Test Suite re-written to Pytest for model Service
- **itim**: API Fields render Functional Test Suite re-written to Pytest for model Service
- **itim**: Model Functional Test Suite re-written to Pytest for model Service
- **itim**: API Metadata Functional Test Suite re-written to Pytest for model Service
- **itim**: Remove old test suites no longer required model Port
- **itim**: ViewSet Unit Test Suite re-written to Pytest for model Port
- **itim**: Serializer Unit Test Suite re-written to Pytest for model Port
- **itim**: API Fields render Functional Test Suite re-written to Pytest for model Port
- **itim**: Model Functional Test Suite re-written to Pytest for model Port
- **itim**: API Metadata Functional Test Suite re-written to Pytest for model Port
- **itim**: Remove old test suites no longer required model ClusterType
- **itim**: ViewSet Unit Test Suite re-written to Pytest for model ClusterType
- **itim**: Serializer Unit Test Suite re-written to Pytest for model ClusterType
- **itim**: API Fields render Functional Test Suite re-written to Pytest for model ClusterType
- **itim**: Model Functional Test Suite re-written to Pytest for model ClusterType
- **itim**: API Metadata Functional Test Suite re-written to Pytest for model ClusterType
- **itim**: Remove old test suites no longer required model Cluster
- **itim**: ViewSet Unit Test Suite re-written to Pytest for model Cluster
- **itim**: Serializer Unit Test Suite re-written to Pytest for model Cluster
- **itim**: API Fields render Functional Test Suite re-written to Pytest for model Cluster
- **itim**: Model Functional Test Suite re-written to Pytest for model Cluster
- **itim**: API Metadata Functional Test Suite re-written to Pytest for model Cluster
- **itam**: Remove old test suites no longer required model SoftwareVersion
- **itam**: ViewSet Unit Test Suite re-written to Pytest for model SoftwareVersion
- **itam**: Serializer Unit Test Suite re-written to Pytest for model SoftwareVersion
- **itam**: Model Functional Test Suite re-written to Pytest for model SoftwareVersion
- **itam**: API Fields render Functional Test Suite re-written to Pytest for model SoftwareVersion
- **itam**: API Metadata Functional Test Suite re-written to Pytest for model SoftwareVersion
- **itam**: Remove old test suites no longer required model SoftwareCategory
- **itam**: ViewSet Unit Test Suite re-written to Pytest for model SoftwareCategory
- **itam**: Serializer Unit Test Suite re-written to Pytest for model SoftwareCategory
- **itam**: Model Functional Test Suite re-written to Pytest for model SoftwareCategory
- **itam**: API Fields render Functional Test Suite re-written to Pytest for model SoftwareCategory
- **itam**: API Metadata Functional Test Suite re-written to Pytest for model SoftwareCategory
- **itam**: Remove old test suites no longer required model Software
- **itam**: ViewSet Unit Test Suite re-written to Pytest for model Software
- **itam**: Serializer Unit Test Suite re-written to Pytest for model Software
- **itam**: Model Functional Test Suite re-written to Pytest for model Software
- **itam**: API Fields render Functional Test Suite re-written to Pytest for model Software
- **itam**: API Metadata Functional Test Suite re-written to Pytest for model Software
- **itam**: Remove old test suites no longer required model OperatingSystemVersion
- **itam**: ViewSet Unit Test Suite re-written to Pytest for model OperatingSystemVersion
- **itam**: Serializer Unit Test Suite re-written to Pytest for model OperatingSystemVersion
- **itam**: Model Functional Test Suite re-written to Pytest for model OperatingSystemVersion
- **itam**: API Fields render Functional Test Suite re-written to Pytest for model OperatingSystemVersion
- **itam**: API Metadata Functional Test Suite re-written to Pytest for model OperatingSystemVersion
- **itam**: Remove old test suites no longer required model OperatingSystem
- **itam**: ViewSet Unit Test Suite re-written to Pytest for model OperatingSystem
- **itam**: Serializer Unit Test Suite re-written to Pytest for model OperatingSystem
- **itam**: Model Functional Test Suite re-written to Pytest for model OperatingSystem
- **itam**: API Fields render Functional Test Suite re-written to Pytest for model OperatingSystem
- **itam**: API Metadata Functional Test Suite re-written to Pytest for model OperatingSystem
- **itam**: API Metadata Functional Test Suite re-written to Pytest for model DeviceType
- **itam**: Model Functional Test Suite re-written to Pytest for model DeviceType
- **itam**: API Fields render Test Suite re-written to Pytest for model DeviceType
- **itam**: Serializer Unit Test Suite re-written to Pytest for model DeviceType
- **itam**: ViewSet Unit Test Suite re-written to Pytest for model DeviceModel
- **itam**: API Metadata Functional Test Suite re-written to Pytest for model DeviceModel
- **itam**: API Field Render Functional Test Suite re-written to PyTest for model Device
- **itam**: Metadate Functional Test Suite re-enabled for model Device
- **itam**: Viewset Unit Test Suite re-written to pytest for model Device
- **itam**: Serializer Unit Test Suite re-enabled for model Device
- **core**: API Render Unit Test Suite re-enabled for model Manufacturer
- **core**: API Metadata Functional Test Suite re-enabled for model Manufacturer
- **core**: Serializer Functional Test Suite re-enabled for model Manufacturer
- **core**: ViewSet Test Suite re-written to pytest for model Manufacturer
- **config_management**: ViewSet Test Suite re-written to pytest for model ConfigGroupSoftware
- **config_management**: API fields Test Suite re-enalbed for model ConfigGroupSoftware
- **config_management**: API Metadata Functional Test Suite for model ConfigGroupSoftware
- **config_management**: Serializer Functional Test Suite Enabled for model ConfigGroupSoftware
- **config_management**: Model Unit Test Suite re-written to pytest for model ConfigGroup
- **config_management**: API Metadata Functional Test Suite re-written to pytest for model ConfigGroup
- **assistance**: Serializer Unit Test Suite re-written to pytest for model KnowledgeBase
- **assistance**: MetaData Unit Test Suite re-written to pytest for model KnowledgeBaseCategory
- **assistance**: Serializer Unit Test Suite re-written to pytest for model KnowledgeBaseCategory
- **assistance**: ViewSet Unit Test Suite re-written to pytest for model KnowledgeBaseCategory
- **assistance**: Serializer Unit Test Suite re-written to pytest for model KnowledgeBaseCategory
- **assistance**: Serializer Unit TestSuite re-written to pytest for model KnowledgeBase
- **assistance**: ViewSet TestSuite re-written to pytest for model KnowledgeBase
- **access**: ViewSet TestSuite re-written to pytest for model Tenant
- **access**: ViewSet TestSuite re-written to pytest for model Person
- **access**: ViewSet TestSuite re-written to pytest for model Entity
- **access**: ViewSet TestSuite re-written to pytest for model Contact
- **access**: ViewSet TestSuite re-written to pytest for model Company
- **api**: migrate Common ViewSet unittest.mock to mocker
- **api**: migrate Common ViewSet Unit Test Suite attribute to use test case `unit_class`
- **api**: Converted Common ViewSet Unit Test Suite to use Pytest
- **api**: partial conversion to pytest for Common ViewSet Unit Test Suite
- **api**: Rename create Serializer unit test to `is_valid`
- **base**: normalize empty/not used to be `models.NOT_PROVIDED`
- **base**: adjust functional model test to use fixture kwargs
- **api**: Update Test Suite for AuthToken model
- **tests**: Unskip tests that'll work now due to model inheritance change
- **api**: Update Test Suite for AuthToken model
- **api**: Update URL route name for Role AuthToken
- **api**: Switch to inherit from Centurion model for model AuthToken
- **access**: When adding model role via api, status is 201/created
- **itim**: Update Test Suite for TicketCommentSolution model
- **itim**: Update Test Suite for TicketSLM model
- **itim**: Update Test Suite for TicketRequest model
- **core**: Update Test Suite for TicketBase model
- **core**: Update Test Suite for TicketCommentSolution model
- **core**: Update Test Suite for TicketCommentAction model
- **core**: Update Test Suite for TicketCommentBase model
- **core**: Initial Update Test Suite for TicketCommentBase model
- **core**: Update Tests to cater for inheritence changes
- **itim**: Update Test Suite for RequestTicket model
- **itim**: Update Test Suite for SLMTicket model
- **itim**: Update Test Suite for SLMTicket model
- **core**: Update Test Suite for TicketBase model
- **core**: Update Test Suite for TicketBase model
- **core**: Switch to inherit from Centurion model for model TicketBase
- **core**: Switch to inherit from Centurion model for model SLMTicketBase
- **core**: Update URL route name for Role TicketCommentBase
- **core**: Switch to inherit from Centurion model for model TicketCommentBase
- **core**: Update URL route name for Role TicketBase
- **core**: Switch to inherit from Centurion model for model TicketBase
- **core**: Add fn get_organization to centurion mixin
- **access**: Adjust add permission test for model Role
- **access**: Migrations for Inheritance change for Role model
- **access**: Update URL route name for Role model
- **access**: Update Test Suite for Role model
- **access**: Switch to inherit from Centurion model for model Role
- Asset and ITAM Asset must use url kwarg model_name not asset_model
- **accounting**: Update existing tests to work due to model inheritance changes
- **itam**: Update URL route name for ITAMAssetBase model
- **itam**: Update Test Suite for ITAMAssetBase model
- **itam**: Switch to inherit from Centurion model for model ITAMAssetBase
- **accounting**: Switch to inherit from Centurion model for model AssetBase
- **accounting**: Update URL route name for AssetBase model
- **accounting**: Update Test Suite for AssetBase model
- **accounting**: Switch to inherit from Centurion model for model AssetBase
- **api**: dont query db for instance, use existing from response
- **api**: additional perms tests if they exist must be inc first
- **devops**: remove ViewSet `get_queryset` function
- **access**: Update Entity model ViewSet attribute `model_kwarg` to `model_name`
- **access**: Update Entity model ViewSet to inherit from submodel-rewrite
- **access**: Update Test Suite for Employee model
- **access**: Update Test Suite for Person model
- **access**: Update Test Suite for Contact model
- **access**: Update Test Suite for Company model
- **access**: Update URL route name for Entity model
- **access**: Update Test Suite for Entity model
- **access**: Update is_tenancy_object to check for CenturionModel
- **access**: For request middleware, use filter and first object so that testing can occur when mre than one exists
- **settings**: Update URL route name for UserSettings model
- **settings**: Update Test Suite for ExternalLink model
- **settings**: Update URL route name for ExternalLink model
- **settings**: Update Test Suite for ExternalLink model
- **settings**: Update URL route name for AppSettings model
- **settings**: Update Test Suite for AppSettings model
- **project_management**: Update URL route name for ProjectType model
- **project_management**: Update Test Suite for ProjectType model
- **project_management**: Update URL route name for ProjectState model
- **project_management**: Update Test Suite for ProjectState model
- **project_management**: Update URL route name for ProjectMilestone model
- **project_management**: Update Test Suite for ProjectMilestone model
- **project_management**: Update URL route name for Project model
- **project_management**: Update Test Suite for Project model
- **itim**: Update URL route name for Service model
- **itim**: Update Test Suite for Service model
- **itim**: Update URL route name for Port model
- **itim**: Update Test Suite for Port model
- **itim**: Update URL route name for ClusterType model
- **itim**: Update Test Suite for ClusterType model
- **itim**: Update URL route name for Cluster model
- **itim**: Update Test Suite for Cluster model
- **itam**: Update Test Suite for SoftwareVersion model
- **itam**: Update URL route name for SoftwareVersion model
- **itam**: Update Test Suite for SoftwareCategory model
- **itam**: Update URL route name for SoftwareCategory model
- cater for dev that does not exist in test cleanup
- **itam**: Update Test Suite for Software model
- **itam**: Update URL route name for Software model
- **itam**: Update Test Suite for OperatingSystemVersion model
- **itam**: Update Test Suite for OperatingSystem model
- **itam**: Update URL route name for DeviceSoftware model
- **itam**: Update Test Suite for DeviceSoftware model
- **itam**: Update Test Suite for DeviceDeviceOperatingSystem model
- **itam**: Update URL route for DeviceDeviceOperatingSystem model
- **itam**: Migration for updating model inheritance for DeviceDeviceOperatingSystem model
- **itam**: Updated Unit model test suite for DeviceType model
- **devops**: Updated Unit model test suite for DeviceModel model
- **devops**: Migration for updating model inheritance for DeviceModel model
- **itam**: Updated Unit model test suite for Device model
- **devops**: Updated Unit model test ssuite for SoftwareEnabledFeatureFlag model
- **devops**: Migration for updating model inheritance for SoftwareEnabledFeatureFlag model
- **devops**: Update url route basename for SoftwareEnabledFeatureFlag model
- **tests**: make all `parameterized_` vars properties
- **core**: adjust CenturionSubModel to not be it's own inheritable class
- **core**: Move CenturionModel logic to Mixin
- **core**: rename mixin -> mixins
- **base**: model instancxe code de-duplicated
- **config_management**: Add ConfigGroupHost Model Tests
- **config_management**: Add ConfigGroupSoftware Model Tests
- **config_management**: Add ConfigGroup Model Tests
- **assistance**: Refactor KnowledgeBaseCategory Unit model tests
- **assistance**: Update KnowledgeBase Unit viewset url basename
- **assistance**: Refactor KnowledgeBase Unit model tests
- **assistance**: Add new history and notes Serializer for KnowledgeBase model
- **assistance**: Add new history and notes Serializer for KnowledgeBaseCategory model
- **assistance**: Change KnowledgeBaseCategory model inheritance TenancyObject -> CenturionModel
- **assistance**: Change KnowledgeBase model inheritance TenancyObject -> CenturionModel
- **assistance**: MV kb category model to its own file
- **tests**: Create global model fixtures
- **devops**: Switch FeatureFlag model unit tests to CenturionModel
- **settings**: move url routes from core.urls to own module `urls_api.py`
- **project_management**: move url routes from core.urls to own module `urls_api.py`
- **itim**: move url routes from core.urls to own module `urls_api.py`
- **itam**: move url routes from core.urls to own module `urls_api.py`
- **core**: move url routes from core.urls to own module `urls_api.py`
- **config_management**: move url routes from core.urls to own module `urls_api.py`
- **assistance**: move url routes from core.urls to own module `urls_api.py`
- **access**: move url routes from core.urls to own module `urls_api.py`
- **api**: Update Common ViewSet methds for re-write
- **devops**: Switch GitGroup Model to CenturionModel
- **core**: Loading of meta models should not be hidden behind program start ags
- **core**: To obtain audit_values loop through model fields
- rejig whats in each inherited centurion model
- **access**: prefetch org with tenancy object
- **core**: Relocate history model class
- **base**: rename app to centurion
### Tests
- **core**: Notes Meta Models API Permissions Test cases for All Notes Models
- Add initial integration tests
- **docker**: Add compose setup for integration testing
- **itam**: ViewSet Unit Test Suite added for model DeviceType
- **itam**: Serializer UnitTest Suite added for model DeviceModel
- **itam**: API Fields render Functional Test Suite added for model DeviceModel
- **itam**: Model Functional Test Suite added for model DeviceModel
- **itam**: Refactor failing tests to cater for uniqueness so they pass
- **itam**: Model Functional Test Suite aded for model Device
- **config_management**: ViewSet Unit Test Suite re-written to pytest for model ConfigGroup
- **config_management**: Serializer Unit Test Suite re-written to pytest for model ConfigGroup
- **config_management**: Model Functional Test Suite re-written to pytest for model ConfigGroup
- **config_management**: API Field Render Functional Test Suite re-written to pytest for model ConfigGroup
- **assistance**: API Field Render Functional Test Suite re-written to pytest for model KnowledgeBaseCategory
- **assistance**: Model Functional Test Suite re-written to pytest for model KnowledgeBaseCategory
- **assistance**: Model Functional Test Suite re-written to pytest for model KnowledgeBase
- **assistance**: API Fields Render Functional Test Suite re-written to pytest for model KnowledgeBase
- **api**: SubModel ViewSet Test Suite to test re-written class
- **api**: Dont test a django object that has not been customised
- **access**: Initial ViewSet Unit Test Suite for Entity Model
- **access**: Add Serializer unit test suit for model Role
- **access**: Add Serializer unit test suit for model Person
- **access**: Add Serializer unit test suit for model Entity
- **access**: Add Serializer unit test suit for model Contact
- **access**: Add Serializer unit test suit for model Company
- **itim**: Refactor TicketSLM model API Fields render test Suite to PyTest
- **itim**: Refactor TicketRequest model API Fields render test Suite to PyTest
- **core**: Refactor TicketBase model API Fields render test Suite to PyTest
- **api**: Refactor Test Suite for API Fields render tests to PyTest
- **itam**: Refactor ITAMAssetBase model API Fields render test Suite to PyTest
- **accounting**: Refactor AssetBase model API Fields render test Suite to PyTest
- **core**: Refactor TicketCommentSolution model API Fields render test Suite to PyTest
- **core**: Refactor TicketCommentAction model API Fields render test Suite to PyTest
- **core**: Refactor TicketCommentBase model API Fields render test Suite to PyTest
- **human_resources**: Refactor Employee model API Fields render test Suite to PyTest
- **access**: Refactor Person model API Fields render test Suite to PyTest
- **access**: Refactor Entity model API Fields render test Suite to PyTest
- **access**: Refactor Contact model API Fields render test Suite to PyTest
- **access**: Refactor Company model API Fields render test Suite to PyTest
- **devops**: Adjust functional model test to use fixture kwargs
- Ensure when obj created via serializer calls full_clean
- Ensure Clean methods called
- Test case for model field type
- **fixture**: if item already exists, when fetching remove modified field from query if not found with
- **access**: Model Role is not usable within global org, remove test
- **devops**: skip Model History entry test as it should be done as part of serializer and viewset
- **devops**: update no_org_serializer test so it works for model SoftwareEnableFeatureFlag
- **itam**: Model DeviceOperatingSystem is not multi-org based skip those tests
- **settings**: Model UserSettings does not allowing adding rows, skip test
- **settings**: Model AppSettings does not allowing adding rows, skip test
- **fixture**: Ensure _meta attribute exists when cleaning up models prior to attempting to use
- **devops**: SoftwareEnableFeatureFlagging model does not use global org, so dont test global org return
- **api**: when testing create object, remove the actual created object prior to testing the add
- **fixture**: when creating object and it exists, rtn that object
- **devops**: If test publically accessable, dont test by user org only as test is NA
- **settings**: UserSettings perms tests are for the user that is accessing them
- **settings**: UserSettings perms tests are not org based, skip those tests
- **settings**: AppSettings perms tests are not org based, skip those tests
- **settings**: for api checks for model AppSettings, make user super_user
- **settings**: Exclude inter-org tests for model AppSettings
- **settings**: Remove old API Permission tests no longer required
- **settings**: Ensure ExternalLink model hasrequired field template added
- **api**: if model lacks list endpoint, check if method alllowed for test cases for Functional API perms test suite
- **api**: if model lacks organization field, xfail returned orgs test cases for Functional API perms test suite
- Ensure service fixture assosiates with device
- **api**: if model lacks organization field, xfail returned orgs test cases for Functional API perms test suite
- Add depreciated models to be excluded from coverage
- **api**: Update Functional API Permission test suite to cater for public RO endpoints
- **core**: Ensure model mehod `get_url_kwargs` returns a dict for all Centurion Models
- **devops**: Add GitLabRepository Unit Model test suite
- **devops**: Add GitHubRepository Unit Model test suite
- **devops**: Add GitRepository Unit Model test suite
- **devops**: Add Checkin Unit Model test suite
- **devops**: correct GitGroup Unit model test suite
- **devops**: correct FeatureFlag Unit model test suite
- **core**: Add TicketCommentCategory Unit model test suite
- **core**: Add TicketCategory Unit model test suite
- **core**: Add Manufacturer Unit model test suite
- **access**: Add Tenant Unit serializer test suite
- Add initial unit serializer test suite
- **access**: Update Tenant URL route basename again
- **itam**: Updated Unit model test for Device Model
- **access**: Update Tenant URL route basename
- **access**: Tenant Model Tests
- **api**: Update Functional API Permissions to support listview models with kwargs
- **api**: exclude model `ConfigGroupHosts` from api permission tests as it has no endpoint
- **api**: API Permissions Functional test to supprt name as unique field
- **config_management**: Completed ConfigGroupSoftware Model Tests
- **config_management**: Completed ConfigGroup Model Tests
- **config_management**: Completed ConfigGroupHost Model Tests
- **core**: mock the user object within the model context
- **core**: creating a model is a functional not unit test
- **devops**: re-implement temp removed test suites.
- **api**: API Permissions Auto-Creator test suite
- **devops**: Add GitGroup API Permissions tests
- **core**: Add fixtures for api permission tests
- **core**: rewrite api permissions test suite to use pytest and fixtures
- **core**: Ensure Method clean_fields functions for CenturionNotesModel
- **core**: Function Model test suite for CenturionModelNote Meta Models
- **core**: Interim Unit Model test suite for CenturionModelNote Meta Models
- **core**: Interim Unit Model test suite for CenturionModelNote
- **core**: Dynamic Unit Test Suites for Meta Models AuditHistory
- **core**: Unit Test Centurion Model method `__str__`
- **core**: Unit Test Centurion Model method `get_url_kwargs`
- **core**: Unite Tesxt Centurion Model method `get_url` attr `_is_submodel` set
- **core**: Unite Tesxt Centurion Model method `get_url` attr `model_name` set
- **core**: Ensure model that has audit enableed has audit model
- **core**: Add Functional model Test Suite for CenturionAuditModel
- **devops**: Ensure that a Github group cant have a parent/"be nested"
- **devops**: Ensure that when create a child git group that the tenancy matches the parent git group
- **devops**: Add Functional model Test Suite
- **core**: Add Base Centurion model Functional Test Suite
- **access**: Add Base Tenancy model Functional Test Suite
- **base**: Add Base model Functional Test Suite
- **core**: Model Unit Tests for AuditHistory `get_model_history` method
- **core**: reset vals so as not to fuck other tests over
- **core**: Correct test for method `get_audit_values` for `CenturionAbstractModel`
- **devops**: Initial Model Unit tests for GitGroup
- **core**: Add field `model_notes` as an excluded field for AuditModels
- **core**: Remaining Unit Model Test Cases for CenturionAuditMeta Model
- **core**: Initial Unit Model Test Cases for CenturionAuditMeta Model
- **core**: Unit Model Test Cases for CenturionSubAbstract model
- **core**: Initial Unit Model Test Cases for CenturionAudit Model
- **core**: Unit test cases for Centurion get_url relative + non-relative
- **access**: Unit Model Tests for TenancyAbstractModel
- **base**: Unit Common Model test cases suite
- **base**: Unit Common Class test cases suite
- **access**: Unit Model Tests for TenancyAbstractModel
## 1.18.0 (2025-07-03)
### feat

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
## Version 1.18.0
## Version 1.19.0
- Added new model for History
@ -18,7 +18,7 @@
- Removed Django UI
[UI](https://github.com/nofusscomputing/centurion_erp) must be deployed seperatly.
[UI](https://github.com/nofusscomputing/centurion_erp_ui) must be deployed seperatly.
- Removed API v1

View File

@ -1,9 +1,10 @@
from django.apps import apps
from django.conf import settings
from django.contrib.auth.models import (
ContentType,
Permission
)
from django.conf import settings
from django.db.models import QuerySet
def permission_queryset():
@ -61,47 +62,66 @@ def permission_queryset():
if not settings.RUNNING_TESTS:
models = apps.get_models()
try:
# This blocks purpose is to cater for fresh install
# so that the app does not crash before the DB is setup.
for model in models:
models = apps.get_models()
if(
not str(model._meta.object_name).endswith('AuditHistory')
and not str(model._meta.model_name).lower().endswith('history')
):
# check `endswith('history')` can be removed when the old history models are removed
continue
content_type = ContentType.objects.get(
app_label = model._meta.app_label,
model = model._meta.model_name
)
permissions = Permission.objects.filter(
content_type = content_type,
)
for permission in permissions:
for model in models:
if(
not permission.codename == 'view_' + str(model._meta.model_name)
and str(model._meta.object_name).endswith('AuditHistory')
):
exclude_permissions += [ permission.codename ]
elif(
not str(model._meta.object_name).endswith('AuditHistory')
and str(model._meta.model_name).lower().endswith('history')
and not str(model._meta.model_name).lower().endswith('history')
):
# This `elif` can be removed when the old history models are removed
# check `endswith('history')` can be removed when the old history models are removed
continue
exclude_permissions += [ permission.codename ]
content_type = ContentType.objects.get(
app_label = model._meta.app_label,
model = model._meta.model_name
)
permissions = Permission.objects.filter(
content_type = content_type,
)
for permission in permissions:
if(
not permission.codename == 'view_' + str(model._meta.model_name)
and str(model._meta.object_name).endswith('AuditHistory')
):
exclude_permissions += [ permission.codename ]
elif(
not str(model._meta.object_name).endswith('AuditHistory')
and str(model._meta.model_name).lower().endswith('history')
):
# This `elif` can be removed when the old history models are removed
exclude_permissions += [ permission.codename ]
return Permission.objects.select_related('content_type').filter(
content_type__app_label__in = centurion_apps,
).exclude(
content_type__model__in = exclude_models
).exclude(
codename__in = exclude_permissions
)
return Permission.objects.select_related('content_type').filter(
content_type__app_label__in = centurion_apps,
).exclude(
content_type__model__in = exclude_models
).exclude(
codename__in = exclude_permissions
)
except:
pass
return QuerySet()
else:
return Permission.objects.select_related('content_type').filter(
content_type__app_label__in = centurion_apps,
).exclude(
content_type__model__in = exclude_models
).exclude(
codename__in = exclude_permissions
)

View File

@ -0,0 +1,526 @@
# Generated by Django 5.1.10 on 2025-08-15 03:21
import access.models.tenancy_abstract
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0010_company_alter_entity_entity_type_alter_person_dob_and_more"),
("core", "0024_centurionaudit_centurionmodelnote_and_more"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RemoveField(
model_name="entity",
name="is_global",
),
migrations.RemoveField(
model_name="tenant",
name="slug",
),
migrations.AlterField(
model_name="entity",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="entity",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="entity",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.AlterField(
model_name="role",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="role",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="role",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.AlterField(
model_name="tenant",
name="manager",
field=models.ForeignKey(
blank=True,
help_text="Manager for this Tenancy",
null=True,
on_delete=django.db.models.deletion.PROTECT,
to=settings.AUTH_USER_MODEL,
verbose_name="Manager",
),
),
migrations.AlterField(
model_name="tenant",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.CreateModel(
name="CompanyAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.company",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Company History",
"verbose_name_plural": "Company Histories",
"db_table": "access_company_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="CompanyCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.company",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Company Note",
"verbose_name_plural": "Company Notes",
"db_table": "access_company_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
migrations.CreateModel(
name="ContactAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.contact",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Contact History",
"verbose_name_plural": "Contact Histories",
"db_table": "access_contact_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="ContactCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.contact",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Contact Note",
"verbose_name_plural": "Contact Notes",
"db_table": "access_contact_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
migrations.CreateModel(
name="EntityAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="access.entity",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Entity History",
"verbose_name_plural": "Entity Histories",
"db_table": "access_entity_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="EntityCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.entity",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Entity Note",
"verbose_name_plural": "Entity Notes",
"db_table": "access_entity_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
migrations.CreateModel(
name="PersonAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.person",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Person History",
"verbose_name_plural": "Person Histories",
"db_table": "access_person_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="PersonCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.person",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Person Note",
"verbose_name_plural": "Person Notes",
"db_table": "access_person_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
migrations.CreateModel(
name="RoleAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="access.role",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Role History",
"verbose_name_plural": "Role Histories",
"db_table": "access_role_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="RoleCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.role",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Role Note",
"verbose_name_plural": "Role Notes",
"db_table": "access_role_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
migrations.CreateModel(
name="TenantAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="access.tenant",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Tenant History",
"verbose_name_plural": "Tenant Histories",
"db_table": "access_tenant_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="TenantCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Tenant Note",
"verbose_name_plural": "Tenant Notes",
"db_table": "access_tenant_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
migrations.DeleteModel(
name="EntityHistory",
),
migrations.DeleteModel(
name="EntityNotes",
),
migrations.DeleteModel(
name="RoleHistory",
),
migrations.DeleteModel(
name="RoleNotes",
),
]

View File

@ -1,110 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-06 01:41
import access.models.tenancy_abstract
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0010_company_alter_entity_entity_type_alter_person_dob_and_more"),
("core", "0028_delete_history"),
]
operations = [
migrations.RemoveField(
model_name="team",
name="is_global",
),
migrations.AlterField(
model_name="team",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="team",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.CreateModel(
name="TeamAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="access.team",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Team History",
"verbose_name_plural": "Team Histories",
"db_table": "access_team_audithistory",
"managed": True,
},
bases=("core.centurionaudit", models.Model),
),
migrations.CreateModel(
name="TeamCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.team",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Team Note",
"verbose_name_plural": "Team Notes",
"db_table": "access_team_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote", models.Model),
)
]

View File

@ -1,102 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-06 01:43
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0011_remove_team_is_global_model_notes_and_more"),
("core", "0028_delete_history"),
]
operations = [
migrations.AddField(
model_name="teamusers",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="teamusers",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.CreateModel(
name="TeamUsersAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="access.teamusers",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Team User History",
"verbose_name_plural": "Team User Histories",
"db_table": "access_teamusers_audithistory",
"managed": True,
},
bases=("core.centurionaudit", models.Model),
),
migrations.CreateModel(
name="TeamUsersCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.teamusers",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Team User Note",
"verbose_name_plural": "Team User Notes",
"db_table": "access_teamusers_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote", models.Model),
),
]

View File

@ -1,16 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-06 05:22
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("access", "0012_teamusers_model_notes_alter_teamusers_id_and_more"),
]
operations = [
migrations.DeleteModel(
name="TeamUsersAuditHistory",
),
]

View File

@ -1,16 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-07 09:05
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("access", "0013_delete_teamusersaudithistory"),
]
operations = [
migrations.DeleteModel(
name="TeamUsersCenturionModelNote",
),
]

View File

@ -1,75 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-07 10:10
import access.models.team
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0014_delete_teamuserscenturionmodelnote"),
]
operations = [
migrations.RemoveField(
model_name="teamcenturionmodelnote",
name="centurionmodelnote_ptr",
),
migrations.RemoveField(
model_name="teamcenturionmodelnote",
name="model",
),
migrations.RemoveField(
model_name="teamusers",
name="model_notes",
),
migrations.AddField(
model_name="team",
name="is_global",
field=models.BooleanField(
default=False,
help_text="Is this a global object?",
verbose_name="Global Object",
),
),
migrations.AlterField(
model_name="team",
name="model_notes",
field=models.TextField(
blank=True,
default=None,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="team",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
to="access.tenant",
validators=[access.models.team.Team.validatate_organization_exists],
verbose_name="Tenant",
),
),
migrations.AlterField(
model_name="teamusers",
name="id",
field=models.AutoField(
help_text="ID of this Team User",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.DeleteModel(
name="TeamAuditHistory",
),
migrations.DeleteModel(
name="TeamCenturionModelNote",
),
]

View File

@ -1,112 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-08 04:18
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
(
"access",
"0015_remove_teamcenturionmodelnote_centurionmodelnote_ptr_and_more",
),
("core", "0031_remove_ticketcategory_is_global_and_more"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RemoveField(
model_name="tenant",
name="slug",
),
migrations.AlterField(
model_name="tenant",
name="manager",
field=models.ForeignKey(
blank=True,
help_text="Manager for this Tenancy",
null=True,
on_delete=django.db.models.deletion.PROTECT,
to=settings.AUTH_USER_MODEL,
verbose_name="Manager",
),
),
migrations.AlterField(
model_name="tenant",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.CreateModel(
name="TenantAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="access.tenant",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Tenant History",
"verbose_name_plural": "Tenant Histories",
"db_table": "access_tenant_audithistory",
"managed": True,
},
bases=("core.centurionaudit", models.Model),
),
migrations.CreateModel(
name="TenantCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Tenant Note",
"verbose_name_plural": "Tenant Notes",
"db_table": "access_tenant_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote", models.Model),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-17 07:27
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("access", "0016_remove_tenant_slug_alter_tenant_manager_and_more"),
("core", "0033_alter_ticketcommentcategory_parent_and_more"),
]
operations = [
migrations.DeleteModel(
name="EntityHistory",
),
migrations.DeleteModel(
name="EntityNotes",
),
]

View File

@ -1,253 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-17 07:32
import access.models.tenancy_abstract
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0017_remove_entitynotes_model_and_more"),
("core", "0033_alter_ticketcommentcategory_parent_and_more"),
]
operations = [
migrations.RemoveField(
model_name="entity",
name="is_global",
),
migrations.AlterField(
model_name="entity",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="entity",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="entity",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.CreateModel(
name="ContactAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.contact",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Contact History",
"verbose_name_plural": "Contact Histories",
"db_table": "access_contact_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="ContactCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.contact",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Contact Note",
"verbose_name_plural": "Contact Notes",
"db_table": "access_contact_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
migrations.CreateModel(
name="EntityAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="access.entity",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Entity History",
"verbose_name_plural": "Entity Histories",
"db_table": "access_entity_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="EntityCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.entity",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Entity Note",
"verbose_name_plural": "Entity Notes",
"db_table": "access_entity_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
migrations.CreateModel(
name="PersonAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.person",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Person History",
"verbose_name_plural": "Person Histories",
"db_table": "access_person_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="PersonCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.person",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Person Note",
"verbose_name_plural": "Person Notes",
"db_table": "access_person_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
]

View File

@ -1,81 +0,0 @@
# Generated by Django 5.1.10 on 2025-07-06 10:38
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0018_remove_entity_is_global_alter_entity_id_and_more"),
("core", "0033_alter_ticketcommentcategory_parent_and_more"),
]
operations = [
migrations.CreateModel(
name="CompanyAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.company",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Company History",
"verbose_name_plural": "Company Histories",
"db_table": "access_company_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="CompanyCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.company",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Company Note",
"verbose_name_plural": "Company Notes",
"db_table": "access_company_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
]

View File

@ -1,56 +0,0 @@
# Generated by Django 5.1.10 on 2025-07-12 07:20
import access.models.tenancy_abstract
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0019_companyaudithistory_companycenturionmodelnote"),
]
operations = [
migrations.AlterField(
model_name="role",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="role",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="role",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.DeleteModel(
name="RoleHistory",
),
migrations.DeleteModel(
name="RoleNotes",
),
]

View File

@ -1,81 +0,0 @@
# Generated by Django 5.1.10 on 2025-07-12 08:50
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0020_remove_rolenotes_model_and_more"),
("core", "0033_alter_ticketcommentcategory_parent_and_more"),
]
operations = [
migrations.CreateModel(
name="RoleAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="access.role",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Role History",
"verbose_name_plural": "Role Histories",
"db_table": "access_role_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="RoleCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.role",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Role Note",
"verbose_name_plural": "Role Notes",
"db_table": "access_role_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
]

View File

@ -0,0 +1,104 @@
import pytest
from django.test import Client
class AdditionalTestCases:
def test_permission_change(self, model_instance, api_request_permissions):
""" Check correct permission for change
Make change with user who has change permission
"""
client = Client()
client.force_login( api_request_permissions['user']['change'] )
kwargs = self.kwargs_create_item.copy()
kwargs.update({
'organization': api_request_permissions['tenancy']['user'],
'model': api_request_permissions['tenancy']['user']
})
change_item = model_instance(
kwargs_create = kwargs,
)
response = client.patch(
path = change_item.get_url( many = False ),
data = self.change_data,
content_type = 'application/json'
)
if response.status_code == 405:
pytest.xfail( reason = 'ViewSet does not have this request method.' )
assert response.status_code == 200, response.content
def test_permission_delete(self, model_instance, api_request_permissions):
""" Check correct permission for delete
Delete item as user with delete permission
"""
client = Client()
client.force_login( api_request_permissions['user']['delete'] )
kwargs = self.kwargs_create_item
kwargs.update({
'organization': api_request_permissions['tenancy']['user'],
'model': api_request_permissions['tenancy']['user']
})
delete_item = model_instance(
kwargs_create = kwargs
)
response = client.delete(
path = delete_item.get_url( many = False ),
)
if response.status_code == 405:
pytest.xfail( reason = 'ViewSet does not have this request method.' )
assert response.status_code == 204, response.content
def test_permission_view(self, model_instance, api_request_permissions):
""" Check correct permission for view
Attempt to view as user with view permission
"""
client = Client()
client.force_login( api_request_permissions['user']['view'] )
kwargs = self.kwargs_create_item
kwargs.update({
'organization': api_request_permissions['tenancy']['user'],
'model': api_request_permissions['tenancy']['user']
})
view_item = model_instance(
kwargs_create = kwargs
)
response = client.get(
path = view_item.get_url( many = False )
)
if response.status_code == 405:
pytest.xfail( reason = 'ViewSet does not have this request method.' )
assert response.status_code == 200, response.content
@pytest.mark.xfail( reason = 'model is not global based')
def test_returned_data_from_user_and_global_organizations_only(self ):
assert False

View File

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

View File

@ -0,0 +1,25 @@
import pytest
@pytest.fixture( scope = 'class')
def model(model_tenant):
yield model_tenant
@pytest.fixture( scope = 'class', autouse = True)
def model_kwargs(request, kwargs_tenant):
request.cls.kwargs_create_item = kwargs_tenant.copy()
yield kwargs_tenant.copy()
if hasattr(request.cls, 'kwargs_create_item'):
del request.cls.kwargs_create_item
@pytest.fixture( scope = 'class')
def model_serializer(serializer_tenant):
yield serializer_tenant

View File

@ -0,0 +1,117 @@
import pytest
from rest_framework.relations import Hyperlink
from django.db import models
from django.test import Client
from api.tests.functional.test_functional_api_fields import (
APIFieldsInheritedCases,
)
@pytest.mark.model_tenant
class TenantAPITestCases(
APIFieldsInheritedCases,
):
@pytest.fixture( scope = 'class')
def make_request(self, django_db_blocker,
request, organization_one,
api_request_permissions,
):
client = Client()
with django_db_blocker.unblock():
organization_one.manager = api_request_permissions['user']['view']
organization_one.model_notes = 'sad'
organization_one.save()
client.force_login( api_request_permissions['user']['view'] )
response = client.get(
organization_one.get_url()
)
request.cls.api_data = response.data
item_two = getattr(request.cls, 'item_two', None)
if item_two:
response_two = client.get( self.item_two.get_url() )
request.cls.api_data_two = response_two.data
else:
request.cls.api_data_two = {}
yield
with django_db_blocker.unblock():
organization_one.manager = None
organization_one.model_notes = None
organization_one.save()
@property
def parameterized_api_fields(self):
return {
'_urls.notes': {
'expected': models.NOT_PROVIDED
},
'organization': {
'expected': models.NOT_PROVIDED
},
'organization.id': {
'expected': models.NOT_PROVIDED
},
'organization.display_name': {
'expected': models.NOT_PROVIDED
},
'organization.url': {
'expected': models.NOT_PROVIDED
},
'name': {
'expected': str
},
'manager': {
'expected': dict
},
'manager.id': {
'expected': int
},
'manager.display_name': {
'expected': str
},
'manager.url': {
'expected': Hyperlink
},
'modified': {
'expected': str
}
}
class TenantAPIInheritedCases(
TenantAPITestCases,
):
pass
@pytest.mark.module_access
class TenantAPIPyTest(
TenantAPITestCases,
):
pass

View File

@ -0,0 +1,28 @@
import pytest
from core.tests.functional.centurion_abstract.test_functional_centurion_abstract_model import (
CenturionAbstractModelInheritedCases
)
@pytest.mark.model_tenant
class TenantModelTestCases(
CenturionAbstractModelInheritedCases
):
pass
class TenantModelInheritedCases(
TenantModelTestCases,
):
pass
@pytest.mark.module_access
class TenantModelPyTest(
TenantModelTestCases,
):
pass

View File

@ -16,6 +16,7 @@ User = django.contrib.auth.get_user_model()
@pytest.mark.model_tenant
class ViewSetBase:
model = Organization
@ -191,16 +192,7 @@ class ViewSetBase:
class OrganizationViewSet(
ViewSetBase,
SerializersTestCases,
TestCase
):
pass
@pytest.mark.module_access
class OrganizationMetadata(
ViewSetBase,
MetadataAttributesFunctional,

View File

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

View File

@ -1,18 +1,94 @@
import pytest
from rest_framework.exceptions import ValidationError
from api.tests.unit.test_unit_serializer import (
SerializerTestCases
)
class TenantSerializerTestCases(
SerializerTestCases
):
pass
from centurion.tests.abstract.mock_view import MockView
@pytest.mark.model_tenant
class TenantSerializerTestCases(
SerializerTestCases
):
@pytest.fixture( scope = 'function' )
def created_model(self, django_db_blocker, model, model_kwargs):
with django_db_blocker.unblock():
item = model.objects.create( **model_kwargs )
yield item
item.delete()
def test_serializer_validation_no_name(self,
kwargs_api_create, model, model_serializer, request_user
):
"""Serializer Validation Check
Ensure that if creating and no name is provided a validation error occurs
"""
mock_view = MockView(
user = request_user,
model = model,
action = 'create',
)
kwargs = kwargs_api_create.copy()
del kwargs['name']
with pytest.raises(ValidationError) as err:
serializer = model_serializer['model'](
context = {
'request': mock_view.request,
'view': mock_view,
},
data = kwargs
)
serializer.is_valid(raise_exception = True)
assert err.value.get_codes()['name'][0] == 'required'
def test_serializer_validation_manager_optional(self,
kwargs_api_create, model, model_serializer, request_user
):
"""Serializer Validation Check
Ensure that if creating and no name is provided a validation error occurs
"""
mock_view = MockView(
user = request_user,
model = model,
action = 'create',
)
kwargs = kwargs_api_create.copy()
del kwargs['manager']
serializer = model_serializer['model'](
context = {
'request': mock_view.request,
'view': mock_view,
},
data = kwargs
)
assert serializer.is_valid(raise_exception = True)
@pytest.mark.module_access
class TenantSerializerPyTest(
TenantSerializerTestCases

View File

@ -34,17 +34,17 @@ router = DefaultRouter(trailing_slash=False)
router.register('', access_v2.Index, basename = '_api_v2_access_home')
router.register(
prefix = '(?P<model_name>[company]+)', viewset = entity.ViewSet,
prefix = '/(?P<model_name>[company]+)', viewset = entity.ViewSet,
feature_flag = '2025-00008',basename = '_api_v2_company'
)
router.register(
prefix=f'entity/(?P<model_name>[{entity_type_names}]+)?', viewset = entity.ViewSet,
prefix=f'/entity/(?P<model_name>[{entity_type_names}]+)?', viewset = entity.ViewSet,
feature_flag = '2025-00002', basename = '_api_entity_sub'
)
router.register(
prefix = 'entity', viewset = entity.NoDocsViewSet,
prefix = '/entity', viewset = entity.NoDocsViewSet,
feature_flag = '2025-00002', basename = '_api_entity'
)
@ -54,7 +54,7 @@ router.register(
# )
router.register(
prefix = 'tenant', viewset = organization.ViewSet,
prefix = '/tenant', viewset = organization.ViewSet,
basename = '_api_tenant'
)
@ -64,7 +64,7 @@ router.register(
# )
router.register(
prefix = 'tenant/(?P<organization_id>[0-9]+)/team', viewset = team_v2.ViewSet,
prefix = '/tenant/(?P<organization_id>[0-9]+)/team', viewset = team_v2.ViewSet,
basename = '_api_v2_organization_team'
)
@ -75,13 +75,13 @@ router.register(
# )
router.register(
prefix = 'access/tenant/(?P<organization_id>[0-9]+)/team/(?P<team_id>[0-9]+)/user',
prefix = '/access/tenant/(?P<organization_id>[0-9]+)/team/(?P<team_id>[0-9]+)/user',
viewset = team_user_v2.ViewSet,
basename = '_api_v2_organization_team_user'
)
router.register(
prefix = 'role', viewset = role.ViewSet,
prefix = '/role', viewset = role.ViewSet,
feature_flag = '2025-00003', basename = '_api_role'
)

View File

@ -1,4 +1,4 @@
# Generated by Django 5.1.10 on 2025-07-10 09:10
# Generated by Django 5.1.10 on 2025-08-15 03:21
import access.models.tenancy_abstract
import django.db.models.deletion
@ -8,9 +8,9 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0019_companyaudithistory_companycenturionmodelnote"),
("access", "0011_remove_entitynotes_model_and_more"),
("accounting", "0001_initial"),
("core", "0033_alter_ticketcommentcategory_parent_and_more"),
("core", "0024_centurionaudit_centurionmodelnote_and_more"),
]
operations = [
@ -115,4 +115,10 @@ class Migration(migrations.Migration):
},
bases=("core.centurionmodelnote",),
),
migrations.DeleteModel(
name="AssetBaseHistory",
),
migrations.DeleteModel(
name="AssetBaseNotes",
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 5.1.10 on 2025-07-10 09:31
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("accounting", "0002_alter_assetbase_id_alter_assetbase_model_notes_and_more"),
]
operations = [
migrations.DeleteModel(
name="AssetBaseHistory",
),
migrations.DeleteModel(
name="AssetBaseNotes",
),
]

View File

@ -42,7 +42,7 @@ asset_type_names = str(asset_type_names)[:-1]
if not asset_type_names:
asset_type_names = 'none'
router.register(f'asset/(?P<model_name>[{asset_type_names}]+)?', asset.ViewSet, feature_flag = '2025-00004', basename='_api_asset_sub')
router.register('asset', asset.NoDocsViewSet, feature_flag = '2025-00004', basename='_api_asset')
router.register(f'/asset/(?P<model_name>[{asset_type_names}]+)?', asset.ViewSet, feature_flag = '2025-00004', basename='_api_asset_sub')
router.register('/asset', asset.NoDocsViewSet, feature_flag = '2025-00004', basename='_api_asset')
urlpatterns = router.urls

View File

@ -1,5 +1,10 @@
from django.core.exceptions import (
ValidationError as DjangoValidationError,
)
from rest_framework import serializers
from rest_framework.exceptions import (
ValidationError
)
from rest_framework.reverse import reverse
from access.serializers.organization import Tenant
@ -44,15 +49,16 @@ class CommonModelSerializer(CommonBaseSerializer):
_**Note:** This serializer is not inherited by the organization Serializer_
_`access.serializers.organization`, this is by design_
This serializer is included within ALL model (Tenancy Model) serilaizers and is intended to be used
to add objects that ALL model serializers will require.
This serializer is included within ALL model (Tenancy Model) serilaizers
and is intended to be used to add objects that ALL model serializers will
require.
Args:
CommonBaseSerializer (Class): Common base serializer
"""
model_notes = centurion_field.MarkdownField( required = False )
organization = OrganizationField(required = False)
@ -169,3 +175,32 @@ class CommonModelSerializer(CommonBaseSerializer):
)
return get_url
def is_valid(self, *, raise_exception=False):
is_valid = False
try:
is_valid = super().is_valid(raise_exception=raise_exception)
except DjangoValidationError as ex:
if raise_exception:
raise ValidationError( serializers.as_serializer_error(ex ) )
return is_valid
def save(self, **kwargs):
save = None
try:
save = super().save( **kwargs )
except DjangoValidationError as ex:
raise ValidationError( serializers.as_serializer_error(ex ) )
return save

View File

@ -0,0 +1,25 @@
import pytest
@pytest.fixture( scope = 'class')
def model(model_authtoken):
yield model_authtoken
@pytest.fixture( scope = 'class', autouse = True)
def model_kwargs(request, kwargs_authtoken):
request.cls.kwargs_create_item = kwargs_authtoken.copy()
yield kwargs_authtoken.copy()
if hasattr(request.cls, 'kwargs_create_item'):
del request.cls.kwargs_create_item
@pytest.fixture( scope = 'class')
def model_serializer(serializer_authtoken):
yield serializer_authtoken

View File

@ -0,0 +1,113 @@
import pytest
from django.db import models
from rest_framework.relations import Hyperlink
from api.tests.functional.test_functional_api_fields import (
APIFieldsInheritedCases,
)
@pytest.mark.model_authtoken
class AuthTokenAPITestCases(
APIFieldsInheritedCases,
):
@pytest.fixture( scope = 'class')
def create_model(self, request, django_db_blocker,
model, model_kwargs, api_request_permissions,
):
item = None
with django_db_blocker.unblock():
kwargs = model_kwargs.copy()
kwargs['user'] = api_request_permissions['user']['view']
item = model.objects.create(
**kwargs
)
request.cls.item = item
yield item
with django_db_blocker.unblock():
item.delete()
@property
def parameterized_api_fields(self):
return {
'_urls.notes': {
'expected': models.NOT_PROVIDED
},
'model_notes': {
'expected': models.NOT_PROVIDED
},
'id': {
'expected': int
},
'note': {
'expected': str
},
'token': { # Must not be in fields
'expected': models.NOT_PROVIDED
},
'user': {
'expected': int
},
'user.id': { # Must not be in fields as object belongs to user requesting
'expected': models.NOT_PROVIDED
},
'user.display_name': { # Must not be in fields as object belongs to user requesting
'expected': models.NOT_PROVIDED
},
'user.url': { # Must not be in fields as object belongs to user requesting
'expected': models.NOT_PROVIDED
},
'organization': { # Not a tenancy model
'expected': models.NOT_PROVIDED
},
'organization.id': { # Not a tenancy model
'expected': models.NOT_PROVIDED
},
'organization.display_name': { # Not a tenancy model
'expected': models.NOT_PROVIDED
},
'organization.url': { # Not a tenancy model
'expected': models.NOT_PROVIDED
},
'expires': {
'expected': str
},
'created': {
'expected': str
},
'modified': {
'expected': str
}
}
class AuthTokenAPIInheritedCases(
AuthTokenAPITestCases,
):
pass
@pytest.mark.module_api
class AuthTokenAPIPyTest(
AuthTokenAPITestCases,
):
pass

View File

@ -0,0 +1,223 @@
import django
import pytest
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from access.models.tenant import Tenant as Organization
from access.models.team import Team
from access.models.team_user import TeamUsers
from api.models.tokens import AuthToken
from api.tests.abstract.test_metadata_functional import (
MetadataAttributesFunctionalEndpoint,
MetadataAttributesFunctionalBase,
)
User = django.contrib.auth.get_user_model()
@pytest.mark.model_authtoken
class ViewSetBase:
model = AuthToken
app_namespace = 'v2'
url_name = '_api_authtoken'
change_data = {'device_model_is_global': True}
delete_data = {}
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create an organization for user and item
. create an organization that is different to item
2. Create a team
3. create teams with each permission: view, add, change, delete
4. create a user per team
"""
organization = Organization.objects.create(name='test_org')
self.organization = organization
different_organization = Organization.objects.create(name='test_different_organization')
self.different_organization = different_organization
view_permissions = Permission.objects.get(
codename = 'view_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
view_team = Team.objects.create(
team_name = 'view_team',
organization = organization,
)
view_team.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.add_user = User.objects.create_user(username="test_user_add", password="password")
teamuser = TeamUsers.objects.create(
team = add_team,
user = self.add_user
)
self.change_user = User.objects.create_user(username="test_user_change", password="password")
teamuser = TeamUsers.objects.create(
team = change_team,
user = self.change_user
)
self.delete_user = User.objects.create_user(username="test_user_delete", password="password")
teamuser = TeamUsers.objects.create(
team = delete_team,
user = self.delete_user
)
self.view_user = User.objects.create_user(username="test_user_view", password="password")
teamuser = TeamUsers.objects.create(
team = view_team,
user = self.view_user
)
self.item = self.model.objects.create(
note = 'a note',
token = self.model().generate,
user = self.view_user,
expires = '2025-02-25T23:14Z'
)
self.item_delete = self.model.objects.create(
note = 'a note',
token = self.model().generate,
user = self.delete_user,
expires = '2025-02-25T23:14Z'
)
# self.item.default_organization = self.organization
# self.item.save()
self.url_view_kwargs = {
'model_id': self.view_user.id,
'pk': self.item.id,
}
self.url_kwargs = {
'model_id': self.view_user.id,
}
self.add_data = {
'note': 'a note',
'token': self.model().generate,
'expires': '2025-02-26T23:14Z'
}
self.different_organization_user = User.objects.create_user(username="test_different_organization_user", password="password")
different_organization_team = Team.objects.create(
team_name = 'different_organization_team',
organization = different_organization,
)
different_organization_team.permissions.set([
view_permissions,
add_permissions,
change_permissions,
delete_permissions,
])
TeamUsers.objects.create(
team = different_organization_team,
user = self.different_organization_user
)
@pytest.mark.module_api
class Metadata(
ViewSetBase,
MetadataAttributesFunctionalEndpoint,
MetadataAttributesFunctionalBase,
TestCase
):
viewset_type = 'detail'
@classmethod
def setUpTestData(self):
super().setUpTestData()
self.url_kwargs = self.url_view_kwargs

View File

@ -0,0 +1,28 @@
import pytest
from core.tests.functional.centurion_abstract.test_functional_centurion_abstract_model import (
CenturionAbstractModelInheritedCases
)
@pytest.mark.model_authtoken
class AuthTokenModelTestCases(
CenturionAbstractModelInheritedCases
):
pass
class AuthTokenModelInheritedCases(
AuthTokenModelTestCases,
):
pass
@pytest.mark.module_api
class AuthTokenModelPyTest(
AuthTokenModelTestCases,
):
pass

View File

@ -1,162 +0,0 @@
import pytest
from django.test import TestCase
from rest_framework.exceptions import ValidationError, PermissionDenied
from access.models.tenant import Tenant as Organization
from api.serializers.auth_token import AuthToken, AuthTokenModelSerializer
from centurion.tests.abstract.mock_view import MockView, User
@pytest.mark.model_authtoken
@pytest.mark.module_api
class ValidationAPI(
TestCase,
):
model = AuthToken
@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.user = User.objects.create_user(username="test_user_view", password="password")
self.valid_data = {
'note': 'a note',
'token': self.model().generate,
'user': self.user.id,
'expires': '2025-02-26T00:09Z'
}
self.mock_view = MockView( user = self.user )
self.mock_view.kwargs = {
'model_id': self.user.id
}
self.item = self.model.objects.create(
note = 'object note',
token = self.model().generate,
user = self.user,
expires = '2025-02-26T00:07Z'
)
def test_serializer_validation_valid_data(self):
"""Serializer Validation Check
Ensure that if creating with valid data, the object is created
"""
serializer = AuthTokenModelSerializer(
context = {
'request': self.mock_view.request,
'view': self.mock_view,
},
data = self.valid_data
)
assert serializer.is_valid(raise_exception = True)
def test_serializer_validation_valid_data_different_user(self):
"""Serializer Validation Check
Ensure that if adding the same manufacturer
it raises a validation error
"""
class MockUser:
id = 99
mock_view = MockView( user = self.user )
mock_view.request.user = MockUser()
mock_view.kwargs = {
'model_id': self.user.id
}
with pytest.raises(PermissionDenied) as err:
serializer = AuthTokenModelSerializer(
context = {
'request': mock_view.request,
'view': mock_view,
},
data = self.valid_data
)
serializer.is_valid(raise_exception = True)
assert err.value.get_codes() == 'permission_denied'
def test_serializer_validation_valid_data_token_not_sha256_same_length(self):
"""Serializer Validation Check
Ensure that if adding the same manufacturer
it raises a validation error
"""
valid_data = self.valid_data.copy()
valid_data['token'] = str(valid_data['token'])[:-5] + 'qwert'
with pytest.raises(ValidationError) as err:
serializer = AuthTokenModelSerializer(
context = {
'request': self.mock_view.request,
'view': self.mock_view,
},
data = valid_data
)
serializer.is_valid(raise_exception = True)
assert err.value.get_codes()['token'][0] == 'token_not_sha256'
def test_serializer_validation_valid_data_token_not_sha256_wrong_length(self):
"""Serializer Validation Check
Ensure that if adding the same manufacturer
it raises a validation error
"""
valid_data = self.valid_data.copy()
valid_data['token'] = str(valid_data['token'])[:-5] + 'qwer'
with pytest.raises(ValidationError) as err:
serializer = AuthTokenModelSerializer(
context = {
'request': self.mock_view.request,
'view': self.mock_view,
},
data = valid_data
)
serializer.is_valid(raise_exception = True)
assert err.value.get_codes()['token'][0] == 'token_not_sha256'

View File

@ -1,388 +0,0 @@
import django
import pytest
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import reverse
from django.test import Client, TestCase
from access.models.tenant import Tenant as Organization
from access.models.team import Team
from access.models.team_user import TeamUsers
from api.models.tokens import AuthToken
from api.tests.abstract.api_permissions_viewset import (
APIPermissionAdd,
APIPermissionDelete,
APIPermissionView,
)
from api.tests.abstract.api_serializer_viewset import (
SerializerAdd,
SerializerDelete,
SerializerView,
)
from api.tests.abstract.test_metadata_functional import (
MetadataAttributesFunctionalEndpoint,
MetadataAttributesFunctionalBase,
)
User = django.contrib.auth.get_user_model()
@pytest.mark.model_authtoken
@pytest.mark.module_api
class ViewSetBase:
model = AuthToken
app_namespace = 'v2'
url_name = '_api_authtoken'
change_data = {'device_model_is_global': True}
delete_data = {}
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create an organization for user and item
. create an organization that is different to item
2. Create a team
3. create teams with each permission: view, add, change, delete
4. create a user per team
"""
organization = Organization.objects.create(name='test_org')
self.organization = organization
different_organization = Organization.objects.create(name='test_different_organization')
self.different_organization = different_organization
view_permissions = Permission.objects.get(
codename = 'view_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
view_team = Team.objects.create(
team_name = 'view_team',
organization = organization,
)
view_team.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.add_user = User.objects.create_user(username="test_user_add", password="password")
teamuser = TeamUsers.objects.create(
team = add_team,
user = self.add_user
)
self.change_user = User.objects.create_user(username="test_user_change", password="password")
teamuser = TeamUsers.objects.create(
team = change_team,
user = self.change_user
)
self.delete_user = User.objects.create_user(username="test_user_delete", password="password")
teamuser = TeamUsers.objects.create(
team = delete_team,
user = self.delete_user
)
self.view_user = User.objects.create_user(username="test_user_view", password="password")
teamuser = TeamUsers.objects.create(
team = view_team,
user = self.view_user
)
self.item = self.model.objects.create(
note = 'a note',
token = self.model().generate,
user = self.view_user,
expires = '2025-02-25T23:14Z'
)
self.item_delete = self.model.objects.create(
note = 'a note',
token = self.model().generate,
user = self.delete_user,
expires = '2025-02-25T23:14Z'
)
# self.item.default_organization = self.organization
# self.item.save()
self.url_view_kwargs = {
'model_id': self.view_user.id,
'pk': self.item.id,
}
self.url_kwargs = {
'model_id': self.view_user.id,
}
self.add_data = {
'note': 'a note',
'token': self.model().generate,
'expires': '2025-02-26T23:14Z'
}
self.different_organization_user = User.objects.create_user(username="test_different_organization_user", password="password")
different_organization_team = Team.objects.create(
team_name = 'different_organization_team',
organization = different_organization,
)
different_organization_team.permissions.set([
view_permissions,
add_permissions,
change_permissions,
delete_permissions,
])
TeamUsers.objects.create(
team = different_organization_team,
user = self.different_organization_user
)
class PermissionsAPI(
ViewSetBase,
APIPermissionAdd,
APIPermissionDelete,
APIPermissionView,
TestCase,
):
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
def test_add_has_permission(self):
""" Check correct permission for add
Attempt to add as user with permission
"""
url_kwargs = self.url_kwargs.copy()
url_kwargs['model_id'] = self.add_user.id
client = Client()
if self.url_kwargs:
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
client.force_login(self.add_user)
response = client.post(url, data=self.add_data)
assert response.status_code == 201
def test_add_permission_view_denied(self):
""" Check correct permission for add
Attempt to add a user with view permission
"""
url_kwargs = self.url_kwargs.copy()
url_kwargs['model_id'] = self.add_user.id
client = Client()
if self.url_kwargs:
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
client.force_login(self.view_user)
response = client.post(url, data=self.add_data)
assert response.status_code == 403
def test_delete_has_permission(self):
""" Check correct permission for delete
Delete item as user with delete permission
"""
url_view_kwargs = self.url_view_kwargs.copy()
url_view_kwargs['model_id'] = self.delete_user.id
url_view_kwargs['pk'] = self.item_delete.id
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=url_view_kwargs)
client.force_login(self.delete_user)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 204
def test_delete_permission_view_denied(self):
""" Check correct permission for delete
Attempt to delete as user with veiw permission only
"""
url_view_kwargs = self.url_view_kwargs.copy()
url_view_kwargs['model_id'] = self.delete_user.id
url_view_kwargs['pk'] = self.item_delete.id
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=url_view_kwargs)
client.force_login(self.view_user)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 403
def test_returned_results_only_user_orgs(self):
"""Test not required
this test is not required as this model is not a tenancy model
"""
pass
def test_view_no_permission_denied(self):
""" Check correct permission for view
This test case is a duplicate of a test case with the same name.
This test is not required for this model as there are no permissions
assosiated with accessing this model.
Attempt to view with user missing permission
"""
pass
class ViewSet(
ViewSetBase,
SerializerAdd,
SerializerDelete,
SerializerView,
TestCase,
):
pass
class Metadata(
ViewSetBase,
MetadataAttributesFunctionalEndpoint,
MetadataAttributesFunctionalBase,
TestCase
):
viewset_type = 'detail'
@classmethod
def setUpTestData(self):
super().setUpTestData()
self.url_kwargs = self.url_view_kwargs

View File

@ -450,15 +450,6 @@ class CommonViewSetTestCases(
}
}
# @classmethod
# def setUpTestData(self):
# self.kwargs: dict = {}
# if self.viewset is CommonViewSet:
# self.viewset.model = Organization
def test_class_inherits_organizationmixin(self, viewset):
"""Class Inheritence check
@ -707,34 +698,6 @@ class ModelViewSetBaseCases(
},
}
# kwargs: dict = {}
# organization: Organization
# view_user: User
# @classmethod
# def setUpTestData(self):
# super().setUpTestData() # Sets attribute self.view_set.model
# self.organization = Organization.objects.create(name='test_org')
# self.view_user = User.objects.create_user(
# username="test_view_user1278", password="password", is_superuser=True)
# @classmethod
# def tearDownClass(cls):
# cls.model = None
# cls.organization.delete()
# cls.view_user.delete()
# super().tearDownClass()
def test_class_inherits_modelviewsetbase(self, viewset):
"""Class Inheritence check
@ -756,21 +719,6 @@ class ModelViewSetBaseCases(
view_set = viewset_mock_request
# view_set.request = MockRequest(
# user = self.view_user,
# model = getattr(self, 'model',None),
# 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()
@ -1078,14 +1026,6 @@ class SubModelViewSetTestCases(
}
}
# kwargs: dict
# organization: Organization
# view_user: User
# viewset = SubModelViewSet
def test_class_inherits_submodelviewsetbase(self, viewset):
"""Class Inheritence check
@ -2245,19 +2185,6 @@ class SubModelViewSetInheritedCases(
Use this Test Suite for ViewSet classes that inherit from SubModelViewSet
"""
# @classmethod
# def setUpTestData(self):
# """Setup Test
# 1. make list request
# """
# self.viewset.kwargs = {}
# self.viewset.kwargs[self.viewset.model_kwarg] = self.model._meta.sub_model_type
# super().setUpTestData()
@pytest.fixture( scope = 'function' )
def viewset_mock_request(self, django_db_blocker, viewset,

View File

@ -17,3 +17,9 @@ def model_kwargs(request, kwargs_authtoken):
if hasattr(request.cls, 'kwargs_create_item'):
del request.cls.kwargs_create_item
@pytest.fixture( scope = 'class')
def model_serializer(serializer_authtoken):
yield serializer_authtoken

View File

@ -1,187 +0,0 @@
import django
import pytest
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import reverse
from django.test import Client, TestCase
from access.models.tenant import Tenant as Organization
from access.models.team import Team
from access.models.team_user import TeamUsers
from api.models.tokens import AuthToken
from api.tests.abstract.api_fields import APIModelFields
User = django.contrib.auth.get_user_model()
@pytest.mark.model_authtoken
@pytest.mark.module_api
class API(
TestCase,
APIModelFields
):
model = AuthToken
@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.view_user = User.objects.create_user(username="test_user_view", password="password")
self.item = self.model.objects.create(
note = 'a note',
token = self.model().generate,
user = self.view_user,
expires = '2099-02-26T00:53Z'
)
self.url_view_kwargs = {
'model_id': self.view_user.id,
'pk': self.item.id
}
view_permissions = Permission.objects.get(
codename = 'view_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
view_team = Team.objects.create(
team_name = 'view_team',
organization = self.organization,
)
view_team.permissions.set([view_permissions])
teamuser = TeamUsers.objects.create(
team = view_team,
user = self.view_user
)
client = Client()
url = reverse('v2:_api_authtoken-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_model_notes(self):
""" Test for existance of API Field
This test case is a duplicate of a test with the same name. This
model is not a tenancy model and does not require this field.
model_notes field must exist
"""
assert 'model_notes' not in self.api_data
def test_api_field_type_model_notes(self):
""" Test for type for API Field
This test case is a duplicate of a test with the same name. This
model is not a tenancy model and does not require this field.
model_notes field must be str
"""
assert 'model_notes' not in self.api_data
def test_api_field_exists_user(self):
""" Test for existance of API Field
user field must exist
"""
assert 'user' in self.api_data
def test_api_field_type_user(self):
""" Test for type for API Field
normallay an object would be serialized. However in this case only
the owner of the object will access the object and therefore would
be a waste to serialize it.
user field must be int
"""
assert type(self.api_data['user']) is int
def test_api_field_exists_note(self):
""" Test for existance of API Field
note field must exist
"""
assert 'note' in self.api_data
def test_api_field_type_user(self):
""" Test for type for API Field
note field must be str
"""
assert type(self.api_data['note']) is str
def test_api_field_exists_expires(self):
""" Test for existance of API Field
expires field must exist
"""
assert 'expires' in self.api_data
def test_api_field_type_expires(self):
""" Test for type for API Field
expires field must be str
"""
assert type(self.api_data['expires']) 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

View File

@ -10,8 +10,7 @@ from core.tests.unit.mixin_centurion.test_unit_centurion_mixin import CenturionM
@pytest.mark.unit
@pytest.mark.centurion_models
@pytest.mark.model_authtoken
class AuthTokenModelTestCases(
CenturionMixnInheritedCases,
):
@ -139,6 +138,7 @@ class AuthTokenModelInheritedCases(
@pytest.mark.module_api
class AuthTokenModelPyTest(
AuthTokenModelTestCases,
):

View File

@ -0,0 +1,279 @@
import pytest
from django.db import models
from rest_framework.exceptions import (
PermissionDenied,
ValidationError,
)
from api.tests.unit.test_unit_serializer import (
SerializerTestCases
)
from centurion.tests.abstract.mock_view import MockView
@pytest.mark.model_authtoken
class AuthTokenSerializerTestCases(
SerializerTestCases
):
@pytest.fixture( scope = 'function' )
def created_model(self, django_db_blocker, model, model_kwargs):
with django_db_blocker.unblock():
kwargs_many_to_many = {}
kwargs = {}
for key, value in model_kwargs.items():
field = model._meta.get_field(key)
if isinstance(field, models.ManyToManyField):
kwargs_many_to_many.update({
key: value
})
else:
kwargs.update({
key: value
})
item = model.objects.create( **kwargs )
for key, value in kwargs_many_to_many.items():
field = getattr(item, key)
for entry in value:
field.add(entry)
yield item
item.delete()
def test_serializer_is_valid(self, kwargs_api_create, model, model_serializer, request_user):
mock_view = MockView(
user = request_user,
model = model,
action = 'create',
)
mock_view.kwargs = {
'model_id': request_user.id
}
serializer = model_serializer['model'](
context = {
'request': mock_view.request,
'view': mock_view,
},
data = kwargs_api_create
)
assert serializer.is_valid(raise_exception = True)
@pytest.mark.regression
def test_serializer_create_calls_model_full_clean(self,
kwargs_api_create, mocker, model, model_serializer, request_user
):
mock_view = MockView(
user = request_user,
model = model,
action = 'create',
)
mock_view.kwargs = {
'model_id': request_user.id
}
serializer = model_serializer['model'](
context = {
'request': mock_view.request,
'view': mock_view,
},
data = kwargs_api_create
)
serializer.is_valid(raise_exception = True)
full_clean = mocker.spy(model, 'full_clean')
serializer.save()
full_clean.assert_called_once()
# def test_serializer_validation_no_title(self,
# kwargs_api_create, model, model_serializer, request_user
# ):
# """Serializer Validation Check
# Ensure that if creating and no title is provided a validation error occurs
# """
# mock_view = MockView(
# user = request_user,
# model = model,
# action = 'create',
# )
# kwargs = kwargs_api_create.copy()
# del kwargs['title']
# with pytest.raises(ValidationError) as err:
# serializer = model_serializer['model'](
# context = {
# 'request': mock_view.request,
# 'view': mock_view,
# },
# data = kwargs
# )
# serializer.is_valid(raise_exception = True)
# assert err.value.get_codes()['title'][0] == 'required'
def test_serializer_validation_valid_data_different_user(self,
kwargs_api_create, model, model_serializer, request_user
):
"""Serializer Validation Check
Ensure that if adding the same manufacturer
it raises a validation error
"""
mock_view = MockView(
user = request_user,
model = model,
action = 'create',
)
kwargs = kwargs_api_create.copy()
mock_view.kwargs = {
'model_id': 99999
}
with pytest.raises(PermissionDenied) as err:
serializer = model_serializer['model'](
context = {
'request': mock_view.request,
'view': mock_view,
},
data = kwargs
)
serializer.is_valid(raise_exception = True)
assert err.value.get_codes() == 'permission_denied'
def test_serializer_validation_valid_data_token_not_sha256_same_length(self,
kwargs_api_create, model, model_serializer, request_user
):
"""Serializer Validation Check
Ensure that if adding the same manufacturer
it raises a validation error
"""
mock_view = MockView(
user = request_user,
model = model,
action = 'create',
)
mock_view.kwargs = {
'model_id': request_user.id
}
kwargs = kwargs_api_create.copy()
kwargs['token'] = str( model().generate )[:-5] + 'qwert'
with pytest.raises(ValidationError) as err:
serializer = model_serializer['model'](
context = {
'request': mock_view.request,
'view': mock_view,
},
data = kwargs
)
serializer.is_valid(raise_exception = True)
assert err.value.get_codes()['token'][0] == 'token_not_sha256'
def test_serializer_validation_valid_data_token_not_sha256_wrong_length(self,
kwargs_api_create, model, model_serializer, request_user
):
"""Serializer Validation Check
Ensure that if adding the same manufacturer
it raises a validation error
"""
mock_view = MockView(
user = request_user,
model = model,
action = 'create',
)
mock_view.kwargs = {
'model_id': request_user.id
}
kwargs = kwargs_api_create.copy()
kwargs['token'] = str( model().generate )[:-5] + 'qwer'
with pytest.raises(ValidationError) as err:
serializer = model_serializer['model'](
context = {
'request': mock_view.request,
'view': mock_view,
},
data = kwargs
)
serializer.is_valid(raise_exception = True)
assert err.value.get_codes()['token'][0] == 'token_not_sha256'
class AuthTokenSerializerInheritedCases(
AuthTokenSerializerTestCases
):
pass
@pytest.mark.module_api
class AuthTokenSerializerPyTest(
AuthTokenSerializerTestCases
):
pass

View File

@ -1,52 +1,90 @@
import pytest
from django.test import Client, TestCase
from rest_framework.reverse import reverse
from api.tests.unit.test_unit_common_viewset import (
ModelCreateViewSetInheritedCases,
ModelListRetrieveDeleteViewSetInheritedCases,
)
from api.viewsets.auth_token import ViewSet
from api.viewsets.auth_token import (
AuthToken,
ViewSet,
)
@pytest.mark.skip(reason = 'see #895, tests being refactored')
@pytest.mark.model_authtoken
@pytest.mark.module_api
class ViewsetList(
class ViewsetTestCases(
ModelCreateViewSetInheritedCases,
ModelListRetrieveDeleteViewSetInheritedCases,
TestCase,
):
viewset = ViewSet
route_name = 'v2:_api_authtoken'
@pytest.fixture( scope = 'function' )
def viewset(self):
return ViewSet
@classmethod
def setUpTestData(self):
"""Setup Test
1. make list request
"""
super().setUpTestData()
self.kwargs = {
'model_id': self.view_user.id
@property
def parameterized_class_attributes(self):
return {
'_log': {
'type': type(None),
},
'_model_documentation': {
'type': type(None),
},
'back_url': {
'type': type(None),
},
'documentation': {
'type': type(None),
'value': None
},
'filterset_fields': {
'value': [
'expires'
]
},
'model': {
'value': AuthToken
},
'model_documentation': {
'type': type(None),
},
'queryset': {
'type': type(None),
},
'serializer_class': {
'type': type(None),
},
'search_fields': {
'value': [
'note'
]
},
'view_description': {
'value': 'User Authentication Tokens'
},
'view_name': {
'type': type(None),
},
'view_serializer_name': {
'type': type(None),
}
}
client = Client()
url = reverse(
self.route_name + '-list',
kwargs = self.kwargs
)
client.force_login(self.view_user)
class AuthTokenViewsetInheritedCases(
ViewsetTestCases,
):
pass
self.http_options_response_list = client.options(url)
@pytest.mark.module_api
class AuthTokenViewsetPyTest(
ViewsetTestCases,
):
pass

View File

@ -61,22 +61,22 @@ router = DefaultRouter(trailing_slash=False)
router.register('', v2.Index, basename='_api_v2_home')
router.register('base', base_index_v2.Index, basename='_api_v2_base_home')
router.register('base/content_type', content_type_v2.ViewSet, basename='_api_v2_content_type')
router.register('base/permission', permission_v2.ViewSet, basename='_api_v2_permission')
router.register('base/user', user_v2.ViewSet, basename='_api_v2_user')
router.register('/base', base_index_v2.Index, basename='_api_v2_base_home')
router.register('/base/content_type', content_type_v2.ViewSet, basename='_api_v2_content_type')
router.register('/base/permission', permission_v2.ViewSet, basename='_api_v2_permission')
router.register('/base/user', user_v2.ViewSet, basename='_api_v2_user')
router.register(
prefix = f'(?P<app_label>[{history_app_labels}]+)/(?P<model_name>[{history_type_names} \
prefix = f'/(?P<app_label>[{history_app_labels}]+)/(?P<model_name>[{history_type_names} \
]+)/(?P<model_id>[0-9]+)/history',
viewset = audit_history.ViewSet,
basename = '_api_centurionaudit_sub'
)
router.register(
prefix = f'(?P<app_label>[{notes_app_labels}]+)/(?P<model_name>[{notes_type_names} \
prefix = f'/(?P<app_label>[{notes_app_labels}]+)/(?P<model_name>[{notes_type_names} \
]+)/(?P<model_id>[0-9]+)/notes',
viewset = centurion_model_notes.ViewSet,
basename = '_api_centurionmodelnote_sub'
@ -85,24 +85,24 @@ router.register(
urlpatterns = [
path('schema', SpectacularAPIView.as_view(api_version='v2'), name='schema-v2',),
path('docs', SpectacularSwaggerView.as_view(url_name='schema-v2'), name='_api_v2_docs'),
path('/schema', SpectacularAPIView.as_view(api_version='v2'), name='schema-v2',),
path('/docs', SpectacularSwaggerView.as_view(url_name='schema-v2'), name='_api_v2_docs'),
]
urlpatterns += router.urls
urlpatterns += [
path(route = "access/", view = include("access.urls_api")),
path(route = "accounting/", view = include("accounting.urls")),
path(route = "assistance/", view = include("assistance.urls_api")),
path(route = "config_management/", view = include("config_management.urls_api")),
path(route = "core/", view = include("core.urls_api")),
path(route = "devops/", view = include("devops.urls")),
path(route = "hr/", view = include('human_resources.urls')),
path(route = "itam/", view = include("itam.urls_api")),
path(route = "itim/", view = include("itim.urls_api")),
path(route = "project_management/", view = include("project_management.urls_api")),
path(route = "settings/", view = include("settings.urls_api")),
path(route = 'public/', view = include('api.urls_public')),
path(route = "/access", view = include("access.urls_api")),
path(route = "/accounting", view = include("accounting.urls")),
path(route = "/assistance", view = include("assistance.urls_api")),
path(route = "/config_management", view = include("config_management.urls_api")),
path(route = "/core", view = include("core.urls_api")),
path(route = "/devops", view = include("devops.urls")),
path(route = "/hr", view = include('human_resources.urls')),
path(route = "/itam", view = include("itam.urls_api")),
path(route = "/itim", view = include("itim.urls_api")),
path(route = "/project_management", view = include("project_management.urls_api")),
path(route = "/settings", view = include("settings.urls_api")),
path(route = '/public', view = include('api.urls_public')),
]

View File

@ -1,16 +1,19 @@
# Generated by Django 5.1.9 on 2025-06-03 22:04
# Generated by Django 5.1.10 on 2025-08-15 03:21
import access.models.tenancy_abstract
import assistance.models.model_knowledge_base_article
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0010_company_alter_entity_entity_type_alter_person_dob_and_more"),
("access", "0011_remove_entitynotes_model_and_more"),
("assistance", "0006_alter_knowledgebase_organization_and_more"),
("core", "0027_centurionmodelnote"),
("core", "0024_centurionaudit_centurionmodelnote_and_more"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
@ -22,6 +25,48 @@ class Migration(migrations.Migration):
model_name="knowledgebasecategory",
name="is_global",
),
migrations.AddField(
model_name="knowledgebase",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="knowledgebase",
name="category",
field=models.ForeignKey(
help_text="Article Category",
max_length=50,
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="assistance.knowledgebasecategory",
verbose_name="Category",
),
),
migrations.AlterField(
model_name="knowledgebase",
name="content",
field=models.TextField(
blank=True,
help_text="Content of the article. Markdown is supported",
null=True,
verbose_name="Article Content",
),
),
migrations.AlterField(
model_name="knowledgebase",
name="expiry_date",
field=models.DateTimeField(
blank=True,
help_text="Date the article will be removed from published articles",
null=True,
verbose_name="End Date",
),
),
migrations.AlterField(
model_name="knowledgebase",
name="id",
@ -47,6 +92,72 @@ class Migration(migrations.Migration):
verbose_name="Tenant",
),
),
migrations.AlterField(
model_name="knowledgebase",
name="release_date",
field=models.DateTimeField(
blank=True,
help_text="Date the article will be published",
null=True,
verbose_name="Publish Date",
),
),
migrations.AlterField(
model_name="knowledgebase",
name="responsible_teams",
field=models.ManyToManyField(
blank=True,
help_text="Team(s) whom is considered the articles owner.",
related_name="responsible_teams",
to="access.team",
verbose_name="Responsible Team(s)",
),
),
migrations.AlterField(
model_name="knowledgebase",
name="responsible_user",
field=models.ForeignKey(
blank=True,
help_text="User(s) whom is considered the articles owner.",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="responsible_user",
to=settings.AUTH_USER_MODEL,
verbose_name="Responsible User",
),
),
migrations.AlterField(
model_name="knowledgebase",
name="summary",
field=models.TextField(
blank=True,
help_text="Short Summary of the article",
null=True,
verbose_name="Summary",
),
),
migrations.AlterField(
model_name="knowledgebase",
name="target_team",
field=models.ManyToManyField(
blank=True,
help_text="Team(s) to grant access to the article",
to="access.team",
verbose_name="Target Team(s)",
),
),
migrations.AlterField(
model_name="knowledgebase",
name="target_user",
field=models.ForeignKey(
blank=True,
help_text="User(s) to grant access to the article",
null=True,
on_delete=django.db.models.deletion.PROTECT,
to=settings.AUTH_USER_MODEL,
verbose_name="Target Users(s)",
),
),
migrations.AlterField(
model_name="knowledgebasecategory",
name="model_notes",
@ -70,5 +181,181 @@ class Migration(migrations.Migration):
],
verbose_name="Tenant",
),
)
),
migrations.AlterField(
model_name="knowledgebasecategory",
name="parent_category",
field=models.ForeignKey(
blank=True,
help_text="Category this category belongs to",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="assistance.knowledgebasecategory",
verbose_name="Parent Category",
),
),
migrations.AlterField(
model_name="knowledgebasecategory",
name="target_team",
field=models.ManyToManyField(
blank=True,
help_text="Team(s) to grant access to the article",
to="access.team",
verbose_name="Target Team(s)",
),
),
migrations.AlterField(
model_name="knowledgebasecategory",
name="target_user",
field=models.ForeignKey(
blank=True,
help_text="User(s) to grant access to the article",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to=settings.AUTH_USER_MODEL,
verbose_name="Target Users(s)",
),
),
migrations.AlterField(
model_name="modelknowledgebasearticle",
name="model",
field=models.CharField(
choices=assistance.models.model_knowledge_base_article.all_models,
help_text="Model type to link to article article",
max_length=80,
verbose_name="Model Type",
),
),
migrations.CreateModel(
name="KnowledgeBaseAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="assistance.knowledgebase",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Knowledge Base History",
"verbose_name_plural": "Knowledge Base Histories",
"db_table": "assistance_knowledgebase_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="KnowledgeBaseCategoryAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="assistance.knowledgebasecategory",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Knowledge Base Category History",
"verbose_name_plural": "Knowledge Base Category Histories",
"db_table": "assistance_knowledgebasecategory_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="KnowledgeBaseCategoryCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="assistance.knowledgebasecategory",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Knowledge Base Category Note",
"verbose_name_plural": "Knowledge Base Category Notes",
"db_table": "assistance_knowledgebasecategory_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
migrations.CreateModel(
name="KnowledgeBaseCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="assistance.knowledgebase",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Knowledge Base Note",
"verbose_name_plural": "Knowledge Base Notes",
"db_table": "assistance_knowledgebase_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
]

View File

@ -1,81 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-03 22:06
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("assistance", "0007_remove_knowledgebase_is_global_and_more"),
("core", "0027_centurionmodelnote"),
]
operations = [
migrations.CreateModel(
name="KnowledgeBaseAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="assistance.knowledgebase",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Knowledge Base History",
"verbose_name_plural": "Knowledge Base Histories",
"db_table": "assistance_knowledgebase_audithistory",
"managed": True,
},
bases=("core.centurionaudit", models.Model),
),
migrations.CreateModel(
name="KnowledgeBaseCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="assistance.knowledgebase",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Knowledge Base Note",
"verbose_name_plural": "Knowledge Base Notes",
"db_table": "assistance_knowledgebase_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote", models.Model),
),
]

View File

@ -1,81 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-03 22:07
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("assistance", "0008_knowledgebaseaudithistory_and_more"),
("core", "0027_centurionmodelnote"),
]
operations = [
migrations.CreateModel(
name="KnowledgeBaseCategoryAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="assistance.knowledgebasecategory",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Knowledge Base Category History",
"verbose_name_plural": "Knowledge Base Category Histories",
"db_table": "assistance_knowledgebasecategory_audithistory",
"managed": True,
},
bases=("core.centurionaudit", models.Model),
),
migrations.CreateModel(
name="KnowledgeBaseCategoryCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="assistance.knowledgebasecategory",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Knowledge Base Category Note",
"verbose_name_plural": "Knowledge Base Category Notes",
"db_table": "assistance_knowledgebasecategory_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote", models.Model),
),
]

View File

@ -1,114 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-03 23:40
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0010_company_alter_entity_entity_type_alter_person_dob_and_more"),
("assistance", "0009_knowledgebasecategoryaudithistory_and_more"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterField(
model_name="knowledgebase",
name="category",
field=models.ForeignKey(
help_text="Article Category",
max_length=50,
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="assistance.knowledgebasecategory",
verbose_name="Category",
),
),
migrations.AlterField(
model_name="knowledgebase",
name="content",
field=models.TextField(
blank=True,
help_text="Content of the article. Markdown is supported",
null=True,
verbose_name="Article Content",
),
),
migrations.AlterField(
model_name="knowledgebase",
name="expiry_date",
field=models.DateTimeField(
blank=True,
help_text="Date the article will be removed from published articles",
null=True,
verbose_name="End Date",
),
),
migrations.AlterField(
model_name="knowledgebase",
name="release_date",
field=models.DateTimeField(
blank=True,
help_text="Date the article will be published",
null=True,
verbose_name="Publish Date",
),
),
migrations.AlterField(
model_name="knowledgebase",
name="responsible_teams",
field=models.ManyToManyField(
blank=True,
help_text="Team(s) whom is considered the articles owner.",
related_name="responsible_teams",
to="access.team",
verbose_name="Responsible Team(s)",
),
),
migrations.AlterField(
model_name="knowledgebase",
name="responsible_user",
field=models.ForeignKey(
help_text="User(s) whom is considered the articles owner.",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="responsible_user",
to=settings.AUTH_USER_MODEL,
verbose_name="Responsible User",
),
),
migrations.AlterField(
model_name="knowledgebase",
name="summary",
field=models.TextField(
blank=True,
help_text="Short Summary of the article",
null=True,
verbose_name="Summary",
),
),
migrations.AlterField(
model_name="knowledgebase",
name="target_team",
field=models.ManyToManyField(
blank=True,
help_text="Team(s) to grant access to the article",
to="access.team",
verbose_name="Target Team(s)",
),
),
migrations.AlterField(
model_name="knowledgebase",
name="target_user",
field=models.ForeignKey(
blank=True,
help_text="User(s) to grant access to the article",
null=True,
on_delete=django.db.models.deletion.PROTECT,
to=settings.AUTH_USER_MODEL,
verbose_name="Target Users(s)",
),
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-03 23:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("assistance", "0010_alter_knowledgebase_category_and_more"),
]
operations = [
migrations.AddField(
model_name="knowledgebase",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
]

View File

@ -1,51 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-04 00:23
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0010_company_alter_entity_entity_type_alter_person_dob_and_more"),
("assistance", "0011_knowledgebase_model_notes"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterField(
model_name="knowledgebasecategory",
name="parent_category",
field=models.ForeignKey(
blank=True,
help_text="Category this category belongs to",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="assistance.knowledgebasecategory",
verbose_name="Parent Category",
),
),
migrations.AlterField(
model_name="knowledgebasecategory",
name="target_team",
field=models.ManyToManyField(
blank=True,
help_text="Team(s) to grant access to the article",
to="access.team",
verbose_name="Target Team(s)",
),
),
migrations.AlterField(
model_name="knowledgebasecategory",
name="target_user",
field=models.ForeignKey(
blank=True,
help_text="User(s) to grant access to the article",
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to=settings.AUTH_USER_MODEL,
verbose_name="Target Users(s)",
),
),
]

View File

@ -1,24 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-06 02:40
import assistance.models.model_knowledge_base_article
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("assistance", "0012_alter_knowledgebasecategory_parent_category_and_more"),
]
operations = [
migrations.AlterField(
model_name="modelknowledgebasearticle",
name="model",
field=models.CharField(
choices=assistance.models.model_knowledge_base_article.all_models,
help_text="Model type to link to article article",
max_length=80,
verbose_name="Model Type",
),
),
]

View File

@ -1,29 +0,0 @@
# Generated by Django 5.1.10 on 2025-07-29 04:13
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("assistance", "0013_alter_modelknowledgebasearticle_model"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterField(
model_name="knowledgebase",
name="responsible_user",
field=models.ForeignKey(
blank=True,
help_text="User(s) whom is considered the articles owner.",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="responsible_user",
to=settings.AUTH_USER_MODEL,
verbose_name="Responsible User",
),
),
]

View File

@ -19,16 +19,16 @@ router.register(
basename = '_api_v2_assistance_home'
)
router.register(
prefix = 'knowledge_base', viewset = knowledge_base_v2.ViewSet,
prefix = '/knowledge_base', viewset = knowledge_base_v2.ViewSet,
basename = '_api_knowledgebase'
)
router.register(
prefix = '(?P<model>.+)/(?P<model_pk>[0-9]+)/knowledge_base',
prefix = '/(?P<model>.+)/(?P<model_pk>[0-9]+)/knowledge_base',
viewset = model_knowledge_base_article.ViewSet,
basename = '_api_v2_model_kb'
)
router.register(
prefix = 'ticket/request', viewset = request_ticket_v2.ViewSet,
prefix = '/ticket/request', viewset = request_ticket_v2.ViewSet,
basename = '_api_v2_ticket_request'
)

View File

@ -20,6 +20,7 @@ import django.db.models.options as options
options.DEFAULT_NAMES = (*options.DEFAULT_NAMES, 'sub_model_type', 'itam_sub_model_type')
APPEND_SLASH = False
AUTH_USER_MODEL = 'auth.User'
# Build paths inside the project like this: BASE_DIR / 'subdir'.

View File

@ -0,0 +1,182 @@
import pytest
import re
import requests
from django.urls import get_resolver, URLPattern, URLResolver
def list_urls(urlpatterns, parent_pattern=''):
urls = []
for entry in urlpatterns:
if isinstance(entry, URLPattern):
urls.append(parent_pattern + str(entry.pattern))
elif isinstance(entry, URLResolver):
urls.extend(list_urls(entry.url_patterns, parent_pattern + str(entry.pattern)))
filtered = [
re.sub(r"\^([a-z\-]+)\$$", r"\1", u).rstrip('/') for u in urls if (
re.sub(r"\^([a-z\-]+)\$$", r"\1", u).startswith('api/')
and '(' not in re.sub(r"\^([a-z\-]+)\$$", r"\1", u).rstrip('/')
and '<' not in re.sub(r"\^([a-z\-]+)\$$", r"\1", u).rstrip('/')
and '$' not in re.sub(r"\^([a-z\-]+)\$$", r"\1", u).rstrip('/')
)
]
return filtered
no_auth_urls = [
'api/v2/auth/login',
'api/v2/docs',
'api/v2/schema',
]
urls_list_view_auth_required_excluded = [
'api/v2/auth/logout',
]
urls_list_view_auth_required_authenticated_excluded = [
'api/v2/itam/inventory',
'api/v2/auth/logout',
]
@pytest.mark.integration
@pytest.mark.regression
class URLChecksPyTest:
@pytest.fixture(scope="class")
def auto_login_client(self):
session = requests.Session()
login_page_url = "http://127.0.0.1:8003/api/v2/auth/login"
login_post_url = "http://127.0.0.1:8003/api/v2/auth/login"
resp = session.get(login_page_url)
resp.raise_for_status()
# Extract CSRF token from cookies (Django sets csrftoken cookie)
csrf_token = session.cookies.get("csrftoken")
if not csrf_token:
raise RuntimeError("CSRF token cookie not found")
login_data = {
"username": "admin",
"password": "admin",
"csrfmiddlewaretoken": csrf_token,
}
headers = {
"Referer": login_page_url,
"X-CSRFToken": csrf_token, # Include CSRF token header
}
resp = session.post(login_post_url, data=login_data, headers=headers, allow_redirects=True)
resp.raise_for_status()
class Client:
def __init__(self, session):
self._session = session
self._unauth_session = requests.Session()
resp = self._unauth_session.get(login_page_url)
resp.raise_for_status()
self._headers = csrf_token = {
"Referer": login_page_url,
"X-CSRFToken": self._unauth_session.cookies.get("csrftoken"),
}
def request(self, method, url, auth = False, **kwargs):
if auth:
session = self._session
else:
session = self._unauth_session
return session.request(method, url, headers=self._headers, **kwargs)
@property
def cookies(self):
return self._session.cookies
return Client(session)
list_view_urls = list_urls(urlpatterns = get_resolver().url_patterns)
@pytest.mark.parametrize(
argnames = "url_path",
argvalues = [
url for url in list_view_urls if( url in no_auth_urls )
],
ids = [
re.sub(r'[^\w_\-.:]', '_', url) for url in list_view_urls if( url in no_auth_urls )
],
)
def test_urls_no_auth_required(self, url_path, auto_login_client):
url = f"http://127.0.0.1:8003/{url_path}"
response = auto_login_client.request("GET", url)
assert response.status_code == 200
@pytest.mark.permissions
@pytest.mark.parametrize(
argnames = "url_path",
argvalues = [
url for url in list_view_urls if(
url not in no_auth_urls
and url not in urls_list_view_auth_required_excluded
)
],
ids = [
re.sub(r'[^\w_\-.:]', '_', url) for url in list_view_urls if(
url not in no_auth_urls
and url not in urls_list_view_auth_required_excluded
)
],
)
def test_urls_list_view_auth_required(self, url_path, auto_login_client):
url = f"http://127.0.0.1:8003/{url_path}"
response = auto_login_client.request("GET", url)
assert response.status_code == 401
@pytest.mark.permissions
@pytest.mark.parametrize(
argnames = "url_path",
argvalues = [
url for url in list_view_urls if(
url not in no_auth_urls
and url not in urls_list_view_auth_required_authenticated_excluded
)
],
ids = [
re.sub(r'[^\w_\-.:]', '_', url) for url in list_view_urls if(
url not in no_auth_urls
and url not in urls_list_view_auth_required_authenticated_excluded
)
],
)
def test_urls_list_view_auth_required_authenticated(self, url_path, auto_login_client):
url = f"http://127.0.0.1:8003/{url_path}"
response = auto_login_client.request(method = "GET", url = url, auth = True)
assert response.status_code == 200

View File

@ -4,14 +4,14 @@ from django.contrib.auth import views as auth_views
from django.views.static import serve
from django.urls import include, path, re_path
from rest_framework import urls
urlpatterns = [
path('admin/', admin.site.urls, name='_administration'),
path('admin', admin.site.urls, name='_administration'),
path('account/password_change/', auth_views.PasswordChangeView.as_view(template_name="password_change.html.j2"), name="change_password"),
path('account/password_change', auth_views.PasswordChangeView.as_view(template_name="password_change.html.j2"), name="change_password"),
path("account/", include("django.contrib.auth.urls")),
path("account", include("django.contrib.auth.urls")),
re_path(r'^static/(?P<path>.*)$', serve,{'document_root': settings.STATIC_ROOT}),
@ -30,15 +30,16 @@ if settings.API_ENABLED:
urlpatterns += [
path("api/", include("api.urls", namespace = 'v1')),
path("api", include("api.urls", namespace = 'v1')),
path("api/v2/", include("api.urls_v2", namespace = 'v2')),
path("api/v2", include("api.urls_v2", namespace = 'v2')),
]
urlpatterns += [
path('api/v2/auth/', include('rest_framework.urls')),
path('api/v2/auth/login', auth_views.LoginView.as_view(template_name='rest_framework/login.html'), name='login'),
path('api/v2/auth/logout', auth_views.LogoutView.as_view(), name='logout'),
]

View File

@ -0,0 +1,359 @@
# Generated by Django 5.1.10 on 2025-08-15 03:21
import access.models.tenancy_abstract
import config_management.models.groups
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0011_remove_entitynotes_model_and_more"),
("config_management", "0008_alter_configgrouphosts_organization_and_more"),
("core", "0024_centurionaudit_centurionmodelnote_and_more"),
("itam", "0012_remove_device_is_global_remove_device_slug_and_more"),
]
operations = [
migrations.RemoveField(
model_name="configgrouphosts",
name="is_global",
),
migrations.RemoveField(
model_name="configgroups",
name="is_global",
),
migrations.RemoveField(
model_name="configgroupsoftware",
name="is_global",
),
migrations.AlterField(
model_name="configgrouphosts",
name="group",
field=models.ForeignKey(
help_text="Group that this host is part of",
on_delete=django.db.models.deletion.PROTECT,
to="config_management.configgroups",
verbose_name="Group",
),
),
migrations.AlterField(
model_name="configgrouphosts",
name="host",
field=models.ForeignKey(
help_text="Host that will be apart of this config group",
on_delete=django.db.models.deletion.PROTECT,
to="itam.device",
validators=[
config_management.models.groups.ConfigGroupHosts.validate_host_no_parent_group
],
verbose_name="Host",
),
),
migrations.AlterField(
model_name="configgrouphosts",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="configgrouphosts",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="configgrouphosts",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.AlterField(
model_name="configgroups",
name="config",
field=models.JSONField(
blank=True,
help_text="Configuration for this Group",
null=True,
validators=[
config_management.models.groups.ConfigGroups.validate_config_keys_not_reserved
],
verbose_name="Configuration",
),
),
migrations.AlterField(
model_name="configgroups",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="configgroups",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="configgroups",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.AlterField(
model_name="configgroups",
name="parent",
field=models.ForeignKey(
blank=True,
help_text="Parent of this Group",
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="config_management.configgroups",
verbose_name="Parent Group",
),
),
migrations.AlterField(
model_name="configgroupsoftware",
name="action",
field=models.IntegerField(
blank=True,
choices=[(1, "Install"), (0, "Remove")],
help_text="ACtion to perform with this software",
null=True,
verbose_name="Action",
),
),
migrations.AlterField(
model_name="configgroupsoftware",
name="config_group",
field=models.ForeignKey(
help_text="Config group this softwre will be linked to",
on_delete=django.db.models.deletion.PROTECT,
to="config_management.configgroups",
verbose_name="Config Group",
),
),
migrations.AlterField(
model_name="configgroupsoftware",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="configgroupsoftware",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="configgroupsoftware",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.AlterField(
model_name="configgroupsoftware",
name="software",
field=models.ForeignKey(
help_text="Software to add to this config Group",
on_delete=django.db.models.deletion.PROTECT,
to="itam.software",
verbose_name="Software",
),
),
migrations.AlterField(
model_name="configgroupsoftware",
name="version",
field=models.ForeignKey(
blank=True,
help_text="Software Version for this config group",
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="itam.softwareversion",
verbose_name="Verrsion",
),
),
migrations.CreateModel(
name="ConfigGroupHostsAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="config_management.configgrouphosts",
verbose_name="Model",
),
),
],
options={
"verbose_name": "config group hosts History",
"verbose_name_plural": "config group hosts Histories",
"db_table": "config_management_configgrouphosts_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="ConfigGroupsAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="config_management.configgroups",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Config Group History",
"verbose_name_plural": "Config Group Histories",
"db_table": "config_management_configgroups_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="ConfigGroupsCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="config_management.configgroups",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Config Group Note",
"verbose_name_plural": "Config Group Notes",
"db_table": "config_management_configgroups_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
migrations.CreateModel(
name="ConfigGroupSoftwareAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="config_management.configgroupsoftware",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Config Group Software History",
"verbose_name_plural": "Config Group Software Histories",
"db_table": "config_management_configgroupsoftware_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
]

View File

@ -1,122 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-06 02:40
import access.models.tenancy_abstract
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0012_teamusers_model_notes_alter_teamusers_id_and_more"),
("config_management", "0008_alter_configgrouphosts_organization_and_more"),
("core", "0028_delete_history"),
]
operations = [
migrations.RemoveField(
model_name="configgroups",
name="is_global",
),
migrations.AlterField(
model_name="configgroups",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="configgroups",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="configgroups",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.CreateModel(
name="ConfigGroupsAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="config_management.configgroups",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Config Group History",
"verbose_name_plural": "Config Group Histories",
"db_table": "config_management_configgroups_audithistory",
"managed": True,
},
bases=("core.centurionaudit", models.Model),
),
migrations.CreateModel(
name="ConfigGroupsCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="config_management.configgroups",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Config Group Note",
"verbose_name_plural": "Config Group Notes",
"db_table": "config_management_configgroups_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote", models.Model),
),
]

View File

@ -1,122 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-06 02:42
import access.models.tenancy_abstract
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0012_teamusers_model_notes_alter_teamusers_id_and_more"),
("config_management", "0009_remove_configgroups_is_global_and_more"),
("core", "0028_delete_history"),
]
operations = [
migrations.RemoveField(
model_name="configgrouphosts",
name="is_global",
),
migrations.AlterField(
model_name="configgrouphosts",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="configgrouphosts",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="configgrouphosts",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.CreateModel(
name="ConfigGroupHostsAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="config_management.configgrouphosts",
verbose_name="Model",
),
),
],
options={
"verbose_name": "config group hosts History",
"verbose_name_plural": "config group hosts Histories",
"db_table": "config_management_configgrouphosts_audithistory",
"managed": True,
},
bases=("core.centurionaudit", models.Model),
),
migrations.CreateModel(
name="ConfigGroupHostsCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="config_management.configgrouphosts",
verbose_name="Model",
),
),
],
options={
"verbose_name": "config group hosts Note",
"verbose_name_plural": "config group hosts Notes",
"db_table": "config_management_configgrouphosts_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote", models.Model),
)
]

View File

@ -1,122 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-06 02:43
import access.models.tenancy_abstract
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0012_teamusers_model_notes_alter_teamusers_id_and_more"),
("config_management", "0010_remove_configgroupsoftware_is_global_and_more"),
("core", "0028_delete_history"),
]
operations = [
migrations.RemoveField(
model_name="configgroupsoftware",
name="is_global",
),
migrations.AlterField(
model_name="configgroupsoftware",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="configgroupsoftware",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="configgroupsoftware",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.CreateModel(
name="ConfigGroupSoftwareAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="config_management.configgroupsoftware",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Config Group Software History",
"verbose_name_plural": "Config Group Software Histories",
"db_table": "config_management_configgroupsoftware_audithistory",
"managed": True,
},
bases=("core.centurionaudit", models.Model),
),
migrations.CreateModel(
name="ConfigGroupSoftwareCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="config_management.configgroupsoftware",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Config Group Software Note",
"verbose_name_plural": "Config Group Software Notes",
"db_table": "config_management_configgroupsoftware_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote", models.Model),
),
]

View File

@ -1,40 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-07 03:57
import config_management.models.groups
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("config_management", "0011_remove_configgroupsoftware_is_global_and_more"),
]
operations = [
migrations.AlterField(
model_name="configgroups",
name="config",
field=models.JSONField(
blank=True,
help_text="Configuration for this Group",
null=True,
validators=[
config_management.models.groups.ConfigGroups.validate_config_keys_not_reserved
],
verbose_name="Configuration",
),
),
migrations.AlterField(
model_name="configgroups",
name="parent",
field=models.ForeignKey(
blank=True,
help_text="Parent of this Group",
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="config_management.configgroups",
verbose_name="Parent Group",
),
),
]

View File

@ -1,61 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-07 04:10
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
(
"config_management",
"0012_alter_configgroups_config_alter_configgroups_parent",
),
("itam", "0014_remove_device_is_global_remove_device_slug_and_more"),
]
operations = [
migrations.AlterField(
model_name="configgroupsoftware",
name="action",
field=models.IntegerField(
blank=True,
choices=[(1, "Install"), (0, "Remove")],
help_text="ACtion to perform with this software",
null=True,
verbose_name="Action",
),
),
migrations.AlterField(
model_name="configgroupsoftware",
name="config_group",
field=models.ForeignKey(
help_text="Config group this softwre will be linked to",
on_delete=django.db.models.deletion.PROTECT,
to="config_management.configgroups",
verbose_name="Config Group",
),
),
migrations.AlterField(
model_name="configgroupsoftware",
name="software",
field=models.ForeignKey(
help_text="Software to add to this config Group",
on_delete=django.db.models.deletion.PROTECT,
to="itam.software",
verbose_name="Software",
),
),
migrations.AlterField(
model_name="configgroupsoftware",
name="version",
field=models.ForeignKey(
blank=True,
help_text="Software Version for this config group",
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="itam.softwareversion",
verbose_name="Verrsion",
),
),
]

View File

@ -1,39 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-07 04:11
import config_management.models.groups
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("config_management", "0013_alter_configgroupsoftware_action_and_more"),
("itam", "0014_remove_device_is_global_remove_device_slug_and_more"),
]
operations = [
migrations.AlterField(
model_name="configgrouphosts",
name="group",
field=models.ForeignKey(
help_text="Group that this host is part of",
on_delete=django.db.models.deletion.PROTECT,
to="config_management.configgroups",
verbose_name="Group",
),
),
migrations.AlterField(
model_name="configgrouphosts",
name="host",
field=models.ForeignKey(
help_text="Host that will be apart of this config group",
on_delete=django.db.models.deletion.PROTECT,
to="itam.device",
validators=[
config_management.models.groups.ConfigGroupHosts.validate_host_no_parent_group
],
verbose_name="Host",
),
),
]

View File

@ -1,27 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-07 08:17
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("config_management", "0014_alter_configgrouphosts_group_and_more"),
]
operations = [
migrations.RemoveField(
model_name="configgroupsoftwarecenturionmodelnote",
name="centurionmodelnote_ptr",
),
migrations.RemoveField(
model_name="configgroupsoftwarecenturionmodelnote",
name="model",
),
migrations.DeleteModel(
name="ConfigGroupHostsCenturionModelNote",
),
migrations.DeleteModel(
name="ConfigGroupSoftwareCenturionModelNote",
),
]

View File

@ -18,15 +18,15 @@ router.register(
basename = '_api_v2_config_management_home'
)
router.register(
prefix = 'group', viewset = config_group_v2.ViewSet,
prefix = '/group', viewset = config_group_v2.ViewSet,
basename = '_api_configgroups'
)
router.register(
prefix = 'group/(?P<parent_group>[0-9]+)/child_group', viewset = config_group_v2.ViewSet,
prefix = '/group/(?P<parent_group>[0-9]+)/child_group', viewset = config_group_v2.ViewSet,
basename = '_api_configgroups_child'
)
router.register(
prefix = 'group/(?P<config_group_id>[0-9]+)/software',
prefix = '/group/(?P<config_group_id>[0-9]+)/software',
viewset = config_group_software_v2.ViewSet,
basename = '_api_configgroupsoftware'
)

View File

@ -1,42 +0,0 @@
# Generated by Django 5.1.9 on 2025-05-20 16:20
import access.fields
import access.models.tenancy_abstract
import core.models.centurion
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0010_company_alter_entity_entity_type_alter_person_dob_and_more'),
('contenttypes', '0002_remove_content_type_name'),
('core', '0023_ticketcommentaction_alter_manufacturer_organization_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='CenturionAudit',
fields=[
('id', models.AutoField(help_text='ID of the item', primary_key=True, serialize=False, unique=True, verbose_name='ID')),
('model_notes', models.TextField(blank=True, help_text='Tid bits of information', null=True, verbose_name='Notes')),
('created', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, help_text='Date and time of creation', verbose_name='Created')),
('before', models.JSONField(blank=True, default=None, help_text='Value before Change', null=True, validators=[core.models.centurion.CenturionModel.validate_field_not_none], verbose_name='Before')),
('after', models.JSONField(blank=True, default=None, help_text='Value Change to', null=True, validators=[core.models.centurion.CenturionModel.validate_field_not_none], verbose_name='After')),
('action', models.IntegerField(choices=[(1, 'Create'), (2, 'Update'), (3, 'Delete')], default=None, help_text='History action performed', null=True, validators=[core.models.centurion.CenturionModel.validate_field_not_none], verbose_name='Action')),
('content_type', models.ForeignKey(blank=True, help_text='Model this history is for', on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype', validators=[core.models.centurion.CenturionModel.validate_field_not_none], verbose_name='Content Model')),
('organization', models.ForeignKey(help_text='Tenant this belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='+', to='access.tenant', validators=[access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists], verbose_name='Tenant')),
('user', models.ForeignKey(help_text='User whom performed the action', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL, validators=[core.models.centurion.CenturionModel.validate_field_not_none], verbose_name='User')),
],
options={
'verbose_name': 'Model History',
'verbose_name_plural': 'Model Histories',
'db_table': 'core_audithistory',
'ordering': ['-created'],
},
),
]

View File

@ -0,0 +1,662 @@
# Generated by Django 5.1.10 on 2025-08-15 03:21
import access.fields
import access.models.tenancy_abstract
import core.models.centurion
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0010_company_alter_entity_entity_type_alter_person_dob_and_more"),
("assistance", "0006_alter_knowledgebase_organization_and_more"),
("contenttypes", "0002_remove_content_type_name"),
("core", "0023_ticketcommentaction_alter_manufacturer_organization_and_more"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="CenturionAudit",
fields=[
(
"id",
models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
(
"created",
access.fields.AutoCreatedField(
default=django.utils.timezone.now,
editable=False,
help_text="Date and time of creation",
verbose_name="Created",
),
),
(
"before",
models.JSONField(
blank=True,
help_text="Value before Change",
null=True,
validators=[
core.models.centurion.CenturionModel.validate_field_not_none
],
verbose_name="Before",
),
),
(
"after",
models.JSONField(
blank=True,
help_text="Value Change to",
null=True,
validators=[
core.models.centurion.CenturionModel.validate_field_not_none
],
verbose_name="After",
),
),
(
"action",
models.IntegerField(
choices=[(1, "Create"), (2, "Update"), (3, "Delete")],
help_text="History action performed",
null=True,
validators=[
core.models.centurion.CenturionModel.validate_field_not_none
],
verbose_name="Action",
),
),
(
"content_type",
models.ForeignKey(
blank=True,
help_text="Model this history is for",
on_delete=django.db.models.deletion.CASCADE,
to="contenttypes.contenttype",
validators=[
core.models.centurion.CenturionModel.validate_field_not_none
],
verbose_name="Content Model",
),
),
(
"organization",
models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
(
"user",
models.ForeignKey(
help_text="User whom performed the action",
on_delete=django.db.models.deletion.DO_NOTHING,
to=settings.AUTH_USER_MODEL,
validators=[
core.models.centurion.CenturionModel.validate_field_not_none
],
verbose_name="User",
),
),
],
options={
"verbose_name": "Model History",
"verbose_name_plural": "Model Histories",
"db_table": "core_audithistory",
"ordering": ["-created"],
},
),
migrations.CreateModel(
name="CenturionModelNote",
fields=[
(
"id",
models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
(
"created",
access.fields.AutoCreatedField(
default=django.utils.timezone.now,
editable=False,
help_text="Date and time of creation",
verbose_name="Created",
),
),
(
"body",
models.TextField(
help_text="The tid bit of information you wish to add",
verbose_name="Note",
),
),
(
"modified",
access.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
editable=False,
help_text="Date and time of last modification",
verbose_name="Modified",
),
),
(
"content_type",
models.ForeignKey(
blank=True,
help_text="Model this note is for",
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="contenttypes.contenttype",
verbose_name="Content Model",
),
),
(
"created_by",
models.ForeignKey(
blank=True,
help_text="User whom added the Note",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="+",
to=settings.AUTH_USER_MODEL,
verbose_name="Created By",
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
help_text="User whom modified the note",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="+",
to=settings.AUTH_USER_MODEL,
verbose_name="Edited By",
),
),
(
"organization",
models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
],
options={
"verbose_name": "Centurion Model Note",
"verbose_name_plural": "Centurion Model Notes",
"ordering": ["-created"],
},
),
migrations.RemoveField(
model_name="manufacturer",
name="is_global",
),
migrations.RemoveField(
model_name="manufacturer",
name="slug",
),
migrations.RemoveField(
model_name="ticketcategory",
name="is_global",
),
migrations.RemoveField(
model_name="ticketcommentcategory",
name="is_global",
),
migrations.AlterField(
model_name="manufacturer",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="manufacturer",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.AlterField(
model_name="ticketbase",
name="external_ref",
field=models.IntegerField(
blank=True,
help_text="External System reference",
null=True,
verbose_name="Reference Number",
),
),
migrations.AlterField(
model_name="ticketbase",
name="external_system",
field=models.IntegerField(
blank=True,
choices=[
(1, "Github"),
(2, "Gitlab"),
(9999, "Custom #1 (Imported)"),
(9998, "Custom #2 (Imported)"),
(9997, "Custom #3 (Imported)"),
(9996, "Custom #4 (Imported)"),
(9995, "Custom #5 (Imported)"),
(9994, "Custom #6 (Imported)"),
(9993, "Custom #7 (Imported)"),
(9992, "Custom #8 (Imported)"),
(9991, "Custom #9 (Imported)"),
],
help_text="External system this item derives",
null=True,
verbose_name="External System",
),
),
migrations.AlterField(
model_name="ticketbase",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="ticketbase",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.AlterField(
model_name="ticketcategory",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="ticketcategory",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="ticketcategory",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.AlterField(
model_name="ticketcategory",
name="parent",
field=models.ForeignKey(
blank=True,
help_text="The Parent Category",
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="core.ticketcategory",
verbose_name="Parent Category",
),
),
migrations.AlterField(
model_name="ticketcategory",
name="runbook",
field=models.ForeignKey(
blank=True,
help_text="The runbook for this category",
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="assistance.knowledgebase",
verbose_name="Runbook",
),
),
migrations.AlterField(
model_name="ticketcommentbase",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="ticketcommentbase",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.AlterField(
model_name="ticketcommentcategory",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="ticketcommentcategory",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="ticketcommentcategory",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.AlterField(
model_name="ticketcommentcategory",
name="parent",
field=models.ForeignKey(
blank=True,
help_text="The Parent Category",
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="core.ticketcommentcategory",
verbose_name="Parent Category",
),
),
migrations.AlterField(
model_name="ticketcommentcategory",
name="runbook",
field=models.ForeignKey(
blank=True,
help_text="The runbook for this category",
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="assistance.knowledgebase",
verbose_name="Runbook",
),
),
migrations.CreateModel(
name="ManufacturerAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="core.manufacturer",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Manufacturer History",
"verbose_name_plural": "Manufacturer Histories",
"db_table": "core_manufacturer_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="TicketCategoryAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="core.ticketcategory",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Ticket Category History",
"verbose_name_plural": "Ticket Category Histories",
"db_table": "core_ticketcategory_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="TicketCommentCategoryAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="core.ticketcommentcategory",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Ticket Comment Category History",
"verbose_name_plural": "Ticket Comment Category Histories",
"db_table": "core_ticketcommentcategory_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="ManufacturerCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="core.manufacturer",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Manufacturer Note",
"verbose_name_plural": "Manufacturer Notes",
"db_table": "core_manufacturer_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
migrations.CreateModel(
name="TicketCategoryCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="core.ticketcategory",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Ticket Category Note",
"verbose_name_plural": "Ticket Category Notes",
"db_table": "core_ticketcategory_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
migrations.CreateModel(
name="TicketCommentCategoryCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="core.ticketcommentcategory",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Ticket Comment Category Note",
"verbose_name_plural": "Ticket Comment Category Notes",
"db_table": "core_ticketcommentcategory_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
migrations.DeleteModel(
name="History",
),
]

View File

@ -1,38 +0,0 @@
# Generated by Django 5.1.9 on 2025-05-24 17:20
import core.models.centurion
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0024_centurionaudit'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterField(
model_name='centurionaudit',
name='action',
field=models.IntegerField(choices=[(1, 'Create'), (2, 'Update'), (3, 'Delete')], help_text='History action performed', null=True, validators=[core.models.centurion.CenturionModel.validate_field_not_none], verbose_name='Action'),
),
migrations.AlterField(
model_name='centurionaudit',
name='after',
field=models.JSONField(blank=True, help_text='Value Change to', null=True, validators=[core.models.centurion.CenturionModel.validate_field_not_none], verbose_name='After'),
),
migrations.AlterField(
model_name='centurionaudit',
name='before',
field=models.JSONField(blank=True, help_text='Value before Change', null=True, validators=[core.models.centurion.CenturionModel.validate_field_not_none], verbose_name='Before'),
),
migrations.AlterField(
model_name='centurionaudit',
name='user',
field=models.ForeignKey(default=None, help_text='User whom performed the action', on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL, validators=[core.models.centurion.CenturionModel.validate_field_not_none], verbose_name='User'),
preserve_default=False,
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 5.1.9 on 2025-05-24 22:39
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('core', '0025_alter_centurionaudit_action_and_more'),
]
operations = [
migrations.RemoveField(
model_name='centurionaudit',
name='model_notes',
),
]

View File

@ -1,112 +0,0 @@
# Generated by Django 5.1.9 on 2025-05-29 04:03
import access.fields
import access.models.tenancy_abstract
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0010_company_alter_entity_entity_type_alter_person_dob_and_more"),
("contenttypes", "0002_remove_content_type_name"),
("core", "0026_remove_centurionaudit_model_notes"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="CenturionModelNote",
fields=[
(
"id",
models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
(
"created",
access.fields.AutoCreatedField(
default=django.utils.timezone.now,
editable=False,
help_text="Date and time of creation",
verbose_name="Created",
),
),
(
"body",
models.TextField(
help_text="The tid bit of information you wish to add",
verbose_name="Note",
),
),
(
"modified",
access.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
editable=False,
help_text="Date and time of last modification",
verbose_name="Modified",
),
),
(
"content_type",
models.ForeignKey(
blank=True,
help_text="Model this note is for",
on_delete=django.db.models.deletion.CASCADE,
to="contenttypes.contenttype",
verbose_name="Content Model",
),
),
(
"created_by",
models.ForeignKey(
blank=True,
help_text="User whom added the Note",
on_delete=django.db.models.deletion.PROTECT,
related_name="+",
to=settings.AUTH_USER_MODEL,
verbose_name="Created By",
),
),
(
"modified_by",
models.ForeignKey(
blank=True,
help_text="User whom modified the note",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="+",
to=settings.AUTH_USER_MODEL,
verbose_name="Edited By",
),
),
(
"organization",
models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
],
options={
"verbose_name": "Centurion Model Note",
"verbose_name_plural": "Centurion Model Notes",
"ordering": ["-created"],
},
),
]

View File

@ -1,16 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-05 06:24
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("core", "0027_centurionmodelnote"),
]
operations = [
migrations.DeleteModel(
name="History",
),
]

View File

@ -1,114 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-06 02:55
import access.models.tenancy_abstract
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0012_teamusers_model_notes_alter_teamusers_id_and_more"),
("core", "0028_delete_history"),
]
operations = [
migrations.RemoveField(
model_name="manufacturer",
name="is_global",
),
migrations.RemoveField(
model_name="manufacturer",
name="slug",
),
migrations.AlterField(
model_name="manufacturer",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="manufacturer",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.CreateModel(
name="ManufacturerAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="core.manufacturer",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Manufacturer History",
"verbose_name_plural": "Manufacturer Histories",
"db_table": "core_manufacturer_audithistory",
"managed": True,
},
bases=("core.centurionaudit", models.Model),
),
migrations.CreateModel(
name="ManufacturerCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="core.manufacturer",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Manufacturer Note",
"verbose_name_plural": "Manufacturer Notes",
"db_table": "core_manufacturer_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote", models.Model),
),
]

View File

@ -1,121 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-06 03:05
import access.models.tenancy_abstract
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0012_teamusers_model_notes_alter_teamusers_id_and_more"),
("core", "0029_remove_manufacturer_is_global_and_more"),
]
operations = [
migrations.RemoveField(
model_name="ticketcommentcategory",
name="is_global",
),
migrations.AlterField(
model_name="ticketcommentcategory",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="ticketcommentcategory",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="ticketcommentcategory",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.CreateModel(
name="TicketCommentCategoryAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="core.ticketcommentcategory",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Ticket Comment Category History",
"verbose_name_plural": "Ticket Comment Category Histories",
"db_table": "core_ticketcommentcategory_audithistory",
"managed": True,
},
bases=("core.centurionaudit", models.Model),
),
migrations.CreateModel(
name="TicketCommentCategoryCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="core.ticketcommentcategory",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Ticket Comment Category Note",
"verbose_name_plural": "Ticket Comment Category Notes",
"db_table": "core_ticketcommentcategory_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote", models.Model),
),
]

View File

@ -1,121 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-06 03:08
import access.models.tenancy_abstract
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0012_teamusers_model_notes_alter_teamusers_id_and_more"),
("core", "0030_remove_ticketcommentcategory_is_global_and_more"),
]
operations = [
migrations.RemoveField(
model_name="ticketcategory",
name="is_global",
),
migrations.AlterField(
model_name="ticketcategory",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="ticketcategory",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="ticketcategory",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.CreateModel(
name="TicketCategoryAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="core.ticketcategory",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Ticket Category History",
"verbose_name_plural": "Ticket Category Histories",
"db_table": "core_ticketcategory_audithistory",
"managed": True,
},
bases=("core.centurionaudit", models.Model),
),
migrations.CreateModel(
name="TicketCategoryCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="core.ticketcategory",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Ticket Category Note",
"verbose_name_plural": "Ticket Category Notes",
"db_table": "core_ticketcategory_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote", models.Model),
),
]

View File

@ -1,39 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-09 00:20
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("assistance", "0013_alter_modelknowledgebasearticle_model"),
("core", "0031_remove_ticketcategory_is_global_and_more"),
]
operations = [
migrations.AlterField(
model_name="ticketcategory",
name="parent",
field=models.ForeignKey(
blank=True,
help_text="The Parent Category",
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="core.ticketcategory",
verbose_name="Parent Category",
),
),
migrations.AlterField(
model_name="ticketcategory",
name="runbook",
field=models.ForeignKey(
blank=True,
help_text="The runbook for this category",
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="assistance.knowledgebase",
verbose_name="Runbook",
),
),
]

View File

@ -1,39 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-09 00:26
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("assistance", "0013_alter_modelknowledgebasearticle_model"),
("core", "0032_alter_ticketcategory_parent_and_more"),
]
operations = [
migrations.AlterField(
model_name="ticketcommentcategory",
name="parent",
field=models.ForeignKey(
blank=True,
help_text="The Parent Category",
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="core.ticketcommentcategory",
verbose_name="Parent Category",
),
),
migrations.AlterField(
model_name="ticketcommentcategory",
name="runbook",
field=models.ForeignKey(
blank=True,
help_text="The runbook for this category",
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="assistance.knowledgebase",
verbose_name="Runbook",
),
),
]

View File

@ -1,41 +0,0 @@
# Generated by Django 5.1.10 on 2025-07-13 13:22
import access.models.tenancy_abstract
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0021_roleaudithistory_rolecenturionmodelnote"),
("core", "0033_alter_ticketcommentcategory_parent_and_more"),
]
operations = [
migrations.AlterField(
model_name="ticketbase",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="ticketbase",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
]

View File

@ -1,41 +0,0 @@
# Generated by Django 5.1.10 on 2025-07-13 13:28
import access.models.tenancy_abstract
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0021_roleaudithistory_rolecenturionmodelnote"),
("core", "0034_alter_ticketbase_id_alter_ticketbase_organization"),
]
operations = [
migrations.AlterField(
model_name="ticketcommentbase",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="ticketcommentbase",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
]

View File

@ -1,46 +0,0 @@
# Generated by Django 5.1.10 on 2025-07-14 16:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0035_alter_ticketcommentbase_id_and_more"),
]
operations = [
migrations.AlterField(
model_name="ticketbase",
name="external_ref",
field=models.IntegerField(
blank=True,
help_text="External System reference",
null=True,
verbose_name="Reference Number",
),
),
migrations.AlterField(
model_name="ticketbase",
name="external_system",
field=models.IntegerField(
blank=True,
choices=[
(1, "Github"),
(2, "Gitlab"),
(9999, "Custom #1 (Imported)"),
(9998, "Custom #2 (Imported)"),
(9997, "Custom #3 (Imported)"),
(9996, "Custom #4 (Imported)"),
(9995, "Custom #5 (Imported)"),
(9994, "Custom #6 (Imported)"),
(9993, "Custom #7 (Imported)"),
(9992, "Custom #8 (Imported)"),
(9991, "Custom #9 (Imported)"),
],
help_text="External system this item derives",
null=True,
verbose_name="External System",
),
),
]

View File

@ -1,42 +0,0 @@
# Generated by Django 5.1.10 on 2025-07-23 00:04
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("contenttypes", "0002_remove_content_type_name"),
("core", "0036_alter_ticketbase_external_ref_and_more"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterField(
model_name="centurionmodelnote",
name="content_type",
field=models.ForeignKey(
blank=True,
help_text="Model this note is for",
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="contenttypes.contenttype",
verbose_name="Content Model",
),
),
migrations.AlterField(
model_name="centurionmodelnote",
name="created_by",
field=models.ForeignKey(
blank=True,
help_text="User whom added the Note",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="+",
to=settings.AUTH_USER_MODEL,
verbose_name="Created By",
),
),
]

View File

@ -94,6 +94,12 @@ class ModelSerializer(
]
def validate(self, attrs):
attrs['created_by'] = self._context['request'].user
return super().validate(attrs)
def is_valid(self, *, raise_exception=False) -> bool:

View File

@ -1,4 +1,6 @@
from django.apps import apps
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.db.models.signals import (
post_migrate,
)
@ -14,6 +16,31 @@ def centurion_model_migrate(sender, **kwargs):
if sender.label != 'core':
return
try:
print('\n\nFetching System User.\n')
user = apps.get_model(settings.AUTH_USER_MODEL).objects.get(
username = 'system'
)
if user.is_active:
print(' System user is set as "Active", disabling.\n')
user.is_active = False
user.save()
except ObjectDoesNotExist:
print(' System user not found, creating.\n')
user = apps.get_model(settings.AUTH_USER_MODEL).objects.create(
username = 'system',
first_name = 'System',
last_name = 'User',
is_active = False,
)
print('\n\nCenturion Model Migration Signal.....\n')
models: list[ dict ] = [
@ -261,7 +288,7 @@ def centurion_model_migrate(sender, **kwargs):
model_name = model.get_history_model_name( model )
)
history = original_history.objects.filter().exclude( user = None )
history = original_history.objects.filter()
print(f' Found {len(history)} history entries to migrate.')
@ -269,18 +296,28 @@ def centurion_model_migrate(sender, **kwargs):
try:
after = {}
if entry.after:
after = entry.after
entry_model = entry.model
if hasattr(entry, 'child_model'):
entry_model = entry.child_model
entry_user = entry.user
if not entry_user:
entry_user = user
migrated_history = audit_history.objects.create(
organization = entry.organization,
content_type = entry.content_type,
model = entry_model,
before = entry.before,
after = entry.after,
after = after,
action = entry.action,
user = entry.user,
user = entry_user,
created = entry.created
)

View File

@ -0,0 +1,218 @@
import pytest
from django.apps import apps
from django.conf import settings
from django.db import models
from django.utils.module_loading import import_string
from core.models.centurion_notes import CenturionModelNote
from api.tests.functional.test_functional_permissions_api import (
APIPermissionsInheritedCases
)
def get_models( excludes: list[ str ] = [] ) -> list[ tuple ]:
"""Fetch models from Centurion Apps
Args:
excludes (list[ str ]): Words that may be in a models name to exclude
Returns:
list[ tuple ]: Centurion ERP Only models
"""
models: list = []
model_apps: list = []
exclude_model_apps = [
'django',
'django_celery_results',
'django_filters',
'drf_spectacular',
'drf_spectacular_sidecar',
'coresheaders',
'corsheaders',
'rest_framework',
'rest_framework_json_api',
'social_django',
]
for app in settings.INSTALLED_APPS:
app = app.split('.')[0]
if app in exclude_model_apps:
continue
model_apps += [ app ]
for model in apps.get_models():
if model._meta.app_label not in model_apps:
continue
skip = False
for exclude in excludes:
if exclude in str(model._meta.model_name):
skip = True
break
if skip:
continue
models += [ model ]
return models
class ModelNotesMetaAPIPermissionsTestCases(
APIPermissionsInheritedCases
):
"""AuditHistory Meta Model Test Cases
This test suite is the base for the dynamic tests that are created
during pytest discover.
"""
@pytest.fixture( scope = 'class' )
def note_model(self, request):
return request.cls.note_model_class
@pytest.fixture( scope = 'class', autouse = True)
def model_kwargs(self, django_db_blocker,
request, note_model, kwargs_centurionmodelnotemeta
):
model_kwargs = kwargs_centurionmodelnotemeta.copy()
with django_db_blocker.unblock():
note_model_kwargs = request.getfixturevalue('kwargs_' + note_model._meta.model_name)
kwargs = {}
many_field = {}
for field, value in note_model_kwargs.items():
if not hasattr(getattr(note_model, field), 'field'):
continue
if isinstance(getattr(note_model, field).field, models.ManyToManyField):
if field in many_field:
many_field[field] += [ value ]
elif isinstance(value, list):
value_list = []
for list_value in value:
value_list += [ list_value ]
value = value_list
else:
many_field.update({
field: [
value
]
})
continue
kwargs.update({
field: value
})
model = note_model.objects.create(
**kwargs
)
for field, values in many_field.items():
for value in values:
getattr(model, field).add( value )
model_kwargs.update({
'model': model
})
request.cls.kwargs_create_item = model_kwargs
yield model_kwargs
with django_db_blocker.unblock():
model.delete()
@pytest.fixture( scope = 'class' )
def model(self, request):
return request.cls.model_class
@pytest.mark.skip( reason = 'ToDo: Figure out how to dynomagic add note_model instance' )
def test_model_creation(self, model, user):
pass
for model in get_models():
if(
not issubclass(model, CenturionModelNote)
or model == CenturionModelNote
):
continue
cls_name: str = f"{model._meta.object_name}MetaAPIPermissionsPyTest"
inc_classes = (ModelNotesMetaAPIPermissionsTestCases,)
try:
additional_testcases = import_string(
model._meta.app_label + '.tests.functional.additional_meta_model_note_' +
str( model._meta.object_name ).replace('CenturionModelNote', '').lower() + '_permissions_api.AdditionalTestCases'
)
inc_classes = (additional_testcases, *inc_classes)
except Exception as ex:
additional_testcases = None
dynamic_class = type(
cls_name,
inc_classes,
{
'note_model_class': apps.get_model(
app_label = model._meta.app_label,
model_name = str( model._meta.object_name ).replace('CenturionModelNote', '')
),
'model_class': model
}
)
dynamic_class = pytest.mark.__getattr__(
'model_' + str(model._meta.model_name).replace('centurionmodelnote', ''))(dynamic_class)
dynamic_class = pytest.mark.__getattr__('module_' + model._meta.app_label)(dynamic_class)
globals()[cls_name] = dynamic_class

View File

@ -41,59 +41,59 @@ router: DefaultRouter = DefaultRouter(trailing_slash=False)
router.register(
'history', audit_history.NoDocsViewSet,
'/history', audit_history.NoDocsViewSet,
basename = '_api_centurionaudit'
)
router.register(
prefix=f'ticket', viewset = ticket.NoDocsViewSet,
prefix=f'/ticket', viewset = ticket.NoDocsViewSet,
feature_flag = '2025-00006', basename = '_api_ticketbase'
)
router.register(
prefix=f'ticket/(?P<ticket_type>[{ticket_type_names}]+)', viewset = ticket.ViewSet,
prefix=f'/ticket/(?P<ticket_type>[{ticket_type_names}]+)', viewset = ticket.ViewSet,
feature_flag = '2025-00006', basename = '_api_ticketbase_sub'
)
router.register(
prefix = 'ticket/(?P<ticket_id>[0-9]+)/comment', viewset = ticket_comment.NoDocsViewSet,
prefix = '/ticket/(?P<ticket_id>[0-9]+)/comment', viewset = ticket_comment.NoDocsViewSet,
feature_flag = '2025-00006', basename = '_api_ticket_comment_base'
)
router.register(
prefix = 'ticket/(?P<ticket_id>[0-9]+)/comment/(?P<parent_id>[0-9]+)/threads',
prefix = '/ticket/(?P<ticket_id>[0-9]+)/comment/(?P<parent_id>[0-9]+)/threads',
viewset = ticket_comment.ViewSet,
feature_flag = '2025-00006', basename = '_api_ticket_comment_base_thread'
)
router.register(
prefix = 'ticket/(?P<ticket_id>[0-9]+)/comments', viewset = ticket_comment_depreciated.ViewSet,
prefix = '/ticket/(?P<ticket_id>[0-9]+)/comments', viewset = ticket_comment_depreciated.ViewSet,
basename = '_api_v2_ticket_comment'
)
router.register(
prefix = 'ticket/(?P<ticket_id>[0-9]+)/comments/(?P<parent_id>[0-9]+)/threads',
prefix = '/ticket/(?P<ticket_id>[0-9]+)/comments/(?P<parent_id>[0-9]+)/threads',
viewset = ticket_comment_depreciated.ViewSet,
basename = '_api_v2_ticket_comment_threads'
)
router.register(
prefix = 'ticket/(?P<ticket_id>[0-9]+)/linked_item', viewset = ticket_linked_item.ViewSet,
prefix = '/ticket/(?P<ticket_id>[0-9]+)/linked_item', viewset = ticket_linked_item.ViewSet,
basename = '_api_v2_ticket_linked_item'
)
router.register(
prefix = 'ticket/(?P<ticket_id>[0-9]+)/related_ticket', viewset = related_ticket.ViewSet,
prefix = '/ticket/(?P<ticket_id>[0-9]+)/related_ticket', viewset = related_ticket.ViewSet,
basename = '_api_v2_ticket_related'
)
router.register(
prefix=f'ticket/(?P<ticket_id>[0-9]+)/(?P<ticket_comment_model>[{ticket_comment_names}]+)',
prefix=f'/ticket/(?P<ticket_id>[0-9]+)/(?P<ticket_comment_model>[{ticket_comment_names}]+)',
viewset = ticket_comment.ViewSet,
feature_flag = '2025-00006', basename = '_api_ticket_comment_base_sub'
)
router.register(
prefix=f'ticket/(?P<ticket_id>[0-9]+)/(?P<ticket_comment_model>[{ticket_comment_names} \
prefix=f'/ticket/(?P<ticket_id>[0-9]+)/(?P<ticket_comment_model>[{ticket_comment_names} \
]+)/(?P<parent_id>[0-9]+)/threads',
viewset = ticket_comment.ViewSet,
feature_flag = '2025-00006', basename = '_api_ticket_comment_base_sub_thread'
)
router.register(
prefix = '(?P<item_class>[a-z_]+)/(?P<item_id>[0-9]+)/item_ticket',
prefix = '/(?P<item_class>[a-z_]+)/(?P<item_id>[0-9]+)/item_ticket',
viewset = ticket_linked_item.ViewSet,
basename = '_api_v2_item_tickets'
)

View File

@ -0,0 +1,526 @@
# Generated by Django 5.1.10 on 2025-08-15 03:21
import access.models.tenancy_abstract
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0011_remove_entitynotes_model_and_more"),
("core", "0024_centurionaudit_centurionmodelnote_and_more"),
("devops", "0012_alter_checkin_organization_and_more"),
]
operations = [
migrations.AlterField(
model_name="checkin",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="checkin",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.AlterField(
model_name="featureflag",
name="description",
field=models.TextField(
blank=True,
help_text="Description of this feature",
max_length=300,
null=True,
verbose_name="Description",
),
),
migrations.AlterField(
model_name="featureflag",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="featureflag",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="featureflag",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.AlterField(
model_name="gitgroup",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="gitgroup",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="gitgroup",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.AlterField(
model_name="gitgroup",
name="parent_group",
field=models.ForeignKey(
blank=True,
help_text="Parent Git Group this repository belongs to.",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="+",
to="devops.gitgroup",
verbose_name="Parent Group",
),
),
migrations.AlterField(
model_name="gitrepository",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="gitrepository",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="gitrepository",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.AlterField(
model_name="softwareenablefeatureflag",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="softwareenablefeatureflag",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.CreateModel(
name="FeatureFlagAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="devops.featureflag",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Feature Flag History",
"verbose_name_plural": "Feature Flag Histories",
"db_table": "devops_featureflag_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="FeatureFlagCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="devops.featureflag",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Feature Flag Note",
"verbose_name_plural": "Feature Flag Notes",
"db_table": "devops_featureflag_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
migrations.CreateModel(
name="GitGroupAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="devops.gitgroup",
verbose_name="Model",
),
),
],
options={
"verbose_name": "GIT Group History",
"verbose_name_plural": "GIT Group Histories",
"db_table": "devops_gitgroup_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="GitGroupCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="devops.gitgroup",
verbose_name="Model",
),
),
],
options={
"verbose_name": "GIT Group Note",
"verbose_name_plural": "GIT Group Notes",
"db_table": "devops_gitgroup_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
migrations.CreateModel(
name="GitHubRepositoryAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="devops.githubrepository",
verbose_name="Model",
),
),
],
options={
"verbose_name": "GitHub Repository History",
"verbose_name_plural": "GitHub Repository Histories",
"db_table": "devops_githubrepository_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="GitHubRepositoryCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="devops.githubrepository",
verbose_name="Model",
),
),
],
options={
"verbose_name": "GitHub Repository Note",
"verbose_name_plural": "GitHub Repository Notes",
"db_table": "devops_githubrepository_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
migrations.CreateModel(
name="GitLabRepositoryAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="devops.gitlabrepository",
verbose_name="Model",
),
),
],
options={
"verbose_name": "GitLab Repository History",
"verbose_name_plural": "GitLab Repository Histories",
"db_table": "devops_gitlabrepository_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="GitLabRepositoryCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="devops.gitlabrepository",
verbose_name="Model",
),
),
],
options={
"verbose_name": "GitLab Repository Note",
"verbose_name_plural": "GitLab Repository Notes",
"db_table": "devops_gitlabrepository_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
migrations.CreateModel(
name="GitRepositoryAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="devops.gitrepository",
verbose_name="Model",
),
),
],
options={
"verbose_name": "GIT Repository History",
"verbose_name_plural": "GIT Repository Histories",
"db_table": "devops_gitrepository_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="GitRepositoryCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="devops.gitrepository",
verbose_name="Model",
),
),
],
options={
"verbose_name": "GIT Repository Note",
"verbose_name_plural": "GIT Repository Notes",
"db_table": "devops_gitrepository_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
]

View File

@ -1,46 +0,0 @@
# Generated by Django 5.1.9 on 2025-05-24 23:48
import access.models.tenancy_abstract
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0010_company_alter_entity_entity_type_alter_person_dob_and_more'),
('core', '0026_remove_centurionaudit_model_notes'),
('devops', '0012_alter_checkin_organization_and_more'),
]
operations = [
migrations.AlterField(
model_name='gitgroup',
name='id',
field=models.AutoField(help_text='ID of the item', primary_key=True, serialize=False, unique=True, verbose_name='ID'),
),
migrations.AlterField(
model_name='gitgroup',
name='model_notes',
field=models.TextField(blank=True, help_text='Tid bits of information', null=True, verbose_name='Notes'),
),
migrations.AlterField(
model_name='gitgroup',
name='organization',
field=models.ForeignKey(help_text='Tenant this belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='+', to='access.tenant', validators=[access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists], verbose_name='Tenant'),
),
migrations.CreateModel(
name='GitGroupAuditHistory',
fields=[
('centurionaudit_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='core.centurionaudit')),
('model', models.ForeignKey(help_text='Model this history belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='audit_history', to='devops.gitgroup', verbose_name='Model')),
],
options={
'verbose_name': 'GIT Group History',
'verbose_name_plural': 'GIT Group Histories',
'db_table': 'devops_gitgroup_audithistory',
'managed': True,
},
bases=('core.centurionaudit', models.Model),
),
]

View File

@ -1,27 +0,0 @@
# Generated by Django 5.1.9 on 2025-05-29 03:48
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("devops", "0013_alter_gitgroup_id_alter_gitgroup_model_notes_and_more"),
]
operations = [
migrations.AlterField(
model_name="gitgroup",
name="parent_group",
field=models.ForeignKey(
blank=True,
help_text="Parent Git Group this repository belongs to.",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="+",
to="devops.gitgroup",
verbose_name="Parent Group",
),
),
]

View File

@ -1,48 +0,0 @@
# Generated by Django 5.1.9 on 2025-05-29 19:49
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0027_centurionmodelnote"),
("devops", "0014_alter_gitgroup_parent_group"),
]
operations = [
migrations.CreateModel(
name="GitGroupCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="devops.gitgroup",
verbose_name="Model",
),
),
],
options={
"verbose_name": "GIT Group Note",
"verbose_name_plural": "GIT Group Notes",
"db_table": "devops_gitgroup_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote", models.Model),
),
]

View File

@ -1,129 +0,0 @@
# Generated by Django 5.1.9 on 2025-05-30 19:28
import access.models.tenancy_abstract
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0010_company_alter_entity_entity_type_alter_person_dob_and_more"),
("core", "0027_centurionmodelnote"),
("devops", "0015_gitgroupcenturionmodelnote"),
]
operations = [
migrations.AlterField(
model_name="featureflag",
name="description",
field=models.TextField(
blank=True,
help_text="Description of this feature",
max_length=300,
null=True,
verbose_name="Description",
),
),
migrations.AlterField(
model_name="featureflag",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="featureflag",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="featureflag",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.CreateModel(
name="FeatureFlagAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="devops.featureflag",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Feature Flag History",
"verbose_name_plural": "Feature Flag Histories",
"db_table": "devops_featureflag_audithistory",
"managed": True,
},
bases=("core.centurionaudit", models.Model),
),
migrations.CreateModel(
name="FeatureFlagCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="devops.featureflag",
verbose_name="Model",
),
),
],
options={
"verbose_name": "Feature Flag Note",
"verbose_name_plural": "Feature Flag Notes",
"db_table": "devops_featureflag_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote", models.Model),
),
]

View File

@ -1,41 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-09 01:01
import access.models.tenancy_abstract
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0016_remove_tenant_slug_alter_tenant_manager_and_more"),
("devops", "0016_alter_featureflag_description_alter_featureflag_id_and_more"),
]
operations = [
migrations.AlterField(
model_name="checkin",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="checkin",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
]

View File

@ -1,118 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-09 07:20
import access.models.tenancy_abstract
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0016_remove_tenant_slug_alter_tenant_manager_and_more"),
("core", "0033_alter_ticketcommentcategory_parent_and_more"),
("devops", "0017_alter_checkin_id_alter_checkin_organization"),
]
operations = [
migrations.AlterField(
model_name="gitrepository",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="gitrepository",
name="model_notes",
field=models.TextField(
blank=True,
help_text="Tid bits of information",
null=True,
verbose_name="Notes",
),
),
migrations.AlterField(
model_name="gitrepository",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
migrations.CreateModel(
name="GitRepositoryAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="audit_history",
to="devops.gitrepository",
verbose_name="Model",
),
),
],
options={
"verbose_name": "GIT Repository History",
"verbose_name_plural": "GIT Repository Histories",
"db_table": "devops_gitrepository_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="GitRepositoryCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="devops.gitrepository",
verbose_name="Model",
),
),
],
options={
"verbose_name": "GIT Repository Note",
"verbose_name_plural": "GIT Repository Notes",
"db_table": "devops_gitrepository_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
]

View File

@ -1,81 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-09 07:21
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0033_alter_ticketcommentcategory_parent_and_more"),
("devops", "0018_gitrepositoryaudithistory_and_more"),
]
operations = [
migrations.CreateModel(
name="GitHubRepositoryAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="devops.githubrepository",
verbose_name="Model",
),
),
],
options={
"verbose_name": "GitHub Repository History",
"verbose_name_plural": "GitHub Repository Histories",
"db_table": "devops_githubrepository_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="GitHubRepositoryCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="devops.githubrepository",
verbose_name="Model",
),
),
],
options={
"verbose_name": "GitHub Repository Note",
"verbose_name_plural": "GitHub Repository Notes",
"db_table": "devops_githubrepository_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
)
]

View File

@ -1,81 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-09 07:25
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0033_alter_ticketcommentcategory_parent_and_more"),
("devops", "0019_githubrepositoryaudithistory_and_more"),
]
operations = [
migrations.CreateModel(
name="GitLabRepositoryAuditHistory",
fields=[
(
"centurionaudit_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionaudit",
),
),
(
"model",
models.ForeignKey(
help_text="Model this history belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="devops.gitlabrepository",
verbose_name="Model",
),
),
],
options={
"verbose_name": "GitLab Repository History",
"verbose_name_plural": "GitLab Repository Histories",
"db_table": "devops_gitlabrepository_audithistory",
"managed": True,
},
bases=("core.centurionaudit",),
),
migrations.CreateModel(
name="GitLabRepositoryCenturionModelNote",
fields=[
(
"centurionmodelnote_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="core.centurionmodelnote",
),
),
(
"model",
models.ForeignKey(
help_text="Model this note belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="devops.gitlabrepository",
verbose_name="Model",
),
),
],
options={
"verbose_name": "GitLab Repository Note",
"verbose_name_plural": "GitLab Repository Notes",
"db_table": "devops_gitlabrepository_centurionmodelnote",
"managed": True,
},
bases=("core.centurionmodelnote",),
),
]

View File

@ -1,41 +0,0 @@
# Generated by Django 5.1.9 on 2025-06-11 05:39
import access.models.tenancy_abstract
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("access", "0016_remove_tenant_slug_alter_tenant_manager_and_more"),
("devops", "0020_gitlabrepositoryaudithistory_and_more"),
]
operations = [
migrations.AlterField(
model_name="softwareenablefeatureflag",
name="id",
field=models.AutoField(
help_text="ID of the item",
primary_key=True,
serialize=False,
unique=True,
verbose_name="ID",
),
),
migrations.AlterField(
model_name="softwareenablefeatureflag",
name="organization",
field=models.ForeignKey(
help_text="Tenant this belongs to",
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="access.tenant",
validators=[
access.models.tenancy_abstract.TenancyAbstractModel.validatate_organization_exists
],
verbose_name="Tenant",
),
),
]

View File

@ -0,0 +1,26 @@
import pytest
@pytest.fixture( scope = 'class')
def model(model_featureflag):
yield model_featureflag
@pytest.fixture( scope = 'class', autouse = True)
def model_kwargs(request, kwargs_featureflag):
request.cls.kwargs_create_item = kwargs_featureflag.copy()
yield kwargs_featureflag.copy()
if hasattr(request.cls, 'kwargs_create_item'):
del request.cls.kwargs_create_item
@pytest.fixture( scope = 'class')
def model_serializer(serializer_featureflag):
yield serializer_featureflag

View File

@ -1,213 +0,0 @@
import pytest
from django.test import TestCase
from rest_framework.exceptions import ValidationError
from access.models.tenant import Tenant as Organization
from devops.models.software_enable_feature_flag import SoftwareEnableFeatureFlag
from devops.serializers.feature_flag import FeatureFlag, ModelSerializer
from itam.models.software import Software
class ValidationAPI(
TestCase,
):
model = FeatureFlag
@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')
software = Software.objects.create(
organization = self.organization,
name = 'soft',
)
SoftwareEnableFeatureFlag.objects.create(
organization = self.organization,
software = software,
enabled = True
)
self.item = self.model.objects.create(
organization = self.organization,
name = 'one',
software = software,
description = 'desc',
model_notes = 'text',
enabled = True
)
self.valid_data = {
'organization': self.organization.id,
'name': 'two',
'software': software.id,
'description': 'a description',
'model_notes': 'dfsdfsd',
'enabled': True
}
self.software_no_feature_flag_enabled = Software.objects.create(
organization = self.organization,
name = 'soft no flagging',
)
def test_serializer_validation_valid_data(self):
"""Serializer Validation Check
Ensure that if creating and no name is provided a validation error occurs
"""
serializer = ModelSerializer(
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 = ModelSerializer(
data = valid_data
)
serializer.is_valid(raise_exception = True)
assert err.value.get_codes()['name'][0] == 'required'
def test_serializer_validation_no_software_exception(self):
"""Serializer Validation Check
Ensure that when creating and field software is not provided, no
validation error occurs
"""
valid_data = self.valid_data.copy()
del valid_data['software']
with pytest.raises(ValidationError) as err:
serializer = ModelSerializer(
data = valid_data
)
serializer.is_valid(raise_exception = True)
assert err.value.get_codes()['software'][0] == 'required'
def test_serializer_validation_feature_flagging_not_enabled(self):
"""Serializer Validation Check
Ensure that when creating and the software doesn't, have feature
flagging enabled an exception is thrown.
"""
valid_data = self.valid_data.copy()
valid_data['software'] = self.software_no_feature_flag_enabled.id
with pytest.raises(ValidationError) as err:
serializer = ModelSerializer(
data = valid_data
)
serializer.is_valid(raise_exception = True)
assert err.value.get_codes()['software'] == 'feature_flagging_disabled'
def test_serializer_validation_feature_flagging_not_enabled_for_organization(self):
"""Serializer Validation Check
Ensure that when creating and the software doesn't, have feature
flagging enabled an exception is thrown.
"""
valid_data = self.valid_data.copy()
valid_data['organization'] = self.diff_organization.id
with pytest.raises(ValidationError) as err:
serializer = ModelSerializer(
data = valid_data
)
serializer.is_valid(raise_exception = True)
assert err.value.get_codes()['organization'] == 'feature_flagging_wrong_organizaiton'
def test_serializer_validation_no_description_ok(self):
"""Serializer Validation Check
Ensure that when creating and field description is not provided, no
validation error occurs
"""
valid_data = self.valid_data.copy()
del valid_data['description']
serializer = ModelSerializer(
data = valid_data
)
assert serializer.is_valid(raise_exception = True)
def test_serializer_validation_no_enabled_ok_default_false(self):
"""Serializer Validation Check
Ensure that when creating and field enabled is not provided, no
validation error occurs and enabled is set to `false`
"""
valid_data = self.valid_data.copy()
del valid_data['enabled']
serializer = ModelSerializer(
data = valid_data
)
assert serializer.is_valid(raise_exception = True)
serializer.save()
assert serializer.instance.enabled is False

View File

@ -0,0 +1,134 @@
import pytest
from django.db import models
from rest_framework.relations import Hyperlink
from api.tests.functional.test_functional_api_fields import (
APIFieldsInheritedCases,
)
@pytest.mark.model_featureflag
class FeatureFlagAPITestCases(
APIFieldsInheritedCases,
):
# @pytest.fixture( scope = 'class')
# def second_model(self, request, django_db_blocker,
# model, model_kwargs
# ):
# item = None
# with django_db_blocker.unblock():
# kwargs_many_to_many = {}
# kwargs = {}
# for key, value in model_kwargs.items():
# field = model._meta.get_field(key)
# if isinstance(field, models.ManyToManyField):
# kwargs_many_to_many.update({
# key: value
# })
# else:
# kwargs.update({
# key: value
# })
# # Switch model fields so all fields can be checked
# kwargs_many_to_many.update({ 'responsible_teams': kwargs_many_to_many['target_team']})
# del kwargs_many_to_many['target_team']
# kwargs.update({ 'target_user': kwargs['responsible_user']})
# del kwargs['responsible_user']
# item_two = model.objects.create(
# **kwargs
# )
# for key, value in kwargs_many_to_many.items():
# field = getattr(item_two, key)
# for entry in value:
# field.add(entry)
# request.cls.item_two = item_two
# yield item_two
# with django_db_blocker.unblock():
# item_two.delete()
# del request.cls.item_two
# @pytest.fixture( scope = 'class', autouse = True)
# def class_setup(self,
# create_model,
# second_model,
# make_request,
# ):
# pass
@property
def parameterized_api_fields(self):
return {
'software': {
'expected': dict
},
'software.id': {
'expected': int
},
'software.display_name': {
'expected': str
},
'software.url': {
'expected': Hyperlink
},
'name': {
'expected': str
},
'description': {
'expected': str
},
'enabled': {
'expected': bool
},
'modified': {
'expected': str
}
}
class FeatureFlagAPIInheritedCases(
FeatureFlagAPITestCases,
):
pass
@pytest.mark.module_devops
class FeatureFlagAPIPyTest(
FeatureFlagAPITestCases,
):
pass

View File

@ -0,0 +1,28 @@
import pytest
from core.tests.functional.centurion_abstract.test_functional_centurion_abstract_model import (
CenturionAbstractModelInheritedCases
)
@pytest.mark.model_featureflag
class FeatureFlagModelTestCases(
CenturionAbstractModelInheritedCases
):
pass
class FeatureFlagModelInheritedCases(
FeatureFlagModelTestCases,
):
pass
@pytest.mark.module_devops
class FeatureFlagModelPyTest(
FeatureFlagModelTestCases,
):
pass

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