Compare commits
34 Commits
c6976d88fd
...
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.17.0
|
||||
version: 1.0.0-b14
|
||||
version_scheme: semver
|
||||
|
121
.github/ISSUE_TEMPLATE/new_model.md
vendored
121
.github/ISSUE_TEMPLATE/new_model.md
vendored
@ -1,121 +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 added to `app/core/lib/slash_commands/linked_model.CommandLinkedModel.get_model()` function
|
||||
|
||||
- [ ] 📘 Tag updated in the [docs](https://nofusscomputing.com/projects/centurion_erp/user/core/markdown/#model-reference)
|
||||
- [ ] tag added to `app/core/lib/slash_commands/linked_model.CommandLinkedModel.get_model()`
|
||||
- [ ] ⚒️ Migration _Ticket Linked Item item_type choices update_
|
||||
|
||||
>[!note]
|
||||
> Ensure that when creating the tag the following is adhered to:
|
||||
> - Two words are not to contain a space char, `\s`. It is to be replaced with an underscore `_`
|
||||
> - As much as practical, keep the tag as close to the model name as possible
|
||||
|
||||
- [ ] 📝 New [History model](https://nofusscomputing.com/projects/centurion_erp/development/core/model_history/) created
|
||||
|
||||
- Sub-Models **_ONLY_**
|
||||
|
||||
- [ ] Model class variable [`history_app_label`](https://nofusscomputing.com/projects/centurion_erp/development/models/#history) set to correct application label
|
||||
|
||||
- [ ] Model class variable [`history_model_name`](https://nofusscomputing.com/projects/centurion_erp/development/models/#history) set to correct model label
|
||||
|
||||
- [ ] 📓 New [Notes model](https://nofusscomputing.com/projects/centurion_erp/development/core/model_notes/) created
|
||||
- [ ] 🆕 Model Created
|
||||
- [ ] 🛠️ Migrations added
|
||||
- [ ] Add `app_label` to KB Models `app/assistance/models/model_knowledge_base_article.all_models().model_apps`
|
||||
- [ ] _(Notes not used/required) -_ Add `model_name` to KB Models `app/assistance/models/model_knowledge_base_article.all_models().excluded_models`
|
||||
- [ ] 🧪 [Unit tested](https://nofusscomputing.com/projects/centurion_erp/development/core/model_notes/#testing)
|
||||
- [ ] 🧪 [Functional tested](https://nofusscomputing.com/projects/centurion_erp/development/core/model_notes/#testing)
|
||||
|
||||
- [ ] 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
|
||||
- [ ] API Render (fields)
|
||||
- [ ] [Model](https://nofusscomputing.com/projects/centurion_erp/development/models/#tests)
|
||||
- [ ] ViewSet
|
||||
- Function Test
|
||||
- [ ] History API Render (fields)
|
||||
- [ ] History Entries
|
||||
- [ ] API Metadata
|
||||
- [ ] API Permissions
|
||||
- [ ] 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/
|
||||
|
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@ -7,6 +7,5 @@
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"qwtel.sqlite-viewer",
|
||||
"jebbs.markdown-extended",
|
||||
"william-voyek.vscode-nginx",
|
||||
]
|
||||
}
|
79
.vscode/launch.json
vendored
79
.vscode/launch.json
vendored
@ -5,7 +5,7 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Centurion",
|
||||
"name": "Debug: Django",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
@ -16,54 +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",
|
||||
"app.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": "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",
|
||||
@ -77,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": "."
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
17
.vscode/settings.json
vendored
17
.vscode/settings.json
vendored
@ -5,23 +5,16 @@
|
||||
"!python"
|
||||
],
|
||||
"python.testing.pytestArgs": [
|
||||
"--override-ini", "addopts=",
|
||||
"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,
|
||||
}
|
2744
CHANGELOG.md
2744
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>
|
||||
|
193
Release-Notes.md
193
Release-Notes.md
@ -1,196 +1,7 @@
|
||||
## 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,5 +1,6 @@
|
||||
from django.db import models
|
||||
from django.utils.timezone import now
|
||||
from django.template.defaultfilters import slugify
|
||||
|
||||
class AutoCreatedField(models.DateTimeField):
|
||||
"""
|
||||
@ -10,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)
|
||||
|
||||
|
||||
@ -35,18 +28,6 @@ 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()
|
||||
@ -64,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 == '_':
|
||||
|
@ -1,9 +1,9 @@
|
||||
from django import forms
|
||||
from django.db.models import Q
|
||||
|
||||
from django.conf import settings
|
||||
from app import settings
|
||||
|
||||
from access.models.tenant import Tenant as Organization
|
||||
from access.models import Organization
|
||||
|
||||
from core.forms.common import CommonModelForm
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
from django import forms
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.db.models import Q
|
||||
from django.forms import inlineformset_factory
|
||||
|
||||
from app import settings
|
||||
|
||||
from .team_users import TeamUsersForm, TeamUsers
|
||||
|
||||
from access.models.team import Team
|
||||
from access.functions import permissions
|
||||
|
||||
from django.conf import settings
|
||||
from access.models import Team
|
||||
|
||||
from core.forms.common import CommonModelForm
|
||||
|
||||
@ -66,4 +66,38 @@ class TeamForm(CommonModelForm):
|
||||
|
||||
self.fields['permissions'].widget.attrs = {'style': "height: 200px;"}
|
||||
|
||||
self.fields['permissions'].queryset = permissions.permission_queryset()
|
||||
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
|
||||
)
|
||||
|
@ -1,8 +1,8 @@
|
||||
from django.db.models import Q
|
||||
|
||||
from django.conf import settings
|
||||
from app import settings
|
||||
|
||||
from access.models.team_user import TeamUsers
|
||||
from access.models import TeamUsers
|
||||
|
||||
from core.forms.common import CommonModelForm
|
||||
|
||||
|
@ -1,61 +0,0 @@
|
||||
from django.contrib.auth.models import Permission
|
||||
|
||||
def permission_queryset():
|
||||
"""Filter Permissions to those used within the application
|
||||
|
||||
Returns:
|
||||
list: Filtered queryset that only contains the used permissions
|
||||
"""
|
||||
|
||||
apps = [
|
||||
'access',
|
||||
'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',
|
||||
]
|
||||
|
||||
return Permission.objects.select_related('content_type').filter(
|
||||
content_type__app_label__in=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 as Organization
|
||||
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').get(
|
||||
owner_organization = None
|
||||
)
|
||||
|
||||
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([Organization]) = None
|
||||
"""Cached User Organizations"""
|
||||
|
||||
_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: Organization) -> 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: Organization, permissions_required: str) -> bool:
|
||||
""" Check if user has permission within organization.
|
||||
|
||||
Args:
|
||||
organization (int): Organization 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 Organization:
|
||||
|
||||
raise TypeError('Organization must be of type Organization')
|
||||
|
||||
|
||||
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,30 +0,0 @@
|
||||
# Generated by Django 5.1.9 on 2025-05-17 17:50
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0010_company_alter_entity_entity_type_alter_person_dob_and_more'),
|
||||
('core', '0026_rename_manufacturerhistory_manufactureraudithistory'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name='EntityHistory',
|
||||
new_name='EntityAuditHistory',
|
||||
),
|
||||
migrations.RenameModel(
|
||||
old_name='OrganizationHistory',
|
||||
new_name='OrganizationAuditHistory',
|
||||
),
|
||||
migrations.RenameModel(
|
||||
old_name='RoleHistory',
|
||||
new_name='RoleAuditHistory',
|
||||
),
|
||||
migrations.RenameModel(
|
||||
old_name='TeamHistory',
|
||||
new_name='TeamAuditHistory',
|
||||
),
|
||||
]
|
@ -1,30 +1,15 @@
|
||||
|
||||
from django.contrib.auth.mixins import AccessMixin
|
||||
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 access.models.tenant import Tenant as Organization
|
||||
from access.models.team import Team
|
||||
|
||||
from .models import Organization, Team
|
||||
|
||||
|
||||
class OrganizationMixin():
|
||||
"""Base Organization class"""
|
||||
|
||||
parent_model: str = None
|
||||
""" Parent Model
|
||||
|
||||
This attribute defines the parent model for the model in question. The parent model when defined
|
||||
will be used as the object to obtain the permissions from.
|
||||
"""
|
||||
|
||||
parent_model_pk_kwarg: str = 'pk'
|
||||
"""Parent Model kwarg
|
||||
|
||||
This value is used to define the kwarg that is used as the parent objects primary key (pk).
|
||||
"""
|
||||
|
||||
request = None
|
||||
|
||||
user_groups = []
|
||||
@ -41,24 +26,20 @@ class OrganizationMixin():
|
||||
parent_model (Model): with PK from kwargs['pk']
|
||||
"""
|
||||
|
||||
return self.parent_model.objects.get(pk=self.kwargs[self.parent_model_pk_kwarg])
|
||||
return self.parent_model.objects.get(pk=self.kwargs['pk'])
|
||||
|
||||
|
||||
def object_organization(self) -> int:
|
||||
|
||||
id = None
|
||||
|
||||
if hasattr(self, '_object_organization'):
|
||||
|
||||
return int(self._object_organization)
|
||||
|
||||
try:
|
||||
|
||||
if hasattr(self, 'get_queryset'):
|
||||
self.get_queryset()
|
||||
|
||||
|
||||
if self.parent_model:
|
||||
if hasattr(self, 'parent_model'):
|
||||
obj = self.get_parent_obj()
|
||||
|
||||
id = obj.get_organization().id
|
||||
@ -80,10 +61,6 @@ class OrganizationMixin():
|
||||
|
||||
id = 0
|
||||
|
||||
if hasattr(self, 'instance') and id is None: # Form Instance
|
||||
|
||||
id = self.instance.get_organization()
|
||||
|
||||
|
||||
except AttributeError:
|
||||
|
||||
@ -107,10 +84,6 @@ class OrganizationMixin():
|
||||
|
||||
pass
|
||||
|
||||
if id is not None:
|
||||
|
||||
self._object_organization = id
|
||||
|
||||
|
||||
return id
|
||||
|
||||
@ -126,13 +99,9 @@ class OrganizationMixin():
|
||||
|
||||
is_member = False
|
||||
|
||||
if organization is None:
|
||||
if organization in self.user_organizations():
|
||||
|
||||
return False
|
||||
|
||||
if int(organization) in self.user_organizations():
|
||||
|
||||
is_member = True
|
||||
return True
|
||||
|
||||
return is_member
|
||||
|
||||
@ -142,10 +111,6 @@ class OrganizationMixin():
|
||||
Override of 'PermissionRequiredMixin' method so that this mixin can obtain the required permission.
|
||||
"""
|
||||
|
||||
if not hasattr(self, 'permission_required'):
|
||||
|
||||
return []
|
||||
|
||||
if self.permission_required is None:
|
||||
raise ImproperlyConfigured(
|
||||
f"{self.__class__.__name__} is missing the "
|
||||
@ -182,10 +147,6 @@ class OrganizationMixin():
|
||||
|
||||
user_organizations = []
|
||||
|
||||
if hasattr(self, '_user_organizations'):
|
||||
|
||||
return self._user_organizations
|
||||
|
||||
teams = Team.objects
|
||||
|
||||
for group in self.request.user.groups.all():
|
||||
@ -196,41 +157,18 @@ class OrganizationMixin():
|
||||
|
||||
user_organizations = user_organizations + [team.organization.id]
|
||||
|
||||
if len(user_organizations) > 0:
|
||||
|
||||
self._user_organizations = user_organizations
|
||||
|
||||
|
||||
return user_organizations
|
||||
|
||||
|
||||
# ToDo: Ensure that the group has access to item
|
||||
def has_organization_permission(self, organization: int = None, permissions_required: list = None) -> bool:
|
||||
""" Check if user has permission within organization.
|
||||
|
||||
Args:
|
||||
organization (int, optional): Organization to check. Defaults to None.
|
||||
permissions_required (list, optional): if doing object level permissions, pass in required permission. Defaults to None.
|
||||
|
||||
Returns:
|
||||
bool: True for yes.
|
||||
"""
|
||||
def has_organization_permission(self, organization: int=None) -> bool:
|
||||
|
||||
has_permission = False
|
||||
|
||||
if permissions_required is None:
|
||||
|
||||
permissions_required = self.get_permission_required()
|
||||
|
||||
if not organization:
|
||||
|
||||
organization = self.object_organization()
|
||||
|
||||
else:
|
||||
|
||||
organization = int(organization)
|
||||
|
||||
|
||||
if self.is_member(organization) or organization == 0:
|
||||
|
||||
groups = Group.objects.filter(pk__in=self.user_groups)
|
||||
@ -244,7 +182,7 @@ class OrganizationMixin():
|
||||
|
||||
assembled_permission = str(permission["content_type__app_label"]) + '.' + str(permission["codename"])
|
||||
|
||||
if assembled_permission in permissions_required and (team['organization_id'] == organization or organization == 0):
|
||||
if assembled_permission in self.get_permission_required() and (team['organization_id'] == organization or organization == 0):
|
||||
|
||||
return True
|
||||
|
||||
@ -260,7 +198,7 @@ class OrganizationMixin():
|
||||
self.permission_required = permissions_required
|
||||
|
||||
organization_manager_models = [
|
||||
'access.tenant',
|
||||
'access.organization',
|
||||
'access.team',
|
||||
'access.teamusers',
|
||||
]
|
||||
@ -304,29 +242,21 @@ class OrganizationMixin():
|
||||
|
||||
return True
|
||||
|
||||
if permissions_required:
|
||||
perms = self.get_permission_required()
|
||||
|
||||
perms = permissions_required
|
||||
|
||||
else:
|
||||
|
||||
perms = self.get_permission_required()
|
||||
|
||||
if self.has_organization_permission(permissions_required = perms):
|
||||
if self.has_organization_permission():
|
||||
|
||||
return True
|
||||
|
||||
if self.request.user.has_perms(perms) and str(self.request.method).lower() == 'get':
|
||||
if self.request.user.has_perms(perms) and len(self.kwargs) == 0 and str(self.request.method).lower() == 'get':
|
||||
|
||||
if len(self.kwargs) == 0 or (len(self.kwargs) == 1 and 'ticket_type' in self.kwargs):
|
||||
|
||||
return True
|
||||
return True
|
||||
|
||||
for required_permission in self.permission_required:
|
||||
|
||||
if required_permission.replace(
|
||||
'view_', ''
|
||||
) == 'access.tenant' and len(self.kwargs) == 0:
|
||||
) == 'access.organization' and len(self.kwargs) == 0:
|
||||
|
||||
return True
|
||||
|
||||
@ -397,12 +327,6 @@ class OrganizationPermission(AccessMixin, OrganizationMixin):
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
return self.handle_no_permission()
|
||||
|
||||
if len(self.permission_required) == 0:
|
||||
|
||||
if hasattr(self, 'get_dynamic_permissions'):
|
||||
|
||||
self.permission_required = self.get_dynamic_permissions()
|
||||
|
||||
if len(self.permission_required) > 0:
|
||||
|
||||
|
@ -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,319 +0,0 @@
|
||||
import traceback
|
||||
|
||||
from rest_framework.permissions import DjangoObjectPermissions
|
||||
|
||||
from access.models.tenancy import Tenant, TenancyObject
|
||||
|
||||
from core import exceptions as centurion_exceptions
|
||||
|
||||
|
||||
|
||||
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, TenancyObject)
|
||||
|
||||
if view.get_parent_model():
|
||||
|
||||
self._is_tenancy_model = issubclass(view.get_parent_model(), TenancyObject)
|
||||
|
||||
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()
|
||||
|
||||
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 request.method not in view.allowed_methods:
|
||||
|
||||
raise centurion_exceptions.MethodNotAllowed(method = request.method)
|
||||
|
||||
|
||||
elif 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))
|
||||
)
|
||||
):
|
||||
|
||||
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,4 +0,0 @@
|
||||
from . import contact # pylint: disable=W0611:unused-import
|
||||
from . import company_base # pylint: disable=W0611:unused-import
|
||||
from . import person # pylint: disable=W0611:unused-import
|
||||
from . import role # pylint: disable=W0611:unused-import
|
@ -1,102 +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.
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
documentation = ''
|
||||
|
||||
history_model_name = 'company'
|
||||
|
||||
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',
|
||||
]
|
||||
|
||||
|
||||
def clean(self):
|
||||
|
||||
super().clean()
|
@ -1,119 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
from access.models.person import Person
|
||||
|
||||
|
||||
|
||||
class Contact(
|
||||
Person
|
||||
):
|
||||
|
||||
|
||||
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
|
||||
|
||||
documentation = ''
|
||||
|
||||
history_model_name = 'contact'
|
||||
|
||||
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,246 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from access.fields import AutoCreatedField, AutoLastModifiedField
|
||||
from access.models.tenancy import TenancyObject
|
||||
|
||||
|
||||
|
||||
class Entity(
|
||||
TenancyObject
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
ordering = [
|
||||
'created',
|
||||
'modified',
|
||||
'organization',
|
||||
]
|
||||
|
||||
sub_model_type = 'entity'
|
||||
|
||||
verbose_name = 'Entity'
|
||||
|
||||
verbose_name_plural = 'Entities'
|
||||
|
||||
|
||||
id = models.AutoField(
|
||||
blank=False,
|
||||
help_text = 'Primary key of the entry',
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
verbose_name = 'ID'
|
||||
)
|
||||
|
||||
|
||||
entity_type = models.CharField(
|
||||
blank = False,
|
||||
help_text = 'Type this entity is',
|
||||
max_length = 30,
|
||||
unique = False,
|
||||
verbose_name = 'Entity Type'
|
||||
)
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
related_model = self.get_related_model()
|
||||
|
||||
if related_model is None:
|
||||
|
||||
return f'{self.entity_type} {self.pk}'
|
||||
|
||||
|
||||
return str( related_model )
|
||||
|
||||
|
||||
|
||||
# app_namespace = 'access'
|
||||
|
||||
history_app_label = 'access'
|
||||
|
||||
history_model_name = 'entity'
|
||||
|
||||
kb_model_name = 'entity'
|
||||
|
||||
note_basename = '_api_v2_entity_note'
|
||||
|
||||
documentation = ''
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
|
||||
table_fields: list = [
|
||||
'organization',
|
||||
'entity_type',
|
||||
'display_name',
|
||||
'created',
|
||||
'modified',
|
||||
]
|
||||
|
||||
|
||||
def get_related_field_name(self) -> str:
|
||||
|
||||
meta = getattr(self, '_meta')
|
||||
|
||||
for related_object in getattr(meta, 'related_objects', []):
|
||||
|
||||
if 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
|
||||
|
||||
|
||||
def get_url_kwargs(self) -> dict:
|
||||
|
||||
model = self.get_related_model()
|
||||
|
||||
if len(self._meta.parents) == 0 and model is None:
|
||||
|
||||
return {
|
||||
'pk': self.id
|
||||
}
|
||||
|
||||
if model is None:
|
||||
|
||||
model = self
|
||||
|
||||
kwargs = {
|
||||
'entity_model': str(model._meta.verbose_name).lower().replace(' ', '_'),
|
||||
}
|
||||
|
||||
if model.pk:
|
||||
|
||||
kwargs.update({
|
||||
'pk': model.id
|
||||
})
|
||||
|
||||
return kwargs
|
||||
|
||||
|
||||
|
||||
def get_url( self, request = None ) -> str:
|
||||
"""Fetch the models URL
|
||||
|
||||
If URL kwargs are required to generate the URL, define a `get_url_kwargs` that returns them.
|
||||
|
||||
Args:
|
||||
request (object, optional): The request object that was made by the end user. Defaults to None.
|
||||
|
||||
Returns:
|
||||
str: Canonical URL of the model if the `request` object was provided. Otherwise the relative URL.
|
||||
"""
|
||||
|
||||
model = None
|
||||
|
||||
if getattr(self, 'get_related_model', None):
|
||||
|
||||
model = self.get_related_model()
|
||||
|
||||
|
||||
|
||||
if model is None:
|
||||
|
||||
model = self
|
||||
|
||||
|
||||
sub_entity = ''
|
||||
if model._meta.model_name != 'entity':
|
||||
|
||||
sub_entity = '_sub'
|
||||
|
||||
|
||||
kwargs = self.get_url_kwargs()
|
||||
|
||||
view = 'list'
|
||||
if 'pk' in kwargs:
|
||||
|
||||
view = 'detail'
|
||||
|
||||
if request:
|
||||
|
||||
return reverse(f"v2:" + model.get_app_namespace() + f"_api_v2_entity" + sub_entity + "-" + view, request=request, kwargs = kwargs )
|
||||
|
||||
return reverse(f"v2:" + model.get_app_namespace() + f"_api_v2_entity" + sub_entity + "-" + view, kwargs = kwargs )
|
||||
|
||||
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
|
||||
related_model = self.get_related_model()
|
||||
|
||||
if related_model is None:
|
||||
|
||||
related_model = self
|
||||
|
||||
if self.entity_type != str(related_model._meta.verbose_name).lower().replace(' ', '_'):
|
||||
|
||||
self.entity_type = str(related_model._meta.verbose_name).lower().replace(' ', '_')
|
||||
|
||||
super().save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
|
||||
|
||||
|
||||
def save_history(self, before: dict, after: dict) -> bool:
|
||||
|
||||
from access.models.entity_history import EntityAuditHistory
|
||||
|
||||
history = super().save_history(
|
||||
before = before,
|
||||
after = after,
|
||||
history_model = EntityAuditHistory
|
||||
)
|
||||
|
||||
return history
|
@ -1,53 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
from access.models.entity import Entity
|
||||
|
||||
from core.models.model_history import ModelHistory
|
||||
|
||||
|
||||
|
||||
class EntityAuditHistory(
|
||||
ModelHistory
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
db_table = 'access_entity_history'
|
||||
|
||||
ordering = ModelHistory._meta.ordering
|
||||
|
||||
verbose_name = 'Entity History'
|
||||
|
||||
verbose_name_plural = 'Entity History'
|
||||
|
||||
|
||||
model = models.ForeignKey(
|
||||
Entity,
|
||||
blank = False,
|
||||
help_text = 'Model this note belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
related_name = 'history',
|
||||
verbose_name = 'Model',
|
||||
)
|
||||
|
||||
table_fields: list = []
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
|
||||
def get_object(self):
|
||||
|
||||
return self
|
||||
|
||||
|
||||
def get_serialized_model(self, serializer_context):
|
||||
|
||||
model = None
|
||||
|
||||
from access.serializers.entity import BaseSerializer
|
||||
|
||||
model = BaseSerializer(self.model, context = serializer_context)
|
||||
|
||||
return model
|
@ -1,45 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
from access.models.entity import Entity
|
||||
|
||||
from core.models.model_notes import ModelNotes
|
||||
|
||||
|
||||
|
||||
class EntityNotes(
|
||||
ModelNotes
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
db_table = 'access_entity_notes'
|
||||
|
||||
ordering = ModelNotes._meta.ordering
|
||||
|
||||
verbose_name = 'Entity Note'
|
||||
|
||||
verbose_name_plural = 'Entity Notes'
|
||||
|
||||
|
||||
model = models.ForeignKey(
|
||||
Entity,
|
||||
blank = False,
|
||||
help_text = 'Model this note belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
related_name = 'notes',
|
||||
verbose_name = 'Model',
|
||||
)
|
||||
|
||||
table_fields: list = []
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
|
||||
def get_url_kwargs(self) -> dict:
|
||||
|
||||
return {
|
||||
'model_id': self.model.pk,
|
||||
'pk': self.pk
|
||||
}
|
@ -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 OrganizationAuditHistory(
|
||||
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
|
||||
):
|
||||
|
||||
|
||||
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})'
|
||||
|
||||
documentation = ''
|
||||
|
||||
history_model_name = 'person'
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
table_fields: list = [
|
||||
'organization',
|
||||
'f_name',
|
||||
'l_name',
|
||||
'dob',
|
||||
'created',
|
||||
]
|
||||
|
||||
|
||||
def clean(self):
|
||||
|
||||
super().clean()
|
||||
|
||||
if self.dob is not None:
|
||||
|
||||
if self.pk:
|
||||
|
||||
duplicate_entry = Person.objects.filter(
|
||||
f_name = self.f_name,
|
||||
l_name = self.l_name,
|
||||
).exclude(
|
||||
pk = self.pk
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
duplicate_entry = Person.objects.filter(
|
||||
f_name = self.f_name,
|
||||
l_name = self.l_name,
|
||||
)
|
||||
|
||||
|
||||
for entry in duplicate_entry:
|
||||
|
||||
if(
|
||||
entry.f_name == self.f_name
|
||||
and entry.m_name == self.m_name
|
||||
and entry.l_name == self.l_name
|
||||
and entry.dob == self.dob
|
||||
):
|
||||
|
||||
raise ValidationError(
|
||||
detail = {
|
||||
'dob': f'Person {self.f_name} {self.l_name} already exists with this birthday {entry.dob}'
|
||||
},
|
||||
code = 'duplicate_person_on_dob'
|
||||
)
|
||||
|
@ -1,174 +0,0 @@
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.db import models
|
||||
|
||||
from access.fields import AutoCreatedField, AutoLastModifiedField
|
||||
from access.models.tenancy import TenancyObject
|
||||
|
||||
|
||||
|
||||
class Role(
|
||||
TenancyObject
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
ordering = [
|
||||
'organization',
|
||||
'name',
|
||||
]
|
||||
|
||||
unique_together = [
|
||||
'organization',
|
||||
'name'
|
||||
]
|
||||
|
||||
verbose_name = 'Role'
|
||||
|
||||
verbose_name_plural = 'Roles'
|
||||
|
||||
|
||||
id = models.AutoField(
|
||||
blank=False,
|
||||
help_text = 'Primary key of the entry',
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
verbose_name = 'ID'
|
||||
)
|
||||
|
||||
name = models.CharField(
|
||||
blank = False,
|
||||
help_text = 'Name of this role',
|
||||
max_length = 30,
|
||||
unique = False,
|
||||
verbose_name = 'Name'
|
||||
)
|
||||
|
||||
permissions = models.ManyToManyField(
|
||||
Permission,
|
||||
blank = True,
|
||||
help_text = 'Permissions part of this role',
|
||||
related_name = 'roles',
|
||||
symmetrical = False,
|
||||
verbose_name = 'Permissions'
|
||||
)
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
is_global = None
|
||||
|
||||
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
return str( self.organization ) + ' / ' + self.name
|
||||
|
||||
|
||||
documentation = ''
|
||||
|
||||
page_layout: dict = [
|
||||
{
|
||||
"name": "Details",
|
||||
"slug": "details",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "double",
|
||||
"left": [
|
||||
'organization',
|
||||
'name',
|
||||
'created',
|
||||
'modified',
|
||||
],
|
||||
"right": [
|
||||
'model_notes',
|
||||
]
|
||||
},
|
||||
{
|
||||
"layout": "single",
|
||||
"name": "Permissions",
|
||||
"fields": [
|
||||
"permissions",
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Knowledge Base",
|
||||
"slug": "kb_articles",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "knowledge_base",
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Tickets",
|
||||
"slug": "tickets",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "tickets",
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "Notes",
|
||||
"slug": "notes",
|
||||
"sections": []
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
table_fields: list = [
|
||||
'organization',
|
||||
'name',
|
||||
'created',
|
||||
'modified',
|
||||
]
|
||||
|
||||
|
||||
_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:
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def save_history(self, before: dict, after: dict) -> bool:
|
||||
|
||||
from access.models.role_history import RoleAuditHistory
|
||||
|
||||
history = super().save_history(
|
||||
before = before,
|
||||
after = after,
|
||||
history_model = RoleAuditHistory
|
||||
)
|
||||
|
||||
return history
|
@ -1,53 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
from core.models.model_history import ModelHistory
|
||||
|
||||
from access.models.role import Role
|
||||
|
||||
|
||||
|
||||
class RoleAuditHistory(
|
||||
ModelHistory
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
db_table = 'access_role_history'
|
||||
|
||||
ordering = ModelHistory._meta.ordering
|
||||
|
||||
verbose_name = 'Role History'
|
||||
|
||||
verbose_name_plural = 'Role History'
|
||||
|
||||
|
||||
model = models.ForeignKey(
|
||||
Role,
|
||||
blank = False,
|
||||
help_text = 'Model this note belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
related_name = 'history',
|
||||
verbose_name = 'Model',
|
||||
)
|
||||
|
||||
table_fields: list = []
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
|
||||
def get_object(self):
|
||||
|
||||
return self
|
||||
|
||||
|
||||
def get_serialized_model(self, serializer_context):
|
||||
|
||||
model = None
|
||||
|
||||
from access.serializers.role import BaseSerializer
|
||||
|
||||
model = BaseSerializer(self.model, context = serializer_context)
|
||||
|
||||
return model
|
@ -1,45 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
from access.models.role import Role
|
||||
|
||||
from core.models.model_notes import ModelNotes
|
||||
|
||||
|
||||
|
||||
class RoleNotes(
|
||||
ModelNotes
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
db_table = 'access_role_notes'
|
||||
|
||||
ordering = ModelNotes._meta.ordering
|
||||
|
||||
verbose_name = 'Role Note'
|
||||
|
||||
verbose_name_plural = 'Role Notes'
|
||||
|
||||
|
||||
model = models.ForeignKey(
|
||||
Role,
|
||||
blank = False,
|
||||
help_text = 'Model this note belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
related_name = 'notes',
|
||||
verbose_name = 'Model',
|
||||
)
|
||||
|
||||
table_fields: list = []
|
||||
|
||||
page_layout: dict = []
|
||||
|
||||
|
||||
def get_url_kwargs(self) -> dict:
|
||||
|
||||
return {
|
||||
'model_id': self.model.pk,
|
||||
'pk': self.pk
|
||||
}
|
@ -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 TeamAuditHistory(
|
||||
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.mixin.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,324 +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.mixin.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 hasattr(self.model, 'organization'):
|
||||
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 getattr(self.model, 'is_global', False) is True:
|
||||
|
||||
if has_tenant_field:
|
||||
|
||||
return super().get_queryset().select_related('organization').filter(
|
||||
models.Q(organization__in=user_organizations)
|
||||
|
|
||||
models.Q(is_global = True)
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
return super().get_queryset().filter(
|
||||
models.Q(organization__in=user_organizations)
|
||||
|
|
||||
models.Q(is_global = True)
|
||||
)
|
||||
|
||||
|
||||
else:
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
|
||||
if has_tenant_field:
|
||||
return super().get_queryset().select_related('organization')
|
||||
else:
|
||||
return super().get_queryset().select_related('organization')
|
||||
|
||||
|
||||
|
||||
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(' ', '_')
|
||||
|
||||
|
||||
if request:
|
||||
|
||||
return reverse(f"v2:" + self.get_app_namespace() + f"_api_v2_{model_name}-detail", request=request, kwargs = self.get_url_kwargs() )
|
||||
|
||||
return reverse(f"v2:" + self.get_app_namespace() + f"_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 !='appsettingsaudithistory' # 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,247 +0,0 @@
|
||||
# import django
|
||||
import logging
|
||||
|
||||
from django.core.exceptions import (
|
||||
ValidationError,
|
||||
)
|
||||
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.mixin.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
|
||||
|
||||
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']
|
||||
|
||||
|
||||
# if user:
|
||||
|
||||
# tencies = user.get_tenancies(int_list = True)
|
||||
|
||||
# if len(tenancies) > 0 and not request.user.is_superuser:
|
||||
|
||||
# if hasattr(self.model, 'organization'):
|
||||
# return super().get_queryset().select_related('organization').filter(
|
||||
# models.Q(organization__in = tenancies)
|
||||
# )
|
||||
|
||||
# return super().get_queryset().select_related('organization').filter(
|
||||
# models.Q(organization__in = tenancies)
|
||||
# )
|
||||
|
||||
request = None
|
||||
|
||||
if hasattr(self.model, 'context'):
|
||||
|
||||
request = self.model.context['request']
|
||||
|
||||
if request is not None:
|
||||
|
||||
tenancies: list(str()) = []
|
||||
|
||||
if request.app_settings.global_organization:
|
||||
|
||||
tenancies += [ request.app_settings.global_organization.id ]
|
||||
|
||||
|
||||
if request.user.is_authenticated:
|
||||
|
||||
for team in request.tenancy._user_teams:
|
||||
|
||||
if team.organization.id in tenancies:
|
||||
continue
|
||||
|
||||
tenancies += [ team.organization.id ]
|
||||
|
||||
|
||||
if len(tenancies) > 0 and not request.user.is_superuser:
|
||||
|
||||
if hasattr(self.model, 'organization'):
|
||||
return super().get_queryset().select_related('organization').filter(
|
||||
models.Q(organization__in = tenancies)
|
||||
)
|
||||
|
||||
return super().get_queryset().select_related('organization').filter(
|
||||
models.Q(organization__in = tenancies)
|
||||
)
|
||||
|
||||
return super().get_queryset().select_related('organization')
|
||||
|
||||
|
||||
class TenancyObjectOld:
|
||||
|
||||
|
||||
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_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
|
||||
}
|
||||
|
||||
|
||||
|
||||
class TenancyAbstractModel(
|
||||
# TenancyObjectOld,
|
||||
# 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 = 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(
|
||||
code = 'required',
|
||||
message = '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 = 'Tenant 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?',
|
||||
null = False,
|
||||
verbose_name = 'Global Object'
|
||||
)
|
||||
|
||||
model_notes = models.TextField(
|
||||
blank = True,
|
||||
default = None, # ToDo: Remove this field
|
||||
help_text = 'Tid bits of information',
|
||||
null = True,
|
||||
verbose_name = 'Notes',
|
||||
)
|
||||
|
||||
|
||||
|
||||
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,161 +0,0 @@
|
||||
import django
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from access.fields import (
|
||||
AutoCreatedField,
|
||||
AutoLastModifiedField,
|
||||
AutoSlugField
|
||||
)
|
||||
|
||||
from core.mixin.history_save import SaveHistory
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
class Tenant(SaveHistory):
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Tenant"
|
||||
verbose_name_plural = "Tenants"
|
||||
ordering = ['name']
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
if self.slug == '_':
|
||||
self.slug = self.name.lower().replace(' ', '_')
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
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 = False,
|
||||
help_text = 'Manager for this Tenancy',
|
||||
null = True,
|
||||
on_delete=models.SET_NULL,
|
||||
verbose_name = 'Manager'
|
||||
)
|
||||
|
||||
model_notes = models.TextField(
|
||||
blank = True,
|
||||
default = None,
|
||||
help_text = 'Tid bits of information',
|
||||
null= True,
|
||||
verbose_name = 'Notes',
|
||||
)
|
||||
|
||||
slug = AutoSlugField()
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
|
||||
def get_organization(self):
|
||||
return self
|
||||
|
||||
def __int__(self):
|
||||
|
||||
return self.id
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
table_fields: list = [
|
||||
'nbsp',
|
||||
'name',
|
||||
'created',
|
||||
'modified',
|
||||
'nbsp'
|
||||
]
|
||||
|
||||
page_layout: list = [
|
||||
{
|
||||
"name": "Details",
|
||||
"slug": "details",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "double",
|
||||
"left": [
|
||||
'name',
|
||||
'manager',
|
||||
'created',
|
||||
'modified',
|
||||
],
|
||||
"right": [
|
||||
'model_notes',
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Teams",
|
||||
"slug": "teams",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "teams"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Knowledge Base",
|
||||
"slug": "kb_articles",
|
||||
"sections": [
|
||||
{
|
||||
"layout": "table",
|
||||
"field": "knowledge_base",
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Notes",
|
||||
"slug": "notes",
|
||||
"sections": []
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def get_url( self, request = None ) -> str:
|
||||
|
||||
if request:
|
||||
|
||||
return reverse("v2:_api_v2_organization-detail", request=request, kwargs={'pk': self.id})
|
||||
|
||||
return reverse("v2:_api_v2_organization-detail", kwargs={'pk': self.id})
|
||||
|
||||
|
||||
def save_history(self, before: dict, after: dict) -> bool:
|
||||
|
||||
from access.models.organization_history import OrganizationAuditHistory
|
||||
|
||||
history = super().save_history(
|
||||
before = before,
|
||||
after = after,
|
||||
history_model = OrganizationAuditHistory
|
||||
)
|
||||
|
||||
|
||||
return history
|
||||
|
||||
|
||||
|
||||
Organization = Tenant
|
@ -1,195 +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:
|
||||
|
||||
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`
|
||||
|
||||
|
||||
|
||||
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,90 +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',
|
||||
'is_global',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'entity_type',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'EntityBaseViewSerializer')
|
||||
class ViewSerializer(ModelSerializer):
|
||||
"""Entity Base View Model"""
|
||||
|
||||
organization = TenantBaseSerializer(many=False, read_only=True)
|
@ -1,70 +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',
|
||||
'is_global',
|
||||
'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,75 +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',
|
||||
'is_global',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'entity_type',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'ContactEntityViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
):
|
||||
"""Contact View Model
|
||||
|
||||
This model inherits from the Person model.
|
||||
"""
|
||||
|
||||
organization = TenantBaseSerializer(many=False, read_only=True)
|
@ -1,41 +0,0 @@
|
||||
from core.serializers.model_notes import (
|
||||
ModelNoteBaseSerializer,
|
||||
ModelNoteModelSerializer,
|
||||
ModelNoteViewSerializer
|
||||
)
|
||||
|
||||
from access.models.entity_notes import EntityNotes
|
||||
|
||||
|
||||
|
||||
class EntityNoteBaseSerializer(ModelNoteBaseSerializer):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class EntityNoteModelSerializer(
|
||||
ModelNoteModelSerializer
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = EntityNotes
|
||||
|
||||
fields = ModelNoteModelSerializer.Meta.fields + [
|
||||
'model',
|
||||
]
|
||||
|
||||
read_only_fields = ModelNoteModelSerializer.Meta.read_only_fields + [
|
||||
'model',
|
||||
'content_type',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class EntityNoteViewSerializer(
|
||||
ModelNoteViewSerializer,
|
||||
EntityNoteModelSerializer,
|
||||
):
|
||||
|
||||
pass
|
@ -1,73 +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',
|
||||
'is_global',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'entity_type',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@extend_schema_serializer(component_name = 'PersonEntityViewSerializer')
|
||||
class ViewSerializer(
|
||||
ModelSerializer,
|
||||
):
|
||||
"""Person View Model
|
||||
|
||||
This model inherits from the Entity base model.
|
||||
"""
|
||||
|
||||
organization = 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_v2_organization-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,41 +0,0 @@
|
||||
from access.models.organization_notes import OrganizationNotes
|
||||
|
||||
from core.serializers.model_notes import (
|
||||
ModelNoteBaseSerializer,
|
||||
ModelNoteModelSerializer,
|
||||
ModelNoteViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
class OrganizationNoteBaseSerializer(ModelNoteBaseSerializer):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class OrganizationNoteModelSerializer(
|
||||
ModelNoteModelSerializer
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = OrganizationNotes
|
||||
|
||||
fields = ModelNoteModelSerializer.Meta.fields + [
|
||||
'model',
|
||||
]
|
||||
|
||||
read_only_fields = ModelNoteModelSerializer.Meta.read_only_fields + [
|
||||
'model',
|
||||
'content_type',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class OrganizationNoteViewSerializer(
|
||||
ModelNoteViewSerializer,
|
||||
OrganizationNoteModelSerializer,
|
||||
):
|
||||
|
||||
pass
|
@ -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,41 +0,0 @@
|
||||
from access.models.role_notes import RoleNotes
|
||||
|
||||
from core.serializers.model_notes import (
|
||||
ModelNoteBaseSerializer,
|
||||
ModelNoteModelSerializer,
|
||||
ModelNoteViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
class RoleNoteBaseSerializer(ModelNoteBaseSerializer):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class RoleNoteModelSerializer(
|
||||
ModelNoteModelSerializer
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = RoleNotes
|
||||
|
||||
fields = ModelNoteModelSerializer.Meta.fields + [
|
||||
'model',
|
||||
]
|
||||
|
||||
read_only_fields = ModelNoteModelSerializer.Meta.read_only_fields + [
|
||||
'model',
|
||||
'content_type',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class RoleNoteViewSerializer(
|
||||
ModelNoteViewSerializer,
|
||||
RoleNoteModelSerializer,
|
||||
):
|
||||
|
||||
pass
|
@ -1,41 +0,0 @@
|
||||
from access.models.team_notes import TeamNotes
|
||||
|
||||
from core.serializers.model_notes import (
|
||||
ModelNoteBaseSerializer,
|
||||
ModelNoteModelSerializer,
|
||||
ModelNoteViewSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
class TeamNoteBaseSerializer(ModelNoteBaseSerializer):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TeamNoteModelSerializer(
|
||||
ModelNoteModelSerializer
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = TeamNotes
|
||||
|
||||
fields = ModelNoteModelSerializer.Meta.fields + [
|
||||
'model',
|
||||
]
|
||||
|
||||
read_only_fields = ModelNoteModelSerializer.Meta.read_only_fields + [
|
||||
'model',
|
||||
'content_type',
|
||||
]
|
||||
|
||||
|
||||
|
||||
class TeamNoteViewSerializer(
|
||||
ModelNoteViewSerializer,
|
||||
TeamNoteModelSerializer,
|
||||
):
|
||||
|
||||
pass
|
@ -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,132 +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',
|
||||
'is_global',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'name',
|
||||
'organization',
|
||||
'created',
|
||||
'modified',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
|
||||
|
||||
def is_valid(self, *, raise_exception=True) -> bool:
|
||||
|
||||
is_valid = False
|
||||
|
||||
is_valid = super().is_valid(raise_exception=raise_exception)
|
||||
|
||||
self.validated_data['organization_id'] = int(self._context['view'].kwargs['organization_id'])
|
||||
|
||||
|
||||
return is_valid
|
||||
|
||||
|
||||
|
||||
class TeamViewSerializer(TeamModelSerializer):
|
||||
|
||||
organization = TenantBaseSerializer(many=False, read_only=True)
|
||||
|
||||
permissions = PermissionBaseSerializer(many = True)
|
@ -1,3 +1,7 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from access.models import TenancyManager
|
||||
|
||||
|
||||
|
||||
@ -7,26 +11,78 @@ class TenancyObject:
|
||||
model = None
|
||||
""" Model to be tested """
|
||||
|
||||
should_model_history_be_saved: bool = True
|
||||
""" Should model history be saved.
|
||||
|
||||
By default this should always be 'True', however in special
|
||||
circumstances, this may not be desired.
|
||||
"""
|
||||
def test_has_attr_get_organization(self):
|
||||
""" TenancyObject attribute check
|
||||
|
||||
TenancyObject has function get_organization
|
||||
"""
|
||||
|
||||
assert hasattr(self.model, 'get_organization')
|
||||
|
||||
|
||||
# def test_history_save(self):
|
||||
# """Confirm the desired intent for saving model history."""
|
||||
def test_has_attr_is_global(self):
|
||||
""" TenancyObject attribute check
|
||||
|
||||
# assert self.model.save_model_history == self.should_model_history_be_saved
|
||||
TenancyObject has field is_global
|
||||
"""
|
||||
|
||||
assert hasattr(self.model, 'is_global')
|
||||
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_edit_no_organization_fails(self):
|
||||
# """ Devices must be assigned an organization
|
||||
def test_has_attr_model_notes(self):
|
||||
""" TenancyObject attribute check
|
||||
|
||||
# Must not be able to edit an item without an organization
|
||||
# """
|
||||
# pass
|
||||
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,24 +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
|
@ -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 = {
|
||||
# 'entity_model': self.model._meta.sub_model_type
|
||||
# }
|
||||
|
||||
# self.url_view_kwargs = {
|
||||
# 'entity_model': self.model._meta.sub_model_type
|
||||
# }
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class CompanyMetadataTest(
|
||||
CompanyMetadataTestCases,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
pass
|
@ -1,43 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from access.tests.functional.entity.test_functional_entity_permission import (
|
||||
EntityPermissionsAPIInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class CompanyPermissionsAPITestCases(
|
||||
EntityPermissionsAPIInheritedCases,
|
||||
):
|
||||
|
||||
add_data: dict = {
|
||||
'name': 'Ian1',
|
||||
}
|
||||
|
||||
kwargs_create_item: dict = {
|
||||
'name': 'Ian2',
|
||||
}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {
|
||||
'name': 'Ian3',
|
||||
}
|
||||
|
||||
|
||||
|
||||
class CompanyPermissionsAPIInheritedCases(
|
||||
CompanyPermissionsAPITestCases,
|
||||
):
|
||||
|
||||
add_data: dict = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
|
||||
|
||||
class CompanyPermissionsAPIPyTest(
|
||||
CompanyPermissionsAPITestCases,
|
||||
):
|
||||
|
||||
pass
|
@ -1,46 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from access.tests.functional.entity.test_functional_entity_serializer import (
|
||||
MockView,
|
||||
EntitySerializerInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class CompanySerializerTestCases(
|
||||
EntitySerializerInheritedCases
|
||||
):
|
||||
|
||||
|
||||
parameterized_test_data: dict = {
|
||||
"name": {
|
||||
'will_create': False,
|
||||
'exception_key': 'required'
|
||||
},
|
||||
}
|
||||
|
||||
valid_data: dict = {
|
||||
'name': 'Ian',
|
||||
}
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
|
||||
class CompanySerializerInheritedCases(
|
||||
CompanySerializerTestCases,
|
||||
):
|
||||
|
||||
parameterized_test_data: dict = None
|
||||
|
||||
valid_data: dict = None
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
|
||||
class CompanySerializerPyTest(
|
||||
CompanySerializerTestCases,
|
||||
):
|
||||
|
||||
parameterized_test_data: dict = None
|
@ -1,58 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.company_base import Company
|
||||
from access.tests.functional.entity.test_functional_entity_viewset import (
|
||||
EntityViewSetInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ViewSetTestCases(
|
||||
EntityViewSetInheritedCases,
|
||||
):
|
||||
|
||||
add_data: dict = {
|
||||
'name': 'Ian',
|
||||
}
|
||||
|
||||
kwargs_create_item: dict = {
|
||||
'name': 'Ian2',
|
||||
}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {
|
||||
'name': 'Ian3',
|
||||
}
|
||||
|
||||
model = Company
|
||||
|
||||
|
||||
|
||||
class CompanyViewSetInheritedCases(
|
||||
ViewSetTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
|
||||
@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 CompanyViewSetTest(
|
||||
ViewSetTestCases,
|
||||
TestCase,
|
||||
):
|
||||
pass
|
@ -1,24 +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
|
@ -1,60 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.contact import Contact
|
||||
from access.tests.functional.person.test_functional_person_history import (
|
||||
PersonHistoryInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ContactTestCases(
|
||||
PersonHistoryInheritedCases,
|
||||
):
|
||||
|
||||
field_name = 'model_notes'
|
||||
|
||||
kwargs_create_obj: dict = {
|
||||
'email': 'ipfunny@unit.test',
|
||||
}
|
||||
|
||||
kwargs_delete_obj: dict = {
|
||||
'email': 'ipweird@unit.test',
|
||||
}
|
||||
|
||||
model = Contact
|
||||
|
||||
|
||||
|
||||
class ContactHistoryInheritedCases(
|
||||
ContactTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
"""Entity model to test"""
|
||||
|
||||
kwargs_create_obj: dict = None
|
||||
|
||||
kwargs_delete_obj: dict = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_obj.update(
|
||||
super().kwargs_create_obj
|
||||
)
|
||||
|
||||
self.kwargs_delete_obj.update(
|
||||
super().kwargs_delete_obj
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class ContactHistoryTest(
|
||||
ContactTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
@ -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,70 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from access.tests.functional.person.test_functional_person_permission import (
|
||||
PersonPermissionsAPIInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ContactPermissionsAPITestCases(
|
||||
PersonPermissionsAPIInheritedCases,
|
||||
):
|
||||
|
||||
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',
|
||||
}
|
||||
|
||||
|
||||
|
||||
class ContactPermissionsAPIInheritedCases(
|
||||
ContactPermissionsAPITestCases,
|
||||
):
|
||||
|
||||
add_data: dict = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
# url_name = '_api_v2_entity_sub'
|
||||
|
||||
|
||||
# @pytest.fixture(scope='class')
|
||||
# def inherited_var_setup(self, request):
|
||||
|
||||
# request.cls.url_kwargs.update({
|
||||
# 'entity_model': self.model._meta.sub_model_type
|
||||
# })
|
||||
|
||||
# request.cls.url_view_kwargs.update({
|
||||
# 'entity_model': self.model._meta.sub_model_type
|
||||
# })
|
||||
|
||||
|
||||
|
||||
# @pytest.fixture(scope='class', autouse = True)
|
||||
# def class_setup(self, request, django_db_blocker,
|
||||
# model,
|
||||
# var_setup,
|
||||
# prepare,
|
||||
# inherited_var_setup,
|
||||
# diff_org_model,
|
||||
# create_model,
|
||||
# ):
|
||||
|
||||
# pass
|
||||
|
||||
|
||||
class ContactPermissionsAPIPyTest(
|
||||
ContactPermissionsAPITestCases,
|
||||
):
|
||||
|
||||
pass
|
@ -1,46 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from access.tests.functional.person.test_functional_person_serializer import (
|
||||
MockView,
|
||||
PersonSerializerInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ContactSerializerTestCases(
|
||||
PersonSerializerInheritedCases
|
||||
):
|
||||
|
||||
|
||||
parameterized_test_data: dict = {
|
||||
"email": {
|
||||
'will_create': False,
|
||||
'exception_key': 'required'
|
||||
}
|
||||
}
|
||||
|
||||
valid_data: dict = {
|
||||
'email': 'contactentityduplicatetwo@unit.test',
|
||||
}
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
|
||||
class ContactSerializerInheritedCases(
|
||||
ContactSerializerTestCases,
|
||||
):
|
||||
|
||||
parameterized_test_data: dict = None
|
||||
|
||||
valid_data: dict = None
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
|
||||
class ContactSerializerPyTest(
|
||||
ContactSerializerTestCases,
|
||||
):
|
||||
|
||||
parameterized_test_data: dict = None
|
@ -1,58 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.contact import Contact
|
||||
from access.tests.functional.person.test_functional_person_viewset import (
|
||||
PersonViewSetInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ViewSetTestCases(
|
||||
PersonViewSetInheritedCases,
|
||||
):
|
||||
|
||||
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 ContactViewSetInheritedCases(
|
||||
ViewSetTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
|
||||
@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 ContactViewSetTest(
|
||||
ViewSetTestCases,
|
||||
TestCase,
|
||||
):
|
||||
pass
|
@ -1,24 +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
|
@ -1,78 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.entity_history import Entity, EntityAuditHistory
|
||||
|
||||
from core.tests.abstract.test_functional_history import HistoryEntriesCommon
|
||||
|
||||
|
||||
|
||||
class HistoryTestCases(
|
||||
HistoryEntriesCommon,
|
||||
):
|
||||
|
||||
field_name = 'model_notes'
|
||||
|
||||
history_model = EntityAuditHistory
|
||||
|
||||
kwargs_create_obj: dict = {}
|
||||
|
||||
kwargs_delete_obj: dict = {}
|
||||
|
||||
model = Entity
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
self.obj = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = self.field_value_original,
|
||||
**self.kwargs_create_obj,
|
||||
)
|
||||
|
||||
self.obj_delete = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = 'another note',
|
||||
**self.kwargs_delete_obj,
|
||||
)
|
||||
|
||||
self.call_the_banners()
|
||||
|
||||
|
||||
|
||||
class EntityAuditHistoryInheritedCases(
|
||||
HistoryTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
"""Entity model to test"""
|
||||
|
||||
kwargs_create_obj: dict = None
|
||||
|
||||
kwargs_delete_obj: dict = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_obj.update(
|
||||
super().kwargs_create_obj
|
||||
)
|
||||
|
||||
self.kwargs_delete_obj.update(
|
||||
super().kwargs_delete_obj
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class EntityAuditHistoryTest(
|
||||
HistoryTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
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_v2_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 = {
|
||||
'entity_model': self.model._meta.sub_model_type
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'entity_model': self.model._meta.sub_model_type
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class EntityMetadataTest(
|
||||
EntityMetadataTestCases,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
|
||||
url_name = '_api_v2_entity'
|
@ -1,89 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from api.tests.functional.test_functional_api_permissions import (
|
||||
APIPermissionsInheritedCases,
|
||||
)
|
||||
|
||||
|
||||
|
||||
class EntityPermissionsAPITestCases(
|
||||
APIPermissionsInheritedCases,
|
||||
):
|
||||
|
||||
add_data: dict = {}
|
||||
|
||||
app_namespace = 'v2'
|
||||
|
||||
change_data = {}
|
||||
|
||||
delete_data = {}
|
||||
|
||||
kwargs_create_item: dict = {}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {}
|
||||
|
||||
url_kwargs: dict = {}
|
||||
|
||||
url_name = '_api_v2_entity'
|
||||
|
||||
url_view_kwargs: dict = {}
|
||||
|
||||
|
||||
|
||||
def test_returned_data_from_user_and_global_organizations_only(self):
|
||||
"""Check items returned
|
||||
|
||||
This test case is a over-ride of a test case with the same name.
|
||||
This model is not a tenancy model making this test not-applicable.
|
||||
|
||||
Items returned from the query Must be from the users organization and
|
||||
global ONLY!
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class EntityPermissionsAPIInheritedCases(
|
||||
EntityPermissionsAPITestCases,
|
||||
):
|
||||
|
||||
add_data: dict = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
url_name = '_api_v2_entity_sub'
|
||||
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
def inherited_var_setup(self, request):
|
||||
|
||||
request.cls.url_kwargs.update({
|
||||
'entity_model': self.model._meta.sub_model_type
|
||||
})
|
||||
|
||||
request.cls.url_view_kwargs.update({
|
||||
'entity_model': self.model._meta.sub_model_type
|
||||
})
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(scope='class', autouse = True)
|
||||
def class_setup(self, request, django_db_blocker,
|
||||
model,
|
||||
var_setup,
|
||||
prepare,
|
||||
inherited_var_setup,
|
||||
diff_org_model,
|
||||
create_model,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class EntityPermissionsAPIPyTest(
|
||||
EntityPermissionsAPITestCases,
|
||||
):
|
||||
|
||||
pass
|
@ -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.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
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,266 +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 api.tests.abstract.api_serializer_viewset import SerializersTestCases
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
|
||||
|
||||
class ViewSetBase:
|
||||
|
||||
add_data: dict = {
|
||||
'model_notes': 'added model note'
|
||||
}
|
||||
|
||||
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 = {
|
||||
'model_notes': 'added model note'
|
||||
}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {
|
||||
'model_notes': 'added model note'
|
||||
}
|
||||
|
||||
model = None
|
||||
|
||||
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_asset_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 `Entity`
|
||||
"""
|
||||
|
||||
assert issubclass(self.model, self.base_model)
|
||||
|
||||
|
||||
|
||||
class ViewSetTestCases(
|
||||
ViewSetBase,
|
||||
SerializersTestCases,
|
||||
):
|
||||
|
||||
model = Entity
|
||||
|
||||
|
||||
|
||||
class EntityViewSetInheritedCases(
|
||||
ViewSetTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
url_name = '_api_v2_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 = {
|
||||
'entity_model': self.model._meta.sub_model_type
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'entity_model': self.model._meta.sub_model_type
|
||||
}
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class EntityViewSetTest(
|
||||
ViewSetTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
url_name = '_api_v2_entity'
|
@ -1,81 +0,0 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.entity_notes import Entity, EntityNotes
|
||||
|
||||
from core.tests.abstract.model_notes_api_fields import ModelNotesNotesAPIFields
|
||||
|
||||
|
||||
|
||||
class NotesAPITestCases(
|
||||
ModelNotesNotesAPIFields,
|
||||
):
|
||||
|
||||
entity_model = None
|
||||
|
||||
model = EntityNotes
|
||||
|
||||
kwargs_model_create: dict = None
|
||||
|
||||
# url_view_kwargs: dict = None
|
||||
|
||||
view_name: str = '_api_v2_entity_note'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Call parent setup
|
||||
2. Create a model note
|
||||
3. add url kwargs
|
||||
4. make the API request
|
||||
|
||||
"""
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
content = 'a random comment',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = self.entity_model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = 'text',
|
||||
**self.kwargs_model_create
|
||||
),
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'model_id': self.item.model.pk,
|
||||
'pk': self.item.pk
|
||||
}
|
||||
|
||||
self.make_request()
|
||||
|
||||
|
||||
|
||||
class EntityNotesAPIInheritedCases(
|
||||
NotesAPITestCases,
|
||||
):
|
||||
|
||||
entity_model = None
|
||||
|
||||
kwargs_model_create = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesAPITest(
|
||||
NotesAPITestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
entity_model = Entity
|
||||
|
||||
kwargs_model_create = {}
|
@ -1,162 +0,0 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.viewsets.entity_notes import ViewSet
|
||||
|
||||
from core.tests.abstract.test_functional_notes_viewset import (
|
||||
ModelNotesViewSetBase,
|
||||
ModelNotesMetadata,
|
||||
ModelNotesPermissionsAPI,
|
||||
ModelNotesSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ViewSetBase(
|
||||
ModelNotesViewSetBase
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
kwargs_create_model_item: dict = {}
|
||||
|
||||
kwargs_create_model_item_other_org: dict = {}
|
||||
|
||||
url_name = '_api_v2_entity_note'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
self.item = self.viewset.model.objects.create(
|
||||
organization = self.organization,
|
||||
content = 'a random comment',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = self.viewset.model.model.field.related_model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = 'text',
|
||||
**self.kwargs_create_model_item
|
||||
),
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
self.other_org_item = self.viewset.model.objects.create(
|
||||
organization = self.different_organization,
|
||||
content = 'a random comment',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = self.viewset.model.model.field.related_model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = 'text',
|
||||
**self.kwargs_create_model_item_other_org
|
||||
),
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
self.url_kwargs = {
|
||||
'model_id': self.item.model.pk,
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'model_id': self.item.model.pk,
|
||||
'pk': self.item.id
|
||||
}
|
||||
|
||||
|
||||
|
||||
class NotesPermissionsAPITestCases(
|
||||
ViewSetBase,
|
||||
ModelNotesPermissionsAPI,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
def test_returned_data_from_user_and_global_organizations_only(self):
|
||||
"""Check items returned
|
||||
|
||||
This test case is a over-ride of a test case with the same name.
|
||||
This model is not a global model making this test not-applicable.
|
||||
|
||||
Items returned from the query Must be from the users organization and
|
||||
global ONLY!
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class EntityNotesPermissionsAPIInheritedCases(
|
||||
NotesPermissionsAPITestCases,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesPermissionsAPITest(
|
||||
NotesPermissionsAPITestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
|
||||
|
||||
class NotesSerializerTestCases(
|
||||
ViewSetBase,
|
||||
ModelNotesSerializer,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesSerializerInheritedCases(
|
||||
NotesSerializerTestCases,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesSerializerTest(
|
||||
NotesSerializerTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
|
||||
|
||||
class NotesMetadataTestCases(
|
||||
ViewSetBase,
|
||||
ModelNotesMetadata,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesMetadataInheritedCases(
|
||||
NotesMetadataTestCases,
|
||||
):
|
||||
|
||||
viewset = None
|
||||
|
||||
|
||||
|
||||
class EntityNotesMetadataTest(
|
||||
NotesMetadataTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
@ -1,32 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.organization_history import Tenant as Organization, OrganizationAuditHistory
|
||||
|
||||
from core.tests.abstract.test_functional_history import HistoryEntriesCommon
|
||||
|
||||
|
||||
|
||||
class History(
|
||||
HistoryEntriesCommon,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
model = Organization
|
||||
|
||||
history_model = OrganizationAuditHistory
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
self.obj = self.model.objects.create(
|
||||
name = self.field_value_original,
|
||||
)
|
||||
|
||||
self.obj_delete = self.model.objects.create(
|
||||
name = self.field_value_delete,
|
||||
)
|
||||
|
||||
self.call_the_banners()
|
@ -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,321 +0,0 @@
|
||||
import django
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import AnonymousUser, Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.shortcuts import reverse
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from access.models.tenant import Tenant as Organization
|
||||
from access.models.team import Team
|
||||
from access.models.team_user import TeamUsers
|
||||
|
||||
from api.tests.abstract.api_permissions_viewset import APIPermissions
|
||||
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
|
||||
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional
|
||||
|
||||
User = django.contrib.auth.get_user_model()
|
||||
|
||||
|
||||
|
||||
class ViewSetBase:
|
||||
|
||||
model = Organization
|
||||
|
||||
app_namespace = 'v2'
|
||||
|
||||
url_name = '_api_v2_organization'
|
||||
|
||||
change_data = {'name': 'device'}
|
||||
|
||||
delete_data = {}
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization for user and item
|
||||
. create an organization that is different to item
|
||||
2. Create a team
|
||||
3. create teams with each permission: view, add, change, delete
|
||||
4. create a user per team
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.item = organization
|
||||
|
||||
different_organization = Organization.objects.create(name='test_different_organization')
|
||||
|
||||
self.different_organization = different_organization
|
||||
|
||||
self.other_org_item = organization
|
||||
|
||||
view_permissions = Permission.objects.get(
|
||||
codename = 'view_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
view_team = Team.objects.create(
|
||||
team_name = 'view_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
view_team_b = Team.objects.create(
|
||||
team_name = 'view_team',
|
||||
organization = different_organization,
|
||||
)
|
||||
|
||||
view_team.permissions.set([view_permissions])
|
||||
|
||||
view_team_b.permissions.set([view_permissions])
|
||||
|
||||
|
||||
|
||||
add_permissions = Permission.objects.get(
|
||||
codename = 'add_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
add_team = Team.objects.create(
|
||||
team_name = 'add_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
add_team.permissions.set([add_permissions])
|
||||
|
||||
|
||||
|
||||
change_permissions = Permission.objects.get(
|
||||
codename = 'change_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
change_team = Team.objects.create(
|
||||
team_name = 'change_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
change_team.permissions.set([change_permissions])
|
||||
|
||||
|
||||
|
||||
delete_permissions = Permission.objects.get(
|
||||
codename = 'delete_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
delete_team = Team.objects.create(
|
||||
team_name = 'delete_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
delete_team.permissions.set([delete_permissions])
|
||||
|
||||
|
||||
self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password")
|
||||
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_user_view", password="password")
|
||||
teamuser = TeamUsers.objects.create(
|
||||
team = view_team,
|
||||
user = self.view_user
|
||||
)
|
||||
|
||||
self.view_user_b = User.objects.create_user(username="test_user_view_b", password="password")
|
||||
teamuser = TeamUsers.objects.create(
|
||||
team = view_team_b,
|
||||
user = self.view_user_b
|
||||
)
|
||||
|
||||
|
||||
self.url_view_kwargs = { 'pk': self.item.id }
|
||||
|
||||
self.add_data = {
|
||||
'name': 'team_post',
|
||||
}
|
||||
|
||||
|
||||
self.super_add_user = User.objects.create_user(username="test_user_add_super", password="password", is_superuser = True)
|
||||
|
||||
self.add_user = User.objects.create_user(username="test_user_add", password="password")
|
||||
teamuser = TeamUsers.objects.create(
|
||||
team = add_team,
|
||||
user = self.add_user
|
||||
)
|
||||
|
||||
self.change_user = User.objects.create_user(username="test_user_change", password="password")
|
||||
teamuser = TeamUsers.objects.create(
|
||||
team = change_team,
|
||||
user = self.change_user
|
||||
)
|
||||
|
||||
self.delete_user = User.objects.create_user(username="test_user_delete", password="password")
|
||||
teamuser = TeamUsers.objects.create(
|
||||
team = delete_team,
|
||||
user = self.delete_user
|
||||
)
|
||||
|
||||
|
||||
self.different_organization_user = User.objects.create_user(username="test_different_organization_user", password="password")
|
||||
|
||||
|
||||
different_organization_team = Team.objects.create(
|
||||
team_name = 'different_organization_team',
|
||||
organization = different_organization,
|
||||
)
|
||||
|
||||
different_organization_team.permissions.set([
|
||||
view_permissions,
|
||||
add_permissions,
|
||||
change_permissions,
|
||||
delete_permissions,
|
||||
])
|
||||
|
||||
TeamUsers.objects.create(
|
||||
team = different_organization_team,
|
||||
user = self.different_organization_user
|
||||
)
|
||||
|
||||
|
||||
|
||||
class OrganizationPermissionsAPI(
|
||||
ViewSetBase,
|
||||
APIPermissions,
|
||||
TestCase
|
||||
):
|
||||
|
||||
def test_returned_data_from_user_and_global_organizations_only(self):
|
||||
"""Check items returned
|
||||
|
||||
This test case is a over-ride of a test case with the same name.
|
||||
This model is not a tenancy model making this test not-applicable.
|
||||
|
||||
Items returned from the query Must be from the users organization and
|
||||
global ONLY!
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def test_add_has_permission(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user with permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
|
||||
if self.url_kwargs:
|
||||
|
||||
url = reverse( self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs )
|
||||
|
||||
else:
|
||||
|
||||
url = reverse( self.app_namespace + ':' + self.url_name + '-list' )
|
||||
|
||||
|
||||
client.force_login( self.add_user )
|
||||
|
||||
response = client.post( url, data = self.add_data )
|
||||
|
||||
assert response.status_code == 201
|
||||
|
||||
|
||||
|
||||
def test_returned_results_only_user_orgs(self):
|
||||
"""Returned results check
|
||||
|
||||
This test case is an override of a test of the same name.
|
||||
organizations are not tenancy objects and therefor are supposed to
|
||||
return all items when a user queries them.
|
||||
|
||||
Ensure that a query to the viewset endpoint does not return
|
||||
items that are not part of the users organizations.
|
||||
"""
|
||||
|
||||
|
||||
# Ensure the other org item exists, without test not able to function
|
||||
print('Check that the different organization item has been defined')
|
||||
assert hasattr(self, 'other_org_item')
|
||||
|
||||
# ensure that the variables for the two orgs are different orgs
|
||||
print('checking that the different and user oganizations are different')
|
||||
assert self.different_organization.id != self.organization.id
|
||||
|
||||
|
||||
client = Client()
|
||||
|
||||
if self.url_kwargs:
|
||||
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
|
||||
|
||||
else:
|
||||
|
||||
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.get(url)
|
||||
|
||||
contains_different_org: bool = False
|
||||
|
||||
# for item in response.data['results']:
|
||||
|
||||
# if int(item['id']) != self.organization.id:
|
||||
|
||||
# contains_different_org = True
|
||||
|
||||
assert len(response.data['results']) == 2
|
||||
|
||||
|
||||
def test_add_different_organization_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
This test is a duplicate of a test case with the same name.
|
||||
Organizations are not tenancy models so this test does nothing of value
|
||||
|
||||
attempt to add as user from different organization
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class OrganizationViewSet(
|
||||
ViewSetBase,
|
||||
SerializersTestCases,
|
||||
TestCase
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class OrganizationMetadata(
|
||||
ViewSetBase,
|
||||
MetadataAttributesFunctional,
|
||||
MetaDataNavigationEntriesFunctional,
|
||||
TestCase
|
||||
):
|
||||
|
||||
menu_id = 'access'
|
||||
|
||||
menu_entry_id = 'tenant'
|
@ -1,116 +0,0 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from access.viewsets.organization_notes import ViewSet
|
||||
|
||||
from core.tests.abstract.test_functional_notes_viewset import (
|
||||
ModelNotesViewSetBase,
|
||||
ModelNotesMetadata,
|
||||
ModelNotesPermissionsAPI,
|
||||
ModelNotesSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ViewSetBase(
|
||||
ModelNotesViewSetBase
|
||||
):
|
||||
|
||||
viewset = ViewSet
|
||||
|
||||
url_name = '_api_v2_organization_note'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
self.item = self.viewset.model.objects.create(
|
||||
organization = self.organization,
|
||||
content = 'a random comment',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = self.organization,
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
self.other_org_item = self.viewset.model.objects.create(
|
||||
organization = self.different_organization,
|
||||
content = 'a random comment',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = self.different_organization,
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
|
||||
self.global_org_item = self.viewset.model.objects.create(
|
||||
organization = self.global_organization,
|
||||
content = 'a random comment global_organization',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = self.global_organization,
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
self.url_kwargs = {
|
||||
'model_id': self.item.model.pk,
|
||||
}
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'model_id': self.item.model.pk,
|
||||
'pk': self.item.id
|
||||
}
|
||||
|
||||
|
||||
|
||||
class OrganizationModelNotesPermissionsAPI(
|
||||
ViewSetBase,
|
||||
ModelNotesPermissionsAPI,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
|
||||
def test_returned_data_from_user_and_global_organizations_only(self):
|
||||
"""Check items returned
|
||||
|
||||
This test case is a over-ride of a test case with the same name.
|
||||
This model is not a global model making this test not-applicable.
|
||||
|
||||
Items returned from the query Must be from the users organization and
|
||||
global ONLY!
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class OrganizationModelNotesSerializer(
|
||||
ViewSetBase,
|
||||
ModelNotesSerializer,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class OrganizationModelNotesMetadata(
|
||||
ViewSetBase,
|
||||
ModelNotesMetadata,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
|
||||
pass
|
@ -1,53 +0,0 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
|
||||
from core.tests.abstract.model_notes_api_fields import ModelNotesNotesAPIFields
|
||||
|
||||
from access.models.tenant import Tenant as Organization
|
||||
from access.models.organization_notes import OrganizationNotes
|
||||
|
||||
|
||||
class OrganizationNotesAPI(
|
||||
ModelNotesNotesAPIFields,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
model = OrganizationNotes
|
||||
|
||||
view_name: str = '_api_v2_organization_note'
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Call parent setup
|
||||
2. Create a model note
|
||||
3. add url kwargs
|
||||
4. make the API request
|
||||
|
||||
"""
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
content = 'a random comment',
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = str(self.model._meta.app_label).lower(),
|
||||
model = str(self.model.model.field.related_model.__name__).replace(' ', '').lower(),
|
||||
),
|
||||
model = Organization.objects.create(
|
||||
name = 'dev'
|
||||
),
|
||||
created_by = self.view_user,
|
||||
modified_by = self.view_user,
|
||||
)
|
||||
|
||||
|
||||
self.url_view_kwargs = {
|
||||
'model_id': self.item.model.pk,
|
||||
'pk': self.item.pk
|
||||
}
|
||||
|
||||
self.make_request()
|
@ -1,24 +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
|
@ -1,65 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.person import Person
|
||||
from access.tests.functional.entity.test_functional_entity_history import (
|
||||
EntityAuditHistoryInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class PersonTestCases(
|
||||
EntityAuditHistoryInheritedCases,
|
||||
):
|
||||
|
||||
field_name = 'model_notes'
|
||||
|
||||
kwargs_create_obj: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Funny',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
kwargs_delete_obj: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Weird',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
model = Person
|
||||
|
||||
|
||||
class PersonHistoryInheritedCases(
|
||||
PersonTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
"""Entity model to test"""
|
||||
|
||||
kwargs_create_obj: dict = None
|
||||
|
||||
kwargs_delete_obj: dict = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
self.kwargs_create_obj.update(
|
||||
super().kwargs_create_obj
|
||||
)
|
||||
|
||||
self.kwargs_delete_obj.update(
|
||||
super().kwargs_delete_obj
|
||||
)
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class PersonHistoryTest(
|
||||
PersonTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
pass
|
@ -1,83 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.person import Person
|
||||
from access.tests.functional.entity.test_functional_entity_metadata import (
|
||||
EntityMetadataInheritedCases
|
||||
)
|
||||
|
||||
from accounting.models.asset_base import AssetBase
|
||||
|
||||
|
||||
|
||||
class PersonMetadataTestCases(
|
||||
EntityMetadataInheritedCases,
|
||||
):
|
||||
|
||||
add_data: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Strange',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
kwargs_create_item: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Weird',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Funny',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
model = Person
|
||||
|
||||
|
||||
|
||||
|
||||
class PersonMetadataInheritedCases(
|
||||
PersonMetadataTestCases,
|
||||
):
|
||||
|
||||
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 = {
|
||||
# 'entity_model': self.model._meta.sub_model_type
|
||||
# }
|
||||
|
||||
# self.url_view_kwargs = {
|
||||
# 'entity_model': self.model._meta.sub_model_type
|
||||
# }
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
|
||||
class PersonMetadataTest(
|
||||
PersonMetadataTestCases,
|
||||
TestCase,
|
||||
|
||||
):
|
||||
pass
|
@ -1,91 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from access.tests.functional.entity.test_functional_entity_permission import (
|
||||
EntityPermissionsAPIInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class PersonPermissionsAPITestCases(
|
||||
EntityPermissionsAPIInheritedCases,
|
||||
):
|
||||
|
||||
add_data: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Strange',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
# app_namespace = 'v2'
|
||||
|
||||
# change_data = {}
|
||||
|
||||
# delete_data = {}
|
||||
|
||||
kwargs_create_item: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Weird',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Funny',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
# url_kwargs: dict = {}
|
||||
|
||||
# url_name = '_api_v2_entity'
|
||||
|
||||
# url_view_kwargs: dict = {}
|
||||
|
||||
|
||||
|
||||
class PersonPermissionsAPIInheritedCases(
|
||||
PersonPermissionsAPITestCases,
|
||||
):
|
||||
|
||||
add_data: dict = None
|
||||
|
||||
kwargs_create_item: dict = None
|
||||
|
||||
kwargs_create_item_diff_org: dict = None
|
||||
|
||||
# url_name = '_api_v2_entity_sub'
|
||||
|
||||
|
||||
# @pytest.fixture(scope='class')
|
||||
# def inherited_var_setup(self, request):
|
||||
|
||||
# request.cls.url_kwargs.update({
|
||||
# 'entity_model': self.model._meta.sub_model_type
|
||||
# })
|
||||
|
||||
# request.cls.url_view_kwargs.update({
|
||||
# 'entity_model': self.model._meta.sub_model_type
|
||||
# })
|
||||
|
||||
|
||||
|
||||
# @pytest.fixture(scope='class', autouse = True)
|
||||
# def class_setup(self, request, django_db_blocker,
|
||||
# model,
|
||||
# var_setup,
|
||||
# prepare,
|
||||
# inherited_var_setup,
|
||||
# diff_org_model,
|
||||
# create_model,
|
||||
# ):
|
||||
|
||||
# pass
|
||||
|
||||
|
||||
class PersonPermissionsAPIPyTest(
|
||||
PersonPermissionsAPITestCases,
|
||||
):
|
||||
|
||||
pass
|
@ -1,111 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
from access.tests.functional.entity.test_functional_entity_serializer import (
|
||||
MockView,
|
||||
EntitySerializerInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class PersonSerializerTestCases(
|
||||
EntitySerializerInheritedCases
|
||||
):
|
||||
|
||||
|
||||
parameterized_test_data: dict = {
|
||||
"model_notes": {
|
||||
'will_create': True,
|
||||
},
|
||||
"f_name": {
|
||||
'will_create': False,
|
||||
'exception_key': 'required'
|
||||
},
|
||||
"m_name": {
|
||||
'will_create': True,
|
||||
},
|
||||
"l_name": {
|
||||
'will_create': False,
|
||||
'exception_key': 'required'
|
||||
},
|
||||
"dob": {
|
||||
'will_create': True,
|
||||
}
|
||||
}
|
||||
|
||||
valid_data: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Funny',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
def test_serializer_validation_duplicate_f_name_l_name_dob(self, model, create_serializer):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that when creating with valid data and fields f_name, l_name and
|
||||
dob already exists in the db a validation error occurs.
|
||||
"""
|
||||
|
||||
valid_data = self.valid_data.copy()
|
||||
|
||||
valid_data['f_name'] = 'duplicate'
|
||||
|
||||
valid_data['organization'] = self.organization
|
||||
|
||||
obj = model.objects.create(
|
||||
**valid_data
|
||||
)
|
||||
|
||||
valid_data['organization'] = self.organization.id
|
||||
|
||||
if 'email' in valid_data: # Contact Entity
|
||||
|
||||
valid_data['email'] = 'abc@xyz.qwe'
|
||||
|
||||
if 'name' in valid_data: # Company Entity
|
||||
|
||||
valid_data['name'] = 'diff'
|
||||
|
||||
if 'employee_number' in valid_data: # Employee Entity
|
||||
|
||||
valid_data['employee_number'] = 13579
|
||||
|
||||
view_set = MockView()
|
||||
|
||||
with pytest.raises(ValidationError) as err:
|
||||
|
||||
serializer = create_serializer(
|
||||
context = {
|
||||
'view': view_set,
|
||||
},
|
||||
data = valid_data
|
||||
)
|
||||
|
||||
serializer.is_valid(raise_exception = True)
|
||||
|
||||
serializer.save()
|
||||
|
||||
assert err.value.get_codes()['dob'] == 'duplicate_person_on_dob'
|
||||
|
||||
|
||||
|
||||
class PersonSerializerInheritedCases(
|
||||
PersonSerializerTestCases,
|
||||
):
|
||||
|
||||
parameterized_test_data: dict = None
|
||||
|
||||
valid_data: dict = None
|
||||
"""Valid data used by serializer to create object"""
|
||||
|
||||
|
||||
|
||||
class PersonSerializerPyTest(
|
||||
PersonSerializerTestCases,
|
||||
):
|
||||
|
||||
parameterized_test_data: dict = None
|
@ -1,67 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.person import Person
|
||||
from access.tests.functional.entity.test_functional_entity_viewset import (
|
||||
EntityViewSetInheritedCases
|
||||
)
|
||||
|
||||
|
||||
|
||||
class ViewSetTestCases(
|
||||
EntityViewSetInheritedCases,
|
||||
):
|
||||
|
||||
add_data: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Strange',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
kwargs_create_item: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Weird',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
kwargs_create_item_diff_org: dict = {
|
||||
'f_name': 'Ian',
|
||||
'm_name': 'Peter',
|
||||
'l_name': 'Funny',
|
||||
'dob': '2025-04-08',
|
||||
}
|
||||
|
||||
model = Person
|
||||
|
||||
|
||||
|
||||
class PersonViewSetInheritedCases(
|
||||
ViewSetTestCases,
|
||||
):
|
||||
|
||||
model = None
|
||||
|
||||
|
||||
@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 PersonViewSetTest(
|
||||
ViewSetTestCases,
|
||||
TestCase,
|
||||
):
|
||||
pass
|
@ -1,55 +0,0 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models.role_history import Role, RoleAuditHistory
|
||||
|
||||
from core.tests.abstract.test_functional_history import HistoryEntriesCommon
|
||||
|
||||
|
||||
|
||||
class HistoryTestCases(
|
||||
HistoryEntriesCommon,
|
||||
):
|
||||
|
||||
history_model = RoleAuditHistory
|
||||
|
||||
kwargs_create_obj: dict = {}
|
||||
|
||||
kwargs_delete_obj: dict = {}
|
||||
|
||||
model = Role
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
|
||||
super().setUpTestData()
|
||||
|
||||
|
||||
self.obj = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = self.field_value_original,
|
||||
**self.kwargs_create_obj,
|
||||
)
|
||||
|
||||
self.obj_delete = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
model_notes = 'another note',
|
||||
**self.kwargs_delete_obj,
|
||||
)
|
||||
|
||||
self.call_the_banners()
|
||||
|
||||
|
||||
|
||||
class RoleAuditHistoryTest(
|
||||
HistoryTestCases,
|
||||
TestCase,
|
||||
):
|
||||
|
||||
kwargs_create_obj: dict = {
|
||||
'name': 'original_name'
|
||||
}
|
||||
|
||||
kwargs_delete_obj: dict = {
|
||||
'name': 'delete obj'
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
|
||||
from access.models.tenant import Tenant as Organization
|
||||
from access.serializers.role import Role, ModelSerializer
|
||||
|
||||
|
||||
|
||||
class ValidationSerializer(
|
||||
TestCase,
|
||||
):
|
||||
|
||||
model = Role
|
||||
|
||||
serializer = ModelSerializer
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an org
|
||||
2. Create an item
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.diff_organization = Organization.objects.create(name='test_org_diff_org')
|
||||
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
name = 'one',
|
||||
)
|
||||
|
||||
self.valid_data = {
|
||||
'organization': self.organization.id,
|
||||
'name': 'two',
|
||||
'model_notes': 'dfsdfsd',
|
||||
}
|
||||
|
||||
|
||||
|
||||
def test_serializer_validation_valid_data(self):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that if creating and no name is provided a validation error occurs
|
||||
"""
|
||||
|
||||
serializer = self.serializer(
|
||||
data = self.valid_data
|
||||
)
|
||||
|
||||
|
||||
assert serializer.is_valid( raise_exception = True )
|
||||
|
||||
|
||||
def test_serializer_validation_no_name_exception(self):
|
||||
"""Serializer Validation Check
|
||||
|
||||
Ensure that when creating and field name is not provided a
|
||||
validation error occurs
|
||||
"""
|
||||
|
||||
valid_data = self.valid_data.copy()
|
||||
|
||||
del valid_data['name']
|
||||
|
||||
with pytest.raises(ValidationError) as err:
|
||||
|
||||
serializer = self.serializer(
|
||||
data = valid_data
|
||||
)
|
||||
|
||||
serializer.is_valid(raise_exception = True)
|
||||
|
||||
assert err.value.get_codes()['name'][0] == 'required'
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user