Compare commits
34 Commits
1b338e4c19
...
22-detail-
Author | SHA1 | Date | |
---|---|---|---|
8d071c68df | |||
3b1691ff62 | |||
a77c43d213 | |||
086959b431 | |||
3f117f9d83 | |||
6a23845a4f | |||
b9c6d04e04 | |||
32c0027ecf | |||
dae52e8646 | |||
890a5651a0 | |||
4cb37f8347 | |||
a2010b9517 | |||
c95736ce14 | |||
b46c61954c | |||
afe4266600 | |||
0c8d1c8da1 | |||
eac998b5cc | |||
5914782252 | |||
73d875c4ac | |||
8f439f0675 | |||
0f102c6aaf | |||
4852c6caeb | |||
3fffba2eba | |||
a1293984ea | |||
4876db50c1 | |||
425cc066af | |||
1086f517fa | |||
2fdbf87ddd | |||
86228836c7 | |||
a6e6c948a5 | |||
dcdfa8feb7 | |||
8388d2e695 | |||
29f269050f | |||
93c4fc2009 |
2
.cz.yaml
2
.cz.yaml
@ -17,5 +17,5 @@ commitizen:
|
||||
prerelease_offset: 1
|
||||
tag_format: $version
|
||||
update_changelog_on_bump: false
|
||||
version: 1.18.0
|
||||
version: 1.0.0-b14
|
||||
version_scheme: semver
|
||||
|
101
.github/ISSUE_TEMPLATE/new_model.md
vendored
101
.github/ISSUE_TEMPLATE/new_model.md
vendored
@ -1,101 +0,0 @@
|
||||
---
|
||||
name: New Database Model
|
||||
about: Use when creating a new database model.
|
||||
title: "New Model - <model table name>"
|
||||
type: Task
|
||||
labels: task::feature, triage, type::task
|
||||
---
|
||||
|
||||
<!-- Add an intro -->
|
||||
|
||||
|
||||
<!-- describe a use case if not covered in intro -->
|
||||
|
||||
|
||||
## 📝 Details
|
||||
<!--
|
||||
|
||||
Describe in detail the following:
|
||||
|
||||
- New model field
|
||||
- if foreign key field, what it's name will be or if it's not to be linked ensure specified and coded with `related_name = '+' to disable the link`.
|
||||
- How the UI will work, be layed out, new ui features etc
|
||||
- custom permissions if required
|
||||
|
||||
-->
|
||||
|
||||
|
||||
## 🚧 Tasks
|
||||
|
||||
<!-- Don't remove tasks strike them out. use `~~` before and after the item. i.e. `- ~~[ ] Model Created~~` note: don't include the list dash-->
|
||||
|
||||
- [ ] 🆕 [Model Created](https://nofusscomputing.com/projects/centurion_erp/development/models/)
|
||||
|
||||
- [ ] 🛠️ Migrations added
|
||||
|
||||
- [ ] ♻️ Serializer Created
|
||||
|
||||
- [ ] 🔄 [ViewSet Created](https://nofusscomputing.com/projects/centurion_erp/development/views/)
|
||||
|
||||
- [ ] 🔗 URL Route Added
|
||||
|
||||
- [ ] 🏷️ [Model tag]().
|
||||
|
||||
- [ ] 📘 Tag updated in the [docs](https://nofusscomputing.com/projects/centurion_erp/user/core/markdown/#model-reference)
|
||||
|
||||
- [ ] tag added to class
|
||||
|
||||
- [ ] Admin Documentation added/updated _if applicable_
|
||||
|
||||
- [ ] Developer Documentation added/updated _if applicable_
|
||||
|
||||
- [ ] User Documentation added/updated
|
||||
|
||||
---
|
||||
|
||||
<!-- Add additional tasks here and as a check box list -->
|
||||
|
||||
|
||||
|
||||
### 🧪 Tests
|
||||
|
||||
- Unit Tests
|
||||
- [ ] [Model](https://nofusscomputing.com/projects/centurion_erp/development/models/#tests)
|
||||
- [ ] ViewSet
|
||||
- [ ] Serializer
|
||||
- Function Test
|
||||
- [ ] API Metadata
|
||||
- [ ] API Permissions
|
||||
- [ ] API Render (fields)
|
||||
- [ ] Model
|
||||
- [ ] Serializer
|
||||
- [ ] ViewSet
|
||||
|
||||
|
||||
## ✅ Requirements
|
||||
|
||||
A Requirement is a must have. In addition will also be tested.
|
||||
|
||||
- [ ] Must have a [model_tag](https://nofusscomputing.com/projects/centurion_erp/user/core/markdown/#model-reference)
|
||||
|
||||
<!--
|
||||
|
||||
When detailing requirements the following must be taken into account:
|
||||
|
||||
- what the user should be able to do
|
||||
|
||||
- what the user should not be able to do
|
||||
|
||||
- what should occur when a user performs an action
|
||||
|
||||
-->
|
||||
|
||||
- Functional Requirements
|
||||
|
||||
|
||||
- Non-Functional Requirements
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- Add additional requirement here and as a check box list -->
|
5
.github/pull_request_template.md
vendored
5
.github/pull_request_template.md
vendored
@ -20,9 +20,6 @@
|
||||
|
||||
<!-- dont remove tasks below strike through including the checkbox by enclosing in double tidle '~~' -->
|
||||
|
||||
- [ ] **Feature Release ONLY** :red_square: [Squash migration files](https://docs.djangoproject.com/en/5.2/topics/migrations/#squashing-migrations) :red_square:
|
||||
_Multiple migration files created as part of this release are to be sqauashed into a few files as possible so as to limit the number of migrations_
|
||||
|
||||
- [ ] :firecracker: Contains breaking-change Any Breaking change(s)?
|
||||
|
||||
_Breaking Change must also be notated in the commit that introduces it and in [Conventional Commit Format](https://www.conventionalcommits.org/en/v1.0.0/)._
|
||||
@ -35,8 +32,6 @@
|
||||
|
||||
- [ ] :checkered_flag: Milestone assigned
|
||||
|
||||
- [ ] :gear: :test_tube: [Functional Test(s) Written](https://nofusscomputing.com/projects/centurion_erp/development/testing/)
|
||||
|
||||
- [ ] :test_tube: [Unit Test(s) Written](https://nofusscomputing.com/projects/centurion_erp/development/testing/)
|
||||
|
||||
_ensure test coverage delta is not less than zero_
|
||||
|
11
.github/workflows/ci.yaml
vendored
11
.github/workflows/ci.yaml
vendored
@ -16,17 +16,6 @@ env:
|
||||
jobs:
|
||||
|
||||
|
||||
mkdocs:
|
||||
name: 'MKDocs'
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
statuses: write
|
||||
checks: write
|
||||
actions: write
|
||||
uses: nofusscomputing/action_mkdocs/.github/workflows/reusable_mkdocs.yaml@development
|
||||
|
||||
|
||||
docker:
|
||||
name: 'Docker'
|
||||
uses: nofusscomputing/action_docker/.github/workflows/docker.yaml@development
|
||||
|
14
.gitignore
vendored
14
.gitignore
vendored
@ -1,23 +1,11 @@
|
||||
venv/**
|
||||
*/static/**
|
||||
__pycache__
|
||||
**.sqlite*
|
||||
**.sqlite3
|
||||
**.sqlite
|
||||
**.coverage
|
||||
.coverage*
|
||||
artifacts/
|
||||
**.tmp.*
|
||||
volumes/
|
||||
build/
|
||||
pages/
|
||||
node_modules/
|
||||
.markdownlint-cli2.jsonc
|
||||
.markdownlint.json
|
||||
package-lock.json
|
||||
package.json
|
||||
**.junit.xml
|
||||
**.JUnit.xml
|
||||
feature_flags.json
|
||||
coverage_*.json
|
||||
*-coverage.xml
|
||||
log/
|
||||
|
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
@ -7,7 +7,5 @@
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"qwtel.sqlite-viewer",
|
||||
"jebbs.markdown-extended",
|
||||
"william-voyek.vscode-nginx",
|
||||
"detachhead.basedpyright",
|
||||
]
|
||||
}
|
102
.vscode/launch.json
vendored
102
.vscode/launch.json
vendored
@ -5,7 +5,7 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Centurion",
|
||||
"name": "Debug: Django",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
@ -16,77 +16,9 @@
|
||||
"autoStartBrowser": false,
|
||||
"program": "${workspaceFolder}/app/manage.py"
|
||||
},
|
||||
{
|
||||
"name": "Debug: Gunicorn",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "gunicorn",
|
||||
"args": [
|
||||
"--config=../includes/etc/gunicorn.conf.py",
|
||||
"--access-logfile",
|
||||
"-",
|
||||
"--workers",
|
||||
"3",
|
||||
"--bind",
|
||||
"0.0.0.0:8002",
|
||||
"centurion.wsgi:application",
|
||||
],
|
||||
"django": true,
|
||||
"autoStartBrowser": false,
|
||||
"cwd": "${workspaceFolder}/app",
|
||||
"env": {
|
||||
"PROMETHEUS_MULTIPROC_DIR": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Centurion Feature Flag (Management Command)",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
"feature_flag",
|
||||
// "0.0.0.0:8002"
|
||||
],
|
||||
"django": true,
|
||||
"autoStartBrowser": false,
|
||||
"program": "${workspaceFolder}/app/manage.py"
|
||||
},
|
||||
{
|
||||
"name": "Centurion Model (Management Command)",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
"models",
|
||||
// "0.0.0.0:8002"
|
||||
],
|
||||
"django": true,
|
||||
"autoStartBrowser": false,
|
||||
"program": "${workspaceFolder}/app/manage.py"
|
||||
},
|
||||
{
|
||||
"name": "Make Migrations",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
"makemigrations"
|
||||
],
|
||||
"django": true,
|
||||
"autoStartBrowser": false,
|
||||
"program": "${workspaceFolder}/app/manage.py"
|
||||
},
|
||||
{
|
||||
"name": "Migrate",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
"migrate"
|
||||
],
|
||||
"django": true,
|
||||
"autoStartBrowser": false,
|
||||
"program": "${workspaceFolder}/app/manage.py"
|
||||
},
|
||||
{
|
||||
"name": "Debug: Celery",
|
||||
"type": "debugpy",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"module": "celery",
|
||||
"console": "integratedTerminal",
|
||||
@ -100,34 +32,6 @@
|
||||
"debug-itsm@%h"
|
||||
],
|
||||
"cwd": "${workspaceFolder}/app"
|
||||
},
|
||||
{
|
||||
"name": "Debug pytest (collect)",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "pytest",
|
||||
"args": [
|
||||
"--override-ini", "addopts=",
|
||||
"--collect-only",
|
||||
"app",
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": false
|
||||
},
|
||||
{
|
||||
"name": "Python Debugger: Local Attach",
|
||||
"type": "debugpy",
|
||||
"request": "attach",
|
||||
"connect": {
|
||||
"host": "localhost",
|
||||
"port": 5678
|
||||
},
|
||||
"pathMappings": [
|
||||
{
|
||||
"localRoot": "${workspaceFolder}",
|
||||
"remoteRoot": "."
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
28
.vscode/settings.json
vendored
28
.vscode/settings.json
vendored
@ -5,34 +5,16 @@
|
||||
"!python"
|
||||
],
|
||||
"python.testing.pytestArgs": [
|
||||
"--override-ini", "addopts=",
|
||||
"--no-migrations",
|
||||
"app",
|
||||
// "-v",
|
||||
// "--cov",
|
||||
// "--cov-report xml",
|
||||
"app"
|
||||
],
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestEnabled": true,
|
||||
"testing.coverageToolbarEnabled": true,
|
||||
"cSpell.words": [
|
||||
"ITSM"
|
||||
],
|
||||
"cSpell.language": "en-AU",
|
||||
"jest.enable": false,
|
||||
"pylint.enabled": true,
|
||||
"testing.showCoverageInExplorer": true,
|
||||
"testing.coverageToolbarEnabled": true,
|
||||
"testing.coverageBarThresholds": {
|
||||
"red": 0,
|
||||
"yellow": 60,
|
||||
"green": 90
|
||||
},
|
||||
"telemetry.feedback.enabled": false,
|
||||
"python.languageServer": "None",
|
||||
"debug.javascript.enableNetworkView": false,
|
||||
"typescript.experimental.expandableHover": false,
|
||||
"ipynb.experimental.serialization": false,
|
||||
"notebook.experimental.generate": false,
|
||||
"extensions.experimental.issueQuickAccess": false,
|
||||
"workbench.commandPalette.experimental.enableNaturalLanguageSearch": false,
|
||||
"multiDiffEditor.experimental.enabled": false,
|
||||
"diffEditor.experimental.showEmptyDecorations": false,
|
||||
"editor.experimental.asyncTokenization": false,
|
||||
}
|
2760
CHANGELOG.md
2760
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
125
CONTRIBUTING.md
125
CONTRIBUTING.md
@ -1,131 +1,6 @@
|
||||
# Contribution Guide
|
||||
|
||||
|
||||
Development of this project has been setup to be done from VSCodium. The following additional requirements need to be met:
|
||||
|
||||
- npm has been installed. _required for `markdown` linting_
|
||||
|
||||
`sudo apt install -y --no-install-recommends npm`
|
||||
|
||||
- setup of other requirements can be done with `make prepare`
|
||||
|
||||
- **ALL** Linting must pass for Merge to be conducted.
|
||||
|
||||
_`make lint`_
|
||||
|
||||
## TL;DR
|
||||
|
||||
|
||||
from the root of the project to start a test server use:
|
||||
|
||||
``` bash
|
||||
|
||||
# activate python venv
|
||||
source /tmp/centurion_erp/bin/activate
|
||||
|
||||
# enter app dir
|
||||
cd app
|
||||
|
||||
# Start dev server can be viewed at http://127.0.0.1:8002
|
||||
python manage.py runserver 8002
|
||||
|
||||
# Run any migrations, if required
|
||||
python manage.py migrate
|
||||
|
||||
# Create a super suer if required
|
||||
python manage.py createsuperuser
|
||||
|
||||
```
|
||||
|
||||
## Makefile
|
||||
|
||||
!!! tip "TL;DR"
|
||||
Common make commands are `make prepare` then `make docs` and `make lint`
|
||||
|
||||
Included within the root of the repository is a makefile that can be used during development to check/run different items as is required during development. The following make targets are available:
|
||||
|
||||
- `prepare`
|
||||
|
||||
_prepare the repository. init's all git submodules and sets up a python virtual env and other make targets_
|
||||
|
||||
- `docs`
|
||||
|
||||
_builds the docs and places them within a directory called build, which can be viewed within a web browser_
|
||||
|
||||
- `lint`
|
||||
|
||||
_conducts all required linting_
|
||||
|
||||
- `docs-lint`
|
||||
|
||||
_lints the markdown documents within the docs directory for formatting errors that MKDocs may/will have an issue with._
|
||||
|
||||
- `clean`
|
||||
|
||||
_cleans up build artifacts and removes the python virtual environment_
|
||||
|
||||
|
||||
> this doc is yet to receive a re-write
|
||||
|
||||
|
||||
## Docker Container
|
||||
|
||||
within the `deploy/` directory there is a docker compose file. running `docker compose up` from this directory will launch a full stack deployment locally containing Centurion API, User Interface, a worker and a RabbitMQ server. once launched you can navigate to `http://127.0.0.1/` to start browsing the site.
|
||||
|
||||
You may need to run migrations if your not mounting your own DB. to do this run `docker exec -ti centurion-erp python manage.py migrate`
|
||||
|
||||
## Page speed tests
|
||||
|
||||
to run page speed tests (requires a working prometheus and grafa setup). use the following
|
||||
|
||||
|
||||
``` bash
|
||||
|
||||
clear; \
|
||||
K6_PROMETHEUS_RW_TREND_STATS="p(99),p(95),p(90),max,min" \
|
||||
K6_PROMETHEUS_RW_SERVER_URL=http://<prometheus url>:9090/api/v1/write \
|
||||
BASE_URL="http://127.0.0.1:8002" \
|
||||
AUTH_TOKEN="< api token of superuser>" \
|
||||
k6 run \
|
||||
-o experimental-prometheus-rw \
|
||||
--tag "commit=$(git rev-parse HEAD)" \
|
||||
--tag "testid=<name of test for ref>" \
|
||||
test/page_speed.js
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Tips / Handy info
|
||||
|
||||
- To obtain a list of models _(in in the same order as the file system)_ using the db shell `python3 manage.py dbshell` run the following sql command:
|
||||
|
||||
``` sql
|
||||
|
||||
SELECT model FROM django_content_type ORDER BY app_label ASC, model ASC;
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Old working docs
|
||||
|
||||
|
||||
## Dev Environment
|
||||
|
||||
It's advised to setup a python virtual env for development. this can be done with the following commands.
|
||||
|
13
README.md
13
README.md
@ -32,14 +32,9 @@ This project is hosted on [Github](https://github.com/NofussComputing/centurion_
|
||||
|
||||
**Stable Branch**
|
||||
|
||||
 
|
||||
|
||||

|
||||
  
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
----
|
||||
@ -48,13 +43,9 @@ This project is hosted on [Github](https://github.com/NofussComputing/centurion_
|
||||
|
||||
|
||||
|
||||
 
|
||||
|
||||

|
||||
  
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
----
|
||||
<br>
|
||||
|
218
Release-Notes.md
218
Release-Notes.md
@ -1,221 +1,7 @@
|
||||
## Version 1.18.0
|
||||
|
||||
- Added new model for History
|
||||
|
||||
!!! info
|
||||
Migration of the old history tables to the new history tables occurs as part of post migration. As such the time it will take to migrate the history is dependent upon how many history entries per model. This should be planned for when upgrading to this version. if for some reason the migration is interrupted, you can safely restart it again by running the migrate command.
|
||||
|
||||
!!! note
|
||||
Permission migration from the old history models to the new Audit History models are not migrated. As such users whom used to be able to access history models will need to be granted the required permission to view the new Audit History models
|
||||
|
||||
- Added new model for notes
|
||||
|
||||
!!! info
|
||||
Migration of the old notes tables to the new note tables occurs as part of post migration. As such the time it will take to migrate the history is dependent upon how many history entries per model. This should be planned for when upgrading to this version. if for some reason the migration is interrupted, you can safely restart it again by running the migrate command.
|
||||
|
||||
!!! note
|
||||
Permission migration from the old history models to the new Centurion Notes models are not migrated. As such users whom used to be able to access notes models will need to be granted the required permission to view the new Centurion Notes models
|
||||
|
||||
- Removed Django UI
|
||||
|
||||
[UI](https://github.com/nofusscomputing/centurion_erp) must be deployed seperatly.
|
||||
|
||||
- Removed API v1
|
||||
|
||||
|
||||
## Version 1.17.0
|
||||
|
||||
- Added setting for log files.
|
||||
|
||||
Enables user to specify a default path for centurion's logging. Add the following to your settings file `/etc/itsm/settings.py`
|
||||
|
||||
``` py
|
||||
LOG_FILES = {
|
||||
"centurion": "/var/log/centurion.log", # Normal Centurion Operations
|
||||
"weblog": "/var/log/weblog.log", # All web requests made to Centurion
|
||||
"rest_api": "/var/log/rest_api.log", # Rest API
|
||||
"catch_all":"/var/log/catch-all.log" # A catch all log. Note: does not log anything that has already been logged.
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
With this new setting, the previous setting `LOGGING` will no longer function.
|
||||
|
||||
- Renamed `Organization` model to `Tenant` so as to reflect what is actually is.
|
||||
|
||||
- `robots.txt` file now being served from the API container at path `/robots.txt` with `User-agent: *` and `Disallow: /`
|
||||
|
||||
|
||||
## Version 1.16.0
|
||||
|
||||
- Employees model added behind feature flag `2025-00002` and will remain behind this flag until production ready.
|
||||
|
||||
- Ticket and Ticket Comment added behind feature flag `2025-00006` and will remain behind this flag until production ready.
|
||||
|
||||
- In preparation of the [Ticket and Ticket Comment model re-write](https://github.com/nofusscomputing/centurion_erp/issues/564)
|
||||
|
||||
- Depreciated Change Ticket
|
||||
|
||||
- Depreciated Ticket Comment Endpoint
|
||||
|
||||
- Depreciated Request Ticket
|
||||
|
||||
- Depreciated Incident Ticket
|
||||
|
||||
- Depreciated Problem Ticket
|
||||
|
||||
- Depreciated Project Task Ticket
|
||||
|
||||
These endpoints still work and will remain so until the new Ticket and Ticket Comment Models are production ready.
|
||||
|
||||
|
||||
## Version 1.15.0
|
||||
|
||||
- Entities model added behind feature flag `2025-00002` and will remain behind this flag until production ready.
|
||||
|
||||
- Roles model added behind feature flag `2025-00003` and will remain behind this flag until production ready.
|
||||
|
||||
- Accounting Module added behind feature flag `2025-00004` and will remain behind this flag until production ready.
|
||||
|
||||
|
||||
## Version 1.14.0
|
||||
|
||||
- Git Repository and Git Group Models added behind feature flag `2025-00001`. They will remain behind this feature flag until the Git features are fully developed and ready for use.
|
||||
|
||||
|
||||
## Version 1.13.0
|
||||
|
||||
- DevOps Module added.
|
||||
|
||||
- Feature Flagging Component added as par of the DevOps module.
|
||||
|
||||
|
||||
## Version 1.11.0
|
||||
|
||||
**Note:** Migrations should be performed offline. **Failing to perform** an online migration, the option provided below will not be available if the migration crashes. Running the below commands to reset the database for the migrations to re-run will cause data loss if users are making changes to Centurion.
|
||||
|
||||
- History views removed from original Centurion interface.
|
||||
|
||||
- History views removed from API v1.
|
||||
|
||||
- A migration exists that will move the history from the old tables to the new ones.
|
||||
|
||||
if for some reason the migration crashes enter the following commands in the dbshell `python manage.py dbshell` and restart the migrations
|
||||
|
||||
``` sql
|
||||
|
||||
delete from access_organization_history;
|
||||
delete from access_team_history;
|
||||
|
||||
delete from assistance_knowledge_base_history;
|
||||
delete from assistance_knowledge_base_category_history;
|
||||
|
||||
delete from config_management_configgroups_history;
|
||||
delete from config_management_configgroupsoftware_history;
|
||||
delete from config_management_configgrouphosts_history;
|
||||
|
||||
delete from core_manufacturer_history;
|
||||
delete from core_ticketcategory_history;
|
||||
delete from core_ticketcommentcategory_history;
|
||||
|
||||
delete from itam_device_history;
|
||||
delete from itam_devicemodel_history;
|
||||
delete from itam_devicetype_history;
|
||||
delete from itam_deviceoperatingsystem_history;
|
||||
delete from itam_devicesoftware_history;
|
||||
delete from itam_operatingsystem_history;
|
||||
delete from itam_operatingsystemversion_history;
|
||||
delete from itam_software_history;
|
||||
delete from itam_softwareversion_history;
|
||||
delete from itam_softwarecategory_history;
|
||||
|
||||
delete from itim_cluster_history;
|
||||
delete from itim_clustertype_history;
|
||||
delete from itim_port_history;
|
||||
delete from itim_service_history;
|
||||
|
||||
delete from project_management_project_history;
|
||||
delete from project_management_projectmilestone_history;
|
||||
delete from project_management_projectstate_history;
|
||||
delete from project_management_projecttype_history;
|
||||
delete from settings_externallink_history;
|
||||
|
||||
delete from core_model_history;
|
||||
|
||||
```
|
||||
|
||||
The above commands truncate the data from the new history tables so the migration can run again.
|
||||
|
||||
|
||||
## Version 1.10.0
|
||||
|
||||
- Nothing significant to report
|
||||
|
||||
|
||||
## Version 1.9.0
|
||||
|
||||
- Nothing significant to report
|
||||
|
||||
|
||||
## Version 1.8.0
|
||||
|
||||
- Prometheus exporter added. To enable metrics for the database you will have to update the database backend. see the [docs](https://nofusscomputing.com/projects/centurion_erp/administration/monitoring/#django-exporter-setup) for further information.
|
||||
|
||||
|
||||
## Version 1.5.0
|
||||
|
||||
- When v1.4.0 was release the migrations were not merged. As part of the work conducted on this release the v1.4 migrations have been squashed. This should not have any effect on any system that when they updated to v1.4, they ran the migrations and they **completed successfully**. Upgrading from <1.4.0 to this release should also have no difficulties as the migrations required still exist. There are less of them, however with more work per migration.
|
||||
|
||||
!!! Note
|
||||
If you require the previously squashed migrations for what ever reason. Clone the repo and go to commit 17f47040d6737905a1769eee5c45d9d15339fdbf, which is the commit prior to the squashing which is commit ca2da06d2cd393cabb7e172ad47dfb2dd922d952.
|
||||
|
||||
|
||||
## Version 1.4.0
|
||||
|
||||
API redesign in preparation for moving the UI out of centurion to it's [own project](https://github.com/nofusscomputing/centurion_erp_ui). This release introduces a **Feature freeze** to the current UI. Only bug fixes will be done for the current UI.
|
||||
API v2 is a beta release and is subject to change. On completion of the new UI, API v2 will more likely than not be set as stable.
|
||||
|
||||
- A large emphasis is being placed upon API stability. This is being achieved by ensuring the following:
|
||||
|
||||
- Actions can only be carried out by users whom have the correct permissions
|
||||
|
||||
- fields are of the correct type and visible when required as part of the API response
|
||||
|
||||
- Data validations work and notify the user of any issue
|
||||
|
||||
We are make the above possible by ensuring a more stringent test policy.
|
||||
|
||||
- New API will be at path `api/v2`.
|
||||
|
||||
- API v1 is now **Feature frozen** with only bug fixes being completed. It's recommended that you move to and start using API v2 as this has feature parity with API v1.
|
||||
|
||||
- API v1 is **depreciated**
|
||||
|
||||
- Depreciation of **ALL** API urls. API v1 Will be [removed in v2.0.0](https://github.com/nofusscomputing/centurion_erp/issues/343) release of Centurion.
|
||||
|
||||
|
||||
# Version 1.3.0
|
||||
|
||||
!!! danger "Security"
|
||||
As is currently the recommended method of deployment, the Centurion Container must be deployed behind a reverse proxy the conducts the SSL termination.
|
||||
|
||||
This release updates the docker container to be a production setup for deployment of Centurion. Prior to this version Centurion ERP was using a development setup for the webserver.
|
||||
|
||||
- Docker now uses SupervisorD for container
|
||||
|
||||
- Gunicorn WSGI setup for Centurion with NginX as the webserver.
|
||||
|
||||
- Container now has a health check.
|
||||
|
||||
- To setup container as "Worker", set `IS_WORKER='True'` environmental variable within container. _**Note:** You can still use command `celery -A app worker -l INFO`, although **not** recommended as the container health check will not be functioning_
|
||||
|
||||
|
||||
## Version 1.0.0
|
||||
|
||||
# Version 1.0.0
|
||||
|
||||
Initial Release of Centurion ERP.
|
||||
|
||||
|
||||
### Breaking changes
|
||||
## Breaking changes
|
||||
|
||||
- Nil
|
||||
|
17
app/.coveragerc
Normal file
17
app/.coveragerc
Normal file
@ -0,0 +1,17 @@
|
||||
[run]
|
||||
source = .
|
||||
omit =
|
||||
*migrations/*
|
||||
*tests/*/*
|
||||
|
||||
[report]
|
||||
omit =
|
||||
*/tests/*/*
|
||||
*/migrations/*
|
||||
*apps.py
|
||||
*manage.py
|
||||
*__init__.py
|
||||
*asgi*
|
||||
*wsgi*
|
||||
*admin.py
|
||||
*urls.py
|
@ -1,14 +1,7 @@
|
||||
import django
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.models import Group
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
|
||||
|
||||
from access.models.tenant import Tenant as Organization
|
||||
from access.models.team import Team
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
from .models import *
|
||||
|
||||
admin.site.unregister(Group)
|
||||
|
||||
@ -32,40 +25,6 @@ class OrganizationAdmin(admin.ModelAdmin):
|
||||
list_filter = ["created"]
|
||||
search_fields = ["team_name"]
|
||||
|
||||
|
||||
admin.site.register(Organization,OrganizationAdmin)
|
||||
|
||||
|
||||
class TeamUserInline(admin.TabularInline):
|
||||
model = TeamUsers
|
||||
extra = 0
|
||||
|
||||
readonly_fields = ['created', 'modified']
|
||||
fields = ['team']
|
||||
|
||||
fk_name = 'user'
|
||||
|
||||
|
||||
admin.site.unregister(User)
|
||||
class UsrAdmin(UserAdmin):
|
||||
|
||||
fieldsets = (
|
||||
(None, {"fields": ("username", "password")}),
|
||||
("Personal info", {"fields": ("first_name", "last_name", "email")}),
|
||||
(
|
||||
"Permissions",
|
||||
{
|
||||
"fields": (
|
||||
"is_active",
|
||||
"is_staff",
|
||||
"is_superuser",
|
||||
),
|
||||
|
||||
},
|
||||
),
|
||||
("Important dates", {"fields": ("last_login", "date_joined")}),
|
||||
)
|
||||
|
||||
inlines = [TeamUserInline]
|
||||
|
||||
admin.site.register(User,UsrAdmin)
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
from django.db import models
|
||||
from django.utils.timezone import now
|
||||
|
||||
|
||||
from django.template.defaultfilters import slugify
|
||||
|
||||
class AutoCreatedField(models.DateTimeField):
|
||||
"""
|
||||
@ -12,20 +11,12 @@ class AutoCreatedField(models.DateTimeField):
|
||||
|
||||
"""
|
||||
|
||||
help_text = 'Date and time of creation'
|
||||
|
||||
verbose_name = 'Created'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
kwargs.setdefault("editable", False)
|
||||
|
||||
kwargs.setdefault("default", now)
|
||||
|
||||
kwargs.setdefault("help_text", self.help_text)
|
||||
|
||||
kwargs.setdefault("verbose_name", self.verbose_name)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
@ -37,21 +28,9 @@ class AutoLastModifiedField(AutoCreatedField):
|
||||
|
||||
"""
|
||||
|
||||
help_text = 'Date and time of last modification'
|
||||
|
||||
verbose_name = 'Modified'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
kwargs.setdefault("help_text", self.help_text)
|
||||
|
||||
kwargs.setdefault("verbose_name", self.verbose_name)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def pre_save(self, model_instance, add):
|
||||
|
||||
value = now().replace(microsecond=0)
|
||||
value = now()
|
||||
|
||||
setattr(model_instance, self.attname, value)
|
||||
|
||||
@ -66,20 +45,6 @@ class AutoSlugField(models.SlugField):
|
||||
|
||||
"""
|
||||
|
||||
help_text = 'slug for this field'
|
||||
|
||||
verbose_name = 'Slug'
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
kwargs.setdefault("help_text", self.help_text)
|
||||
|
||||
kwargs.setdefault("verbose_name", self.verbose_name)
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
def pre_save(self, model_instance, add):
|
||||
|
||||
if not model_instance.slug or model_instance.slug == '_':
|
||||
|
38
app/access/forms/organization.py
Normal file
38
app/access/forms/organization.py
Normal file
@ -0,0 +1,38 @@
|
||||
from django import forms
|
||||
from django.db.models import Q
|
||||
|
||||
from app import settings
|
||||
|
||||
from access.models import Organization
|
||||
|
||||
from core.forms.common import CommonModelForm
|
||||
|
||||
class OrganizationForm(CommonModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Organization
|
||||
fields = [
|
||||
'name',
|
||||
'manager',
|
||||
'model_notes',
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['created'] = forms.DateTimeField(
|
||||
label="Created",
|
||||
input_formats=settings.DATETIME_FORMAT,
|
||||
initial=kwargs['instance'].created,
|
||||
disabled=True,
|
||||
required=False,
|
||||
)
|
||||
|
||||
self.fields['modified'] = forms.DateTimeField(
|
||||
label="Modified",
|
||||
input_formats=settings.DATETIME_FORMAT,
|
||||
initial=kwargs['instance'].modified,
|
||||
disabled=True,
|
||||
required=False,
|
||||
)
|
103
app/access/forms/team.py
Normal file
103
app/access/forms/team.py
Normal file
@ -0,0 +1,103 @@
|
||||
from django import forms
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.db.models import Q
|
||||
from django.forms import inlineformset_factory
|
||||
|
||||
from app import settings
|
||||
|
||||
from .team_users import TeamUsersForm, TeamUsers
|
||||
|
||||
from access.models import Team
|
||||
|
||||
from core.forms.common import CommonModelForm
|
||||
|
||||
TeamUserFormSet = inlineformset_factory(
|
||||
model=TeamUsers,
|
||||
parent_model= Team,
|
||||
extra = 1,
|
||||
fields=[
|
||||
'user',
|
||||
'manager'
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
|
||||
class TeamFormAdd(CommonModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Team
|
||||
fields = [
|
||||
'team_name',
|
||||
'model_notes',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class TeamForm(CommonModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Team
|
||||
fields = [
|
||||
'team_name',
|
||||
'permissions',
|
||||
'model_notes',
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['created'] = forms.DateTimeField(
|
||||
label="Created",
|
||||
input_formats=settings.DATETIME_FORMAT,
|
||||
initial=kwargs['instance'].created,
|
||||
disabled=True,
|
||||
required=False,
|
||||
)
|
||||
|
||||
self.fields['modified'] = forms.DateTimeField(
|
||||
label="Modified",
|
||||
input_formats=settings.DATETIME_FORMAT,
|
||||
initial=kwargs['instance'].modified,
|
||||
disabled=True,
|
||||
required=False,
|
||||
)
|
||||
|
||||
self.fields['permissions'].widget.attrs = {'style': "height: 200px;"}
|
||||
|
||||
apps = [
|
||||
'access',
|
||||
'assistance',
|
||||
'config_management',
|
||||
'core',
|
||||
'django_celery_results',
|
||||
'itam',
|
||||
'settings',
|
||||
]
|
||||
|
||||
exclude_models = [
|
||||
'appsettings',
|
||||
'chordcounter',
|
||||
'groupresult',
|
||||
'organization'
|
||||
'settings',
|
||||
'usersettings',
|
||||
]
|
||||
|
||||
exclude_permissions = [
|
||||
'add_organization',
|
||||
'add_taskresult',
|
||||
'change_organization',
|
||||
'change_taskresult',
|
||||
'delete_organization',
|
||||
'delete_taskresult',
|
||||
]
|
||||
|
||||
self.fields['permissions'].queryset = Permission.objects.filter(
|
||||
content_type__app_label__in=apps,
|
||||
).exclude(
|
||||
content_type__model__in=exclude_models
|
||||
).exclude(
|
||||
codename__in = exclude_permissions
|
||||
)
|
16
app/access/forms/team_users.py
Normal file
16
app/access/forms/team_users.py
Normal file
@ -0,0 +1,16 @@
|
||||
from django.db.models import Q
|
||||
|
||||
from app import settings
|
||||
|
||||
from access.models import TeamUsers
|
||||
|
||||
from core.forms.common import CommonModelForm
|
||||
|
||||
class TeamUsersForm(CommonModelForm):
|
||||
|
||||
class Meta:
|
||||
model = TeamUsers
|
||||
fields = [
|
||||
'user',
|
||||
'manager',
|
||||
]
|
@ -1,107 +0,0 @@
|
||||
from django.apps import apps
|
||||
from django.contrib.auth.models import (
|
||||
ContentType,
|
||||
Permission
|
||||
)
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def permission_queryset():
|
||||
"""Filter Permissions to those used within the application
|
||||
|
||||
Returns:
|
||||
list: Filtered queryset that only contains the used permissions
|
||||
"""
|
||||
|
||||
centurion_apps = [
|
||||
'access',
|
||||
'accounting',
|
||||
'assistance',
|
||||
'config_management',
|
||||
'core',
|
||||
'devops',
|
||||
'django_celery_results',
|
||||
'human_resources',
|
||||
'itam',
|
||||
'itim',
|
||||
'project_management',
|
||||
'settings',
|
||||
]
|
||||
|
||||
exclude_models = [
|
||||
'appsettings',
|
||||
'chordcounter',
|
||||
'comment',
|
||||
'groupresult',
|
||||
'history',
|
||||
'modelnotes',
|
||||
'usersettings',
|
||||
]
|
||||
|
||||
exclude_permissions = [
|
||||
'add_checkin',
|
||||
'add_history',
|
||||
'add_organization',
|
||||
'add_taskresult',
|
||||
'add_ticketcommentaction',
|
||||
'change_checkin',
|
||||
'change_history',
|
||||
'change_organization',
|
||||
'change_taskresult',
|
||||
'change_ticketcommentaction',
|
||||
'delete_checkin',
|
||||
'delete_history',
|
||||
'delete_organization',
|
||||
'delete_taskresult',
|
||||
'delete_ticketcommentaction',
|
||||
'view_checkin',
|
||||
'view_history',
|
||||
]
|
||||
|
||||
|
||||
if not settings.RUNNING_TESTS:
|
||||
|
||||
models = apps.get_models()
|
||||
|
||||
for model in 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:
|
||||
|
||||
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
|
||||
)
|
@ -1,160 +0,0 @@
|
||||
import django
|
||||
|
||||
from django.contrib.auth.models import Group
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
|
||||
from access.models.tenant import Tenant
|
||||
from access.models.team import Team
|
||||
|
||||
|
||||
from settings.models.app_settings import AppSettings
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
|
||||
class RequestTenancy(MiddlewareMixin):
|
||||
"""Access Middleware
|
||||
|
||||
Serves the purpose of adding the users tenancy details to rhe request
|
||||
object.
|
||||
"""
|
||||
|
||||
|
||||
def process_request(self, request):
|
||||
|
||||
request.app_settings = AppSettings.objects.select_related('global_organization').filter(
|
||||
owner_organization = None
|
||||
)[0]
|
||||
|
||||
request.tenancy = Tenancy(user = request.user, app_settings = request.app_settings)
|
||||
|
||||
|
||||
|
||||
class Tenancy:
|
||||
|
||||
user: User = None
|
||||
|
||||
groups: list([Group]) = None
|
||||
|
||||
_app_settings: AppSettings = None
|
||||
|
||||
|
||||
_user_organizations: list([Tenant]) = None
|
||||
"""Cached User Tenants"""
|
||||
|
||||
_user_teams: list([Team]) = None
|
||||
"""Cached User Teams"""
|
||||
|
||||
|
||||
_user_permissions: list([str]) = None
|
||||
"""Cached User User Permissions"""
|
||||
|
||||
|
||||
|
||||
def __init__(self, user: User, app_settings: AppSettings):
|
||||
|
||||
self.user = user
|
||||
|
||||
self. _app_settings = app_settings
|
||||
|
||||
self.groups = user.groups.select_related('team', 'team__organization').prefetch_related('team__permissions__content_type')
|
||||
|
||||
self._user_organizations = []
|
||||
|
||||
self._user_groups = []
|
||||
|
||||
self._user_teams = []
|
||||
|
||||
self._user_permissions = []
|
||||
|
||||
|
||||
for group in self.groups:
|
||||
|
||||
if group.team not in self._user_teams:
|
||||
|
||||
self._user_teams += [ group.team ]
|
||||
|
||||
for permission in group.team.permissions.all():
|
||||
|
||||
permission_value = str( permission.content_type.app_label + '.' + permission.codename )
|
||||
|
||||
if permission_value not in self._user_permissions:
|
||||
|
||||
self._user_permissions += [ permission_value ]
|
||||
|
||||
|
||||
if group.team.organization not in self._user_organizations:
|
||||
|
||||
self._user_organizations += [ group.team.organization ]
|
||||
|
||||
|
||||
|
||||
def is_member(self, organization: Tenant) -> bool:
|
||||
"""Returns true if the current user is a member of the organization
|
||||
|
||||
iterates over the user_organizations list and returns true if the user is a member
|
||||
|
||||
Returns:
|
||||
bool: _description_
|
||||
"""
|
||||
|
||||
is_member: bool = False
|
||||
|
||||
if organization is None:
|
||||
|
||||
return False
|
||||
|
||||
if int(organization) in self._user_organizations:
|
||||
|
||||
is_member = True
|
||||
|
||||
return is_member
|
||||
|
||||
|
||||
|
||||
def has_organization_permission(self, organization: Tenant, permissions_required: str) -> bool:
|
||||
""" Check if user has permission within organization.
|
||||
|
||||
Args:
|
||||
organization (int): Tenant to check.
|
||||
permissions_required (list): if doing object level permissions, pass in required permission.
|
||||
|
||||
Returns:
|
||||
bool: True for yes.
|
||||
"""
|
||||
|
||||
has_permission: bool = False
|
||||
|
||||
if type(organization) is not Tenant:
|
||||
|
||||
raise TypeError('Tenant must be of type Tenant')
|
||||
|
||||
|
||||
if type(permissions_required) is not str:
|
||||
|
||||
raise TypeError('permissions_required must be of type str')
|
||||
|
||||
|
||||
if not organization:
|
||||
|
||||
return has_permission
|
||||
|
||||
|
||||
for team in self._user_teams:
|
||||
|
||||
if(
|
||||
team.organization.id == int(organization)
|
||||
or getattr(self._app_settings.global_organization, 'id', 0) == int(organization)
|
||||
):
|
||||
|
||||
for permission in team.permissions.all():
|
||||
|
||||
assembled_permission = str(permission.content_type.app_label) + '.' + str( permission.codename )
|
||||
|
||||
if assembled_permission == permissions_required:
|
||||
|
||||
has_permission = True
|
||||
|
||||
|
||||
return has_permission
|
@ -44,7 +44,7 @@ class Migration(migrations.Migration):
|
||||
('team_name', models.CharField(default='', max_length=50, verbose_name='Name')),
|
||||
('created', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False)),
|
||||
('modified', access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False)),
|
||||
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.tenancy.TenancyObject.validatate_organization_exists])),
|
||||
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists])),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Teams',
|
||||
|
@ -1,89 +0,0 @@
|
||||
# Generated by Django 5.1.2 on 2024-12-06 06:47
|
||||
|
||||
import access.models
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='organization',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Organization', 'verbose_name_plural': 'Organizations'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='team',
|
||||
options={'ordering': ['team_name'], 'verbose_name': 'Team', 'verbose_name_plural': 'Teams'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='teamusers',
|
||||
options={'ordering': ['user'], 'verbose_name': 'Team User', 'verbose_name_plural': 'Team Users'},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organization',
|
||||
name='id',
|
||||
field=models.AutoField(help_text='ID of this item', primary_key=True, serialize=False, unique=True, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organization',
|
||||
name='manager',
|
||||
field=models.ForeignKey(help_text='Manager for this organization', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Manager'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organization',
|
||||
name='model_notes',
|
||||
field=models.TextField(blank=True, default=None, help_text='Tid bits of information', null=True, verbose_name='Notes'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organization',
|
||||
name='name',
|
||||
field=models.CharField(help_text='Name of this Organization', max_length=50, unique=True, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='team',
|
||||
name='is_global',
|
||||
field=models.BooleanField(default=False, help_text='Is this a global object?', verbose_name='Global Object'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='team',
|
||||
name='model_notes',
|
||||
field=models.TextField(blank=True, default=None, help_text='Tid bits of information', null=True, verbose_name='Notes'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='team',
|
||||
name='organization',
|
||||
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.tenancy.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='team',
|
||||
name='team_name',
|
||||
field=models.CharField(help_text='Name to give this team', max_length=50, verbose_name='Name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='teamusers',
|
||||
name='id',
|
||||
field=models.AutoField(help_text='ID of this Team User', primary_key=True, serialize=False, unique=True, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='teamusers',
|
||||
name='manager',
|
||||
field=models.BooleanField(blank=True, default=False, help_text='Is this user to be a manager of this team', verbose_name='manager'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='teamusers',
|
||||
name='team',
|
||||
field=models.ForeignKey(help_text='Team user belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='team', to='access.team', verbose_name='Team'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='teamusers',
|
||||
name='user',
|
||||
field=models.ForeignKey(help_text='User who will be added to the team', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User'),
|
||||
),
|
||||
]
|
@ -1,49 +0,0 @@
|
||||
# Generated by Django 5.1.5 on 2025-02-09 11:07
|
||||
|
||||
import access.models
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0002_alter_organization_options_alter_team_options_and_more'),
|
||||
('core', '0012_modelnotes'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='team',
|
||||
name='organization',
|
||||
field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.team.Team.validatate_organization_exists], verbose_name='Organization'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OrganizationNotes',
|
||||
fields=[
|
||||
('modelnotes_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='core.modelnotes')),
|
||||
('model', models.ForeignKey(help_text='Model this note belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='access.organization', verbose_name='Model')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Organization Note',
|
||||
'verbose_name_plural': 'Organization Notes',
|
||||
'db_table': 'access_organization_notes',
|
||||
'ordering': ['-created'],
|
||||
},
|
||||
bases=('core.modelnotes',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TeamNotes',
|
||||
fields=[
|
||||
('modelnotes_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='core.modelnotes')),
|
||||
('model', models.ForeignKey(help_text='Model this note belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='access.team', verbose_name='Model')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Team Note',
|
||||
'verbose_name_plural': 'Team Notes',
|
||||
'db_table': 'access_team_notes',
|
||||
'ordering': ['-created'],
|
||||
},
|
||||
bases=('core.modelnotes',),
|
||||
),
|
||||
]
|
@ -1,43 +0,0 @@
|
||||
# Generated by Django 5.1.5 on 2025-02-20 13:25
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0003_alter_team_organization_organizationnotes_teamnotes'),
|
||||
('core', '0015_modelhistory_manufacturerhistory_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='OrganizationHistory',
|
||||
fields=[
|
||||
('modelhistory_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='core.modelhistory')),
|
||||
('model', models.ForeignKey(help_text='Model this note belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='history', to='access.organization', verbose_name='Model')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Organization History',
|
||||
'verbose_name_plural': 'Organization History',
|
||||
'db_table': 'access_organization_history',
|
||||
'ordering': ['-created'],
|
||||
},
|
||||
bases=('core.modelhistory',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TeamHistory',
|
||||
fields=[
|
||||
('modelhistory_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='core.modelhistory')),
|
||||
('model', models.ForeignKey(help_text='Model this note belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='history', to='access.team', verbose_name='Model')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Team History',
|
||||
'verbose_name_plural': 'Team History',
|
||||
'db_table': 'access_team_history',
|
||||
'ordering': ['-created'],
|
||||
},
|
||||
bases=('core.modelhistory',),
|
||||
),
|
||||
]
|
@ -1,140 +0,0 @@
|
||||
# Generated by Django 5.2 on 2025-04-10 02:34
|
||||
|
||||
import access.fields
|
||||
import access.models.tenancy
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0004_organizationhistory_teamhistory'),
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
('core', '0021_alter_ticketlinkeditem_item_type'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Entity',
|
||||
fields=[
|
||||
('is_global', models.BooleanField(default=False, help_text='Is this a global object?', verbose_name='Global Object')),
|
||||
('model_notes', models.TextField(blank=True, default=None, help_text='Tid bits of information', null=True, verbose_name='Notes')),
|
||||
('id', models.AutoField(help_text='Primary key of the entry', primary_key=True, serialize=False, unique=True, verbose_name='ID')),
|
||||
('entity_type', models.CharField(default='entity', help_text='Type this entity is', max_length=30, verbose_name='Entity Type')),
|
||||
('created', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, help_text='Date and time of creation', verbose_name='Created')),
|
||||
('modified', access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, help_text='Date and time of last modification', verbose_name='Modified')),
|
||||
('organization', models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='+', to='access.organization', validators=[access.models.tenancy.TenancyObject.validatate_organization_exists], verbose_name='Organization')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Entity',
|
||||
'verbose_name_plural': 'Entities',
|
||||
'ordering': ['created', 'modified', 'organization'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Person',
|
||||
fields=[
|
||||
('entity_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='access.entity')),
|
||||
('f_name', models.CharField(help_text='The persons first name', max_length=64, verbose_name='First Name')),
|
||||
('m_name', models.CharField(blank=True, default=None, help_text='The persons middle name(s)', max_length=100, null=True, verbose_name='Middle Name(s)')),
|
||||
('l_name', models.CharField(help_text='The persons Last name', max_length=64, verbose_name='Last Name')),
|
||||
('dob', models.DateField(blank=True, default=None, help_text='The Persons Date of Birth (DOB)', null=True, verbose_name='DOB')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Person',
|
||||
'verbose_name_plural': 'People',
|
||||
'ordering': ['l_name', 'm_name', 'f_name', 'dob'],
|
||||
},
|
||||
bases=('access.entity',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EntityHistory',
|
||||
fields=[
|
||||
('modelhistory_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='core.modelhistory')),
|
||||
('model', models.ForeignKey(help_text='Model this note belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='history', to='access.entity', verbose_name='Model')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Entity History',
|
||||
'verbose_name_plural': 'Entity History',
|
||||
'db_table': 'access_entity_history',
|
||||
'ordering': ['-created'],
|
||||
},
|
||||
bases=('core.modelhistory',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EntityNotes',
|
||||
fields=[
|
||||
('modelnotes_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='core.modelnotes')),
|
||||
('model', models.ForeignKey(help_text='Model this note belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='access.entity', verbose_name='Model')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Entity Note',
|
||||
'verbose_name_plural': 'Entity Notes',
|
||||
'db_table': 'access_entity_notes',
|
||||
'ordering': ['-created'],
|
||||
},
|
||||
bases=('core.modelnotes',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Role',
|
||||
fields=[
|
||||
('model_notes', models.TextField(blank=True, default=None, help_text='Tid bits of information', null=True, verbose_name='Notes')),
|
||||
('id', models.AutoField(help_text='Primary key of the entry', primary_key=True, serialize=False, unique=True, verbose_name='ID')),
|
||||
('name', models.CharField(help_text='Name of this role', max_length=30, verbose_name='Name')),
|
||||
('created', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, help_text='Date and time of creation', verbose_name='Created')),
|
||||
('modified', access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, help_text='Date and time of last modification', verbose_name='Modified')),
|
||||
('organization', models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='+', to='access.organization', validators=[access.models.tenancy.TenancyObject.validatate_organization_exists], verbose_name='Organization')),
|
||||
('permissions', models.ManyToManyField(blank=True, help_text='Permissions part of this role', related_name='roles', to='auth.permission', verbose_name='Permissions')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Role',
|
||||
'verbose_name_plural': 'Roles',
|
||||
'ordering': ['organization', 'name'],
|
||||
'unique_together': {('organization', 'name')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RoleHistory',
|
||||
fields=[
|
||||
('modelhistory_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='core.modelhistory')),
|
||||
('model', models.ForeignKey(help_text='Model this note belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='history', to='access.role', verbose_name='Model')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Role History',
|
||||
'verbose_name_plural': 'Role History',
|
||||
'db_table': 'access_role_history',
|
||||
'ordering': ['-created'],
|
||||
},
|
||||
bases=('core.modelhistory',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RoleNotes',
|
||||
fields=[
|
||||
('modelnotes_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='core.modelnotes')),
|
||||
('model', models.ForeignKey(help_text='Model this note belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='notes', to='access.role', verbose_name='Model')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Role Note',
|
||||
'verbose_name_plural': 'Role Notes',
|
||||
'db_table': 'access_role_notes',
|
||||
'ordering': ['-created'],
|
||||
},
|
||||
bases=('core.modelnotes',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Contact',
|
||||
fields=[
|
||||
('person_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='access.person')),
|
||||
('directory', models.BooleanField(blank=True, default=True, help_text='Show contact details in directory', verbose_name='Show in Directory')),
|
||||
('email', models.EmailField(help_text='E-mail address for this person', max_length=254, unique=True, verbose_name='E-Mail')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Contact',
|
||||
'verbose_name_plural': 'Contacts',
|
||||
'ordering': ['email'],
|
||||
},
|
||||
bases=('access.person',),
|
||||
),
|
||||
]
|
@ -1,25 +0,0 @@
|
||||
# Generated by Django 5.1.9 on 2025-05-14 11:06
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0005_entity_person_entityhistory_entitynotes_role_and_more'),
|
||||
('assistance', '0005_knowledgebasecategoryhistory_knowledgebasehistory'),
|
||||
('config_management', '0007_configgroupshistory_configgrouphostshistory_and_more'),
|
||||
('core', '0022_ticketcommentbase_ticketbase_ticketcommentsolution_and_more'),
|
||||
('devops', '0011_alter_gitgroup_unique_together_and_more'),
|
||||
('itam', '0010_alter_software_organization'),
|
||||
('itim', '0009_slmticket_requestticket'),
|
||||
('project_management', '0005_projecthistory_projectmilestonehistory_and_more'),
|
||||
('settings', '0011_appsettingshistory_externallinkhistory'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name = 'Organization',
|
||||
new_name = 'Tenant'
|
||||
),
|
||||
]
|
@ -1,47 +0,0 @@
|
||||
# Generated by Django 5.1.9 on 2025-05-14 13:48
|
||||
|
||||
import access.models.team
|
||||
import access.models.tenancy
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0007_rename_organization_tenant'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='tenant',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Tenant', 'verbose_name_plural': 'Tenants'},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='entity',
|
||||
name='organization',
|
||||
field=models.ForeignKey(help_text='Tenancy this belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='+', to='access.tenant', validators=[access.models.tenancy.TenancyObject.validatate_organization_exists], verbose_name='Tenant'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='role',
|
||||
name='organization',
|
||||
field=models.ForeignKey(help_text='Tenancy this belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='+', to='access.tenant', validators=[access.models.tenancy.TenancyObject.validatate_organization_exists], verbose_name='Tenant'),
|
||||
),
|
||||
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='tenant',
|
||||
name='manager',
|
||||
field=models.ForeignKey(help_text='Manager for this Tenancy', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Manager'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tenant',
|
||||
name='name',
|
||||
field=models.CharField(help_text='Name of this Tenancy', max_length=50, unique=True, verbose_name='Name'),
|
||||
),
|
||||
]
|
@ -1,135 +0,0 @@
|
||||
|
||||
from django.contrib.auth.models import ContentType, Permission
|
||||
from django.db import migrations
|
||||
|
||||
from access.models.team import Team
|
||||
|
||||
ContentType.DoesNotExist
|
||||
|
||||
def add_tenancy_permissions(apps, schema_editor):
|
||||
|
||||
print('')
|
||||
print(f"Begin permission migration for rename of Organization to Tenant.")
|
||||
|
||||
try:
|
||||
|
||||
add_permission = Permission.objects.get(
|
||||
codename = 'add_tenant',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = 'access',
|
||||
model = 'tenant',
|
||||
)
|
||||
)
|
||||
|
||||
change_permission = Permission.objects.get(
|
||||
codename = 'change_tenant',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = 'access',
|
||||
model = 'tenant',
|
||||
)
|
||||
)
|
||||
|
||||
delete_permission = Permission.objects.get(
|
||||
codename = 'delete_tenant',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = 'access',
|
||||
model = 'tenant',
|
||||
)
|
||||
)
|
||||
|
||||
view_permission = Permission.objects.get(
|
||||
codename = 'view_tenant',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = 'access',
|
||||
model = 'tenant',
|
||||
)
|
||||
)
|
||||
|
||||
print(f' Searching for Teams.')
|
||||
|
||||
teams = Team.objects.select_related('group_ptr__permissions')
|
||||
|
||||
print(f'Found {str(len(teams))} Teams.')
|
||||
|
||||
for team in teams:
|
||||
|
||||
print(f' Processing Team {str(team.team_name)}.')
|
||||
|
||||
permissions = team.group_ptr.permissions.all()
|
||||
|
||||
print(f' Searching for Organization Permissions.')
|
||||
print(f' Found {str(len(permissions))} Permissions.')
|
||||
|
||||
for permission in permissions:
|
||||
|
||||
if '_organization' not in permission.codename:
|
||||
|
||||
continue
|
||||
|
||||
action = str(permission.codename).split('_')[0]
|
||||
|
||||
print(f' Found Organization Permission {str(action)}')
|
||||
|
||||
if action == 'add':
|
||||
|
||||
team.group_ptr.permissions.add( add_permission )
|
||||
|
||||
print(f' Add Tenant Permission {str(action)}')
|
||||
|
||||
team.group_ptr.permissions.remove( permission )
|
||||
|
||||
print(f' Remove Organization Permission {str(action)}')
|
||||
|
||||
elif action == 'change':
|
||||
|
||||
team.group_ptr.permissions.add( change_permission )
|
||||
|
||||
print(f' Add Tenant Permission {str(action)}')
|
||||
|
||||
team.group_ptr.permissions.remove( permission )
|
||||
|
||||
print(f' Remove Organization Permission {str(action)}')
|
||||
|
||||
elif action == 'delete':
|
||||
|
||||
team.group_ptr.permissions.add( delete_permission )
|
||||
|
||||
print(f' Add Tenant Permission {str(action)}')
|
||||
|
||||
team.group_ptr.permissions.remove( permission )
|
||||
|
||||
print(f' Remove Organization Permission {str(action)}')
|
||||
|
||||
elif action == 'view':
|
||||
|
||||
team.group_ptr.permissions.add( view_permission )
|
||||
|
||||
print(f' Add Tenant Permission {str(action)}')
|
||||
|
||||
team.group_ptr.permissions.remove( permission )
|
||||
|
||||
print(f' Remove Organization Permission {str(action)}')
|
||||
|
||||
|
||||
print(f' Completed Team {str(team.team_name)}.')
|
||||
|
||||
except ContentType.DoesNotExist:
|
||||
# DB is new so no content types. no migration to be done.
|
||||
pass
|
||||
|
||||
print(' Permission Migration Actions Complete.')
|
||||
|
||||
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0008_alter_tenant_options_alter_entity_organization_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(add_tenancy_permissions),
|
||||
]
|
||||
|
||||
|
@ -1,43 +0,0 @@
|
||||
# Generated by Django 5.1.9 on 2025-05-16 09:58
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0009_migrate_organization_permission_tenant'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Company',
|
||||
fields=[
|
||||
('entity_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='access.entity')),
|
||||
('name', models.CharField(help_text='The name of this entity', max_length=80, verbose_name='Name')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Company',
|
||||
'verbose_name_plural': 'Companies',
|
||||
'ordering': ['name'],
|
||||
'sub_model_type': 'company',
|
||||
},
|
||||
bases=('access.entity',),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='entity',
|
||||
name='entity_type',
|
||||
field=models.CharField(help_text='Type this entity is', max_length=30, verbose_name='Entity Type'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='person',
|
||||
name='dob',
|
||||
field=models.DateField(blank=True, help_text='The Persons Date of Birth (DOB)', null=True, verbose_name='DOB'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='person',
|
||||
name='m_name',
|
||||
field=models.CharField(blank=True, help_text='The persons middle name(s)', max_length=100, null=True, verbose_name='Middle Name(s)'),
|
||||
),
|
||||
]
|
@ -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),
|
||||
)
|
||||
]
|
@ -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),
|
||||
),
|
||||
]
|
@ -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",
|
||||
),
|
||||
]
|
@ -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",
|
||||
),
|
||||
]
|
@ -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",
|
||||
),
|
||||
]
|
@ -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),
|
||||
),
|
||||
]
|
@ -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",
|
||||
),
|
||||
]
|
@ -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",),
|
||||
),
|
||||
]
|
@ -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",),
|
||||
),
|
||||
]
|
@ -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",
|
||||
),
|
||||
]
|
@ -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",),
|
||||
),
|
||||
]
|
362
app/access/mixin.py
Normal file
362
app/access/mixin.py
Normal file
@ -0,0 +1,362 @@
|
||||
|
||||
from django.contrib.auth.mixins import AccessMixin, PermissionRequiredMixin
|
||||
from django.contrib.auth.models import Group
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
from .models import Organization, Team
|
||||
|
||||
|
||||
class OrganizationMixin():
|
||||
"""Base Organization class"""
|
||||
|
||||
request = None
|
||||
|
||||
user_groups = []
|
||||
|
||||
|
||||
def get_parent_obj(self):
|
||||
""" Get the Parent Model Object
|
||||
|
||||
Use in views where the the model has no organization and the organization should be fetched from the parent model.
|
||||
|
||||
Requires attribute `parent_model` within the view with the value of the parent's model class
|
||||
|
||||
Returns:
|
||||
parent_model (Model): with PK from kwargs['pk']
|
||||
"""
|
||||
|
||||
return self.parent_model.objects.get(pk=self.kwargs['pk'])
|
||||
|
||||
|
||||
def object_organization(self) -> int:
|
||||
|
||||
id = None
|
||||
|
||||
try:
|
||||
|
||||
if hasattr(self, 'get_queryset'):
|
||||
self.get_queryset()
|
||||
|
||||
|
||||
if hasattr(self, 'parent_model'):
|
||||
obj = self.get_parent_obj()
|
||||
|
||||
id = obj.get_organization().id
|
||||
|
||||
if obj.is_global:
|
||||
|
||||
id = 0
|
||||
|
||||
|
||||
if hasattr(self, 'get_object') and id is None:
|
||||
|
||||
obj = self.get_object()
|
||||
|
||||
id = obj.get_organization().id
|
||||
|
||||
if hasattr(obj, 'is_global'):
|
||||
|
||||
if obj.is_global:
|
||||
|
||||
id = 0
|
||||
|
||||
|
||||
except AttributeError:
|
||||
|
||||
if self.request.method == 'POST':
|
||||
|
||||
if self.request.POST.get("organization", ""):
|
||||
|
||||
id = int(self.request.POST.get("organization", ""))
|
||||
|
||||
for field in self.request.POST.dict(): # cater for fields prefixed '<prefix>-<field name>'
|
||||
|
||||
a_field = str(field).split('-')
|
||||
|
||||
if len(a_field) == 2:
|
||||
|
||||
if a_field[1] == 'organization':
|
||||
|
||||
id = int(self.request.POST.get(field))
|
||||
|
||||
except:
|
||||
|
||||
pass
|
||||
|
||||
|
||||
return id
|
||||
|
||||
|
||||
def is_member(self, organization: int) -> bool:
|
||||
"""Returns true if the current user is a member of the organization
|
||||
|
||||
iterates over the user_organizations list and returns true if the user is a member
|
||||
|
||||
Returns:
|
||||
bool: _description_
|
||||
"""
|
||||
|
||||
is_member = False
|
||||
|
||||
if organization in self.user_organizations():
|
||||
|
||||
return True
|
||||
|
||||
return is_member
|
||||
|
||||
|
||||
def get_permission_required(self):
|
||||
"""
|
||||
Override of 'PermissionRequiredMixin' method so that this mixin can obtain the required permission.
|
||||
"""
|
||||
|
||||
if self.permission_required is None:
|
||||
raise ImproperlyConfigured(
|
||||
f"{self.__class__.__name__} is missing the "
|
||||
f"permission_required attribute. Define "
|
||||
f"{self.__class__.__name__}.permission_required, or override "
|
||||
f"{self.__class__.__name__}.get_permission_required()."
|
||||
)
|
||||
if isinstance(self.permission_required, str):
|
||||
perms = (self.permission_required,)
|
||||
else:
|
||||
perms = self.permission_required
|
||||
return perms
|
||||
|
||||
|
||||
@cached_property
|
||||
def is_manager(self) -> bool:
|
||||
""" Returns true if the current user is a member of the organization"""
|
||||
is_manager = False
|
||||
|
||||
return is_manager
|
||||
|
||||
|
||||
def user_organizations(self) -> list():
|
||||
"""Current Users organizations
|
||||
|
||||
Fetches the Organizations the user is apart of.
|
||||
|
||||
Get All groups the user is part of, fetch the associated team,
|
||||
iterate over the results adding the organization ID to a list to be returned.
|
||||
|
||||
Returns:
|
||||
_type_: User Organizations.
|
||||
"""
|
||||
|
||||
user_organizations = []
|
||||
|
||||
teams = Team.objects
|
||||
|
||||
for group in self.request.user.groups.all():
|
||||
|
||||
team = teams.get(pk=group.id)
|
||||
|
||||
self.user_groups = self.user_groups + [group.id]
|
||||
|
||||
user_organizations = user_organizations + [team.organization.id]
|
||||
|
||||
return user_organizations
|
||||
|
||||
|
||||
# ToDo: Ensure that the group has access to item
|
||||
def has_organization_permission(self, organization: int=None) -> bool:
|
||||
|
||||
has_permission = False
|
||||
|
||||
if not organization:
|
||||
|
||||
organization = self.object_organization()
|
||||
|
||||
if self.is_member(organization) or organization == 0:
|
||||
|
||||
groups = Group.objects.filter(pk__in=self.user_groups)
|
||||
|
||||
for group in groups:
|
||||
|
||||
team = Team.objects.filter(pk=group.id)
|
||||
team = team.values('organization_id').get()
|
||||
|
||||
for permission in group.permissions.values('content_type__app_label', 'codename').all():
|
||||
|
||||
assembled_permission = str(permission["content_type__app_label"]) + '.' + str(permission["codename"])
|
||||
|
||||
if assembled_permission in self.get_permission_required() and (team['organization_id'] == organization or organization == 0):
|
||||
|
||||
return True
|
||||
|
||||
return has_permission
|
||||
|
||||
|
||||
def permission_check(self, request, permissions_required: list = None) -> bool:
|
||||
|
||||
self.request = request
|
||||
|
||||
if permissions_required:
|
||||
|
||||
self.permission_required = permissions_required
|
||||
|
||||
organization_manager_models = [
|
||||
'access.organization',
|
||||
'access.team',
|
||||
'access.teamusers',
|
||||
]
|
||||
|
||||
is_organization_manager = False
|
||||
|
||||
queryset = None
|
||||
|
||||
if hasattr(self, 'get_queryset'):
|
||||
|
||||
queryset = self.get_queryset()
|
||||
|
||||
obj = None
|
||||
|
||||
if hasattr(self, 'get_object'):
|
||||
|
||||
|
||||
try:
|
||||
|
||||
obj = self.get_object()
|
||||
|
||||
except:
|
||||
|
||||
pass
|
||||
|
||||
|
||||
if hasattr(self, 'model'):
|
||||
|
||||
if self.model._meta.label_lower in organization_manager_models:
|
||||
|
||||
organization = Organization.objects.get(pk=self.object_organization())
|
||||
|
||||
if organization.manager == request.user:
|
||||
|
||||
is_organization_manager = True
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if request.user.is_superuser:
|
||||
|
||||
return True
|
||||
|
||||
perms = self.get_permission_required()
|
||||
|
||||
if self.has_organization_permission():
|
||||
|
||||
return True
|
||||
|
||||
if self.request.user.has_perms(perms) and len(self.kwargs) == 0 and str(self.request.method).lower() == 'get':
|
||||
|
||||
return True
|
||||
|
||||
for required_permission in self.permission_required:
|
||||
|
||||
if required_permission.replace(
|
||||
'view_', ''
|
||||
) == 'access.organization' and len(self.kwargs) == 0:
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
|
||||
class OrganizationPermission(AccessMixin, OrganizationMixin):
|
||||
"""## Permission Checking
|
||||
|
||||
The base django permissions have not been modified with this app providing Multi-Tenancy. This is done by a mixin, that checks if the item is apart of an organization, if it is; confirmation is made that the user is part of the same organization and as long as they have the correct permission within the organization, access is granted.
|
||||
|
||||
|
||||
### How it works
|
||||
|
||||
The overall permissions system of django has not been modified with it remaining fully functional. The multi-tenancy has been setup based off of an organization with teams. A team to the underlying django system is an extension of the django auth group and for every team created a django auth group is created. THe group name is set using the following format: `<organization>_<team name>` and contains underscores `_` instead of spaces.
|
||||
|
||||
A User who is added to an team as a "Manager" can modify the team members or if they have permission `access.change_team` which also allows the changing of team permissions. Modification of an organization can be done by the django administrator (super user) or any user with permission `access._change_organization`.
|
||||
|
||||
Items can be set as `Global`, meaning that all users who have the correct permission regardless of organization will be able to take action against the object.
|
||||
|
||||
Permissions that can be modified for a team have been limited to application permissions only unless adjust the permissions from the django admin site.
|
||||
|
||||
|
||||
### Multi-Tenancy workflow
|
||||
|
||||
The workflow is conducted as part of the view and has the following flow:
|
||||
|
||||
1. Checks if user is member of organization the object the action is being performed on. Will also return true if the object has field `is_global` set to `true`.
|
||||
|
||||
1. Fetches all teams the user is part of.
|
||||
|
||||
1. obtains all permissions that are linked to the team.
|
||||
|
||||
1. checks if user has the required permission for the action.
|
||||
|
||||
1. confirms that the team the permission came from is part of the same organization as the object the action is being conducted on.
|
||||
|
||||
1. ONLY on success of the above items, grants access.
|
||||
"""
|
||||
|
||||
permission_required: list = []
|
||||
""" Permission required for the view
|
||||
|
||||
Not specifying this property adjusts the permission check logic so that you can
|
||||
use the `permission_check()` function directly.
|
||||
|
||||
An example of a get request....
|
||||
|
||||
``` py
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
|
||||
return self.handle_no_permission()
|
||||
|
||||
if not self.permission_check(request, [ 'access.view_organization' ]):
|
||||
|
||||
raise PermissionDenied('You are not part of this organization')
|
||||
|
||||
return super().get(request, *args, **kwargs)
|
||||
```
|
||||
this example details manual usage of the `permission_check()` function for a get request.
|
||||
"""
|
||||
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
return self.handle_no_permission()
|
||||
|
||||
if len(self.permission_required) > 0:
|
||||
|
||||
non_organization_models = [
|
||||
'TaskResult'
|
||||
]
|
||||
|
||||
if hasattr(self, 'model'):
|
||||
|
||||
|
||||
if hasattr(self.model, '__name__'):
|
||||
|
||||
if self.model.__name__ in non_organization_models:
|
||||
|
||||
if hasattr(self, 'get_object'):
|
||||
|
||||
self.get_object()
|
||||
|
||||
perms = self.get_permission_required()
|
||||
|
||||
|
||||
if not self.request.user.has_perms(perms):
|
||||
|
||||
return self.handle_no_permission()
|
||||
|
||||
return super().dispatch(self.request, *args, **kwargs)
|
||||
|
||||
|
||||
if not self.permission_check(request):
|
||||
|
||||
raise PermissionDenied('You are not part of this organization')
|
||||
|
||||
return super().dispatch(self.request, *args, **kwargs)
|
@ -1,274 +0,0 @@
|
||||
import django
|
||||
|
||||
from django.db import models
|
||||
|
||||
from access.models.tenant import Tenant as Organization
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
|
||||
|
||||
class OrganizationMixin:
|
||||
"""Organization Tenancy Mixin
|
||||
|
||||
This class is intended to be included in **ALL** View / Viewset classes as
|
||||
it contains the functions/methods required to conduct the permission
|
||||
checking.
|
||||
"""
|
||||
|
||||
|
||||
_obj_organization: int = None
|
||||
"""Cached Object Organization"""
|
||||
|
||||
def get_obj_organization(self, obj = None, request = None) -> Organization:
|
||||
"""Fetch the objects Organization
|
||||
|
||||
Args:
|
||||
obj (Model): Model of object
|
||||
|
||||
Raises:
|
||||
ValueError: When `obj` and `request` are both missing
|
||||
|
||||
Returns:
|
||||
Organization: Organization the object is from
|
||||
None: No Organization was found
|
||||
"""
|
||||
|
||||
if obj is None and request is None:
|
||||
|
||||
raise ValueError('Missing Parameter. obj or request must be supplied')
|
||||
|
||||
|
||||
if self._obj_organization:
|
||||
|
||||
return self._obj_organization
|
||||
|
||||
|
||||
if obj:
|
||||
|
||||
self._obj_organization = getattr(obj, 'organization', None)
|
||||
|
||||
|
||||
if not self._obj_organization:
|
||||
|
||||
self._obj_organization = getattr(obj, 'get_organization', lambda: None)()
|
||||
|
||||
elif (
|
||||
request
|
||||
and not self.kwargs.get('pk', None)
|
||||
):
|
||||
|
||||
if getattr(request.stream, 'method', '') != 'DELETE':
|
||||
|
||||
data = getattr(request, 'data', None)
|
||||
|
||||
if data:
|
||||
|
||||
data_organization = self.kwargs.get('organization_id', None)
|
||||
|
||||
if not data_organization:
|
||||
|
||||
data_organization = request.data.get('organization_id', None)
|
||||
|
||||
|
||||
if not data_organization:
|
||||
|
||||
data_organization = request.data.get('organization', None)
|
||||
|
||||
|
||||
if data_organization:
|
||||
|
||||
self._obj_organization = Organization.objects.get(
|
||||
pk = int( data_organization )
|
||||
)
|
||||
|
||||
elif self.kwargs.get('pk', None):
|
||||
|
||||
|
||||
obj = self.model.objects.get( pk = self.kwargs.get('pk', None) )
|
||||
|
||||
if getattr(obj, 'organization', None):
|
||||
|
||||
self._obj_organization = obj.organization
|
||||
|
||||
elif str(self.model._meta.verbose_name).lower() == 'tenant':
|
||||
|
||||
self._obj_organization = obj
|
||||
|
||||
|
||||
if self.get_parent_model(): # if defined is to overwrite object organization
|
||||
|
||||
parent_obj = self.get_parent_obj()
|
||||
|
||||
self._obj_organization = parent_obj.get_organization()
|
||||
|
||||
|
||||
return self._obj_organization
|
||||
|
||||
|
||||
|
||||
def get_parent_model(self):
|
||||
"""Get the Parent Model
|
||||
|
||||
This function exists so that dynamic parent models can be defined.
|
||||
They are defined by overriding this method.
|
||||
|
||||
Returns:
|
||||
Model: Parent Model
|
||||
"""
|
||||
|
||||
return self.parent_model
|
||||
|
||||
|
||||
|
||||
def get_parent_obj(self):
|
||||
""" Get the Parent Model Object
|
||||
|
||||
Use in views where the the model has no organization and the organization should be fetched from the parent model.
|
||||
|
||||
Requires attribute `parent_model` within the view with the value of the parent's model class
|
||||
|
||||
Returns:
|
||||
parent_model (Model): with PK from kwargs['pk']
|
||||
"""
|
||||
|
||||
return self.get_parent_model().objects.get(pk=self.kwargs[self.parent_model_pk_kwarg])
|
||||
|
||||
|
||||
|
||||
def get_permission_organizations(self, permission: str ) -> list([ int ]):
|
||||
"""Return Organization(s) the permission belongs to
|
||||
|
||||
Searches the users organizations for the required permission, if found
|
||||
the organization is added to the list to return.
|
||||
|
||||
Args:
|
||||
permission (str): Permission to search users organizations for
|
||||
|
||||
Returns:
|
||||
Organizations (list): All Organizations where the permission was found.
|
||||
"""
|
||||
|
||||
_permission_organizations: list = []
|
||||
|
||||
for team in self.request.tenancy._user_teams:
|
||||
|
||||
for team_permission in team.permissions.all():
|
||||
|
||||
permission_value = str( team_permission.content_type.app_label + '.' + team_permission.codename )
|
||||
|
||||
if permission_value == permission:
|
||||
|
||||
_permission_organizations += [ team.organization.id ]
|
||||
|
||||
|
||||
return _permission_organizations
|
||||
|
||||
|
||||
_permission_required: str = None
|
||||
"""Cached Permissions required"""
|
||||
|
||||
|
||||
def get_permission_required(self) -> str:
|
||||
""" Get / Generate Permission Required
|
||||
|
||||
If there is a requirement that there be custom/dynamic permissions,
|
||||
this function can be safely overridden.
|
||||
|
||||
Raises:
|
||||
ValueError: Unable to determin the view action
|
||||
|
||||
Returns:
|
||||
str: Permission in format `<app_name>.<action>_<model_name>`
|
||||
"""
|
||||
|
||||
if self._permission_required:
|
||||
|
||||
return self._permission_required
|
||||
|
||||
|
||||
if hasattr(self, 'get_dynamic_permissions'):
|
||||
|
||||
self._permission_required = self.get_dynamic_permissions()
|
||||
|
||||
if type(self._permission_required) is list:
|
||||
|
||||
self._permission_required = self._permission_required[0]
|
||||
|
||||
return self._permission_required
|
||||
|
||||
|
||||
view_action: str = None
|
||||
|
||||
if(
|
||||
self.action == 'create'
|
||||
or getattr(self.request._stream, 'method', '') == 'POST'
|
||||
):
|
||||
|
||||
view_action = 'add'
|
||||
|
||||
elif (
|
||||
self.action == 'partial_update'
|
||||
or self.action == 'update'
|
||||
or getattr(self.request._stream, 'method', '') == 'PATCH'
|
||||
or getattr(self.request._stream, 'method', '') == 'PUT'
|
||||
):
|
||||
|
||||
view_action = 'change'
|
||||
|
||||
elif(
|
||||
self.action == 'destroy'
|
||||
or getattr(self.request._stream, 'method', '') == 'DELETE'
|
||||
):
|
||||
|
||||
view_action = 'delete'
|
||||
|
||||
elif (
|
||||
self.action == 'list'
|
||||
):
|
||||
|
||||
view_action = 'view'
|
||||
|
||||
elif self.action == 'retrieve':
|
||||
|
||||
view_action = 'view'
|
||||
|
||||
elif self.action == 'metadata':
|
||||
|
||||
view_action = 'view'
|
||||
|
||||
elif self.action is None:
|
||||
|
||||
return False
|
||||
|
||||
|
||||
|
||||
if view_action is None:
|
||||
|
||||
raise ValueError('view_action could not be defined.')
|
||||
|
||||
|
||||
permission = self.model._meta.app_label + '.' + view_action + '_' + self.model._meta.model_name
|
||||
|
||||
permission_required = permission
|
||||
|
||||
|
||||
self._permission_required = permission_required
|
||||
|
||||
return self._permission_required
|
||||
|
||||
|
||||
|
||||
parent_model: models.Model = None
|
||||
""" Parent Model
|
||||
|
||||
This attribute defines the parent model for the model in question. The parent model when defined
|
||||
will be used as the object to obtain the permissions from.
|
||||
"""
|
||||
|
||||
|
||||
parent_model_pk_kwarg: str = 'pk'
|
||||
"""Parent Model kwarg
|
||||
|
||||
This value is used to define the kwarg that is used as the parent objects primary key (pk).
|
||||
"""
|
@ -1,326 +0,0 @@
|
||||
import traceback
|
||||
|
||||
from rest_framework.permissions import DjangoObjectPermissions
|
||||
|
||||
from access.models.tenancy import Tenant
|
||||
|
||||
from core import exceptions as centurion_exceptions
|
||||
from core.mixins.centurion import Centurion
|
||||
|
||||
|
||||
|
||||
class OrganizationPermissionMixin(
|
||||
DjangoObjectPermissions,
|
||||
):
|
||||
"""Tenant Permission Mixin
|
||||
|
||||
This class is to be used as the permission class for API `Views`/`ViewSets`.
|
||||
In combination with the `TenantPermissionsMixin`, permission checking
|
||||
will be done to ensure the user has the correct permissions to perform the
|
||||
CRUD operation.
|
||||
|
||||
**Note:** If the user is not authenticated, they will be denied access
|
||||
globally.
|
||||
|
||||
Permissions are broken down into two areas:
|
||||
|
||||
- `Tenancy` Objects
|
||||
|
||||
This object requires that the user have the correct permission and that
|
||||
permission be assigned within the organiztion the object belongs to.
|
||||
|
||||
- `Non-Tenancy` Objects.
|
||||
|
||||
This object requires the the use have the correct permission assigned,
|
||||
regardless of the organization the object is from. This includes objects
|
||||
that have no organization.
|
||||
|
||||
"""
|
||||
|
||||
_is_tenancy_model: bool = None
|
||||
|
||||
def is_tenancy_model(self, view) -> bool:
|
||||
"""Determin if the Model is a `Tenancy` Model
|
||||
|
||||
Will look at the model defined within the view unless a parent
|
||||
model is found. If the latter is true, the parent_model will be used to
|
||||
determin if the model is a `Tenancy` model
|
||||
|
||||
Args:
|
||||
view (object): The View the HTTP request was mad to
|
||||
|
||||
Returns:
|
||||
True (bool): Model is a Tenancy Model.
|
||||
False (bool): Model is not a Tenancy model.
|
||||
"""
|
||||
|
||||
if not self._is_tenancy_model:
|
||||
|
||||
if hasattr(view, 'model'):
|
||||
|
||||
self._is_tenancy_model = issubclass(view.model, Centurion)
|
||||
|
||||
if view.get_parent_model():
|
||||
|
||||
self._is_tenancy_model = issubclass(view.get_parent_model(), Centurion)
|
||||
|
||||
return self._is_tenancy_model
|
||||
|
||||
|
||||
|
||||
def has_permission(self, request, view):
|
||||
""" Check if user has the required permission
|
||||
|
||||
Permission flow is as follows:
|
||||
|
||||
- Un-authenticated users. Access Denied
|
||||
|
||||
- Authenticated user whom make a request using wrong method. Access
|
||||
Denied
|
||||
|
||||
- Authenticated user who is not in same organization as object. Access
|
||||
Denied
|
||||
|
||||
- Authenticated user who is in same organization as object, however is
|
||||
missing the correct permission. Access Denied
|
||||
|
||||
Depending upon user type, they will recieve different feedback. In order
|
||||
they are:
|
||||
|
||||
- Non-authenticated users will **always** recieve HTTP/401
|
||||
|
||||
- Authenticated users who use an unsupported method, HTTP/405
|
||||
|
||||
- Authenticated users missing the correct permission recieve HTTP/403
|
||||
|
||||
Args:
|
||||
request (object): The HTTP Request Object
|
||||
view (_type_): The View/Viewset Object the request was made to
|
||||
|
||||
Raises:
|
||||
PermissionDenied: User does not have the required permission.
|
||||
NotAuthenticated: User is not logged into Centurion.
|
||||
ValueError: Could not determin the view action.
|
||||
|
||||
Returns:
|
||||
True (bool): User has the required permission.
|
||||
False (bool): User does not have the required permission
|
||||
"""
|
||||
|
||||
if request.user.is_anonymous:
|
||||
|
||||
raise centurion_exceptions.NotAuthenticated()
|
||||
|
||||
|
||||
if request.method not in view.allowed_methods:
|
||||
|
||||
raise centurion_exceptions.MethodNotAllowed(method = request.method)
|
||||
|
||||
|
||||
try:
|
||||
|
||||
if (
|
||||
(
|
||||
view.model.__name__ == 'UserSettings'
|
||||
and request._user.id == int(view.kwargs.get('pk', 0))
|
||||
)
|
||||
or (
|
||||
view.model.__name__ == 'AuthToken'
|
||||
and request._user.id == int(view.kwargs.get('model_id', 0))
|
||||
)
|
||||
):
|
||||
|
||||
return True
|
||||
|
||||
elif (
|
||||
(
|
||||
view.model.__name__ == 'UserSettings'
|
||||
and request._user.id != int(view.kwargs.get('pk', 0))
|
||||
)
|
||||
or (
|
||||
view.model.__name__ == 'AuthToken'
|
||||
and request._user.id != int(view.kwargs.get('model_id', 0))
|
||||
)
|
||||
):
|
||||
|
||||
|
||||
return False
|
||||
|
||||
|
||||
has_permission_required: bool = False
|
||||
|
||||
user_permissions = request.tenancy._user_permissions
|
||||
|
||||
permission_required = view.get_permission_required()
|
||||
|
||||
if permission_required and user_permissions:
|
||||
# No permission_required couldnt get permissions
|
||||
# No user_permissions, user missing the required permission
|
||||
|
||||
has_permission_required: bool = permission_required in user_permissions
|
||||
|
||||
|
||||
if not has_permission_required and not request.user.is_superuser:
|
||||
|
||||
raise centurion_exceptions.PermissionDenied()
|
||||
|
||||
|
||||
obj_organization: Tenant = view.get_obj_organization(
|
||||
request = request
|
||||
)
|
||||
|
||||
view_action: str = None
|
||||
|
||||
if(
|
||||
view.action == 'create'
|
||||
and request.method == 'POST'
|
||||
):
|
||||
|
||||
view_action = 'add'
|
||||
|
||||
elif(
|
||||
view.action == 'destroy'
|
||||
and request.method == 'DELETE'
|
||||
):
|
||||
|
||||
view_action = 'delete'
|
||||
|
||||
elif (
|
||||
view.action == 'list'
|
||||
):
|
||||
|
||||
view_action = 'view'
|
||||
|
||||
elif (
|
||||
view.action == 'partial_update'
|
||||
and request.method == 'PATCH'
|
||||
):
|
||||
|
||||
view_action = 'change'
|
||||
|
||||
elif (
|
||||
view.action == 'update'
|
||||
and request.method == 'PUT'
|
||||
):
|
||||
|
||||
view_action = 'change'
|
||||
|
||||
elif(
|
||||
view.action == 'retrieve'
|
||||
and request.method == 'GET'
|
||||
):
|
||||
|
||||
view_action = 'view'
|
||||
|
||||
elif(
|
||||
view.action == 'metadata'
|
||||
and request.method == 'OPTIONS'
|
||||
):
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if view_action is None:
|
||||
|
||||
raise ValueError('view_action could not be defined.')
|
||||
|
||||
|
||||
if obj_organization is None or request.user.is_superuser:
|
||||
|
||||
return True
|
||||
|
||||
elif obj_organization is not None:
|
||||
|
||||
if request.tenancy.has_organization_permission(
|
||||
organization = obj_organization,
|
||||
permissions_required = view.get_permission_required()
|
||||
):
|
||||
|
||||
return True
|
||||
|
||||
|
||||
except ValueError as e:
|
||||
|
||||
# ToDo: This exception could be used in traces as it provides
|
||||
# information as to dodgy requests. This exception is raised
|
||||
# when the method does not match the view action.
|
||||
|
||||
print(traceback.format_exc())
|
||||
|
||||
except centurion_exceptions.Http404 as e:
|
||||
# This exception genrally means that the user is not in the same
|
||||
# organization as the object as objects are filtered to users
|
||||
# organizations ONLY.
|
||||
|
||||
pass
|
||||
|
||||
except centurion_exceptions.ObjectDoesNotExist as e:
|
||||
# This exception genrally means that the user is not in the same
|
||||
# organization as the object as objects are filtered to users
|
||||
# organizations ONLY.
|
||||
|
||||
pass
|
||||
|
||||
except centurion_exceptions.PermissionDenied as e:
|
||||
# This Exception will be raised after this function has returned
|
||||
# False.
|
||||
|
||||
pass
|
||||
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
|
||||
try:
|
||||
|
||||
|
||||
if request.user.is_anonymous:
|
||||
|
||||
return False
|
||||
|
||||
|
||||
if (
|
||||
(
|
||||
view.model.__name__ == 'UserSettings'
|
||||
and request._user.id == int(view.kwargs.get('pk', 0))
|
||||
)
|
||||
or (
|
||||
view.model.__name__ == 'AuthToken'
|
||||
and request._user.id == int(view.kwargs.get('model_id', 0))
|
||||
)
|
||||
or ( # org=None is the application wide settings.
|
||||
view.model.__name__ == 'AppSettings'
|
||||
and request.user.is_superuser
|
||||
and obj.organization is None
|
||||
)
|
||||
):
|
||||
|
||||
return True
|
||||
|
||||
|
||||
object_organization = view._obj_organization
|
||||
|
||||
if object_organization:
|
||||
|
||||
if(
|
||||
int(object_organization)
|
||||
in view.get_permission_organizations( view.get_permission_required() )
|
||||
or request.user.is_superuser
|
||||
or getattr(request.app_settings.global_organization, 'id', 0) == int(object_organization)
|
||||
):
|
||||
|
||||
return True
|
||||
|
||||
|
||||
elif not self.is_tenancy_model( view ) or request.user.is_superuser:
|
||||
|
||||
return True
|
||||
|
||||
|
||||
except Exception as e:
|
||||
|
||||
print(traceback.format_exc())
|
||||
|
||||
return False
|
324
app/access/models.py
Normal file
324
app/access/models.py
Normal file
@ -0,0 +1,324 @@
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User, Group, Permission
|
||||
from django.forms import ValidationError
|
||||
|
||||
from .fields import *
|
||||
|
||||
from core.middleware.get_request import get_request
|
||||
from core.mixin.history_save import SaveHistory
|
||||
|
||||
|
||||
class Organization(SaveHistory):
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = "Organizations"
|
||||
ordering = ['name']
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
if self.slug == '_':
|
||||
self.slug = self.name.lower().replace(' ', '_')
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
id = models.AutoField(
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
blank=False
|
||||
)
|
||||
|
||||
name = models.CharField(
|
||||
blank = False,
|
||||
max_length = 50,
|
||||
unique = True,
|
||||
)
|
||||
|
||||
manager = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.SET_NULL,
|
||||
blank = False,
|
||||
null = True,
|
||||
help_text = 'Organization Manager'
|
||||
)
|
||||
|
||||
model_notes = models.TextField(
|
||||
blank = True,
|
||||
default = None,
|
||||
null= True,
|
||||
verbose_name = 'Notes',
|
||||
)
|
||||
|
||||
slug = AutoSlugField()
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
|
||||
def get_organization(self):
|
||||
return self
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
|
||||
class TenancyManager(models.Manager):
|
||||
"""Multi-Tennant Object Manager
|
||||
|
||||
This manager specifically caters for the multi-tenancy features of Centurion ERP.
|
||||
"""
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
""" Fetch the data
|
||||
|
||||
This function filters the data fetched from the database to that which is from the organizations
|
||||
the user is a part of.
|
||||
|
||||
!!! danger "Requirement"
|
||||
This method may be overridden however must still be called from the overriding function. i.e. `super().get_queryset()`
|
||||
|
||||
## Workflow
|
||||
|
||||
This functions workflow is as follows:
|
||||
|
||||
- Fetch the user from the request
|
||||
|
||||
- Check if the user is authenticated
|
||||
|
||||
- Iterate over the users teams
|
||||
|
||||
- Store unique organizations from users teams
|
||||
|
||||
- return results
|
||||
|
||||
Returns:
|
||||
(queryset): **super user**: return unfiltered data.
|
||||
(queryset): **not super user**: return data from the stored unique organizations.
|
||||
"""
|
||||
|
||||
request = get_request()
|
||||
|
||||
user_organizations: list(str()) = []
|
||||
|
||||
|
||||
if request:
|
||||
|
||||
user = request.user._wrapped if hasattr(request.user,'_wrapped') else request.user
|
||||
|
||||
|
||||
if user.is_authenticated:
|
||||
|
||||
for team_user in TeamUsers.objects.filter(user=user):
|
||||
|
||||
|
||||
if team_user.team.organization.name not in user_organizations:
|
||||
|
||||
|
||||
if not user_organizations:
|
||||
|
||||
self.user_organizations = []
|
||||
|
||||
user_organizations += [ team_user.team.organization.id ]
|
||||
|
||||
|
||||
if len(user_organizations) > 0 and not user.is_superuser:
|
||||
|
||||
return super().get_queryset().filter(
|
||||
models.Q(organization__in=user_organizations)
|
||||
|
|
||||
models.Q(is_global = True)
|
||||
)
|
||||
|
||||
return super().get_queryset()
|
||||
|
||||
|
||||
|
||||
class TenancyObject(SaveHistory):
|
||||
""" Tenancy Model Abstrct class.
|
||||
|
||||
This class is for inclusion wihtin **every** model within Centurion ERP.
|
||||
Provides the required fields, functions and methods for multi tennant objects.
|
||||
Unless otherwise stated, **no** object within this class may be overridden.
|
||||
|
||||
Raises:
|
||||
ValidationError: User failed to supply organization
|
||||
"""
|
||||
|
||||
objects = TenancyManager()
|
||||
""" Multi-Tenanant Objects """
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
def validatate_organization_exists(self):
|
||||
"""Ensure that the user did provide an organization
|
||||
|
||||
Raises:
|
||||
ValidationError: User failed to supply organization.
|
||||
"""
|
||||
|
||||
if not self:
|
||||
raise ValidationError('You must provide an organization')
|
||||
|
||||
|
||||
organization = models.ForeignKey(
|
||||
Organization,
|
||||
on_delete=models.CASCADE,
|
||||
blank = False,
|
||||
null = True,
|
||||
validators = [validatate_organization_exists],
|
||||
)
|
||||
|
||||
is_global = models.BooleanField(
|
||||
default = False,
|
||||
blank = False
|
||||
)
|
||||
|
||||
model_notes = models.TextField(
|
||||
blank = True,
|
||||
default = None,
|
||||
null= True,
|
||||
verbose_name = 'Notes',
|
||||
)
|
||||
|
||||
def get_organization(self) -> Organization:
|
||||
return self.organization
|
||||
|
||||
|
||||
|
||||
class Team(Group, TenancyObject):
|
||||
class Meta:
|
||||
# proxy = True
|
||||
verbose_name_plural = "Teams"
|
||||
ordering = ['team_name']
|
||||
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
|
||||
self.name = self.organization.name.lower().replace(' ', '_') + '_' + self.team_name.lower().replace(' ', '_')
|
||||
|
||||
super().save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
|
||||
|
||||
|
||||
team_name = models.CharField(
|
||||
verbose_name = 'Name',
|
||||
blank = False,
|
||||
max_length = 50,
|
||||
unique = False,
|
||||
default = ''
|
||||
)
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
|
||||
@property
|
||||
def parent_object(self):
|
||||
""" Fetch the parent object """
|
||||
|
||||
return self.organization
|
||||
|
||||
|
||||
def permission_list(self) -> list:
|
||||
|
||||
permission_list = []
|
||||
|
||||
for permission in self.permissions.all():
|
||||
|
||||
if str(permission.content_type.app_label + '.' + permission.codename) in permission_list:
|
||||
continue
|
||||
|
||||
permission_list += [ str(permission.content_type.app_label + '.' + permission.codename) ]
|
||||
|
||||
return [permission_list, self.permissions.all()]
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self.team_name
|
||||
|
||||
|
||||
|
||||
class TeamUsers(SaveHistory):
|
||||
|
||||
class Meta:
|
||||
# proxy = True
|
||||
verbose_name_plural = "Team Users"
|
||||
ordering = ['user']
|
||||
|
||||
id = models.AutoField(
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
blank=False
|
||||
)
|
||||
|
||||
team = models.ForeignKey(
|
||||
Team,
|
||||
related_name="team",
|
||||
on_delete=models.CASCADE)
|
||||
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE
|
||||
)
|
||||
|
||||
manager = models.BooleanField(
|
||||
verbose_name='manager',
|
||||
default=False,
|
||||
blank=True
|
||||
)
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
""" Delete Team
|
||||
|
||||
Overrides, post-action
|
||||
As teams are an extension of Groups, remove the user to the team.
|
||||
"""
|
||||
|
||||
super().delete(using=using, keep_parents=keep_parents)
|
||||
|
||||
group = Group.objects.get(pk=self.team.id)
|
||||
|
||||
user = User.objects.get(pk=self.user_id)
|
||||
|
||||
user.groups.remove(group)
|
||||
|
||||
|
||||
def get_organization(self) -> Organization:
|
||||
return self.team.organization
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
""" Save Team
|
||||
|
||||
Overrides, post-action
|
||||
As teams are an extension of groups, add the user to the matching group.
|
||||
"""
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
group = Group.objects.get(pk=self.team.id)
|
||||
|
||||
user = User.objects.get(pk=self.user_id)
|
||||
|
||||
user.groups.add(group)
|
||||
|
||||
|
||||
@property
|
||||
def parent_object(self):
|
||||
""" Fetch the parent object """
|
||||
|
||||
return self.team
|
||||
|
||||
def __str__(self):
|
||||
return self.user.username
|
||||
|
@ -1,3 +0,0 @@
|
||||
from .organization_history import OrganizationHistory # pylint: disable=W0611:unused-import
|
||||
|
||||
from .organization_notes import OrganizationNotes # pylint: disable=W0611:unused-import
|
@ -1,97 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
from access.models.entity import Entity
|
||||
|
||||
|
||||
|
||||
class Company(
|
||||
Entity
|
||||
):
|
||||
# This model is intended to be called `Organization`, however at the time of
|
||||
# creation this was not possible as Tenant (ne Organization) still has
|
||||
# references in code to `organization` witch clashes with the intended name of
|
||||
# this model.
|
||||
|
||||
_is_submodel = True
|
||||
|
||||
documentation = ''
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
ordering = [
|
||||
'name',
|
||||
]
|
||||
|
||||
sub_model_type = 'company'
|
||||
|
||||
verbose_name = 'Company'
|
||||
|
||||
verbose_name_plural = 'Companies'
|
||||
|
||||
|
||||
name = models.CharField(
|
||||
blank = False,
|
||||
help_text = 'The name of this entity',
|
||||
max_length = 80,
|
||||
unique = False,
|
||||
verbose_name = 'Name'
|
||||
)
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
return self.name
|
||||
|
||||
|
||||
page_layout: dict = [
|
||||
{
|
||||
"name": "Details",
|
||||
"slug": "details",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "double",
|
||||
"left": [
|
||||
'organization',
|
||||
'name',
|
||||
],
|
||||
"right": [
|
||||
'model_notes',
|
||||
'created',
|
||||
'modified',
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Knowledge Base",
|
||||
"slug": "kb_articles",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "knowledge_base",
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Tickets",
|
||||
"slug": "tickets",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "tickets",
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Notes",
|
||||
"slug": "notes",
|
||||
"sections": []
|
||||
},
|
||||
]
|
||||
|
||||
table_fields: list = [
|
||||
'name',
|
||||
'organization',
|
||||
'created',
|
||||
]
|
@ -1,119 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
from access.models.person import Person
|
||||
|
||||
|
||||
|
||||
class Contact(
|
||||
Person
|
||||
):
|
||||
|
||||
documentation = ''
|
||||
|
||||
_is_submodel = True
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
ordering = [
|
||||
'email',
|
||||
]
|
||||
|
||||
sub_model_type = 'contact'
|
||||
|
||||
verbose_name = 'Contact'
|
||||
|
||||
verbose_name_plural = 'Contacts'
|
||||
|
||||
|
||||
directory = models.BooleanField(
|
||||
blank = True,
|
||||
default = True,
|
||||
help_text = 'Show contact details in directory',
|
||||
null = False,
|
||||
verbose_name = 'Show in Directory',
|
||||
)
|
||||
|
||||
email = models.EmailField(
|
||||
blank = False,
|
||||
help_text = 'E-mail address for this person',
|
||||
unique = True,
|
||||
verbose_name = 'E-Mail',
|
||||
)
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
return self.f_name + ' ' + self.l_name
|
||||
|
||||
page_layout: list = [
|
||||
{
|
||||
"name": "Details",
|
||||
"slug": "details",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "double",
|
||||
"left": [
|
||||
'organization',
|
||||
'created',
|
||||
'modified',
|
||||
],
|
||||
"right": [
|
||||
'model_notes',
|
||||
'directory',
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Personal Details",
|
||||
"layout": "double",
|
||||
"left": [
|
||||
'display_name',
|
||||
'dob',
|
||||
],
|
||||
"right": [
|
||||
'f_name',
|
||||
'm_name',
|
||||
'l_name',
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"layout": "double",
|
||||
"left": [
|
||||
'email',
|
||||
],
|
||||
"right": [
|
||||
'',
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Knowledge Base",
|
||||
"slug": "kb_articles",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "knowledge_base",
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Notes",
|
||||
"slug": "notes",
|
||||
"sections": []
|
||||
},
|
||||
]
|
||||
|
||||
table_fields: list = [
|
||||
{
|
||||
"field": "display_name",
|
||||
"type": "link",
|
||||
"key": "_self"
|
||||
},
|
||||
'f_name',
|
||||
'l_name',
|
||||
'email',
|
||||
'organization',
|
||||
'created',
|
||||
]
|
@ -1,143 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
from access.fields import AutoLastModifiedField
|
||||
|
||||
from core.models.centurion import CenturionModel
|
||||
|
||||
|
||||
|
||||
class Entity(
|
||||
CenturionModel
|
||||
):
|
||||
|
||||
model_tag = 'entity'
|
||||
|
||||
documentation = ''
|
||||
|
||||
kb_model_name = 'entity'
|
||||
|
||||
url_model_name = 'entity'
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
ordering = [
|
||||
'created',
|
||||
'modified',
|
||||
'organization',
|
||||
]
|
||||
|
||||
sub_model_type = 'entity'
|
||||
|
||||
verbose_name = 'Entity'
|
||||
|
||||
verbose_name_plural = 'Entities'
|
||||
|
||||
|
||||
entity_type = models.CharField(
|
||||
blank = False,
|
||||
help_text = 'Type this entity is',
|
||||
max_length = 30,
|
||||
unique = False,
|
||||
verbose_name = 'Entity Type'
|
||||
)
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
related_model = self.get_related_model()
|
||||
|
||||
if related_model is None:
|
||||
|
||||
return f'{self.entity_type} {self.pk}'
|
||||
|
||||
|
||||
return str( related_model )
|
||||
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
table_fields: list = [
|
||||
'organization',
|
||||
'entity_type',
|
||||
'display_name',
|
||||
'created',
|
||||
'modified',
|
||||
]
|
||||
|
||||
|
||||
|
||||
def clean_fields(self, exclude = None ):
|
||||
|
||||
related_model = self.get_related_model()
|
||||
|
||||
if related_model is None:
|
||||
|
||||
related_model = self
|
||||
|
||||
if self.entity_type != str(related_model._meta.verbose_name).lower().replace(' ', '_'):
|
||||
|
||||
self.entity_type = str(related_model._meta.verbose_name).lower().replace(' ', '_')
|
||||
|
||||
super().clean_fields( exclude = exclude )
|
||||
|
||||
|
||||
|
||||
def get_related_field_name(self) -> str:
|
||||
|
||||
meta = getattr(self, '_meta')
|
||||
|
||||
for related_object in getattr(meta, 'related_objects', []):
|
||||
|
||||
if not issubclass(related_object.related_model, Entity):
|
||||
|
||||
continue
|
||||
|
||||
if getattr(self, related_object.name, None):
|
||||
|
||||
if(
|
||||
not str(related_object.name).endswith('history')
|
||||
and not str(related_object.name).endswith('notes')
|
||||
):
|
||||
|
||||
return related_object.name
|
||||
|
||||
|
||||
return ''
|
||||
|
||||
|
||||
def get_related_model(self):
|
||||
"""Recursive model Fetch
|
||||
|
||||
Returns the lowest model found in a chain of inherited models.
|
||||
|
||||
Args:
|
||||
model (models.Model, optional): Model to fetch the child model from. Defaults to None.
|
||||
|
||||
Returns:
|
||||
models.Model: Lowset model found in inherited model chain
|
||||
"""
|
||||
|
||||
related_model_name = self.get_related_field_name()
|
||||
|
||||
related_model = getattr(self, related_model_name, None)
|
||||
|
||||
if related_model_name == '':
|
||||
|
||||
related_model = None
|
||||
|
||||
elif related_model is None:
|
||||
|
||||
related_model = self
|
||||
|
||||
elif hasattr(related_model, 'get_related_field_name'):
|
||||
|
||||
if related_model.get_related_field_name() != '':
|
||||
|
||||
related_model = related_model.get_related_model()
|
||||
|
||||
|
||||
return related_model
|
@ -1 +0,0 @@
|
||||
from .tenant import Tenant as Organization # pylint: disable=W0611:unused-import
|
@ -1,53 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
from core.models.model_history import ModelHistory
|
||||
|
||||
from access.models.tenant import Tenant
|
||||
|
||||
|
||||
|
||||
class OrganizationHistory(
|
||||
ModelHistory
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
db_table = 'access_organization_history'
|
||||
|
||||
ordering = ModelHistory._meta.ordering
|
||||
|
||||
verbose_name = 'Organization History'
|
||||
|
||||
verbose_name_plural = 'Organization History'
|
||||
|
||||
|
||||
model = models.ForeignKey(
|
||||
Tenant,
|
||||
blank = False,
|
||||
help_text = 'Model this note belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
related_name = 'history',
|
||||
verbose_name = 'Model',
|
||||
)
|
||||
|
||||
table_fields: list = []
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
|
||||
def get_object(self):
|
||||
|
||||
return self
|
||||
|
||||
|
||||
def get_serialized_model(self, serializer_context):
|
||||
|
||||
model = None
|
||||
|
||||
from access.serializers.organization import TenantBaseSerializer
|
||||
|
||||
model = TenantBaseSerializer(self.model, context = serializer_context)
|
||||
|
||||
return model
|
@ -1,45 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
from access.models.tenant import Tenant
|
||||
|
||||
from core.models.model_notes import ModelNotes
|
||||
|
||||
|
||||
|
||||
class OrganizationNotes(
|
||||
ModelNotes
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
db_table = 'access_organization_notes'
|
||||
|
||||
ordering = ModelNotes._meta.ordering
|
||||
|
||||
verbose_name = 'Organization Note'
|
||||
|
||||
verbose_name_plural = 'Organization Notes'
|
||||
|
||||
|
||||
model = models.ForeignKey(
|
||||
Tenant,
|
||||
blank = False,
|
||||
help_text = 'Model this note belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
related_name = 'notes',
|
||||
verbose_name = 'Model',
|
||||
)
|
||||
|
||||
table_fields: list = []
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
|
||||
def get_url_kwargs(self) -> dict:
|
||||
|
||||
return {
|
||||
'model_id': self.model.pk,
|
||||
'pk': self.pk
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
from core.exceptions import ValidationError
|
||||
|
||||
from access.models.entity import Entity
|
||||
|
||||
|
||||
|
||||
class Person(
|
||||
Entity
|
||||
):
|
||||
|
||||
_is_submodel = True
|
||||
|
||||
documentation = ''
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
ordering = [
|
||||
'l_name',
|
||||
'm_name',
|
||||
'f_name',
|
||||
'dob',
|
||||
]
|
||||
|
||||
sub_model_type = 'person'
|
||||
|
||||
verbose_name = 'Person'
|
||||
|
||||
verbose_name_plural = 'People'
|
||||
|
||||
f_name = models.CharField(
|
||||
blank = False,
|
||||
help_text = 'The persons first name',
|
||||
max_length = 64,
|
||||
unique = False,
|
||||
verbose_name = 'First Name'
|
||||
)
|
||||
|
||||
m_name = models.CharField(
|
||||
blank = True,
|
||||
help_text = 'The persons middle name(s)',
|
||||
max_length = 100,
|
||||
null = True,
|
||||
unique = False,
|
||||
verbose_name = 'Middle Name(s)'
|
||||
)
|
||||
|
||||
l_name = models.CharField(
|
||||
blank = False,
|
||||
help_text = 'The persons Last name',
|
||||
max_length = 64,
|
||||
unique = False,
|
||||
verbose_name = 'Last Name'
|
||||
)
|
||||
|
||||
dob = models.DateField(
|
||||
blank = True,
|
||||
help_text = 'The Persons Date of Birth (DOB)',
|
||||
null = True,
|
||||
unique = False,
|
||||
verbose_name = 'DOB',
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
return self.f_name + ' ' + self.l_name + f' (DOB: {self.dob})'
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
table_fields: list = [
|
||||
'organization',
|
||||
'f_name',
|
||||
'l_name',
|
||||
'dob',
|
||||
'created',
|
||||
]
|
||||
|
||||
|
||||
def clean(self):
|
||||
|
||||
super().clean()
|
||||
|
||||
if self.dob is not None:
|
||||
|
||||
if self.pk:
|
||||
|
||||
duplicate_entry = Person.objects.filter(
|
||||
f_name = self.f_name,
|
||||
l_name = self.l_name,
|
||||
).exclude(
|
||||
pk = self.pk
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
duplicate_entry = Person.objects.filter(
|
||||
f_name = self.f_name,
|
||||
l_name = self.l_name,
|
||||
)
|
||||
|
||||
|
||||
for entry in duplicate_entry:
|
||||
|
||||
if(
|
||||
entry.f_name == self.f_name
|
||||
and entry.m_name == self.m_name
|
||||
and entry.l_name == self.l_name
|
||||
and entry.dob == self.dob
|
||||
):
|
||||
|
||||
raise ValidationError(
|
||||
detail = {
|
||||
'dob': f'Person {self.f_name} {self.l_name}' \
|
||||
f'already exists with this birthday {entry.dob}'
|
||||
},
|
||||
code = 'duplicate_person_on_dob'
|
||||
)
|
@ -1,149 +0,0 @@
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.db import models
|
||||
|
||||
from access.fields import AutoLastModifiedField
|
||||
|
||||
from core.models.centurion import CenturionModel
|
||||
|
||||
|
||||
|
||||
class Role(
|
||||
CenturionModel
|
||||
):
|
||||
|
||||
documentation = ''
|
||||
|
||||
model_tag = 'role'
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
ordering = [
|
||||
'organization',
|
||||
'name',
|
||||
]
|
||||
|
||||
unique_together = [
|
||||
'organization',
|
||||
'name'
|
||||
]
|
||||
|
||||
verbose_name = 'Role'
|
||||
|
||||
verbose_name_plural = 'Roles'
|
||||
|
||||
|
||||
name = models.CharField(
|
||||
blank = False,
|
||||
help_text = 'Name of this role',
|
||||
max_length = 30,
|
||||
unique = False,
|
||||
verbose_name = 'Name'
|
||||
)
|
||||
|
||||
permissions = models.ManyToManyField(
|
||||
Permission,
|
||||
blank = True,
|
||||
help_text = 'Permissions part of this role',
|
||||
related_name = 'roles',
|
||||
symmetrical = False,
|
||||
verbose_name = 'Permissions'
|
||||
)
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
return str( self.organization ) + ' / ' + self.name
|
||||
|
||||
|
||||
page_layout: dict = [
|
||||
{
|
||||
"name": "Details",
|
||||
"slug": "details",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "double",
|
||||
"left": [
|
||||
'organization',
|
||||
'name',
|
||||
'created',
|
||||
'modified',
|
||||
],
|
||||
"right": [
|
||||
'model_notes',
|
||||
]
|
||||
},
|
||||
{
|
||||
"layout": "single",
|
||||
"name": "Permissions",
|
||||
"fields": [
|
||||
"permissions",
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Knowledge Base",
|
||||
"slug": "kb_articles",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "knowledge_base",
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Tickets",
|
||||
"slug": "tickets",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "tickets",
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "Notes",
|
||||
"slug": "notes",
|
||||
"sections": []
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
table_fields: list = [
|
||||
'organization',
|
||||
'name',
|
||||
'created',
|
||||
'modified',
|
||||
]
|
||||
|
||||
|
||||
_permissions: list[ Permission ] = None
|
||||
|
||||
_permissions_int: list[ int ] = None
|
||||
|
||||
def get_permissions(self, as_int_list = False ):
|
||||
|
||||
if self._permissions is None:
|
||||
|
||||
permissions = []
|
||||
permissions_int = []
|
||||
|
||||
for permission in self.permissions: # pylint: disable=E1133:not-an-iterable
|
||||
|
||||
if permission in _permissions:
|
||||
continue
|
||||
|
||||
permissions += [ permission ]
|
||||
permissions_int += [ permission.id ]
|
||||
|
||||
self._permissions = permissions
|
||||
self._permissions_int = permissions_int
|
||||
|
||||
if as_int_list:
|
||||
return self._permissions_int
|
||||
|
||||
return self._permissions_int
|
@ -1,191 +0,0 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from access.fields import (
|
||||
AutoCreatedField,
|
||||
AutoLastModifiedField
|
||||
)
|
||||
|
||||
from access.models.tenant import Tenant
|
||||
from access.models.tenancy import TenancyObject
|
||||
|
||||
from core import exceptions as centurion_exceptions
|
||||
|
||||
|
||||
class Team(Group, TenancyObject):
|
||||
|
||||
class Meta:
|
||||
|
||||
ordering = [ 'team_name' ]
|
||||
|
||||
verbose_name = 'Team'
|
||||
|
||||
verbose_name_plural = "Teams"
|
||||
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
|
||||
if self.organization_id:
|
||||
|
||||
self.name = self.organization.name.lower().replace(' ', '_') + '_' + self.team_name.lower().replace(' ', '_')
|
||||
|
||||
super().save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
|
||||
|
||||
|
||||
def validatate_organization_exists(self):
|
||||
"""Ensure that the user did provide an organization
|
||||
|
||||
Raises:
|
||||
ValidationError: User failed to supply organization.
|
||||
"""
|
||||
|
||||
if not self:
|
||||
raise centurion_exceptions.ValidationError('You must provide an organization')
|
||||
|
||||
|
||||
|
||||
team_name = models.CharField(
|
||||
blank = False,
|
||||
help_text = 'Name to give this team',
|
||||
max_length = 50,
|
||||
unique = False,
|
||||
verbose_name = 'Name',
|
||||
)
|
||||
|
||||
organization = models.ForeignKey(
|
||||
Tenant,
|
||||
blank = False,
|
||||
help_text = 'Tenant this belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
validators = [validatate_organization_exists],
|
||||
verbose_name = 'Tenant'
|
||||
)
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
page_layout: dict = [
|
||||
{
|
||||
"name": "Details",
|
||||
"slug": "details",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "double",
|
||||
"left": [
|
||||
'organization',
|
||||
'team_name',
|
||||
'created',
|
||||
'modified',
|
||||
],
|
||||
"right": [
|
||||
'model_notes',
|
||||
]
|
||||
},
|
||||
{
|
||||
"layout": "table",
|
||||
"name": "Users",
|
||||
"field": "users",
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Knowledge Base",
|
||||
"slug": "kb_articles",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "knowledge_base",
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Notes",
|
||||
"slug": "notes",
|
||||
"sections": []
|
||||
},
|
||||
]
|
||||
|
||||
table_fields: list = [
|
||||
'team_name',
|
||||
'modified',
|
||||
'created',
|
||||
]
|
||||
|
||||
|
||||
def get_url( self, request = None ) -> str:
|
||||
|
||||
if request:
|
||||
|
||||
return reverse(f"v2:_api_v2_organization_team-detail", request=request, kwargs = self.get_url_kwargs() )
|
||||
|
||||
return reverse(f"v2:_api_v2_organization_team-detail", kwargs = self.get_url_kwargs() )
|
||||
|
||||
|
||||
def get_url_kwargs(self) -> dict:
|
||||
"""Fetch the URL kwargs
|
||||
|
||||
Returns:
|
||||
dict: kwargs required for generating the URL with `reverse`
|
||||
"""
|
||||
|
||||
return {
|
||||
'organization_id': self.organization.id,
|
||||
'pk': self.id
|
||||
}
|
||||
|
||||
|
||||
def get_url_kwargs_notes(self) -> dict:
|
||||
"""Fetch the URL kwargs for model notes
|
||||
|
||||
Returns:
|
||||
dict: notes kwargs required for generating the URL with `reverse`
|
||||
"""
|
||||
|
||||
return {
|
||||
'organization_id': self.organization.id,
|
||||
'model_id': self.id
|
||||
}
|
||||
|
||||
|
||||
|
||||
# @property
|
||||
# def parent_object(self):
|
||||
# """ Fetch the parent object """
|
||||
|
||||
# return self.organization
|
||||
|
||||
|
||||
def permission_list(self) -> list:
|
||||
|
||||
permission_list = []
|
||||
|
||||
for permission in self.permissions.all():
|
||||
|
||||
if str(permission.content_type.app_label + '.' + permission.codename) in permission_list:
|
||||
continue
|
||||
|
||||
permission_list += [ str(permission.content_type.app_label + '.' + permission.codename) ]
|
||||
|
||||
return [permission_list, self.permissions.all()]
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self.organization.name + ', ' + self.team_name
|
||||
|
||||
|
||||
def save_history(self, before: dict, after: dict) -> bool:
|
||||
|
||||
from access.models.team_history import TeamAuditHistory
|
||||
|
||||
history = super().save_history(
|
||||
before = before,
|
||||
after = after,
|
||||
history_model = TeamAuditHistory
|
||||
)
|
||||
|
||||
|
||||
return history
|
@ -1,53 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
from core.models.model_history import ModelHistory
|
||||
|
||||
from access.models.team import Team
|
||||
|
||||
|
||||
|
||||
class TeamHistory(
|
||||
ModelHistory
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
db_table = 'access_team_history'
|
||||
|
||||
ordering = ModelHistory._meta.ordering
|
||||
|
||||
verbose_name = 'Team History'
|
||||
|
||||
verbose_name_plural = 'Team History'
|
||||
|
||||
|
||||
model = models.ForeignKey(
|
||||
Team,
|
||||
blank = False,
|
||||
help_text = 'Model this note belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
related_name = 'history',
|
||||
verbose_name = 'Model',
|
||||
)
|
||||
|
||||
table_fields: list = []
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
|
||||
def get_object(self):
|
||||
|
||||
return self
|
||||
|
||||
|
||||
def get_serialized_model(self, serializer_context):
|
||||
|
||||
model = None
|
||||
|
||||
from access.serializers.teams import TeamBaseSerializer
|
||||
|
||||
model = TeamBaseSerializer(self.model, context = serializer_context)
|
||||
|
||||
return model
|
@ -1,54 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from access.models.team import Team
|
||||
|
||||
from core.models.model_notes import ModelNotes
|
||||
|
||||
|
||||
|
||||
class TeamNotes(
|
||||
ModelNotes
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
db_table = 'access_team_notes'
|
||||
|
||||
ordering = ModelNotes._meta.ordering
|
||||
|
||||
verbose_name = 'Team Note'
|
||||
|
||||
verbose_name_plural = 'Team Notes'
|
||||
|
||||
|
||||
model = models.ForeignKey(
|
||||
Team,
|
||||
blank = False,
|
||||
help_text = 'Model this note belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
related_name = 'notes',
|
||||
verbose_name = 'Model',
|
||||
)
|
||||
|
||||
table_fields: list = []
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
|
||||
def get_url( self, request = None ) -> str:
|
||||
|
||||
kwargs = {
|
||||
'organization_id': self.organization.pk,
|
||||
'model_id': self.model.pk,
|
||||
'pk': self.pk
|
||||
}
|
||||
|
||||
if request:
|
||||
|
||||
return reverse("v2:_api_v2_team_note-detail", request=request, kwargs = kwargs )
|
||||
|
||||
return reverse("v2:_api_v2_team_note-detail", kwargs = kwargs )
|
@ -1,152 +0,0 @@
|
||||
import django
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from access.fields import (
|
||||
AutoCreatedField,
|
||||
AutoLastModifiedField
|
||||
)
|
||||
|
||||
from access.models.tenant import Tenant
|
||||
from access.models.team import Team
|
||||
|
||||
from core.lib.feature_not_used import FeatureNotUsed
|
||||
from core.mixins.history_save import SaveHistory
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
|
||||
|
||||
class TeamUsers(SaveHistory):
|
||||
|
||||
class Meta:
|
||||
|
||||
ordering = ['user']
|
||||
|
||||
verbose_name = "Team User"
|
||||
|
||||
verbose_name_plural = "Team Users"
|
||||
|
||||
|
||||
id = models.AutoField(
|
||||
blank=False,
|
||||
help_text = 'ID of this Team User',
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
verbose_name = 'ID'
|
||||
)
|
||||
|
||||
team = models.ForeignKey(
|
||||
Team,
|
||||
blank = False,
|
||||
help_text = 'Team user belongs to',
|
||||
null = False,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="team",
|
||||
verbose_name = 'Team'
|
||||
)
|
||||
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
blank = False,
|
||||
help_text = 'User who will be added to the team',
|
||||
null = False,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name = 'User'
|
||||
)
|
||||
|
||||
manager = models.BooleanField(
|
||||
blank=True,
|
||||
default=False,
|
||||
help_text = 'Is this user to be a manager of this team',
|
||||
verbose_name='manager',
|
||||
)
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
page_layout: list = []
|
||||
|
||||
table_fields: list = [
|
||||
'user',
|
||||
'manager'
|
||||
]
|
||||
|
||||
history_app_label: str = None
|
||||
history_model_name: str = None
|
||||
kb_model_name: str = None
|
||||
note_basename: str = None
|
||||
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
""" Delete Team
|
||||
|
||||
Overrides, post-action
|
||||
As teams are an extension of Groups, remove the user to the team.
|
||||
"""
|
||||
|
||||
super().delete(using=using, keep_parents=keep_parents)
|
||||
|
||||
group = Group.objects.get(pk=self.team.id)
|
||||
|
||||
user = User.objects.get(pk=self.user_id)
|
||||
|
||||
user.groups.remove(group)
|
||||
|
||||
|
||||
def get_organization(self) -> Tenant:
|
||||
return self.team.organization
|
||||
|
||||
|
||||
def get_url( self, request = None ) -> str:
|
||||
|
||||
url_kwargs: dict = {
|
||||
'organization_id': self.team.organization.id,
|
||||
'team_id': self.team.id,
|
||||
'pk': self.id
|
||||
}
|
||||
|
||||
print(f'url kwargs are: {url_kwargs}')
|
||||
|
||||
|
||||
if request:
|
||||
|
||||
return reverse(f"v2:_api_v2_organization_team_user-detail", request=request, kwargs = url_kwargs )
|
||||
|
||||
return reverse(f"v2:_api_v2_organization_team_user-detail", kwargs = url_kwargs )
|
||||
|
||||
|
||||
def get_url_kwargs_notes(self):
|
||||
|
||||
return FeatureNotUsed
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
""" Save Team
|
||||
|
||||
Overrides, post-action
|
||||
As teams are an extension of groups, add the user to the matching group.
|
||||
"""
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
group = Group.objects.get(pk=self.team.id)
|
||||
|
||||
user = User.objects.get(pk=self.user_id)
|
||||
|
||||
user.groups.add(group)
|
||||
|
||||
|
||||
@property
|
||||
def parent_object(self):
|
||||
""" Fetch the parent object """
|
||||
|
||||
return self.team
|
||||
|
||||
def __str__(self):
|
||||
return self.user.username
|
@ -1,311 +0,0 @@
|
||||
import logging
|
||||
|
||||
from django.db import models
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from access.models.tenant import Tenant
|
||||
|
||||
from core import exceptions as centurion_exceptions
|
||||
from core.middleware.get_request import get_request
|
||||
from core.mixins.history_save import SaveHistory
|
||||
|
||||
|
||||
|
||||
class TenancyManager(models.Manager):
|
||||
"""Multi-Tennant Object Manager
|
||||
|
||||
This manager specifically caters for the multi-tenancy features of Centurion ERP.
|
||||
"""
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
""" Fetch the data
|
||||
|
||||
This function filters the data fetched from the database to that which is from the organizations
|
||||
the user is a part of.
|
||||
|
||||
!!! danger "Requirement"
|
||||
This method may be overridden however must still be called from the overriding function. i.e. `super().get_queryset()`
|
||||
|
||||
## Workflow
|
||||
|
||||
This functions workflow is as follows:
|
||||
|
||||
- Fetch the user from the request
|
||||
|
||||
- Check if the user is authenticated
|
||||
|
||||
- Iterate over the users teams
|
||||
|
||||
- Store unique organizations from users teams
|
||||
|
||||
- return results
|
||||
|
||||
Returns:
|
||||
(queryset): **super user**: return unfiltered data.
|
||||
(queryset): **not super user**: return data from the stored unique organizations.
|
||||
"""
|
||||
|
||||
request = get_request()
|
||||
|
||||
user_organizations: list(str()) = []
|
||||
|
||||
has_tenant_field = False
|
||||
if getattr(self.model, 'organization', None) is not None:
|
||||
has_tenant_field = True
|
||||
|
||||
|
||||
if request:
|
||||
|
||||
if request.app_settings.global_organization:
|
||||
|
||||
user_organizations += [ request.app_settings.global_organization.id ]
|
||||
|
||||
|
||||
user = request.user
|
||||
|
||||
|
||||
if user.is_authenticated:
|
||||
|
||||
for team in request.tenancy._user_teams:
|
||||
|
||||
|
||||
if team.organization.id not in user_organizations:
|
||||
|
||||
# if not user_organizations:
|
||||
|
||||
# self.user_organizations = []
|
||||
|
||||
user_organizations += [ team.organization.id ]
|
||||
|
||||
|
||||
if len(user_organizations) > 0 and not user.is_superuser:
|
||||
|
||||
if has_tenant_field:
|
||||
|
||||
return super().get_queryset().select_related('organization').filter(
|
||||
models.Q(organization__in=user_organizations)
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
# return super().get_queryset().filter(
|
||||
# models.Q(organization__in=user_organizations)
|
||||
# )
|
||||
|
||||
return super().get_queryset().filter()
|
||||
|
||||
|
||||
if has_tenant_field:
|
||||
return super().get_queryset().select_related('organization')
|
||||
|
||||
|
||||
return super().get_queryset()
|
||||
|
||||
|
||||
|
||||
class TenancyObject(SaveHistory):
|
||||
""" Tenancy Model Abstrct class.
|
||||
|
||||
This class is for inclusion wihtin **every** model within Centurion ERP.
|
||||
Provides the required fields, functions and methods for multi tennant objects.
|
||||
Unless otherwise stated, **no** object within this class may be overridden.
|
||||
|
||||
Raises:
|
||||
ValidationError: User failed to supply organization
|
||||
"""
|
||||
|
||||
objects = TenancyManager()
|
||||
""" Multi-Tenanant Objects """
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
def validatate_organization_exists(self):
|
||||
"""Ensure that the user did provide an organization
|
||||
|
||||
Raises:
|
||||
ValidationError: User failed to supply organization.
|
||||
"""
|
||||
|
||||
if not self:
|
||||
raise centurion_exceptions.ValidationError('You must provide an organization')
|
||||
|
||||
|
||||
id = models.AutoField(
|
||||
blank=False,
|
||||
help_text = 'ID of the item',
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
verbose_name = 'ID'
|
||||
)
|
||||
|
||||
organization = models.ForeignKey(
|
||||
Tenant,
|
||||
blank = False,
|
||||
help_text = 'Tenancy this belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
related_name = '+',
|
||||
validators = [validatate_organization_exists],
|
||||
verbose_name = 'Tenant'
|
||||
)
|
||||
|
||||
is_global = models.BooleanField(
|
||||
blank = False,
|
||||
default = False,
|
||||
help_text = 'Is this a global object?',
|
||||
verbose_name = 'Global Object'
|
||||
)
|
||||
|
||||
model_notes = models.TextField(
|
||||
blank = True,
|
||||
default = None,
|
||||
help_text = 'Tid bits of information',
|
||||
null = True,
|
||||
verbose_name = 'Notes',
|
||||
)
|
||||
|
||||
def get_organization(self) -> Tenant:
|
||||
return self.organization
|
||||
|
||||
app_namespace: str = None
|
||||
"""Application namespace.
|
||||
|
||||
Specify the applications namespace i.e. `devops`, without including
|
||||
the API version, i.e. `v2:devops`.
|
||||
"""
|
||||
|
||||
history_app_label: str = None
|
||||
"""History Model Application Label
|
||||
|
||||
This value is derived from `<model>._meta.app_label`. This value should
|
||||
only be used when there is model inheritence.
|
||||
"""
|
||||
|
||||
history_model_name: str = None
|
||||
"""History Model Model Name
|
||||
|
||||
This value is derived from `<model>._meta.model_name`. This value should
|
||||
only be used when there is model inheritence.
|
||||
"""
|
||||
|
||||
kb_model_name: str = None
|
||||
"""Model name to use for KB article linking
|
||||
|
||||
This value is derived from `<model>._meta.model_name`. This value should
|
||||
only be used when there is model inheritence.
|
||||
"""
|
||||
|
||||
_log: logging.Logger = None
|
||||
|
||||
def get_log(self):
|
||||
|
||||
if self._log is None:
|
||||
|
||||
self._log = logging.getLogger('centurion.' + self._meta.app_label)
|
||||
|
||||
return self._log
|
||||
|
||||
page_layout: list = None
|
||||
|
||||
note_basename: str = None
|
||||
"""URL BaseName for the notes endpoint.
|
||||
|
||||
Don't specify the `app_namespace`, use property `app_namespace` above.
|
||||
"""
|
||||
|
||||
|
||||
def get_page_layout(self):
|
||||
""" FEtch the page layout"""
|
||||
|
||||
return self.page_layout
|
||||
|
||||
|
||||
def get_app_namespace(self) -> str:
|
||||
"""Fetch the Application namespace if specified.
|
||||
|
||||
Returns:
|
||||
str: Application namespace suffixed with colin `:`
|
||||
None: No application namespace found.
|
||||
"""
|
||||
|
||||
app_namespace = ''
|
||||
|
||||
if self.app_namespace:
|
||||
|
||||
app_namespace = self.app_namespace
|
||||
|
||||
return str(app_namespace)
|
||||
|
||||
|
||||
def get_url( self, request = None ) -> str:
|
||||
"""Fetch the models URL
|
||||
|
||||
If URL kwargs are required to generate the URL, define a `get_url_kwargs` that returns them.
|
||||
|
||||
Args:
|
||||
request (object, optional): The request object that was made by the end user. Defaults to None.
|
||||
|
||||
Returns:
|
||||
str: Canonical URL of the model if the `request` object was provided. Otherwise the relative URL.
|
||||
"""
|
||||
|
||||
model_name = str(self._meta.verbose_name.lower()).replace(' ', '_')
|
||||
|
||||
namespace = f'v2'
|
||||
|
||||
if self.get_app_namespace():
|
||||
namespace = namespace + ':' + self.get_app_namespace()
|
||||
|
||||
|
||||
if request:
|
||||
|
||||
return reverse(f"{namespace}:_api_v2_{model_name}-detail", request=request, kwargs = self.get_url_kwargs() )
|
||||
|
||||
return reverse(f"{namespace}:_api_v2_{model_name}-detail", kwargs = self.get_url_kwargs() )
|
||||
|
||||
|
||||
def get_url_kwargs(self) -> dict:
|
||||
"""Fetch the URL kwargs
|
||||
|
||||
Returns:
|
||||
dict: kwargs required for generating the URL with `reverse`
|
||||
"""
|
||||
|
||||
return {
|
||||
'pk': self.id
|
||||
}
|
||||
|
||||
|
||||
def get_url_kwargs_notes(self) -> dict:
|
||||
"""Fetch the URL kwargs for model notes
|
||||
|
||||
Returns:
|
||||
dict: notes kwargs required for generating the URL with `reverse`
|
||||
"""
|
||||
|
||||
return {
|
||||
'model_id': self.id
|
||||
}
|
||||
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
|
||||
self.clean()
|
||||
|
||||
if(
|
||||
not getattr(self, 'organization', None)
|
||||
and self._meta.model_name !='appsettingshistory' # App Settings for
|
||||
):
|
||||
|
||||
raise centurion_exceptions.ValidationError(
|
||||
detail = {
|
||||
'organization': 'Tenant is required'
|
||||
},
|
||||
code = 'required'
|
||||
)
|
||||
|
||||
super().save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
|
@ -1,126 +0,0 @@
|
||||
from django.core.exceptions import (
|
||||
ValidationError,
|
||||
)
|
||||
from django.db import models
|
||||
|
||||
from access.models.tenancy import (
|
||||
TenancyManager as TenancyManagerDepreciated
|
||||
)
|
||||
from access.models.tenant import Tenant
|
||||
|
||||
|
||||
|
||||
class TenancyManager(
|
||||
models.Manager
|
||||
):
|
||||
"""Multi-Tennant Object Manager
|
||||
|
||||
This manager specifically caters for the multi-tenancy features of Centurion ERP.
|
||||
"""
|
||||
|
||||
def get_queryset(self):
|
||||
""" Fetch the data
|
||||
|
||||
When the model contains the user data, the query is filtered to their
|
||||
and the globally defined Tenancy only.
|
||||
|
||||
Returns:
|
||||
(queryset): **super user**: return unfiltered data.
|
||||
(queryset): **not super user**: return data from the stored unique organizations.
|
||||
"""
|
||||
|
||||
user = None # When CenturionUser in use
|
||||
|
||||
if hasattr(self.model, 'context'):
|
||||
|
||||
user = self.model.context['user']
|
||||
|
||||
|
||||
has_tenant_field = False
|
||||
if getattr(self.model, 'organization', None) is not None:
|
||||
has_tenant_field = True
|
||||
|
||||
|
||||
if user:
|
||||
|
||||
tenancies = user.get_tenancies(int_list = True)
|
||||
|
||||
if len(tenancies) > 0 and not request.user.is_superuser:
|
||||
|
||||
if has_tenant_field:
|
||||
return super().get_queryset().select_related('organization').filter(
|
||||
models.Q(organization__in = tenancies)
|
||||
)
|
||||
|
||||
|
||||
return super().get_queryset().filter()
|
||||
|
||||
|
||||
if has_tenant_field:
|
||||
return super().get_queryset().select_related('organization')
|
||||
|
||||
|
||||
return super().get_queryset()
|
||||
|
||||
|
||||
|
||||
class TenancyAbstractModel(
|
||||
models.Model,
|
||||
):
|
||||
""" Tenancy Model Abstract class.
|
||||
|
||||
This class is for inclusion within **every** model within Centurion ERP.
|
||||
Provides the required fields, functions and methods for multi tennant objects.
|
||||
Unless otherwise stated, **no** object within this class may be overridden.
|
||||
|
||||
Raises:
|
||||
ValidationError: User failed to supply organization
|
||||
"""
|
||||
|
||||
objects = TenancyManagerDepreciated()
|
||||
""" ~~Multi-Tenant Manager~~
|
||||
|
||||
**Note:** ~~This manager relies upon the model class having `context['user']`
|
||||
set. without a user the manager can not perform multi-tenant queries.~~
|
||||
"""
|
||||
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
def validatate_organization_exists(self):
|
||||
"""Ensure that the user did provide an organization
|
||||
|
||||
Raises:
|
||||
ValidationError: User failed to supply organization.
|
||||
"""
|
||||
|
||||
if not self:
|
||||
raise ValidationError(
|
||||
code = 'required',
|
||||
message = 'You must provide an organization'
|
||||
)
|
||||
|
||||
organization = models.ForeignKey(
|
||||
Tenant,
|
||||
blank = False,
|
||||
help_text = 'Tenant this belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
related_name = '+',
|
||||
validators = [
|
||||
validatate_organization_exists
|
||||
],
|
||||
verbose_name = 'Tenant'
|
||||
)
|
||||
|
||||
|
||||
|
||||
def get_tenant(self) -> Tenant:
|
||||
""" Return the models Tenancy
|
||||
|
||||
This model can be safely over-ridden as long as it returns the models
|
||||
tenancy
|
||||
"""
|
||||
return self.organization
|
@ -1,143 +0,0 @@
|
||||
import django
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
from access.fields import (
|
||||
AutoCreatedField,
|
||||
AutoLastModifiedField,
|
||||
)
|
||||
|
||||
from core.mixins.centurion import Centurion
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
class Tenant(
|
||||
Centurion,
|
||||
):
|
||||
|
||||
@property
|
||||
def organization(self):
|
||||
return self
|
||||
|
||||
model_tag = 'tenant'
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = "Tenant"
|
||||
|
||||
verbose_name_plural = "Tenants"
|
||||
|
||||
ordering = [
|
||||
'name'
|
||||
]
|
||||
|
||||
|
||||
id = models.AutoField(
|
||||
blank = False,
|
||||
help_text = 'ID of this item',
|
||||
primary_key = True,
|
||||
unique = True,
|
||||
verbose_name = 'ID'
|
||||
)
|
||||
|
||||
name = models.CharField(
|
||||
blank = False,
|
||||
help_text = 'Name of this Tenancy',
|
||||
max_length = 50,
|
||||
unique = True,
|
||||
verbose_name = 'Name'
|
||||
)
|
||||
|
||||
manager = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
blank = True,
|
||||
help_text = 'Manager for this Tenancy',
|
||||
null = True,
|
||||
on_delete = models.PROTECT,
|
||||
verbose_name = 'Manager'
|
||||
)
|
||||
|
||||
model_notes = models.TextField(
|
||||
blank = True,
|
||||
help_text = 'Tid bits of information',
|
||||
null = True,
|
||||
verbose_name = 'Notes',
|
||||
)
|
||||
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
|
||||
def __int__(self):
|
||||
|
||||
return self.id
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
def get_organization(self):
|
||||
return self
|
||||
|
||||
|
||||
table_fields: list = [
|
||||
'nbsp',
|
||||
'name',
|
||||
'created',
|
||||
'modified',
|
||||
'nbsp'
|
||||
]
|
||||
|
||||
page_layout: list = [
|
||||
{
|
||||
"name": "Details",
|
||||
"slug": "details",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "double",
|
||||
"left": [
|
||||
'name',
|
||||
'manager',
|
||||
'created',
|
||||
'modified',
|
||||
],
|
||||
"right": [
|
||||
'model_notes',
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Teams",
|
||||
"slug": "teams",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "teams"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Knowledge Base",
|
||||
"slug": "kb_articles",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "knowledge_base",
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Notes",
|
||||
"slug": "notes",
|
||||
"sections": []
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
|
||||
Organization = Tenant
|
@ -1,197 +0,0 @@
|
||||
from django.contrib.auth.models import Permission, User
|
||||
from django.core.exceptions import PermissionDenied
|
||||
|
||||
from access.models.tenant import Tenant
|
||||
|
||||
|
||||
|
||||
class CenturionUser(
|
||||
User,
|
||||
):
|
||||
"""Centurion User
|
||||
|
||||
A Multi-Tenant User wirh permission Checking.
|
||||
|
||||
ToDo:
|
||||
- Add to Roles user field `related_name = roles`
|
||||
- Add to Roles group field `related_name = roles`
|
||||
# - have group lookup prefetch related roles__permissions
|
||||
- have user lookup prefetch related roles__permissions and groups__roles__permissions
|
||||
|
||||
Args:
|
||||
User (Model): Django Base User
|
||||
"""
|
||||
|
||||
_tenancies: list[Tenant] = None
|
||||
|
||||
_tenancies_int: list[int] = None
|
||||
|
||||
_permissions: list[Permission] = None
|
||||
|
||||
_permissions_by_tenancy: dict[ str, list[ Permission ] ] = None
|
||||
"""Permissions by Tenancy
|
||||
|
||||
`{ 'tenancy_{id}': [ Permission ] }`
|
||||
"""
|
||||
|
||||
# EMAIL_FIELD = 'email' # Update contact email field name so it's different to the user model.
|
||||
|
||||
# REQUIRED_FIELDS = [
|
||||
# EMAIL_FIELD,
|
||||
# 'f_name',
|
||||
# 'l_name',
|
||||
# ]
|
||||
|
||||
class Meta:
|
||||
abstract = False
|
||||
proxy = True # User will be linked to Employee/Customer entity via related_name from the entity.
|
||||
# ToDo: refactory Employee/Customer to inherit from a new model. entity_user
|
||||
|
||||
verbose_name = 'Centurion User'
|
||||
|
||||
verbose_name_plural = 'Centurion Users'
|
||||
|
||||
|
||||
|
||||
def get_full_name(self) -> str:
|
||||
return f'{self.entity_user.f_name} {self.entity_user.l_name}'
|
||||
|
||||
|
||||
|
||||
def get_group_permissions(self, tenancy: bool = True) -> dict[ str, list[ Permission ] ] | list[ Permission ]:
|
||||
""" Get the Users Permissions
|
||||
|
||||
Args:
|
||||
tenancy (bool, optional): Return permission in list. Defaults to True.
|
||||
|
||||
Returns:
|
||||
dict[ str, list[ Permission ] ]: Permissions listed by tenancy
|
||||
list[ Permission ]: All Permissions
|
||||
"""
|
||||
|
||||
for group in self.groups: # pylint: disable=E1133:not-an-iterable
|
||||
|
||||
for role in group.roles:
|
||||
pass
|
||||
|
||||
# role.get_permissions()
|
||||
|
||||
|
||||
|
||||
def get_permissions(self, tenancy: bool = True) -> dict[ str, list[ Permission ] ] | list[ Permission ]:
|
||||
""" Get the Users Permissions
|
||||
|
||||
Args:
|
||||
tenancy (bool, optional): Return permission in list. Defaults to True.
|
||||
|
||||
Returns:
|
||||
dict[ str, list[ Permission ] ]: Permissions listed by tenancy
|
||||
list[ Permission ]: All Permissions
|
||||
"""
|
||||
|
||||
# also get group permissions. self.get_group_permissions()
|
||||
|
||||
for role in self.roles:
|
||||
pass
|
||||
|
||||
# role.get_permissions()
|
||||
|
||||
# also populate `self._tenancies` and `self._tenancies_int`
|
||||
|
||||
return []
|
||||
|
||||
|
||||
|
||||
def get_short_name() -> str:
|
||||
return self.entity_user.f_name
|
||||
|
||||
|
||||
|
||||
def get_tenancies(self, int_list = False) -> list[ Tenant ] | list[ int ]:
|
||||
"""Get the Tenancies the user is in.
|
||||
|
||||
Args:
|
||||
int_list (bool, optional): Return Tenancy list as int values. Defaults to False.
|
||||
|
||||
Returns:
|
||||
list[ Tenant ] | list[ int ]: All Tenancies the user is in.
|
||||
"""
|
||||
|
||||
if self._tenancies is None:
|
||||
|
||||
if self._permissions is None:
|
||||
self.get_permissions
|
||||
|
||||
tenancies: list = []
|
||||
tenancies_int: list = []
|
||||
|
||||
for role in self.roles:
|
||||
|
||||
if role.organization in tenancies:
|
||||
continue
|
||||
|
||||
tenancies += [ role.organization ]
|
||||
tenancies_int += [ role.organization.id ]
|
||||
|
||||
self._tenancies = tenancies
|
||||
self._tenancies_int = tenancies_int
|
||||
|
||||
|
||||
if as_int_list:
|
||||
return self._tenancies_int
|
||||
|
||||
return self._tenancies
|
||||
|
||||
|
||||
|
||||
def has_module_perms(self, app_label): # is this needed?
|
||||
|
||||
# if has app_label in perms
|
||||
|
||||
raise PermissionDenied
|
||||
|
||||
|
||||
|
||||
def has_perm(self, permission: Permission, obj = None, tenancy: Tenant = None) -> bool:
|
||||
|
||||
if(
|
||||
obj is None
|
||||
and tenancy is None
|
||||
):
|
||||
raise ValueError('Both obj and tenancy cant be None')
|
||||
|
||||
if tenancy is None:
|
||||
tenancy = obj.organization
|
||||
|
||||
# if self.has_tenancy_permission(perm, tenancy):
|
||||
# for tenancy, permissions in self.get_permissions().items()
|
||||
|
||||
if tenancy is None:
|
||||
raise ValueError('tenancy cant be None')
|
||||
|
||||
permissions = self.get_permissions()
|
||||
|
||||
if f'tenancy_{tenancy.id}' not in permissions:
|
||||
raise PermissionDenied
|
||||
|
||||
|
||||
for tenancy, permissions in self.get_permissions().items():
|
||||
|
||||
if(
|
||||
tenancy == f'tenancy_{tenancy.id}'
|
||||
and perm in permissions
|
||||
):
|
||||
return True
|
||||
|
||||
|
||||
raise PermissionDenied
|
||||
|
||||
|
||||
|
||||
def has_perms(self, permission_list: list[ Permission ], obj = None, tenancy: Tenant = None):
|
||||
|
||||
for perm in perm_list:
|
||||
|
||||
self.has_perm( perm, obj )
|
||||
|
||||
return True
|
@ -1,56 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from centurion.models.meta import CompanyAuditHistory # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionaudit import (
|
||||
BaseSerializer,
|
||||
ViewSerializer as AuditHistoryViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'CompanyAuditHistoryModelSerializer')
|
||||
class ModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
BaseSerializer
|
||||
):
|
||||
"""Git Group Audit History Base Model"""
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = CompanyAuditHistory
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'content_type',
|
||||
'model',
|
||||
'before',
|
||||
'after',
|
||||
'action',
|
||||
'user',
|
||||
'created',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'CompanyAuditHistoryViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
AuditHistoryViewSerializer,
|
||||
):
|
||||
"""Git Group Audit History Base View Model"""
|
||||
pass
|
@ -1,56 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from centurion.models.meta import ContactAuditHistory # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionaudit import (
|
||||
BaseSerializer,
|
||||
ViewSerializer as AuditHistoryViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'ContactAuditHistoryModelSerializer')
|
||||
class ModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
BaseSerializer
|
||||
):
|
||||
"""Git Group Audit History Base Model"""
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = ContactAuditHistory
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'content_type',
|
||||
'model',
|
||||
'before',
|
||||
'after',
|
||||
'action',
|
||||
'user',
|
||||
'created',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'ContactAuditHistoryViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
AuditHistoryViewSerializer,
|
||||
):
|
||||
"""Git Group Audit History Base View Model"""
|
||||
pass
|
@ -1,56 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from centurion.models.meta import EntityAuditHistory # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionaudit import (
|
||||
BaseSerializer,
|
||||
ViewSerializer as AuditHistoryViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'EntityAuditHistoryModelSerializer')
|
||||
class ModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
BaseSerializer
|
||||
):
|
||||
"""Git Group Audit History Base Model"""
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = EntityAuditHistory
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'content_type',
|
||||
'model',
|
||||
'before',
|
||||
'after',
|
||||
'action',
|
||||
'user',
|
||||
'created',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'EntityAuditHistoryViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
AuditHistoryViewSerializer,
|
||||
):
|
||||
"""Git Group Audit History Base View Model"""
|
||||
pass
|
@ -1,56 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from centurion.models.meta import PersonAuditHistory # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionaudit import (
|
||||
BaseSerializer,
|
||||
ViewSerializer as AuditHistoryViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'PersonAuditHistoryModelSerializer')
|
||||
class ModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
BaseSerializer
|
||||
):
|
||||
"""Git Group Audit History Base Model"""
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = PersonAuditHistory
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'content_type',
|
||||
'model',
|
||||
'before',
|
||||
'after',
|
||||
'action',
|
||||
'user',
|
||||
'created',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'PersonAuditHistoryViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
AuditHistoryViewSerializer,
|
||||
):
|
||||
"""Git Group Audit History Base View Model"""
|
||||
pass
|
@ -1,56 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from centurion.models.meta import RoleAuditHistory # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionaudit import (
|
||||
BaseSerializer,
|
||||
ViewSerializer as AuditHistoryViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'RoleAuditHistoryModelSerializer')
|
||||
class ModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
BaseSerializer
|
||||
):
|
||||
"""Git Group Audit History Base Model"""
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = RoleAuditHistory
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'content_type',
|
||||
'model',
|
||||
'before',
|
||||
'after',
|
||||
'action',
|
||||
'user',
|
||||
'created',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'RoleAuditHistoryViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
AuditHistoryViewSerializer,
|
||||
):
|
||||
"""Git Group Audit History Base View Model"""
|
||||
pass
|
@ -1,56 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from centurion.models.meta import TenantAuditHistory # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionaudit import (
|
||||
BaseSerializer,
|
||||
ViewSerializer as AuditHistoryViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'TenantAuditHistoryModelSerializer')
|
||||
class ModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
BaseSerializer
|
||||
):
|
||||
"""Git Group Audit History Base Model"""
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = TenantAuditHistory
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'content_type',
|
||||
'model',
|
||||
'before',
|
||||
'after',
|
||||
'action',
|
||||
'user',
|
||||
'created',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'TenantAuditHistoryViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
AuditHistoryViewSerializer,
|
||||
):
|
||||
"""Git Group Audit History Base View Model"""
|
||||
pass
|
@ -1,87 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.serializers.organization import (TenantBaseSerializer)
|
||||
|
||||
from centurion.models.meta import CompanyCenturionModelNote # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionmodelnote import ( # pylint: disable=W0611:unused-import
|
||||
BaseSerializer,
|
||||
ModelSerializer as BaseModelModelSerializer,
|
||||
ViewSerializer as BaseModelViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'CompanyModelNoteModelSerializer')
|
||||
class ModelSerializer(
|
||||
BaseModelModelSerializer,
|
||||
):
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> dict:
|
||||
|
||||
return {
|
||||
'_self': item.get_url( request = self._context['view'].request ),
|
||||
}
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = CompanyCenturionModelNote
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'body',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'content_type',
|
||||
'model',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'organization',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'content_type',
|
||||
'model',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
def validate(self, attrs):
|
||||
|
||||
is_valid = False
|
||||
|
||||
note_model = self.Meta.model.model.field.related_model
|
||||
|
||||
attrs['model'] = note_model.objects.get(
|
||||
id = int( self.context['view'].kwargs['model_id'] )
|
||||
)
|
||||
|
||||
|
||||
is_valid = super().validate(attrs)
|
||||
|
||||
return is_valid
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'CompanyModelNoteViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
BaseModelViewSerializer,
|
||||
):
|
||||
|
||||
organization = TenantBaseSerializer( many = False, read_only = True )
|
@ -1,87 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.serializers.organization import (TenantBaseSerializer)
|
||||
|
||||
from centurion.models.meta import ContactCenturionModelNote # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionmodelnote import ( # pylint: disable=W0611:unused-import
|
||||
BaseSerializer,
|
||||
ModelSerializer as BaseModelModelSerializer,
|
||||
ViewSerializer as BaseModelViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'ContactModelNoteModelSerializer')
|
||||
class ModelSerializer(
|
||||
BaseModelModelSerializer,
|
||||
):
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> dict:
|
||||
|
||||
return {
|
||||
'_self': item.get_url( request = self._context['view'].request ),
|
||||
}
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = ContactCenturionModelNote
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'body',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'content_type',
|
||||
'model',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'organization',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'content_type',
|
||||
'model',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
def validate(self, attrs):
|
||||
|
||||
is_valid = False
|
||||
|
||||
note_model = self.Meta.model.model.field.related_model
|
||||
|
||||
attrs['model'] = note_model.objects.get(
|
||||
id = int( self.context['view'].kwargs['model_id'] )
|
||||
)
|
||||
|
||||
|
||||
is_valid = super().validate(attrs)
|
||||
|
||||
return is_valid
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'ContactModelNoteViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
BaseModelViewSerializer,
|
||||
):
|
||||
|
||||
organization = TenantBaseSerializer( many = False, read_only = True )
|
@ -1,87 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.serializers.organization import (TenantBaseSerializer)
|
||||
|
||||
from centurion.models.meta import EntityCenturionModelNote # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionmodelnote import ( # pylint: disable=W0611:unused-import
|
||||
BaseSerializer,
|
||||
ModelSerializer as BaseModelModelSerializer,
|
||||
ViewSerializer as BaseModelViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'EntityModelNoteModelSerializer')
|
||||
class ModelSerializer(
|
||||
BaseModelModelSerializer,
|
||||
):
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> dict:
|
||||
|
||||
return {
|
||||
'_self': item.get_url( request = self._context['view'].request ),
|
||||
}
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = EntityCenturionModelNote
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'body',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'content_type',
|
||||
'model',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'organization',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'content_type',
|
||||
'model',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
def validate(self, attrs):
|
||||
|
||||
is_valid = False
|
||||
|
||||
note_model = self.Meta.model.model.field.related_model
|
||||
|
||||
attrs['model'] = note_model.objects.get(
|
||||
id = int( self.context['view'].kwargs['model_id'] )
|
||||
)
|
||||
|
||||
|
||||
is_valid = super().validate(attrs)
|
||||
|
||||
return is_valid
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'EntityModelNoteViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
BaseModelViewSerializer,
|
||||
):
|
||||
|
||||
organization = TenantBaseSerializer( many = False, read_only = True )
|
@ -1,87 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.serializers.organization import (TenantBaseSerializer)
|
||||
|
||||
from centurion.models.meta import PersonCenturionModelNote # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionmodelnote import ( # pylint: disable=W0611:unused-import
|
||||
BaseSerializer,
|
||||
ModelSerializer as BaseModelModelSerializer,
|
||||
ViewSerializer as BaseModelViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'PersonModelNoteModelSerializer')
|
||||
class ModelSerializer(
|
||||
BaseModelModelSerializer,
|
||||
):
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> dict:
|
||||
|
||||
return {
|
||||
'_self': item.get_url( request = self._context['view'].request ),
|
||||
}
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = PersonCenturionModelNote
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'body',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'content_type',
|
||||
'model',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'organization',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'content_type',
|
||||
'model',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
def validate(self, attrs):
|
||||
|
||||
is_valid = False
|
||||
|
||||
note_model = self.Meta.model.model.field.related_model
|
||||
|
||||
attrs['model'] = note_model.objects.get(
|
||||
id = int( self.context['view'].kwargs['model_id'] )
|
||||
)
|
||||
|
||||
|
||||
is_valid = super().validate(attrs)
|
||||
|
||||
return is_valid
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'PersonModelNoteViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
BaseModelViewSerializer,
|
||||
):
|
||||
|
||||
organization = TenantBaseSerializer( many = False, read_only = True )
|
@ -1,87 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.serializers.organization import (TenantBaseSerializer)
|
||||
|
||||
from centurion.models.meta import RoleCenturionModelNote # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionmodelnote import ( # pylint: disable=W0611:unused-import
|
||||
BaseSerializer,
|
||||
ModelSerializer as BaseModelModelSerializer,
|
||||
ViewSerializer as BaseModelViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'RoleModelNoteModelSerializer')
|
||||
class ModelSerializer(
|
||||
BaseModelModelSerializer,
|
||||
):
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> dict:
|
||||
|
||||
return {
|
||||
'_self': item.get_url( request = self._context['view'].request ),
|
||||
}
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = RoleCenturionModelNote
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'body',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'content_type',
|
||||
'model',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'organization',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'content_type',
|
||||
'model',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
def validate(self, attrs):
|
||||
|
||||
is_valid = False
|
||||
|
||||
note_model = self.Meta.model.model.field.related_model
|
||||
|
||||
attrs['model'] = note_model.objects.get(
|
||||
id = int( self.context['view'].kwargs['model_id'] )
|
||||
)
|
||||
|
||||
|
||||
is_valid = super().validate(attrs)
|
||||
|
||||
return is_valid
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'RoleModelNoteViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
BaseModelViewSerializer,
|
||||
):
|
||||
|
||||
organization = TenantBaseSerializer( many = False, read_only = True )
|
@ -1,87 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.serializers.organization import (TenantBaseSerializer)
|
||||
|
||||
from centurion.models.meta import TenantCenturionModelNote # pylint: disable=E0401:import-error disable=E0611:no-name-in-module
|
||||
|
||||
from core.serializers.centurionmodelnote import ( # pylint: disable=W0611:unused-import
|
||||
BaseSerializer,
|
||||
ModelSerializer as BaseModelModelSerializer,
|
||||
ViewSerializer as BaseModelViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'TeamModelNoteModelSerializer')
|
||||
class ModelSerializer(
|
||||
BaseModelModelSerializer,
|
||||
):
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> dict:
|
||||
|
||||
return {
|
||||
'_self': item.get_url( request = self._context['view'].request ),
|
||||
}
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = TenantCenturionModelNote
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'body',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'content_type',
|
||||
'model',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'organization',
|
||||
'created_by',
|
||||
'modified_by',
|
||||
'content_type',
|
||||
'model',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
def validate(self, attrs):
|
||||
|
||||
is_valid = False
|
||||
|
||||
note_model = self.Meta.model.model.field.related_model
|
||||
|
||||
attrs['model'] = note_model.objects.get(
|
||||
id = int( self.context['view'].kwargs['model_id'] )
|
||||
)
|
||||
|
||||
|
||||
is_valid = super().validate(attrs)
|
||||
|
||||
return is_valid
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'TeamModelNoteViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
BaseModelViewSerializer,
|
||||
):
|
||||
|
||||
organization = TenantBaseSerializer( many = False, read_only = True )
|
@ -1,89 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.models.entity import Entity
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from access.serializers.organization import TenantBaseSerializer
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'EntityBaseBaseSerializer')
|
||||
class BaseSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
display_name = serializers.SerializerMethodField('get_display_name')
|
||||
|
||||
def get_display_name(self, item) -> str:
|
||||
|
||||
return str( item )
|
||||
|
||||
url = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> str:
|
||||
|
||||
return item.get_url( request = self.context['view'].request )
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Entity
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'url',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'EntityBaseModelSerializer')
|
||||
class ModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
BaseSerializer
|
||||
):
|
||||
"""Entity Base Model"""
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Entity
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'entity_type',
|
||||
'display_name',
|
||||
'model_notes',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'entity_type',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'EntityBaseViewSerializer')
|
||||
class ViewSerializer(ModelSerializer):
|
||||
"""Entity Base View Model"""
|
||||
|
||||
organization = TenantBaseSerializer(many=False, read_only=True)
|
@ -1,69 +0,0 @@
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.models.company_base import Company
|
||||
|
||||
from access.serializers.entity import (
|
||||
BaseSerializer as BaseBaseSerializer,
|
||||
ModelSerializer as BaseModelSerializer,
|
||||
)
|
||||
from access.serializers.organization import TenantBaseSerializer
|
||||
|
||||
|
||||
|
||||
class BaseSerializer(
|
||||
BaseBaseSerializer,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'CompanyEntityModelSerializer')
|
||||
class ModelSerializer(
|
||||
BaseSerializer,
|
||||
BaseModelSerializer,
|
||||
):
|
||||
"""Company Model
|
||||
|
||||
This model inherits from the Entity base model.
|
||||
"""
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Company
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'entity_ptr_id',
|
||||
'organization',
|
||||
'entity_type',
|
||||
'display_name',
|
||||
'name',
|
||||
'model_notes',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'entity_type',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'CompanyEntityViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
):
|
||||
"""Company View Model
|
||||
|
||||
This model inherits from the Entity base model.
|
||||
"""
|
||||
|
||||
organization = TenantBaseSerializer(many=False, read_only=True)
|
@ -1,74 +0,0 @@
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.models.contact import Contact
|
||||
|
||||
from access.serializers.entity_person import (
|
||||
BaseSerializer as BaseBaseSerializer,
|
||||
ModelSerializer as BaseModelSerializer,
|
||||
)
|
||||
from access.serializers.organization import TenantBaseSerializer
|
||||
|
||||
|
||||
|
||||
class BaseSerializer(
|
||||
BaseBaseSerializer,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'ContactEntityModelSerializer')
|
||||
class ModelSerializer(
|
||||
BaseSerializer,
|
||||
BaseModelSerializer,
|
||||
):
|
||||
"""Contact Model
|
||||
|
||||
This model first inherits from Person then inherits from the Entity Base model.
|
||||
"""
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Contact
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'person_ptr_id',
|
||||
'organization',
|
||||
'entity_type',
|
||||
'display_name',
|
||||
'f_name',
|
||||
'm_name',
|
||||
'l_name',
|
||||
'dob',
|
||||
'email',
|
||||
'directory',
|
||||
'model_notes',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'entity_type',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'ContactEntityViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
):
|
||||
"""Contact View Model
|
||||
|
||||
This model inherits from the Person model.
|
||||
"""
|
||||
|
||||
organization = TenantBaseSerializer(many=False, read_only=True)
|
@ -1,72 +0,0 @@
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.models.person import Person
|
||||
|
||||
from access.serializers.entity import (
|
||||
BaseSerializer as BaseBaseSerializer,
|
||||
ModelSerializer as BaseModelSerializer,
|
||||
)
|
||||
from access.serializers.organization import TenantBaseSerializer
|
||||
|
||||
|
||||
|
||||
class BaseSerializer(
|
||||
BaseBaseSerializer,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'PersonEntityModelSerializer')
|
||||
class ModelSerializer(
|
||||
BaseSerializer,
|
||||
BaseModelSerializer,
|
||||
):
|
||||
"""Person Model
|
||||
|
||||
This model inherits from the Entity base model.
|
||||
"""
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Person
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'entity_ptr_id',
|
||||
'organization',
|
||||
'entity_type',
|
||||
'display_name',
|
||||
'f_name',
|
||||
'm_name',
|
||||
'l_name',
|
||||
'dob',
|
||||
'model_notes',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'entity_type',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'PersonEntityViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
):
|
||||
"""Person View Model
|
||||
|
||||
This model inherits from the Entity base model.
|
||||
"""
|
||||
|
||||
organization = TenantBaseSerializer(many=False, read_only=True)
|
@ -1,105 +0,0 @@
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from access.models.tenant import Tenant
|
||||
|
||||
from centurion.serializers.user import UserBaseSerializer
|
||||
|
||||
from core import fields as centurion_field
|
||||
|
||||
Organization = Tenant
|
||||
|
||||
|
||||
class TenantBaseSerializer(serializers.ModelSerializer):
|
||||
|
||||
display_name = serializers.SerializerMethodField('get_display_name')
|
||||
|
||||
def get_display_name(self, item) -> str:
|
||||
|
||||
return str( item )
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="v2:_api_tenant-detail", format="html"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Tenant
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'name',
|
||||
'url',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'name',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class TenantModelSerializer(
|
||||
TenantBaseSerializer
|
||||
):
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> dict:
|
||||
|
||||
return {
|
||||
'_self': item.get_url( request = self._context['view'].request ),
|
||||
'knowledge_base': reverse(
|
||||
"v2:_api_v2_model_kb-list",
|
||||
request=self._context['view'].request,
|
||||
kwargs={
|
||||
'model': self.Meta.model._meta.model_name,
|
||||
'model_pk': item.pk
|
||||
}
|
||||
),
|
||||
# 'notes': reverse(
|
||||
# "v2:_api_v2_organization_note-list",
|
||||
# request=self._context['view'].request,
|
||||
# kwargs={
|
||||
# 'model_id': item.pk
|
||||
# }
|
||||
# ),
|
||||
'teams': reverse("v2:_api_v2_organization_team-list", request=self._context['view'].request, kwargs={'organization_id': item.pk}),
|
||||
}
|
||||
|
||||
model_notes = centurion_field.MarkdownField( required = False )
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Tenant
|
||||
|
||||
fields = '__all__'
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'name',
|
||||
'model_notes',
|
||||
'manager',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
class TenantViewSerializer(TenantModelSerializer):
|
||||
pass
|
||||
|
||||
manager = UserBaseSerializer(many=False, read_only = True)
|
@ -1,114 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from drf_spectacular.utils import extend_schema_serializer
|
||||
|
||||
from access.functions.permissions import permission_queryset
|
||||
from access.models.role import Role
|
||||
from access.serializers.organization import TenantBaseSerializer
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from centurion.serializers.permission import PermissionBaseSerializer
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'RoleBaseSerializer')
|
||||
class BaseSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
display_name = serializers.SerializerMethodField('get_display_name')
|
||||
|
||||
def get_display_name(self, item) -> str:
|
||||
|
||||
return str( item )
|
||||
|
||||
url = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> str:
|
||||
|
||||
return item.get_url( request = self.context['view'].request )
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Role
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'url',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'RoleModelSerializer')
|
||||
class ModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
BaseSerializer
|
||||
):
|
||||
"""Role Base Model"""
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> dict:
|
||||
|
||||
get_url = super().get_url( item = item )
|
||||
|
||||
get_url.update({
|
||||
'tickets': reverse(
|
||||
"v2:_api_v2_item_tickets-list",
|
||||
request=self._context['view'].request,
|
||||
kwargs={
|
||||
'item_class': self.Meta.model._meta.model_name,
|
||||
'item_id': item.pk
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return get_url
|
||||
|
||||
|
||||
permissions = serializers.PrimaryKeyRelatedField(many = True, queryset=permission_queryset(), required = False)
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Role
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'organization',
|
||||
'display_name',
|
||||
'name',
|
||||
'permissions',
|
||||
'model_notes',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'RoleViewSerializer')
|
||||
class ViewSerializer(ModelSerializer):
|
||||
"""Role Base View Model"""
|
||||
|
||||
organization = TenantBaseSerializer( many=False, read_only=True )
|
||||
|
||||
permissions = PermissionBaseSerializer( many=True, read_only=True )
|
@ -1,101 +0,0 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from centurion.serializers.user import UserBaseSerializer
|
||||
|
||||
|
||||
|
||||
class TeamUserBaseSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
display_name = serializers.SerializerMethodField('get_display_name')
|
||||
|
||||
def get_display_name(self, item) -> str:
|
||||
|
||||
return str( item )
|
||||
|
||||
url = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> str:
|
||||
|
||||
return item.get_url( request = self.context['view'].request )
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = TeamUsers
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'url',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class TeamUserModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
TeamUserBaseSerializer
|
||||
):
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> dict:
|
||||
|
||||
get_url = super().get_url( item = item )
|
||||
|
||||
# del get_url['history']
|
||||
|
||||
del get_url['knowledge_base']
|
||||
|
||||
return get_url
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = TeamUsers
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'manager',
|
||||
'user',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
def is_valid(self, *, raise_exception=True) -> bool:
|
||||
|
||||
is_valid = False
|
||||
|
||||
is_valid = super().is_valid(raise_exception=raise_exception)
|
||||
|
||||
self.validated_data['team_id'] = int(self._context['view'].kwargs['team_id'])
|
||||
|
||||
return is_valid
|
||||
|
||||
|
||||
|
||||
class TeamUserViewSerializer(TeamUserModelSerializer):
|
||||
|
||||
user = UserBaseSerializer(read_only = True)
|
@ -1,131 +0,0 @@
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from access.models.team import Team
|
||||
|
||||
from api.serializers import common
|
||||
|
||||
from access.functions.permissions import permission_queryset
|
||||
from access.serializers.organization import TenantBaseSerializer
|
||||
|
||||
from centurion.serializers.permission import PermissionBaseSerializer
|
||||
|
||||
from core import fields as centurion_field
|
||||
|
||||
|
||||
|
||||
class TeamBaseSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
display_name = serializers.SerializerMethodField('get_display_name')
|
||||
|
||||
def get_display_name(self, item) -> str:
|
||||
|
||||
return str( item )
|
||||
|
||||
url = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> str:
|
||||
|
||||
return item.get_url( request = self.context['view'].request )
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Team
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'team_name',
|
||||
'url',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'team_name',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class TeamModelSerializer(
|
||||
common.CommonModelSerializer,
|
||||
TeamBaseSerializer
|
||||
):
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> dict:
|
||||
|
||||
get_url = super().get_url( item = item )
|
||||
|
||||
get_url.update({
|
||||
'users': reverse(
|
||||
'v2:_api_v2_organization_team_user-list',
|
||||
request=self.context['view'].request,
|
||||
kwargs={
|
||||
'organization_id': item.organization.id,
|
||||
'team_id': item.pk
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return get_url
|
||||
|
||||
|
||||
team_name = centurion_field.CharField( autolink = True )
|
||||
|
||||
permissions = serializers.PrimaryKeyRelatedField(many = True, queryset=permission_queryset(), required = False)
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Team
|
||||
|
||||
fields = '__all__'
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'team_name',
|
||||
'model_notes',
|
||||
'permissions',
|
||||
'organization',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'name',
|
||||
'organization',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
def is_valid(self, *, raise_exception=True) -> bool:
|
||||
|
||||
is_valid = False
|
||||
|
||||
is_valid = super().is_valid(raise_exception=raise_exception)
|
||||
|
||||
self.validated_data['organization_id'] = int(self._context['view'].kwargs['organization_id'])
|
||||
|
||||
|
||||
return is_valid
|
||||
|
||||
|
||||
|
||||
class TeamViewSerializer(TeamModelSerializer):
|
||||
|
||||
organization = TenantBaseSerializer(many=False, read_only=True)
|
||||
|
||||
permissions = PermissionBaseSerializer(many = True)
|
22
app/access/templates/access/index.html.j2
Normal file
22
app/access/templates/access/index.html.j2
Normal file
@ -0,0 +1,22 @@
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% block content_header_icon %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<table class="data">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Created</th>
|
||||
<th>Modified</th>
|
||||
</tr>
|
||||
{% for org in organization_list %}
|
||||
<tr>
|
||||
<td><a href="/organization/{{ org.id }}/">{{ org.name }}</a></td>
|
||||
<td>{{ org.created }}</td>
|
||||
<td>{{ org.modified }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</table>
|
||||
{% endblock %}
|
106
app/access/templates/access/organization.html.j2
Normal file
106
app/access/templates/access/organization.html.j2
Normal file
@ -0,0 +1,106 @@
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% load markdown %}
|
||||
|
||||
{% block title %}Organization - {{ organization.name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
form div .helptext {
|
||||
background-color: rgb(0, 140, 255);
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.detail-view-field {
|
||||
display:unset;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 0px 20px 40px 20px;
|
||||
|
||||
}
|
||||
|
||||
.detail-view-field label {
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
width: 200px;
|
||||
margin: 10px;
|
||||
/*padding: 10px;*/
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
|
||||
}
|
||||
|
||||
.detail-view-field span {
|
||||
display: inline-block;
|
||||
width: 340px;
|
||||
margin: 10px;
|
||||
/*padding: 10px;*/
|
||||
border-bottom: 1px solid #ccc;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<div style="align-items:flex-start; align-content: center; display: flexbox; width: 100%">
|
||||
<div style="display: inline; width: 40%; margin: 30px;">
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.name.label }}</label>
|
||||
<span>{{ form.name.value }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.manager.label }}</label>
|
||||
<span>{{ organization.manager }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.created.label }}</label>
|
||||
<span>{{ form.created.value }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.modified.label }}</label>
|
||||
<span>{{ form.modified.value }}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div style="display: inline; width: 40%; margin: 30px; text-align: left;">
|
||||
<div>
|
||||
<label style="font-weight: bold; width: 100%; border-bottom: 1px solid #ccc; display: block; text-align: inherit;">{{ form.model_notes.label }}</label>
|
||||
|
||||
<div style="display: inline-block; text-align: left;">{{ form.model_notes.value | markdown | safe }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: block;">
|
||||
<input type="button" value="<< Back" onclick="window.location='{% url 'Access:Organizations' %}';">
|
||||
<input type="button" value="New Team" onclick="window.location='{% url 'Access:_team_add' organization.id %}';">
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Team Name</th>
|
||||
<th>Created</th>
|
||||
<th>Modified</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for field in teams %}
|
||||
<tr>
|
||||
<td><a href="{% url 'Access:_team_view' organization_id=organization.id pk=field.id %}">{{ field.team_name }}</a></td>
|
||||
<td>{{ field.created }}</td>
|
||||
<td>{{ field.modified }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
48
app/access/templates/access/team.html.j2
Normal file
48
app/access/templates/access/team.html.j2
Normal file
@ -0,0 +1,48 @@
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% block title %}Team - {{ team.team_name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{{ form.as_div }}
|
||||
|
||||
<input style="display:unset;" type="submit" value="Submit">
|
||||
</form>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
<input type="button" value="<< Back" onclick="window.location='{% url 'Access:_organization_view' pk=organization.id %}';">
|
||||
<input type="button" value="Delete Team"
|
||||
onclick="window.location='{% url 'Access:_team_delete' organization_id=organization.id pk=team.id %}';">
|
||||
<input type="button" value="Assign User"
|
||||
onclick="window.location='{% url 'Access:_team_user_add' organization_id=organization.id pk=team.id %}';">
|
||||
{{ formset.management_form }}
|
||||
|
||||
<table id="formset" class="form">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>Manager</th>
|
||||
<th>Created</th>
|
||||
<th>Modified</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
{% for field in teamusers %}
|
||||
<tr>
|
||||
<td>{{ field.user }}</td>
|
||||
<td><input type="checkbox" {% if field.manager %}checked{% endif %} disabled></td>
|
||||
<td>{{ field.created }}</td>
|
||||
<td>{{ field.modified }}</td>
|
||||
<td><a
|
||||
href="{% url 'Access:_team_user_delete' organization_id=organization.id team_id=field.team_id pk=field.id %}">Delete</a></a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
88
app/access/tests/abstract/tenancy_object.py
Normal file
88
app/access/tests/abstract/tenancy_object.py
Normal file
@ -0,0 +1,88 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from access.models import TenancyManager
|
||||
|
||||
|
||||
|
||||
class TenancyObject:
|
||||
""" Tests for checking TenancyObject """
|
||||
|
||||
model = None
|
||||
""" Model to be tested """
|
||||
|
||||
|
||||
def test_has_attr_get_organization(self):
|
||||
""" TenancyObject attribute check
|
||||
|
||||
TenancyObject has function get_organization
|
||||
"""
|
||||
|
||||
assert hasattr(self.model, 'get_organization')
|
||||
|
||||
|
||||
def test_has_attr_is_global(self):
|
||||
""" TenancyObject attribute check
|
||||
|
||||
TenancyObject has field is_global
|
||||
"""
|
||||
|
||||
assert hasattr(self.model, 'is_global')
|
||||
|
||||
|
||||
|
||||
def test_has_attr_model_notes(self):
|
||||
""" TenancyObject attribute check
|
||||
|
||||
TenancyObject has field model_notes
|
||||
"""
|
||||
|
||||
assert hasattr(self.model, 'model_notes')
|
||||
|
||||
|
||||
|
||||
def test_has_attr_organization(self):
|
||||
""" TenancyObject attribute check
|
||||
|
||||
TenancyObject has field organization
|
||||
"""
|
||||
|
||||
assert hasattr(self.model, 'organization')
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_create_no_organization_fails(self):
|
||||
""" Devices must be assigned an organization
|
||||
|
||||
Must not be able to create an item without an organization
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_edit_no_organization_fails(self):
|
||||
""" Devices must be assigned an organization
|
||||
|
||||
Must not be able to edit an item without an organization
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def test_has_attr_organization(self):
|
||||
""" TenancyObject attribute check
|
||||
|
||||
TenancyObject has function objects
|
||||
"""
|
||||
|
||||
assert hasattr(self.model, 'objects')
|
||||
|
||||
|
||||
def test_attribute_is_type_objects(self):
|
||||
""" Attribute Check
|
||||
|
||||
attribute `objects` must be set to `access.models.TenancyManager()`
|
||||
"""
|
||||
|
||||
assert type(self.model.objects) is TenancyManager
|
@ -1,204 +0,0 @@
|
||||
import pytest
|
||||
import random
|
||||
|
||||
from django.test import Client
|
||||
|
||||
|
||||
class AdditionalTestCases:
|
||||
|
||||
|
||||
def test_permission_add(self, model_instance, api_request_permissions,
|
||||
model_kwargs, kwargs_api_create
|
||||
):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user with permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
|
||||
client.force_login( api_request_permissions['user']['add'] )
|
||||
|
||||
the_model = model_instance( kwargs_create = self.kwargs_create_item )
|
||||
|
||||
url = the_model.get_url( many = True )
|
||||
|
||||
the_model.delete()
|
||||
|
||||
kwargs = kwargs_api_create.copy()
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
response = client.post(
|
||||
path = url,
|
||||
data = kwargs,
|
||||
content_type = 'application/json'
|
||||
)
|
||||
|
||||
|
||||
assert response.status_code == 201, response.content
|
||||
|
||||
|
||||
|
||||
def test_returned_results_only_user_orgs(self, model_instance, model_kwargs, api_request_permissions):
|
||||
"""Returned results check
|
||||
|
||||
Ensure that a query to the viewset endpoint does not return
|
||||
items that are not part of the users organizations.
|
||||
"""
|
||||
|
||||
if model_kwargs.get('organization', None) is None:
|
||||
pytest.xfail( reason = 'Model lacks organization field. test is n/a' )
|
||||
|
||||
|
||||
client = Client()
|
||||
|
||||
viewable_organizations = [
|
||||
api_request_permissions['tenancy']['user'].id,
|
||||
]
|
||||
|
||||
if getattr(self, 'global_organization', None):
|
||||
# Cater for above test that also has global org
|
||||
|
||||
viewable_organizations += [ api_request_permissions['tenancy']['global'] ]
|
||||
|
||||
|
||||
client.force_login( api_request_permissions['user']['view'] )
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs.update({
|
||||
'organization': api_request_permissions['tenancy']['different']
|
||||
})
|
||||
|
||||
model_instance(
|
||||
kwargs_create = kwargs
|
||||
)
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs.update({
|
||||
'organization': api_request_permissions['tenancy']['global']
|
||||
})
|
||||
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
model_instance(
|
||||
kwargs_create = kwargs
|
||||
)
|
||||
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
the_model = model_instance( kwargs_create = kwargs )
|
||||
|
||||
response = client.get(
|
||||
path = the_model.get_url( many = True )
|
||||
)
|
||||
|
||||
# if response.status_code == 405:
|
||||
# pytest.xfail( reason = 'ViewSet does not have this request method.' )
|
||||
|
||||
# elif IsAuthenticatedOrReadOnly in response.renderer_context['view'].permission_classes:
|
||||
|
||||
# pytest.xfail( reason = 'ViewSet is public viewable, test is N/A' )
|
||||
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
contains_different_org: bool = False
|
||||
|
||||
for item in response.data['results']:
|
||||
|
||||
if 'organization' not in item:
|
||||
pytest.xfail( reason = 'Model lacks organization field. test is n/a' )
|
||||
|
||||
if(
|
||||
int(item['organization']['id']) not in viewable_organizations
|
||||
and
|
||||
int(item['organization']['id']) != api_request_permissions['tenancy']['global'].id
|
||||
):
|
||||
|
||||
contains_different_org = True
|
||||
print(f'Failed returned row was: {item}')
|
||||
|
||||
assert not contains_different_org
|
||||
|
||||
|
||||
|
||||
def test_returned_data_from_user_and_global_organizations_only(
|
||||
self, model_instance, model_kwargs, api_request_permissions
|
||||
):
|
||||
"""Check items returned
|
||||
|
||||
Items returned from the query Must be from the users organization and
|
||||
global ONLY!
|
||||
"""
|
||||
|
||||
if model_kwargs.get('organization', None) is None:
|
||||
pytest.xfail( reason = 'Model lacks organization field. test is n/a' )
|
||||
|
||||
client = Client()
|
||||
|
||||
only_from_user_org: bool = True
|
||||
|
||||
viewable_organizations = [
|
||||
api_request_permissions['tenancy']['user'].id,
|
||||
api_request_permissions['tenancy']['global'].id
|
||||
]
|
||||
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs.update({
|
||||
'organization': api_request_permissions['tenancy']['different']
|
||||
})
|
||||
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
the_model = model_instance(
|
||||
kwargs_create = kwargs
|
||||
)
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs.update({
|
||||
'organization': api_request_permissions['tenancy']['global']
|
||||
})
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
model_instance(
|
||||
kwargs_create = kwargs
|
||||
)
|
||||
|
||||
|
||||
client.force_login( api_request_permissions['user']['view'] )
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
the_model = model_instance( kwargs_create = kwargs )
|
||||
|
||||
response = client.get(
|
||||
path = the_model.get_url( many = True )
|
||||
)
|
||||
|
||||
assert len(response.data['results']) >= 2 # fail if only one item extist.
|
||||
|
||||
|
||||
for row in response.data['results']:
|
||||
|
||||
if 'organization' not in row:
|
||||
pytest.xfail( reason = 'Model lacks organization field. test is n/a' )
|
||||
|
||||
if row['organization']['id'] not in viewable_organizations:
|
||||
|
||||
only_from_user_org = False
|
||||
|
||||
print(f"Users org: {api_request_permissions['tenancy']['user'].id}")
|
||||
print(f"global org: {api_request_permissions['tenancy']['global'].id}")
|
||||
print(f'Failed returned row was: {row}')
|
||||
|
||||
assert only_from_user_org
|
@ -1,204 +0,0 @@
|
||||
import pytest
|
||||
import random
|
||||
|
||||
from django.test import Client
|
||||
|
||||
|
||||
class AdditionalTestCases:
|
||||
|
||||
|
||||
def test_permission_add(self, model_instance, api_request_permissions,
|
||||
model_kwargs, kwargs_api_create
|
||||
):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user with permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
|
||||
client.force_login( api_request_permissions['user']['add'] )
|
||||
|
||||
the_model = model_instance( kwargs_create = self.kwargs_create_item )
|
||||
|
||||
url = the_model.get_url( many = True )
|
||||
|
||||
the_model.delete()
|
||||
|
||||
kwargs = kwargs_api_create.copy()
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
response = client.post(
|
||||
path = url,
|
||||
data = kwargs,
|
||||
content_type = 'application/json'
|
||||
)
|
||||
|
||||
|
||||
assert response.status_code == 201, response.content
|
||||
|
||||
|
||||
|
||||
def test_returned_results_only_user_orgs(self, model_instance, model_kwargs, api_request_permissions):
|
||||
"""Returned results check
|
||||
|
||||
Ensure that a query to the viewset endpoint does not return
|
||||
items that are not part of the users organizations.
|
||||
"""
|
||||
|
||||
if model_kwargs.get('organization', None) is None:
|
||||
pytest.xfail( reason = 'Model lacks organization field. test is n/a' )
|
||||
|
||||
|
||||
client = Client()
|
||||
|
||||
viewable_organizations = [
|
||||
api_request_permissions['tenancy']['user'].id,
|
||||
]
|
||||
|
||||
if getattr(self, 'global_organization', None):
|
||||
# Cater for above test that also has global org
|
||||
|
||||
viewable_organizations += [ api_request_permissions['tenancy']['global'] ]
|
||||
|
||||
|
||||
client.force_login( api_request_permissions['user']['view'] )
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs.update({
|
||||
'organization': api_request_permissions['tenancy']['different']
|
||||
})
|
||||
|
||||
model_instance(
|
||||
kwargs_create = kwargs
|
||||
)
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs.update({
|
||||
'organization': api_request_permissions['tenancy']['global']
|
||||
})
|
||||
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
model_instance(
|
||||
kwargs_create = kwargs
|
||||
)
|
||||
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
the_model = model_instance( kwargs_create = kwargs )
|
||||
|
||||
response = client.get(
|
||||
path = the_model.get_url( many = True )
|
||||
)
|
||||
|
||||
# if response.status_code == 405:
|
||||
# pytest.xfail( reason = 'ViewSet does not have this request method.' )
|
||||
|
||||
# elif IsAuthenticatedOrReadOnly in response.renderer_context['view'].permission_classes:
|
||||
|
||||
# pytest.xfail( reason = 'ViewSet is public viewable, test is N/A' )
|
||||
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
contains_different_org: bool = False
|
||||
|
||||
for item in response.data['results']:
|
||||
|
||||
if 'organization' not in item:
|
||||
pytest.xfail( reason = 'Model lacks organization field. test is n/a' )
|
||||
|
||||
if(
|
||||
int(item['organization']['id']) not in viewable_organizations
|
||||
and
|
||||
int(item['organization']['id']) != api_request_permissions['tenancy']['global'].id
|
||||
):
|
||||
|
||||
contains_different_org = True
|
||||
print(f'Failed returned row was: {item}')
|
||||
|
||||
assert not contains_different_org
|
||||
|
||||
|
||||
|
||||
def test_returned_data_from_user_and_global_organizations_only(
|
||||
self, model_instance, model_kwargs, api_request_permissions
|
||||
):
|
||||
"""Check items returned
|
||||
|
||||
Items returned from the query Must be from the users organization and
|
||||
global ONLY!
|
||||
"""
|
||||
|
||||
if model_kwargs.get('organization', None) is None:
|
||||
pytest.xfail( reason = 'Model lacks organization field. test is n/a' )
|
||||
|
||||
client = Client()
|
||||
|
||||
only_from_user_org: bool = True
|
||||
|
||||
viewable_organizations = [
|
||||
api_request_permissions['tenancy']['user'].id,
|
||||
api_request_permissions['tenancy']['global'].id
|
||||
]
|
||||
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs.update({
|
||||
'organization': api_request_permissions['tenancy']['different']
|
||||
})
|
||||
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
the_model = model_instance(
|
||||
kwargs_create = kwargs
|
||||
)
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs.update({
|
||||
'organization': api_request_permissions['tenancy']['global']
|
||||
})
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
model_instance(
|
||||
kwargs_create = kwargs
|
||||
)
|
||||
|
||||
|
||||
client.force_login( api_request_permissions['user']['view'] )
|
||||
|
||||
kwargs = self.kwargs_create_item.copy()
|
||||
kwargs['dob'] = str(random.randint(1972, 2037)) + '-' + str(
|
||||
random.randint(1, 12)) + '-' + str(random.randint(1, 28))
|
||||
|
||||
the_model = model_instance( kwargs_create = kwargs )
|
||||
|
||||
response = client.get(
|
||||
path = the_model.get_url( many = True )
|
||||
)
|
||||
|
||||
assert len(response.data['results']) >= 2 # fail if only one item extist.
|
||||
|
||||
|
||||
for row in response.data['results']:
|
||||
|
||||
if 'organization' not in row:
|
||||
pytest.xfail( reason = 'Model lacks organization field. test is n/a' )
|
||||
|
||||
if row['organization']['id'] not in viewable_organizations:
|
||||
|
||||
only_from_user_org = False
|
||||
|
||||
print(f"Users org: {api_request_permissions['tenancy']['user'].id}")
|
||||
print(f"global org: {api_request_permissions['tenancy']['global'].id}")
|
||||
print(f'Failed returned row was: {row}')
|
||||
|
||||
assert only_from_user_org
|
@ -1,44 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from django.test import Client
|
||||
|
||||
|
||||
class AdditionalTestCases:
|
||||
|
||||
|
||||
def test_permission_add(self, model_instance, api_request_permissions,
|
||||
model_kwargs, kwargs_api_create
|
||||
):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user with permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
|
||||
client.force_login( api_request_permissions['user']['add'] )
|
||||
|
||||
the_model = model_instance( kwargs_create = model_kwargs )
|
||||
|
||||
url = the_model.get_url( many = True )
|
||||
|
||||
response = client.post(
|
||||
path = url,
|
||||
data = kwargs_api_create,
|
||||
content_type = 'application/json'
|
||||
)
|
||||
|
||||
assert response.status_code == 201, response.content
|
||||
|
||||
|
||||
|
||||
def test_returned_data_from_user_and_global_organizations_only(
|
||||
self
|
||||
):
|
||||
"""Check items returned
|
||||
|
||||
Items returned from the query Must be from the users organization and
|
||||
global ONLY!
|
||||
"""
|
||||
|
||||
pytest.mark.xfail( reason = 'model is not for global use' )
|
@ -1,35 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from access.models.company_base import Company
|
||||
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def model(request):
|
||||
|
||||
request.cls.model = Company
|
||||
|
||||
yield request.cls.model
|
||||
|
||||
del request.cls.model
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_serializer():
|
||||
|
||||
from access.serializers.entity_company import ModelSerializer
|
||||
|
||||
|
||||
yield ModelSerializer
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def model_kwargs(request, kwargs_company):
|
||||
|
||||
request.cls.kwargs_create_item = kwargs_company.copy()
|
||||
|
||||
yield kwargs_company.copy()
|
||||
|
||||
if hasattr(request.cls, 'kwargs_create_item'):
|
||||
del request.cls.kwargs_create_item
|
@ -1,36 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from access.tests.functional.entity.test_functional_entity_api_fields import (
|
||||
EntityAPIInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
@pytest.mark.model_company
|
||||
class CompanyAPITestCases(
|
||||
EntityAPIInheritedCases,
|
||||
):
|
||||
|
||||
@property
|
||||
def parameterized_api_fields(self):
|
||||
|
||||
return {
|
||||
'name': {
|
||||
'expected': str
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CompanyAPIInheritedCases(
|
||||
CompanyAPITestCases,
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.module_access
|
||||
class CompanyAPIPyTest(
|
||||
CompanyAPITestCases,
|
||||
):
|
||||
|
||||
pass
|
@ -1,72 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.company_base import Company
|
||||
from access.tests.functional.entity.test_functional_entity_metadata import (
|
||||
EntityMetadataInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class CompanyMetadataTestCases(
|
||||
EntityMetadataInheritedCases,
|
||||
):
|
||||
|
||||
add_data: dict = {
|
||||
'name': 'Ian1'
|
||||
}
|
||||
|
||||
kwargs_create_item: dict = {
|
||||
'name': 'Ian2',
|
||||
}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {
|
||||
'name': 'Ian3',
|
||||
}
|
||||
|
||||
model = Company
|
||||
|
||||
|
||||
|
||||
|
||||
class CompanyMetadataInheritedCases(
|
||||
CompanyMetadataTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_item = {
|
||||
**super().kwargs_create_item,
|
||||
**self.kwargs_create_item
|
||||
}
|
||||
|
||||
self.kwargs_create_item_diff_org = {
|
||||
**super().kwargs_create_item_diff_org,
|
||||
**self.kwargs_create_item_diff_org
|
||||
}
|
||||
|
||||
# self.url_kwargs = {
|
||||
# 'model_name': self.model._meta.sub_model_type
|
||||
# }
|
||||
|
||||
# self.url_view_kwargs = {
|
||||
# 'model_name': self.model._meta.sub_model_type
|
||||
# }
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class CompanyMetadataTest(
|
||||
CompanyMetadataTestCases,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
pass
|
@ -1,34 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from access.models.contact import Contact
|
||||
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def model(request):
|
||||
|
||||
request.cls.model = Contact
|
||||
|
||||
yield request.cls.model
|
||||
|
||||
del request.cls.model
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_serializer():
|
||||
|
||||
from access.serializers.entity_contact import ModelSerializer
|
||||
|
||||
|
||||
yield ModelSerializer
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def model_kwargs(request, kwargs_contact):
|
||||
|
||||
request.cls.kwargs_create_item = kwargs_contact.copy()
|
||||
|
||||
yield kwargs_contact.copy()
|
||||
|
||||
if hasattr(request.cls, 'kwargs_create_item'):
|
||||
del request.cls.kwargs_create_item
|
@ -1,41 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from access.tests.functional.person.test_functional_person_api_fields import (
|
||||
PersonAPIInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
@pytest.mark.model_contact
|
||||
class ContactAPITestCases(
|
||||
PersonAPIInheritedCases,
|
||||
):
|
||||
|
||||
property
|
||||
def parameterized_api_fields(self):
|
||||
|
||||
return {
|
||||
'email': {
|
||||
'expected': str
|
||||
},
|
||||
'directory': {
|
||||
'expected': bool
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class ContactAPIInheritedCases(
|
||||
ContactAPITestCases,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.module_access
|
||||
class ContactAPIPyTest(
|
||||
ContactAPITestCases,
|
||||
):
|
||||
|
||||
pass
|
@ -1,65 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.contact import Contact
|
||||
|
||||
from access.tests.functional.person.test_functional_person_metadata import (
|
||||
PersonMetadataInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ContactMetadataTestCases(
|
||||
PersonMetadataInheritedCases,
|
||||
):
|
||||
|
||||
add_data: dict = {
|
||||
'email': 'ipfunny@unit.test',
|
||||
}
|
||||
|
||||
kwargs_create_item: dict = {
|
||||
'email': 'ipweird@unit.test',
|
||||
}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {
|
||||
'email': 'ipstrange@unit.test',
|
||||
}
|
||||
|
||||
model = Contact
|
||||
|
||||
|
||||
|
||||
|
||||
class ContactMetadataInheritedCases(
|
||||
ContactMetadataTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_item = {
|
||||
**super().kwargs_create_item,
|
||||
**self.kwargs_create_item
|
||||
}
|
||||
|
||||
self.kwargs_create_item_diff_org = {
|
||||
**super().kwargs_create_item_diff_org,
|
||||
**self.kwargs_create_item_diff_org
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class ContactMetadataTest(
|
||||
ContactMetadataTestCases,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
pass
|
@ -1,34 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from access.models.entity import Entity
|
||||
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def model(request):
|
||||
|
||||
request.cls.model = Entity
|
||||
|
||||
yield request.cls.model
|
||||
|
||||
del request.cls.model
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_serializer():
|
||||
|
||||
from access.serializers.entity import ModelSerializer
|
||||
|
||||
|
||||
yield ModelSerializer
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def model_kwargs(request, kwargs_entity):
|
||||
|
||||
request.cls.kwargs_create_item = kwargs_entity.copy()
|
||||
|
||||
yield kwargs_entity.copy()
|
||||
|
||||
if hasattr(request.cls, 'kwargs_create_item'):
|
||||
del request.cls.kwargs_create_item
|
@ -1,48 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from access.models.entity import Entity
|
||||
|
||||
from api.tests.functional.test_functional_api_fields import (
|
||||
APIFieldsInheritedCases,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@pytest.mark.model_entity
|
||||
class EntityAPITestCases(
|
||||
APIFieldsInheritedCases,
|
||||
):
|
||||
|
||||
base_model = Entity
|
||||
|
||||
|
||||
@property
|
||||
def parameterized_api_fields(self):
|
||||
|
||||
return {
|
||||
'entity_type': {
|
||||
'expected': str
|
||||
},
|
||||
'_urls.history': {
|
||||
'expected': str
|
||||
},
|
||||
'_urls.knowledge_base': {
|
||||
'expected': str
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class EntityAPIInheritedCases(
|
||||
EntityAPITestCases,
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.module_access
|
||||
class EntityAPIPyTest(
|
||||
EntityAPITestCases,
|
||||
):
|
||||
|
||||
pass
|
@ -1,260 +0,0 @@
|
||||
import django
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.entity import Entity
|
||||
from access.models.tenant import Tenant as Organization
|
||||
from access.models.team import Team
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
from accounting.models.asset_base import AssetBase
|
||||
|
||||
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
|
||||
|
||||
class EntityMetadataTestCases(
|
||||
MetadataAttributesFunctional,
|
||||
):
|
||||
|
||||
add_data: dict = {}
|
||||
|
||||
app_namespace = 'v2'
|
||||
|
||||
base_model = Entity
|
||||
"""Base model for this sub model
|
||||
don't change or override this value
|
||||
"""
|
||||
|
||||
change_data = None
|
||||
|
||||
delete_data = {}
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
model = Entity
|
||||
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
url_name = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization for user and item
|
||||
. create an organization that is different to item
|
||||
2. Create a team
|
||||
3. create teams with each permission: view, add, change, delete
|
||||
4. create a user per team
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.different_organization = Organization.objects.create(name='test_different_organization')
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_user_view", password="password")
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = organization,
|
||||
**self.kwargs_create_item
|
||||
)
|
||||
|
||||
self.other_org_item = self.model.objects.create(
|
||||
organization = self.different_organization,
|
||||
**self.kwargs_create_item_diff_org
|
||||
)
|
||||
|
||||
|
||||
self.url_view_kwargs.update({ 'pk': self.item.id })
|
||||
|
||||
if self.add_data is not None:
|
||||
|
||||
self.add_data.update({
|
||||
'organization': self.organization.id,
|
||||
})
|
||||
|
||||
|
||||
view_permissions = Permission.objects.get(
|
||||
codename = 'view_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
view_team = Team.objects.create(
|
||||
team_name = 'view_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
view_team.permissions.set([view_permissions])
|
||||
|
||||
|
||||
|
||||
add_permissions = Permission.objects.get(
|
||||
codename = 'add_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
add_team = Team.objects.create(
|
||||
team_name = 'add_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
add_team.permissions.set([add_permissions])
|
||||
|
||||
|
||||
|
||||
change_permissions = Permission.objects.get(
|
||||
codename = 'change_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
change_team = Team.objects.create(
|
||||
team_name = 'change_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
change_team.permissions.set([change_permissions])
|
||||
|
||||
|
||||
|
||||
delete_permissions = Permission.objects.get(
|
||||
codename = 'delete_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
delete_team = Team.objects.create(
|
||||
team_name = 'delete_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
delete_team.permissions.set([delete_permissions])
|
||||
|
||||
|
||||
self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password")
|
||||
|
||||
|
||||
TeamUsers.objects.create(
|
||||
team = view_team,
|
||||
user = self.view_user
|
||||
)
|
||||
|
||||
self.add_user = User.objects.create_user(username="test_user_add", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = add_team,
|
||||
user = self.add_user
|
||||
)
|
||||
|
||||
self.change_user = User.objects.create_user(username="test_user_change", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = change_team,
|
||||
user = self.change_user
|
||||
)
|
||||
|
||||
self.delete_user = User.objects.create_user(username="test_user_delete", password="password")
|
||||
TeamUsers.objects.create(
|
||||
team = delete_team,
|
||||
user = self.delete_user
|
||||
)
|
||||
|
||||
|
||||
self.different_organization_user = User.objects.create_user(username="test_different_organization_user", password="password")
|
||||
|
||||
|
||||
different_organization_team = Team.objects.create(
|
||||
team_name = 'different_organization_team',
|
||||
organization = self.different_organization,
|
||||
)
|
||||
|
||||
different_organization_team.permissions.set([
|
||||
view_permissions,
|
||||
add_permissions,
|
||||
change_permissions,
|
||||
delete_permissions,
|
||||
])
|
||||
|
||||
TeamUsers.objects.create(
|
||||
team = different_organization_team,
|
||||
user = self.different_organization_user
|
||||
)
|
||||
|
||||
|
||||
def test_sanity_is_entity_sub_model(self):
|
||||
"""Sanity Test
|
||||
|
||||
This test ensures that the model being tested `self.model` is a
|
||||
sub-model of `self.base_model`.
|
||||
This test is required as the same viewset is used for all sub-models
|
||||
of `AssetBase`
|
||||
"""
|
||||
|
||||
assert issubclass(self.model, self.base_model)
|
||||
|
||||
|
||||
|
||||
class EntityMetadataInheritedCases(
|
||||
EntityMetadataTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
url_name = '_api_entity_sub'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_item = {
|
||||
**super().kwargs_create_item,
|
||||
**self.kwargs_create_item
|
||||
}
|
||||
|
||||
self.kwargs_create_item_diff_org = {
|
||||
**super().kwargs_create_item_diff_org,
|
||||
**self.kwargs_create_item_diff_org
|
||||
}
|
||||
|
||||
self.url_kwargs = {
|
||||
'model_name': self.model._meta.sub_model_type
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'model_name': self.model._meta.sub_model_type
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class EntityMetadataTest(
|
||||
EntityMetadataTestCases,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
|
||||
url_name = '_api_entity'
|
@ -1,213 +0,0 @@
|
||||
import django
|
||||
import pytest
|
||||
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
|
||||
|
||||
class MockView:
|
||||
|
||||
_has_import: bool = False
|
||||
"""User Permission
|
||||
|
||||
get_permission_required() sets this to `True` when user has import permission.
|
||||
"""
|
||||
|
||||
_has_purge: bool = False
|
||||
"""User Permission
|
||||
|
||||
get_permission_required() sets this to `True` when user has purge permission.
|
||||
"""
|
||||
|
||||
_has_triage: bool = False
|
||||
"""User Permission
|
||||
|
||||
get_permission_required() sets this to `True` when user has triage permission.
|
||||
"""
|
||||
|
||||
|
||||
@pytest.mark.skip(reason = 'see #874, tests being refactored')
|
||||
class EntitySerializerTestCases:
|
||||
|
||||
|
||||
parameterized_test_data: dict = {
|
||||
"model_notes": {
|
||||
'will_create': True,
|
||||
}
|
||||
}
|
||||
|
||||
valid_data: dict = {
|
||||
'model_notes': 'model notes field'
|
||||
}
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def setup_data(self,
|
||||
request,
|
||||
model,
|
||||
django_db_blocker,
|
||||
organization_one,
|
||||
):
|
||||
|
||||
with django_db_blocker.unblock():
|
||||
|
||||
request.cls.organization = organization_one
|
||||
|
||||
valid_data = {}
|
||||
|
||||
for base in reversed(request.cls.__mro__):
|
||||
|
||||
if hasattr(base, 'valid_data'):
|
||||
|
||||
if base.valid_data is None:
|
||||
|
||||
continue
|
||||
|
||||
valid_data.update(**base.valid_data)
|
||||
|
||||
|
||||
if len(valid_data) > 0:
|
||||
|
||||
request.cls.valid_data = valid_data
|
||||
|
||||
|
||||
if 'organization' not in request.cls.valid_data:
|
||||
|
||||
request.cls.valid_data.update({
|
||||
'organization': request.cls.organization.pk
|
||||
})
|
||||
|
||||
|
||||
request.cls.view_user = User.objects.create_user(username="cafs_test_user_view", password="password")
|
||||
|
||||
|
||||
yield
|
||||
|
||||
with django_db_blocker.unblock():
|
||||
|
||||
request.cls.view_user.delete()
|
||||
|
||||
del request.cls.valid_data
|
||||
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class', autouse = True)
|
||||
def class_setup(self,
|
||||
setup_data,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def test_serializer_valid_data(self, create_serializer):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that when creating an object with valid data, no validation
|
||||
error occurs.
|
||||
"""
|
||||
|
||||
view_set = MockView()
|
||||
|
||||
serializer = create_serializer(
|
||||
context = {
|
||||
'view': view_set,
|
||||
},
|
||||
data = self.valid_data
|
||||
)
|
||||
|
||||
assert serializer.is_valid(raise_exception = True)
|
||||
|
||||
|
||||
|
||||
def test_serializer_valid_data_missing_field_is_valid(self, parameterized, param_key_test_data,
|
||||
create_serializer,
|
||||
param_value,
|
||||
param_will_create,
|
||||
):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that when creating an object with a user with import permission
|
||||
and with valid data, no validation error occurs.
|
||||
"""
|
||||
|
||||
valid_data = self.valid_data.copy()
|
||||
|
||||
del valid_data[param_value]
|
||||
|
||||
view_set = MockView()
|
||||
|
||||
view_set._has_import = True
|
||||
|
||||
serializer = create_serializer(
|
||||
context = {
|
||||
'view': view_set,
|
||||
},
|
||||
data = valid_data
|
||||
)
|
||||
|
||||
is_valid = serializer.is_valid(raise_exception = False)
|
||||
|
||||
assert (
|
||||
(
|
||||
not param_will_create
|
||||
and param_will_create == is_valid
|
||||
)
|
||||
or param_will_create == is_valid
|
||||
)
|
||||
|
||||
|
||||
|
||||
class EntitySerializerInheritedCases(
|
||||
EntitySerializerTestCases,
|
||||
):
|
||||
|
||||
parameterized_test_data: dict = None
|
||||
|
||||
model = None
|
||||
"""Model to test"""
|
||||
|
||||
valid_data: dict = None
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
def test_serializer_valid_data_missing_field_raises_exception(self, parameterized, param_key_test_data,
|
||||
create_serializer,
|
||||
param_value,
|
||||
param_exception_key,
|
||||
):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that when creating an object with a user with import permission
|
||||
and with valid data, no validation error occurs.
|
||||
"""
|
||||
|
||||
valid_data = self.valid_data.copy()
|
||||
|
||||
del valid_data[param_value]
|
||||
|
||||
view_set = MockView()
|
||||
|
||||
with pytest.raises(ValidationError) as err:
|
||||
|
||||
serializer = create_serializer(
|
||||
context = {
|
||||
'view': view_set,
|
||||
},
|
||||
data = valid_data
|
||||
)
|
||||
|
||||
is_valid = serializer.is_valid(raise_exception = True)
|
||||
|
||||
assert err.value.get_codes()[param_value][0] == param_exception_key
|
||||
|
||||
|
||||
|
||||
class EntitySerializerPyTest(
|
||||
EntitySerializerTestCases,
|
||||
):
|
||||
|
||||
parameterized_test_data: dict = None
|
@ -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)
|
@ -1,34 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from access.models.person import Person
|
||||
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def model(request):
|
||||
|
||||
request.cls.model = Person
|
||||
|
||||
yield request.cls.model
|
||||
|
||||
del request.cls.model
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def create_serializer():
|
||||
|
||||
from access.serializers.entity_person import ModelSerializer
|
||||
|
||||
|
||||
yield ModelSerializer
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def model_kwargs(request, kwargs_person):
|
||||
|
||||
request.cls.kwargs_create_item = kwargs_person.copy()
|
||||
|
||||
yield kwargs_person.copy()
|
||||
|
||||
if hasattr(request.cls, 'kwargs_create_item'):
|
||||
del request.cls.kwargs_create_item
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user