Compare commits

..

34 Commits

Author SHA1 Message Date
Jon
8d071c68df chore: add Merge/Pull request template
#226
2024-08-14 01:52:22 +09:30
Jon
3b1691ff62 docs(roadmap): update completed features
#226
2024-08-14 01:46:21 +09:30
Jon
a77c43d213 docs(base): detail view template
. #24 #226 closes #22
2024-08-14 01:33:48 +09:30
Jon
086959b431 refactor(itim): services now use details template
. #22 #226
2024-08-14 01:23:17 +09:30
Jon
3f117f9d83 feat(base): create detail view templates
purpose is to aid in the development of a detail form

#22 #24 #226
2024-08-14 00:02:25 +09:30
Jon
6a23845a4f docs: initial adding of template page
#22
2024-08-13 15:03:10 +09:30
Jon
b9c6d04e04 chore: add services navigation icon
!43 #69
2024-08-13 13:23:45 +09:30
Jon
32c0027ecf fix(itim): ensure that the service template config is also rendered as part of device config
!43 #69
2024-08-13 13:23:45 +09:30
Jon
dae52e8646 docs: fluff the port and services
!43 closes #69
2024-08-13 13:23:45 +09:30
Jon
890a5651a0 fix(itim): dont render link if no device
!43 #69
2024-08-13 13:23:45 +09:30
Jon
4cb37f8347 feat(itam): Render Service Config with device config
!43 #69
2024-08-13 13:23:45 +09:30
Jon
a2010b9517 feat(itam): Display deployed services for devices
!43 #69
2024-08-13 13:23:45 +09:30
Jon
c95736ce14 feat(itim): Prevent circular service dependencies
!43 #69
2024-08-13 13:23:45 +09:30
Jon
b46c61954c feat(itim): Port number validation to check for valid port numbers
!43 #69
2024-08-13 13:23:45 +09:30
Jon
afe4266600 feat(itim): Prevent Service template from being assigned as dependent service
!43 #69
2024-08-13 13:23:45 +09:30
Jon
0c8d1c8da1 feat(itim): Add service template support
!43 #69
2024-08-13 13:23:45 +09:30
Jon
eac998b5cc fix(itim): Dont show self within service dependencies
!43 #69
2024-08-13 13:23:45 +09:30
Jon
5914782252 feat(itim): Ports for service management
!43 #69
2024-08-13 13:23:45 +09:30
Jon
73d875c4ac feat(itim): Service Management
!43 #69
2024-08-13 13:23:45 +09:30
Jon
8f439f0675 fix(assistance): Only return distinct values when limiting KB articles
!43 #10
2024-08-13 13:23:45 +09:30
Jon
0f102c6aaf docs(assistance): document kb categories for user
!43 closes #10
2024-08-13 13:23:45 +09:30
Jon
4852c6caeb feat(assistance): Filter KB articles to target user
only intended to filter for users whom dont have change perm.

!43 #10
2024-08-13 13:23:45 +09:30
Jon
3fffba2eba feat(assistance): Add date picker to date fields for KB articles
!43 #10
2024-08-13 13:23:45 +09:30
Jon
a1293984ea feat(assistance): Dont display expired articles for "view" users
!43 #10
2024-08-13 13:23:45 +09:30
Jon
4876db50c1 docs(assistance): document kb for user
!43 #10
2024-08-13 13:23:45 +09:30
Jon
425cc066af feat(base): add code highlighting to markdown
!43 #10
2024-08-13 13:23:45 +09:30
Jon
1086f517fa feat(assistance): Categorised Knowledge base articles
!43 #10
2024-08-13 13:23:45 +09:30
Jon
2fdbf87ddd docs(assistance): added pages for knowledgebase
!43 #10
2024-08-13 13:23:45 +09:30
Jon
86228836c7 chore(base): rename information -> assistance
!43 #10
2024-08-13 13:23:45 +09:30
Jon
a6e6c948a5 feat(itim): Add menu entry
!43 #69 #71
2024-08-13 13:23:45 +09:30
Jon
dcdfa8feb7 feat(itam): Ability to add device configuration
!43 fixes #44
2024-08-13 13:23:45 +09:30
Jon
8388d2e695 test(external_link): add tests
!43 fixes #6
2024-08-13 13:23:45 +09:30
Jon
29f269050f feat(settings): New model to allow adding templated links to devices and software
!43 #6
2024-08-13 13:23:45 +09:30
Jon
93c4fc2009 docs: move settings pages into sub-directory
!43 #6
2024-08-13 13:23:45 +09:30
1286 changed files with 8031 additions and 138201 deletions

View File

@ -17,5 +17,5 @@ commitizen:
prerelease_offset: 1
tag_format: $version
update_changelog_on_bump: false
version: 1.13.1
version: 1.0.0-b14
version_scheme: semver

View File

@ -1,90 +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
- [ ] 🛠️ Migrations 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/models/ticket/ticket_linked_items.TicketLinkedItem.__str__()`
- [ ] tag added to `app/core/lib/slash_commands/linked_model.CommandLinkedModel.get_model()`
- [ ] ⚒️ Migration _Ticket Linked Item item_type choices update_
>[!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
- [ ] 📓 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 Test Model
- [ ] Unit Test Tenancy Object
- [ ] Unit Test Serializer
- [ ] Unit Test ViewSet
- [ ] Function Test ViewSet
- [ ] Function Test API Metadata
- [ ] Function Test API Permissions
- [ ] Function Test API Render (fields)
- [ ] Function Test History Entries
- [ ] Function Test History API Render (fields)
### ✅ 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)
---
<!-- Add additional requirement here and as a check box list -->

View File

@ -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 :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_

View File

@ -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

7
.gitignore vendored
View File

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

View File

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

54
.vscode/launch.json vendored
View File

@ -4,9 +4,8 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Centurion",
"name": "Debug: Django",
"type": "debugpy",
"request": "launch",
"args": [
@ -17,58 +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",

View File

@ -8,8 +8,7 @@
// "-v",
// "--cov",
// "--cov-report xml",
"-s",
"app",
"app"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
@ -18,6 +17,4 @@
"ITSM"
],
"cSpell.language": "en-AU",
"jest.enable": false,
"pylint.enabled": true,
}

File diff suppressed because it is too large Load Diff

View File

@ -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.

View File

@ -32,14 +32,9 @@ This project is hosted on [Github](https://github.com/NofussComputing/centurion_
**Stable Branch**
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/nofusscomputing/centurion_erp/ci.yaml?branch=master&style=plastic&logo=github&label=Build&color=%23000) ![GitHub Release](https://img.shields.io/github/v/release/nofusscomputing/centurion_erp?sort=date&style=plastic&logo=github&label=Release&color=000)
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fnofusscomputing%2F.github%2Fmaster%2Frepositories%2Fnofusscomputing%2Fcenturion_erp%2Fmaster%2Fbadge_endpoint_coverage.json&style=plastic)
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/nofusscomputing/centurion_erp/ci.yaml?branch=master&style=plastic&logo=github&label=Build&color=%23000) ![GitHub Release](https://img.shields.io/github/v/release/nofusscomputing/centurion_erp?sort=date&style=plastic&logo=github&label=Release&color=000) ![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fnofusscomputing%2F.github%2Fmaster%2Frepositories%2Fnofusscomputing%2Fcenturion_erp%2Fmaster%2Fbadge_endpoint_coverage.json&style=plastic)
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fnofusscomputing%2F.github%2Fmaster%2Frepositories%2Fnofusscomputing%2Fcenturion_erp%2Fmaster%2Fbadge_endpoint_unit_test.json)
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fnofusscomputing%2F.github%2Fmaster%2Frepositories%2Fnofusscomputing%2Fcenturion_erp%2Fmaster%2Fbadge_endpoint_coverage_functional.json&style=plastic)
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fnofusscomputing%2F.github%2Fmaster%2Frepositories%2Fnofusscomputing%2Fcenturion_erp%2Fmaster%2Fbadge_endpoint_functional_test.json)
----
@ -48,13 +43,9 @@ This project is hosted on [Github](https://github.com/NofussComputing/centurion_
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/nofusscomputing/centurion_erp/ci.yaml?branch=development&style=plastic&logo=github&label=Build&color=%23000) ![GitHub Release](https://img.shields.io/github/v/release/nofusscomputing/centurion_erp?include_prereleases&sort=date&style=plastic&logo=github&label=Release&color=000)
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fnofusscomputing%2F.github%2Fmaster%2Frepositories%2Fnofusscomputing%2Fcenturion_erp%2Fdevelopment%2Fbadge_endpoint_coverage.json&style=plastic)
![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/nofusscomputing/centurion_erp/ci.yaml?branch=development&style=plastic&logo=github&label=Build&color=%23000) ![GitHub Release](https://img.shields.io/github/v/release/nofusscomputing/centurion_erp?include_prereleases&sort=date&style=plastic&logo=github&label=Release&color=000) ![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fnofusscomputing%2F.github%2Fmaster%2Frepositories%2Fnofusscomputing%2Fcenturion_erp%2Fdevelopment%2Fbadge_endpoint_coverage.json&style=plastic)
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fnofusscomputing%2F.github%2Fmaster%2Frepositories%2Fnofusscomputing%2Fcenturion_erp%2Fdevelopment%2Fbadge_endpoint_unit_test.json)
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fnofusscomputing%2F.github%2Fmaster%2Frepositories%2Fnofusscomputing%2Fcenturion_erp%2Fdevelopment%2Fbadge_endpoint_coverage_functional.json&style=plastic)
![Endpoint Badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fnofusscomputing%2F.github%2Fmaster%2Frepositories%2Fnofusscomputing%2Fcenturion_erp%2Fdevelopment%2Fbadge_endpoint_functional_test.json)
----
<br>

View File

@ -1,136 +1,7 @@
## 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

View File

@ -1,11 +1,7 @@
from django.contrib import admin
from django.contrib.auth.models import Group, User
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import Group
from access.models.organization import Organization
from access.models.team import Team
from access.models.team_user import TeamUsers
from .models import *
admin.site.unregister(Group)
@ -29,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)

View File

@ -11,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)
@ -36,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()
@ -65,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 == '_':

View File

@ -3,7 +3,7 @@ from django.db.models import Q
from app import settings
from access.models.organization import Organization
from access.models import Organization
from core.forms.common import CommonModelForm

View File

@ -1,13 +1,13 @@
from django import forms
from django.contrib.auth.models import Permission
from django.db.models import Q
from django.forms import inlineformset_factory
from app import settings
from .team_users import TeamUsersForm, TeamUsers
from access.models.team import Team
from access.functions import permissions
from app 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
)

View File

@ -2,7 +2,7 @@ from django.db.models import Q
from app import settings
from access.models.team_user import TeamUsers
from access.models import TeamUsers
from core.forms.common import CommonModelForm

View File

@ -1,56 +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',
'assistance',
'config_management',
'core',
'devops',
'django_celery_results',
'itam',
'itim',
'project_management',
'settings',
]
exclude_models = [
'appsettings',
'chordcounter',
'comment',
'groupresult',
'history',
'modelnotes',
'usersettings',
]
exclude_permissions = [
'add_checkin',
'add_history',
'add_organization',
'add_taskresult',
'change_checkin',
'change_history',
'change_organization',
'change_taskresult',
'delete_checkin',
'delete_history',
'delete_organization',
'delete_taskresult',
'view_checkin',
'view_history',
]
return Permission.objects.filter(
content_type__app_label__in=apps,
).exclude(
content_type__model__in=exclude_models
).exclude(
codename__in = exclude_permissions
)

View File

@ -1,161 +0,0 @@
from django.contrib.auth.middleware import (
AuthenticationMiddleware,
SimpleLazyObject,
partial,
)
from django.contrib.auth.models import User, Group
from django.utils.deprecation import MiddlewareMixin
from access.models.organization import Organization
from access.models.team import Team
from settings.models.app_settings import AppSettings
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

View File

@ -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',

View File

@ -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'),
),
]

View File

@ -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',),
),
]

View File

@ -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',),
),
]

View File

@ -4,27 +4,12 @@ from django.contrib.auth.models import Group
from django.core.exceptions import PermissionDenied
from django.utils.functional import cached_property
from access.models.organization import 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
@ -304,23 +242,15 @@ 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:
@ -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:

View File

@ -1,272 +0,0 @@
from django.contrib.auth.models import User, Group
from django.db import models
from access.models.organization import Organization
from access.models.team import Team
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() == 'organization':
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.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).
"""

View File

@ -1,322 +0,0 @@
import traceback
from django.core.exceptions import ObjectDoesNotExist
from rest_framework import exceptions
from rest_framework.permissions import DjangoObjectPermissions
from access.models.tenancy import Organization, TenancyObject
from core import exceptions as centurion_exceptions
class OrganizationPermissionMixin(
DjangoObjectPermissions,
):
"""Organization Permission Mixin
This class is to be used as the permission class for API `Views`/`ViewSets`.
In combination with the `OrganizationPermissionsMixin`, 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: Organization = 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
View 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

View File

@ -1,155 +0,0 @@
from django.db import models
from django.contrib.auth.models import User
from rest_framework.reverse import reverse
from access.fields import (
AutoCreatedField,
AutoLastModifiedField,
AutoSlugField
)
from core.mixin.history_save import SaveHistory
class Organization(SaveHistory):
class Meta:
verbose_name = "Organization"
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(
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 Organization',
max_length = 50,
unique = True,
verbose_name = 'Name'
)
manager = models.ForeignKey(
User,
blank = False,
help_text = 'Manager for this organization',
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 OrganizationHistory
history = super().save_history(
before = before,
after = after,
history_model = OrganizationHistory
)
return history

View File

@ -1,53 +0,0 @@
from django.db import models
from core.models.model_history import ModelHistory
from access.models.organization import Organization
class OrganizationHistory(
ModelHistory
):
class Meta:
db_table = 'access_organization_history'
ordering = ModelHistory._meta.ordering
verbose_name = 'Organization History'
verbose_name_plural = 'Organization History'
model = models.ForeignKey(
Organization,
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 OrganizationBaseSerializer
model = OrganizationBaseSerializer(self.model, context = serializer_context)
return model

View File

@ -1,45 +0,0 @@
from django.db import models
from access.models.organization import Organization
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(
Organization,
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
}

View File

@ -1,189 +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.organization import Organization
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):
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(
Organization,
blank = False,
help_text = 'Organization this belongs to',
null = False,
on_delete = models.CASCADE,
validators = [validatate_organization_exists],
verbose_name = 'Organization'
)
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 TeamHistory
history = super().save_history(
before = before,
after = after,
history_model = TeamHistory
)
return history

View File

@ -1,53 +0,0 @@
from django.db import models
from core.models.model_history import ModelHistory
from access.models.team import Team
class TeamHistory(
ModelHistory
):
class Meta:
db_table = 'access_team_history'
ordering = ModelHistory._meta.ordering
verbose_name = 'Team History'
verbose_name_plural = 'Team History'
model = models.ForeignKey(
Team,
blank = False,
help_text = 'Model this note belongs to',
null = False,
on_delete = models.CASCADE,
related_name = 'history',
verbose_name = 'Model',
)
table_fields: list = []
page_layout: dict = []
def get_object(self):
return self
def get_serialized_model(self, serializer_context):
model = None
from access.serializers.teams import TeamBaseSerializer
model = TeamBaseSerializer(self.model, context = serializer_context)
return model

View File

@ -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 )

View File

@ -1,143 +0,0 @@
from django.conf import settings
from django.db import models
from django.contrib.auth.models import User, Group
from rest_framework.reverse import reverse
from access.fields import (
AutoCreatedField,
AutoLastModifiedField
)
from access.models.organization import Organization
from access.models.team import Team
from core.lib.feature_not_used import FeatureNotUsed
from core.mixin.history_save import SaveHistory
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'
]
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 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

View File

@ -1,254 +0,0 @@
# from django.conf import settings
from django.db import models
# from django.contrib.auth.models import User, Group
from rest_framework.reverse import reverse
# from .fields import *
from access.models.organization import Organization
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()) = []
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 and self.model.is_global is not None:
if len(user_organizations) > 0 and not user.is_superuser:
if getattr(self.model, 'is_global', False) is True:
return super().get_queryset().filter(
models.Q(organization__in=user_organizations)
|
models.Q(is_global = True)
)
else:
return super().get_queryset().filter(
models.Q(organization__in=user_organizations)
)
return super().get_queryset()
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(
Organization,
blank = False,
help_text = 'Organization this belongs to',
null = False,
on_delete = models.CASCADE,
related_name = '+',
validators = [validatate_organization_exists],
verbose_name = 'Organization'
)
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) -> Organization:
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`.
"""
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 !='appsettingshistory' # App Settings for
):
raise centurion_exceptions.ValidationError(
detail = {
'organization': 'Organization is required'
},
code = 'required'
)
super().save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)

View File

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

View File

@ -1,48 +0,0 @@
from rest_framework import serializers
from access.models.organization_notes import OrganizationNotes
from api.serializers import common
from app.serializers.user import UserBaseSerializer
from core.serializers.model_notes import (
ModelNotes,
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

View File

@ -1,48 +0,0 @@
from rest_framework import serializers
from access.models.team_notes import TeamNotes
from api.serializers import common
from app.serializers.user import UserBaseSerializer
from core.serializers.model_notes import (
ModelNotes,
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

View File

@ -1,103 +0,0 @@
from rest_framework.reverse import reverse
from rest_framework import serializers
from access.models.team_user import TeamUsers
from api.serializers import common
from app.serializers.user import UserBaseSerializer
class TeamUserBaseSerializer(serializers.ModelSerializer):
display_name = serializers.SerializerMethodField('get_display_name')
def get_display_name(self, item) -> str:
return str( item )
url = serializers.SerializerMethodField('get_url')
def get_url(self, item) -> str:
return item.get_url( request = self.context['view'].request )
class Meta:
model = TeamUsers
fields = [
'id',
'display_name',
'url',
]
read_only_fields = [
'id',
'display_name',
'url',
]
class TeamUserModelSerializer(
common.CommonModelSerializer,
TeamUserBaseSerializer
):
_urls = serializers.SerializerMethodField('get_url')
def get_url(self, item) -> dict:
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)

View File

@ -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 OrganizationBaseSerializer
from app.serializers.permission import Permission, PermissionBaseSerializer
from core import fields as centurion_field
class TeamBaseSerializer(serializers.ModelSerializer):
display_name = serializers.SerializerMethodField('get_display_name')
def get_display_name(self, item) -> str:
return str( item )
url = serializers.SerializerMethodField('get_url')
def get_url(self, item) -> str:
return item.get_url( request = self.context['view'].request )
class Meta:
model = Team
fields = [
'id',
'display_name',
'team_name',
'url',
]
read_only_fields = [
'id',
'display_name',
'team_name',
'url',
]
class TeamModelSerializer(
common.CommonModelSerializer,
TeamBaseSerializer
):
_urls = serializers.SerializerMethodField('get_url')
def get_url(self, item) -> dict:
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 = OrganizationBaseSerializer(many=False, read_only=True)
permissions = PermissionBaseSerializer(many = True)

View File

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

View File

@ -1,32 +0,0 @@
from django.test import TestCase
from access.models.organization_history import Organization, OrganizationHistory
from core.tests.abstract.test_functional_history import HistoryEntriesCommon
class History(
HistoryEntriesCommon,
TestCase,
):
model = Organization
history_model = OrganizationHistory
@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()

View File

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

View File

@ -1,318 +0,0 @@
import pytest
import unittest
import requests
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser, Permission, User
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import reverse
from django.test import Client, TestCase
from access.models.organization import 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
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 = 'organization'

View File

@ -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

View File

@ -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.organization import 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()

View File

@ -1,37 +0,0 @@
from django.test import TestCase
from access.models.team_history import Team, TeamHistory
from core.tests.abstract.test_functional_history import HistoryEntriesCommon
class History(
HistoryEntriesCommon,
TestCase,
):
model = Team
history_model = TeamHistory
@classmethod
def setUpTestData(self):
super().setUpTestData()
self.field_name = 'team_name'
self.obj = self.model.objects.create(
organization = self.organization,
# name = self.field_value_original,
team_name = self.field_value_original
)
self.obj_delete = self.model.objects.create(
organization = self.organization,
name = self.field_value_delete,
)
self.call_the_banners()

View File

@ -1,314 +0,0 @@
import pytest
import unittest
import requests
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser, Permission, User
from django.contrib.contenttypes.models import ContentType
from django.test import Client, TestCase
from rest_framework.reverse import reverse
from access.models.organization import Organization
from access.models.team import Team
from access.models.team_user import TeamUsers
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
class ViewSetBase:
model = Team
app_namespace = 'API'
url_name = '_api_v2_organization_team'
change_data = {'name': 'device'}
delete_data = {'device': 'device'}
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create an organization for user and item
. create an organization that is different to item
2. Create a team
3. create teams with each permission: view, add, change, delete
4. create a user per team
"""
organization = Organization.objects.create(name='test_org')
self.organization = organization
different_organization = Organization.objects.create(name='test_different_organization')
self.different_organization = different_organization
self.item = self.model.objects.create(
organization=organization,
name = 'teamone'
)
self.other_org_item = self.model.objects.create(
organization=different_organization,
name = 'teamtwo'
)
self.url_kwargs = {'organization_id': self.organization.id}
self.url_view_kwargs = {'organization_id': self.organization.id, 'pk': self.item.id}
self.add_data = {'team_name': 'team_post'}
view_permissions = Permission.objects.get(
codename = 'view_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
view_team = Team.objects.create(
team_name = 'view_team',
organization = organization,
)
view_team.permissions.set([view_permissions])
add_permissions = Permission.objects.get(
codename = 'add_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
add_team = Team.objects.create(
team_name = 'add_team',
organization = organization,
)
add_team.permissions.set([add_permissions])
change_permissions = Permission.objects.get(
codename = 'change_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
change_team = Team.objects.create(
team_name = 'change_team',
organization = organization,
)
change_team.permissions.set([change_permissions])
delete_permissions = Permission.objects.get(
codename = 'delete_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
delete_team = Team.objects.create(
team_name = 'delete_team',
organization = organization,
)
delete_team.permissions.set([delete_permissions])
self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password")
self.view_user = User.objects.create_user(username="test_user_view", password="password")
teamuser = TeamUsers.objects.create(
team = view_team,
user = self.view_user
)
self.add_user = User.objects.create_user(username="test_user_add", password="password")
teamuser = TeamUsers.objects.create(
team = add_team,
user = self.add_user
)
self.change_user = User.objects.create_user(username="test_user_change", password="password")
teamuser = TeamUsers.objects.create(
team = change_team,
user = self.change_user
)
self.delete_user = User.objects.create_user(username="test_user_delete", password="password")
teamuser = TeamUsers.objects.create(
team = delete_team,
user = self.delete_user
)
self.different_organization_user = User.objects.create_user(username="test_different_organization_user", password="password")
different_organization_team = Team.objects.create(
team_name = 'different_organization_team',
organization = different_organization,
)
different_organization_team.permissions.set([
view_permissions,
add_permissions,
change_permissions,
delete_permissions,
])
TeamUsers.objects.create(
team = different_organization_team,
user = self.different_organization_user
)
class TeamPermissionsAPI(
ViewSetBase,
APIPermissions,
TestCase,
):
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 TeamViewSet(
ViewSetBase,
SerializersTestCases,
TestCase,
):
pass
class TeamMetadata(
ViewSetBase,
MetadataAttributesFunctional,
TestCase,
):
def test_method_options_request_detail_data_has_key_urls_back(self):
"""Test HTTP/Options Method
Ensure the request data returned has key `urls.back`
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-detail',
kwargs=self.url_view_kwargs
),
content_type='application/json'
)
assert 'back' in response.data['urls']
def test_method_options_request_detail_data_key_urls_back_is_str(self):
"""Test HTTP/Options Method
Ensure the request data key `urls.back` is str
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-detail',
kwargs=self.url_view_kwargs
),
content_type='application/json'
)
assert type(response.data['urls']['back']) is str
def test_method_options_request_list_data_has_key_urls_return_url(self):
"""Test HTTP/Options Method
Ensure the request data returned has key `urls.return_url`
"""
client = Client()
client.force_login(self.view_user)
if self.url_kwargs:
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
response = client.options( url, content_type='application/json' )
assert 'return_url' in response.data['urls']
def test_method_options_request_list_data_key_urls_return_url_is_str(self):
"""Test HTTP/Options Method
Ensure the request data key `urls.return_url` is str
"""
client = Client()
client.force_login(self.view_user)
if self.url_kwargs:
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
response = client.options( url, content_type='application/json' )
assert type(response.data['urls']['return_url']) is str

View File

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

View File

@ -1,127 +0,0 @@
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from access.viewsets.team_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_team_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,
name = 'note model'
),
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.different_organization,
name = 'note model'
),
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.viewset.model.model.field.related_model.objects.create(
organization = self.global_organization,
name = 'note model global_organization'
),
created_by = self.view_user,
modified_by = self.view_user,
)
self.url_kwargs = {
'organization_id': self.organization.id,
'model_id': self.item.model.pk,
}
self.url_view_kwargs = {
'organization_id': self.organization.id,
'model_id': self.item.model.pk,
'pk': self.item.id
}
class TeamModelNotesPermissionsAPI(
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 TeamModelNotesSerializer(
ViewSetBase,
ModelNotesSerializer,
TestCase,
):
pass
class TeamModelNotesMetadata(
ViewSetBase,
ModelNotesMetadata,
TestCase,
):
pass

View File

@ -1,56 +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.team import Team
from access.models.team_notes import TeamNotes
class TeamNotesAPI(
ModelNotesNotesAPIFields,
TestCase,
):
model = TeamNotes
view_name: str = '_api_v2_team_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 = Team.objects.create(
organization = self.organization,
name = 'note model'
),
created_by = self.view_user,
modified_by = self.view_user,
)
self.url_view_kwargs = {
'organization_id': self.organization.pk,
'model_id': self.item.model.pk,
'pk': self.item.pk
}
self.make_request()

View File

@ -1,326 +0,0 @@
import pytest
import unittest
import requests
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser, Permission, User
from django.contrib.contenttypes.models import ContentType
from django.test import Client, TestCase
from rest_framework.reverse import reverse
from access.models.organization import Organization
from access.models.team import Team
from access.models.team_user import TeamUsers
from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional
from api.tests.abstract.api_permissions_viewset import APIPermissions
from api.tests.abstract.api_serializer_viewset import SerializersTestCases
class ViewSetBase:
model = TeamUsers
app_namespace = 'API'
url_name = '_api_v2_organization_team_user'
change_data = {'name': 'device'}
delete_data = {'device': 'device'}
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create an organization for user and item
2. Create a team
3. create teams with each permission: view, add, change, delete
4. create a user per team
"""
organization = Organization.objects.create(name='test_org')
self.organization = organization
different_organization = Organization.objects.create(name='test_different_organization')
self.different_organization = different_organization
view_permissions = Permission.objects.get(
codename = 'view_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
view_team = Team.objects.create(
team_name = 'view_team',
organization = organization,
)
view_team_b = Team.objects.create(
team_name = 'view_team',
organization = different_organization,
)
view_team.permissions.set([view_permissions])
view_team_b.permissions.set([view_permissions])
add_permissions = Permission.objects.get(
codename = 'add_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
add_team = Team.objects.create(
team_name = 'add_team',
organization = organization,
)
add_team.permissions.set([add_permissions])
change_permissions = Permission.objects.get(
codename = 'change_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
change_team = Team.objects.create(
team_name = 'change_team',
organization = organization,
)
change_team.permissions.set([change_permissions])
delete_permissions = Permission.objects.get(
codename = 'delete_' + self.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = self.model._meta.app_label,
model = self.model._meta.model_name,
)
)
delete_team = Team.objects.create(
team_name = 'delete_team',
organization = organization,
)
delete_team.permissions.set([delete_permissions])
self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password")
self.view_user = User.objects.create_user(username="test_user_view", password="password")
self.view_user_b = User.objects.create_user(username="test_user_view_b", password="password")
self.item = TeamUsers.objects.create(
team = view_team,
user = self.view_user
)
self.other_org_item = TeamUsers.objects.create(
team = view_team_b,
user = self.view_user_b
)
self.url_view_kwargs = {'organization_id': self.organization.id, 'team_id': view_team.id, 'pk': self.item.id}
self.url_kwargs = {'organization_id': self.organization.id, 'team_id': view_team.id}
random_user = User.objects.create_user(username="random_user", password="password")
self.add_data = {'user': random_user.id}
self.add_user = User.objects.create_user(username="test_user_add", password="password")
teamuser = TeamUsers.objects.create(
team = add_team,
user = self.add_user
)
self.change_user = User.objects.create_user(username="test_user_change", password="password")
teamuser = TeamUsers.objects.create(
team = change_team,
user = self.change_user
)
self.delete_user = User.objects.create_user(username="test_user_delete", password="password")
teamuser = TeamUsers.objects.create(
team = delete_team,
user = self.delete_user
)
self.different_organization_user = User.objects.create_user(username="test_different_organization_user", password="password")
different_organization_team = Team.objects.create(
team_name = 'different_organization_team',
organization = different_organization,
)
different_organization_team.permissions.set([
view_permissions,
add_permissions,
change_permissions,
delete_permissions,
])
TeamUsers.objects.create(
team = different_organization_team,
user = self.different_organization_user
)
class TeamUserPermissionsAPI(
ViewSetBase,
APIPermissions,
TestCase
):
def test_returned_results_only_user_orgs(self):
"""This test is not applicable for team_user as users are not tenancy objects
"""
pass
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 TeamUserViewSet(
ViewSetBase,
SerializersTestCases,
TestCase
):
pass
class TeamUserMetadata(
ViewSetBase,
MetadataAttributesFunctional,
TestCase,
):
def test_method_options_request_detail_data_has_key_urls_back(self):
"""Test HTTP/Options Method
Ensure the request data returned has key `urls.back`
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-detail',
kwargs=self.url_view_kwargs
),
content_type='application/json'
)
assert 'back' in response.data['urls']
def test_method_options_request_detail_data_key_urls_back_is_str(self):
"""Test HTTP/Options Method
Ensure the request data key `urls.back` is str
"""
client = Client()
client.force_login(self.view_user)
response = client.options(
reverse(
self.app_namespace + ':' + self.url_name + '-detail',
kwargs=self.url_view_kwargs
),
content_type='application/json'
)
assert type(response.data['urls']['back']) is str
def test_method_options_request_list_data_has_key_urls_return_url(self):
"""Test HTTP/Options Method
Ensure the request data returned has key `urls.return_url`
"""
client = Client()
client.force_login(self.view_user)
if self.url_kwargs:
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
response = client.options( url, content_type='application/json' )
assert 'return_url' in response.data['urls']
def test_method_options_request_list_data_key_urls_return_url_is_str(self):
"""Test HTTP/Options Method
Ensure the request data key `urls.return_url` is str
"""
client = Client()
client.force_login(self.view_user)
if self.url_kwargs:
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-list')
response = client.options( url, content_type='application/json' )
assert type(response.data['urls']['return_url']) is str

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -2,17 +2,14 @@ import pytest
import unittest
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser, Permission, User
from django.contrib.auth.models import AnonymousUser, User
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import reverse
from django.test import Client, TestCase
from rest_framework.relations import Hyperlink
from access.models.organization import Organization
from access.models.team import Team
from access.models.team_user import TeamUsers
from access.models import Organization, Team, TeamUsers, Permission
@ -20,7 +17,7 @@ class OrganizationAPI(TestCase):
model = Organization
app_namespace = 'v1'
app_namespace = 'API'
url_name = '_api_organization'

View File

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

View File

@ -1,6 +1,6 @@
# from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser, Permission, User
from django.contrib.auth.models import AnonymousUser, User
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import reverse
from django.test import TestCase, Client
@ -9,9 +9,7 @@ import pytest
import unittest
import requests
from access.models.organization import Organization
from access.models.team import Team
from access.models.team_user import TeamUsers
from access.models import Organization, Team, TeamUsers, Permission
from access.tests.abstract.model_permissions_organization_manager import OrganizationManagerModelPermissionChange, OrganizationManagerModelPermissionView
from app.tests.abstract.model_permissions import ModelPermissionsView, ModelPermissionsChange

View File

@ -2,14 +2,12 @@ import pytest
import unittest
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser, Permission, User
from django.contrib.auth.models import AnonymousUser, User
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import reverse
from django.test import Client, TestCase
from access.models.organization import Organization
from access.models.team import Team
from access.models.team_user import TeamUsers
from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions import APIPermissionChange, APIPermissionView
@ -22,7 +20,7 @@ class OrganizationPermissionsAPI(TestCase, APIPermissionChange, APIPermissionVie
model_name = 'organization'
app_label = 'access'
app_namespace = 'v1'
app_namespace = 'API'
url_name = '_api_organization'

View File

@ -6,8 +6,5 @@ import unittest
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from access.models.organization import Organization
from access.models.team import Team
from access.models import Organization, Team

View File

@ -0,0 +1,187 @@
import pytest
import unittest
import requests
from django.test import TestCase, Client
from access.models import Organization
from core.models.history import History
from access.models import Organization
class OrganizationHistory(TestCase):
model = Organization
model_name = 'organization'
@classmethod
def setUpTestData(self):
""" Setup Test """
organization = Organization.objects.create(name='test_org')
self.organization = organization
self.item_create = self.model.objects.create(
name = 'test_item_' + self.model_name,
)
self.history_create = History.objects.get(
action = History.Actions.ADD[0],
item_pk = self.item_create.pk,
item_class = self.model._meta.model_name,
)
self.item_change = self.item_create
self.item_change.name = 'test_item_' + self.model_name + '_changed'
self.item_change.save()
self.history_change = History.objects.get(
action = History.Actions.UPDATE[0],
item_pk = self.item_change.pk,
item_class = self.model._meta.model_name,
)
self.item_delete = self.model.objects.create(
name = 'test_item_delete_' + self.model_name,
)
self.item_delete.delete()
self.history_delete = History.objects.filter(
item_pk = self.item_delete.pk,
item_class = self.model._meta.model_name,
)
self.history_delete_children = History.objects.filter(
item_parent_pk = self.item_delete.pk,
item_parent_class = self.model._meta.model_name,
)
def test_history_entry_item_add_field_action(self):
""" Ensure action is "add" for item creation """
history = self.history_create.__dict__
assert history['action'] == int(History.Actions.ADD[0])
# assert type(history['action']) is int
@pytest.mark.skip(reason="to be written")
def test_history_entry_item_add_field_after(self):
""" Ensure after field contains correct value """
history = self.history_create.__dict__
assert history['after'] == str('{}')
# assert type(history['after']) is str
def test_history_entry_item_add_field_before(self):
""" Ensure before field is an empty JSON string for create """
history = self.history_create.__dict__
assert history['before'] == str('{}')
# assert type(history['before']) is str
def test_history_entry_item_add_field_item_pk(self):
""" Ensure history entry field item_pk is the created items pk """
history = self.history_create.__dict__
assert history['item_pk'] == self.item_create.pk
# assert type(history['item_pk']) is int
def test_history_entry_item_add_field_item_class(self):
""" Ensure history entry field item_class is the model name """
history = self.history_create.__dict__
assert history['item_class'] == self.model._meta.model_name
# assert type(history['item_class']) is str
################################## Change ##################################
def test_history_entry_item_change_field_action(self):
""" Ensure action is "add" for item creation """
history = self.history_change.__dict__
assert history['action'] == int(History.Actions.UPDATE[0])
# assert type(history['action']) is int
def test_history_entry_item_change_field_after(self):
""" Ensure after field contains correct value """
history = self.history_change.__dict__
assert history['after'] == str('{"name": "test_item_' + self.model_name + '_changed"}')
# assert type(history['after']) is str
@pytest.mark.skip(reason="to be written")
def test_history_entry_item_change_field_before(self):
""" Ensure before field is an empty JSON string for create """
history = self.history_change.__dict__
assert history['before'] == str('{}')
# assert type(history['before']) is str
def test_history_entry_item_change_field_item_pk(self):
""" Ensure history entry field item_pk is the created items pk """
history = self.history_change.__dict__
assert history['item_pk'] == self.item_create.pk
# assert type(history['item_pk']) is int
def test_history_entry_item_change_field_item_class(self):
""" Ensure history entry field item_class is the model name """
history = self.history_change.__dict__
assert history['item_class'] == self.model._meta.model_name
# assert type(history['item_class']) is str
################################## Delete ##################################
def test_device_history_entry_delete(self):
""" When an item is deleted, it's history entries must be removed """
assert self.history_delete.exists() is False
def test_device_history_entry_children_delete(self):
""" When an item is deleted, it's history entries must be removed """
assert self.history_delete_children.exists() is False

View File

@ -0,0 +1,165 @@
# from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser, User
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import reverse
from django.test import TestCase, Client
import pytest
import unittest
import requests
from access.models import Organization, Team, TeamUsers, Permission
from core.models.history import History
class OrganizationHistoryPermissions(TestCase):
item_model = Organization
model = History
model_name = 'history'
app_label = 'core'
namespace = ''
name_view = '_history'
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create an organization for user and item
2. create an organization that is different to item
3. Create a device
4. Add history device history entry as item
5. create a user
6. create user in different organization (with the required permission)
"""
organization = Organization.objects.create(name='test_org')
self.organization = organization
different_organization = Organization.objects.create(name='test_different_organization')
self.item = self.organization
self.history_model_name = self.item._meta.model_name
self.history = self.model.objects.get(
item_pk = self.item.id,
item_class = self.item._meta.model_name,
action = self.model.Actions.ADD,
)
view_permissions = Permission.objects.get(
codename = 'view_' + self.model_name,
content_type = ContentType.objects.get(
app_label = self.app_label,
model = self.model_name,
)
)
view_team = Team.objects.create(
team_name = 'view_team',
organization = organization,
)
view_team.permissions.set([view_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.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,
])
TeamUsers.objects.create(
team = different_organization_team,
user = self.different_organization_user
)
def test_auth_view_history_user_anon_denied(self):
""" Check correct permission for view
Attempt to view as anon user
"""
client = Client()
url = reverse(self.namespace + self.name_view, kwargs={'model_name': self.history_model_name, 'model_pk': self.item.id})
response = client.get(url)
assert response.status_code == 302 and response.url.startswith('/account/login')
def test_auth_view_history_no_permission_denied(self):
""" Check correct permission for view
Attempt to view with user missing permission
"""
client = Client()
url = reverse(self.namespace + self.name_view, kwargs={'model_name': self.history_model_name, 'model_pk': self.item.id})
client.force_login(self.no_permissions_user)
response = client.get(url)
assert response.status_code == 403
def test_auth_view_history_different_organizaiton_denied(self):
""" Check correct permission for view
Attempt to view with user from different organization
"""
client = Client()
url = reverse(self.namespace + self.name_view, kwargs={'model_name': self.history_model_name, 'model_pk': self.item.id})
client.force_login(self.different_organization_user)
response = client.get(url)
assert response.status_code == 403
def test_auth_view_history_has_permission(self):
""" Check correct permission for view
Attempt to view as user with view permission
"""
client = Client()
url = reverse(self.namespace + self.name_view, kwargs={'model_name': self.history_model_name, 'model_pk': self.item.id})
client.force_login(self.view_user)
response = client.get(url)
assert response.status_code == 200

View File

@ -1,66 +0,0 @@
import pytest
from django.contrib.auth.models import User
from django.test import Client, TestCase
from rest_framework.reverse import reverse
from access.models.organization import Organization
from access.viewsets.organization import ViewSet
from api.tests.abstract.viewsets import ViewSetModel
class ViewsetCommon(
ViewSetModel,
):
viewset = ViewSet
route_name = 'API:_api_v2_organization'
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create an organization
3. create super user
"""
organization = Organization.objects.create(name='test_org')
self.organization = organization
self.view_user = User.objects.create_user(username="test_view_user", password="password", is_superuser=True)
self.kwargs = {}
class OrganizationViewsetList(
ViewsetCommon,
TestCase,
):
@classmethod
def setUpTestData(self):
"""Setup Test
1. make list request
"""
super().setUpTestData()
client = Client()
url = reverse(
self.route_name + '-list'
)
client.force_login(self.view_user)
self.http_options_response_list = client.options(url)

View File

@ -1,40 +0,0 @@
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from core.tests.abstract.test_unit_model_history_api_v2 import PrimaryModelHistoryAPI
from access.models.organization_history import Organization, OrganizationHistory
class ModelHistoryAPI(
PrimaryModelHistoryAPI,
TestCase,
):
audit_model = Organization
model = OrganizationHistory
@classmethod
def setUpTestData(self):
super().setUpTestData()
self.audit_object = self.organization
self.history_entry = self.model.objects.create(
organization = self.audit_object,
action = self.model.Actions.ADD,
user = self.view_user,
before = {},
after = {},
content_type = ContentType.objects.get(
app_label = self.audit_object._meta.app_label,
model = self.audit_object._meta.model_name,
),
model = self.audit_object,
)
self.make_request()

View File

@ -1,36 +0,0 @@
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from core.tests.abstract.test_unit_model_notes_model import ModelNotesModel
from access.models.organization_notes import OrganizationNotes
class OrganizationNotesModel(
ModelNotesModel,
TestCase,
):
model = OrganizationNotes
@classmethod
def setUpTestData(self):
"""Setup Test"""
super().setUpTestData()
self.item = self.model.objects.create(
organization = self.organization,
content = 'a random comment for an exiting item',
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.model.model.field.related_model.objects.create(
name = 'note model existing item',
),
created_by = self.user,
)

View File

@ -1,57 +0,0 @@
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from core.tests.abstract.test_unit_model_notes_serializer import ModelNotesSerializerTestCases
from access.serializers.organization_notes import OrganizationNotes, OrganizationNoteModelSerializer
class OrganizationNotesSerializer(
ModelNotesSerializerTestCases,
TestCase,
):
model = OrganizationNotes
model_serializer = OrganizationNoteModelSerializer
@classmethod
def setUpTestData(self):
"""Setup Test"""
super().setUpTestData()
self.note_model = self.model.model.field.related_model.objects.create(
name = 'note model',
)
self.note_model_two = self.model.model.field.related_model.objects.create(
name = 'note model two',
)
self.item = self.model.objects.create(
organization = self.organization,
content = 'a random comment for an exiting item',
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.model.model.field.related_model.objects.create(
name = 'note model existing item',
),
created_by = self.user_two,
)
self.valid_data = {
'organization': self.organization_two.id,
'content': 'a random comment',
'content_type': self.content_type_two.id,
'model': self.note_model_two.id,
'created_by': self.user_two.id,
'modified_by': self.user_two.id,
}

View File

@ -1,72 +0,0 @@
import pytest
from django.contrib.auth.models import User
from django.test import Client, TestCase
from rest_framework.reverse import reverse
from access.models.organization import Organization
from access.viewsets.team_notes import ViewSet
from api.tests.abstract.viewsets import ViewSetModel
class ViewsetCommon(
ViewSetModel,
):
viewset = ViewSet
route_name = 'v2:_api_v2_organization_note'
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create an organization
3. create super user
"""
organization = Organization.objects.create(name='test_org')
self.organization = organization
self.view_user = User.objects.create_user(username="test_view_user", password="password", is_superuser=True)
class OrganizationNotesViewsetList(
ViewsetCommon,
TestCase,
):
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create object that is to be tested against
2. add kwargs
3. make list request
"""
super().setUpTestData()
self.kwargs = {
'model_id': self.organization.id,
}
client = Client()
url = reverse(
self.route_name + '-list',
kwargs = self.kwargs
)
client.force_login(self.view_user)
self.http_options_response_list = client.options(url)

View File

@ -1,12 +1,9 @@
import pytest
import unittest
from django.contrib.auth.models import Permission
from django.test import TestCase, Client
from access.models.organization import Organization
from access.models.team import Team
from access.models.team_user import TeamUsers
from access.models import Organization, Team, TeamUsers, Permission
from app.tests.abstract.models import TenancyModel
@ -37,22 +34,22 @@ class TeamModel(
)
# def test_model_has_property_parent_object(self):
# """ Check if model contains 'parent_object'
def test_model_has_property_parent_object(self):
""" Check if model contains 'parent_object'
# This is a required property for all models that have a parent
# """
This is a required property for all models that have a parent
"""
# assert hasattr(self.model, 'parent_object')
assert hasattr(self.model, 'parent_object')
# def test_model_property_parent_object_returns_object(self):
# """ Check if model contains 'parent_object'
def test_model_property_parent_object_returns_object(self):
""" Check if model contains 'parent_object'
# This is a required property for all models that have a parent
# """
This is a required property for all models that have a parent
"""
# assert self.item.parent_object is self.parent_item
assert self.item.parent_object is self.parent_item
@pytest.mark.skip(reason="to be written")
@ -70,74 +67,4 @@ class TeamModel(
@pytest.mark.skip(reason="uses Django group manager")
def test_model_class_tenancy_manager_function_get_queryset_called(self):
pass
def test_model_fields_parameter_not_empty_help_text(self):
"""Test Field called with Parameter
This is a custom test of a test derived of the samae name. It's required
as the team model extends the Group model.
During field creation, paramater `help_text` must not be `None` or empty ('')
"""
group_mode_fields_to_ignore: list = [
'id',
'name',
'group_ptr_id'
]
fields_have_test_value: bool = True
for field in self.model._meta.fields:
if field.attname in group_mode_fields_to_ignore:
continue
print(f'Checking field {field.attname} is not empty')
if (
field.help_text is None
or field.help_text == ''
):
print(f' Failure on field {field.attname}')
fields_have_test_value = False
assert fields_have_test_value
def test_model_fields_parameter_type_verbose_name(self):
"""Test Field called with Parameter
This is a custom test of a test derived of the samae name. It's required
as the team model extends the Group model.
During field creation, paramater `verbose_name` must be of type str
"""
group_mode_fields_to_ignore: list = [
'name',
]
fields_have_test_value: bool = True
for field in self.model._meta.fields:
if field.attname in group_mode_fields_to_ignore:
continue
print(f'Checking field {field.attname} is of type str')
if not type(field.verbose_name) is str:
print(f' Failure on field {field.attname}')
fields_have_test_value = False
assert fields_have_test_value
pass

View File

@ -4,16 +4,14 @@ import requests
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser, Permission, User
from django.contrib.auth.models import AnonymousUser, User
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import reverse
from django.test import Client, TestCase
from rest_framework.relations import Hyperlink
from access.models.organization import Organization
from access.models.team import Team
from access.models.team_user import TeamUsers
from access.models import Organization, Team, TeamUsers, Permission
# from api.tests.abstract.api_permissions import APIPermissions
@ -23,7 +21,7 @@ class TeamAPI(TestCase):
model = Team
app_namespace = 'v1'
app_namespace = 'API'
url_name = '_api_team'

View File

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

View File

@ -0,0 +1,79 @@
import pytest
import unittest
import requests
from django.test import TestCase, Client
from access.models import Organization
from core.models.history import History
from core.tests.abstract.history_entry import HistoryEntry
from core.tests.abstract.history_entry_child_model import HistoryEntryChildItem
from access.models import Team
from django.contrib.auth.models import Group
class TeamHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
model = Team
@classmethod
def setUpTestData(self):
""" Setup Test """
organization = Organization.objects.create(name='test_org')
self.organization = organization
self.item_parent = organization
self.item_create = self.model.objects.create(
team_name = 'test_item_' + self.model._meta.model_name,
organization = self.organization
)
self.history_create = History.objects.get(
action = History.Actions.ADD[0],
item_pk = self.item_create.pk,
item_class = self.model._meta.model_name,
)
self.item_change = self.item_create
self.item_change.team_name = 'test_item_' + self.model._meta.model_name + '_changed'
self.item_change.save()
self.field_after_expected_value = '{"name": "test_org_' + self.item_change.team_name + '", "team_name": "' + self.item_change.team_name + '"}'
self.history_change = History.objects.get(
action = History.Actions.UPDATE[0],
item_pk = self.item_change.pk,
item_class = self.model._meta.model_name,
)
debug = Group.objects.all()
self.item_delete = self.model.objects.create(
team_name = 'test_item_delete_' + self.model._meta.model_name,
organization = self.organization
)
self.deleted_pk = self.item_delete.pk
self.item_delete.delete()
self.history_delete = History.objects.get(
action = History.Actions.DELETE[0],
item_pk = self.deleted_pk,
item_class = self.model._meta.model_name,
)
self.history_delete_children = History.objects.filter(
item_parent_pk = self.deleted_pk,
item_parent_class = self.item_parent._meta.model_name,
)

View File

@ -0,0 +1,168 @@
# from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser, User
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import reverse
from django.test import TestCase, Client
import pytest
import unittest
import requests
from access.models import Organization, Team, TeamUsers, Permission
from core.models.history import History
class TeamHistoryPermissions(TestCase):
item_model = Team
model = History
model_name = 'history'
app_label = 'core'
namespace = ''
name_view = '_history'
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create an organization for user and item
2. create an organization that is different to item
3. Create a device
4. Add history device history entry as item
5. create a user
6. create user in different organization (with the required permission)
"""
organization = Organization.objects.create(name='test_org')
self.organization = organization
different_organization = Organization.objects.create(name='test_different_organization')
self.item = self.item_model.objects.create(
organization=organization,
name = 'deviceone'
)
self.history_model_name = self.item._meta.model_name
self.history = self.model.objects.get(
item_pk = self.item.id,
item_class = self.item._meta.model_name,
action = self.model.Actions.ADD,
)
view_permissions = Permission.objects.get(
codename = 'view_' + self.model_name,
content_type = ContentType.objects.get(
app_label = self.app_label,
model = self.model_name,
)
)
view_team = Team.objects.create(
team_name = 'view_team',
organization = organization,
)
view_team.permissions.set([view_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.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,
])
TeamUsers.objects.create(
team = different_organization_team,
user = self.different_organization_user
)
def test_auth_view_history_user_anon_denied(self):
""" Check correct permission for view
Attempt to view as anon user
"""
client = Client()
url = reverse(self.namespace + self.name_view, kwargs={'model_name': self.history_model_name, 'model_pk': self.item.id})
response = client.get(url)
assert response.status_code == 302 and response.url.startswith('/account/login')
def test_auth_view_history_no_permission_denied(self):
""" Check correct permission for view
Attempt to view with user missing permission
"""
client = Client()
url = reverse(self.namespace + self.name_view, kwargs={'model_name': self.history_model_name, 'model_pk': self.item.id})
client.force_login(self.no_permissions_user)
response = client.get(url)
assert response.status_code == 403
def test_auth_view_history_different_organizaiton_denied(self):
""" Check correct permission for view
Attempt to view with user from different organization
"""
client = Client()
url = reverse(self.namespace + self.name_view, kwargs={'model_name': self.history_model_name, 'model_pk': self.item.id})
client.force_login(self.different_organization_user)
response = client.get(url)
assert response.status_code == 403
def test_auth_view_history_has_permission(self):
""" Check correct permission for view
Attempt to view as user with view permission
"""
client = Client()
url = reverse(self.namespace + self.name_view, kwargs={'model_name': self.history_model_name, 'model_pk': self.item.id})
client.force_login(self.view_user)
response = client.get(url)
assert response.status_code == 200

View File

@ -1,6 +1,6 @@
# from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser, Permission, User
from django.contrib.auth.models import AnonymousUser, User
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import reverse
from django.test import TestCase, Client
@ -9,9 +9,7 @@ import pytest
import unittest
import requests
from access.models.organization import Organization
from access.models.team import Team
from access.models.team_user import TeamUsers
from access.models import Organization, Team, TeamUsers, Permission
from access.tests.abstract.model_permissions_organization_manager import OrganizationManagerModelPermissions
from app.tests.abstract.model_permissions import ModelPermissions

View File

@ -4,13 +4,11 @@ import requests
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser, Permission, User
from django.contrib.auth.models import AnonymousUser, User
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from access.models.organization import Organization
from access.models.team import Team
from access.models.team_user import TeamUsers
from access.models import Organization, Team, TeamUsers, Permission
from api.tests.abstract.api_permissions import APIPermissions
@ -20,7 +18,7 @@ class TeamPermissionsAPI(TestCase, APIPermissions):
model = Team
app_namespace = 'v1'
app_namespace = 'API'
url_name = '_api_team'

View File

@ -1,43 +0,0 @@
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from core.tests.abstract.test_unit_model_history_api_v2 import PrimaryModelHistoryAPI
from access.models.team_history import Team, TeamHistory
class ModelHistoryAPI(
PrimaryModelHistoryAPI,
TestCase,
):
audit_model = Team
model = TeamHistory
@classmethod
def setUpTestData(self):
super().setUpTestData()
self.audit_object = self.audit_model.objects.create(
organization = self.organization,
name = 'one',
)
self.history_entry = self.model.objects.create(
organization = self.audit_object.organization,
action = self.model.Actions.ADD,
user = self.view_user,
before = {},
after = {},
content_type = ContentType.objects.get(
app_label = self.audit_object._meta.app_label,
model = self.audit_object._meta.model_name,
),
model = self.audit_object,
)
self.make_request()

View File

@ -1,67 +0,0 @@
import pytest
from django.contrib.auth.models import User
from django.test import Client, TestCase
from rest_framework.reverse import reverse
from access.models.organization import Organization
from access.viewsets.team import ViewSet
from api.tests.abstract.viewsets import ViewSetModel
class ViewsetCommon(
ViewSetModel,
):
viewset = ViewSet
route_name = 'API:_api_v2_organization_team'
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create an organization
3. create super user
"""
organization = Organization.objects.create(name='test_org')
self.organization = organization
self.view_user = User.objects.create_user(username="test_view_user", password="password", is_superuser=True)
self.kwargs = { 'organization_id': self.organization.id }
class TeamViewsetList(
ViewsetCommon,
TestCase,
):
@classmethod
def setUpTestData(self):
"""Setup Test
1. make list request
"""
super().setUpTestData()
client = Client()
url = reverse(
self.route_name + '-list',
kwargs = self.kwargs
)
client.force_login(self.view_user)
self.http_options_response_list = client.options(url)

View File

@ -1,37 +0,0 @@
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from core.tests.abstract.test_unit_model_notes_model import ModelNotesModel
from access.models.team_notes import TeamNotes
class TeamNotesModel(
ModelNotesModel,
TestCase,
):
model = TeamNotes
@classmethod
def setUpTestData(self):
"""Setup Test"""
super().setUpTestData()
self.item = self.model.objects.create(
organization = self.organization,
content = 'a random comment for an exiting item',
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.model.model.field.related_model.objects.create(
organization = self.organization,
name = 'note model existing item',
),
created_by = self.user,
)

View File

@ -1,60 +0,0 @@
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from core.tests.abstract.test_unit_model_notes_serializer import ModelNotesSerializerTestCases
from access.serializers.team_notes import TeamNotes, TeamNoteModelSerializer
class TeamNotesSerializer(
ModelNotesSerializerTestCases,
TestCase,
):
model = TeamNotes
model_serializer = TeamNoteModelSerializer
@classmethod
def setUpTestData(self):
"""Setup Test"""
super().setUpTestData()
self.note_model = self.model.model.field.related_model.objects.create(
organization = self.organization,
team_name = 'note model',
)
self.note_model_two = self.model.model.field.related_model.objects.create(
organization = self.organization,
team_name = 'note model two',
)
self.item = self.model.objects.create(
organization = self.organization,
content = 'a random comment for an exiting item',
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.model.model.field.related_model.objects.create(
organization = self.organization,
team_name = 'note model existing item',
),
created_by = self.user_two,
)
self.valid_data = {
'organization': self.organization_two.id,
'content': 'a random comment',
'content_type': self.content_type_two.id,
'model': self.note_model_two.id,
'created_by': self.user_two.id,
'modified_by': self.user_two.id,
}

View File

@ -1,78 +0,0 @@
import pytest
from django.contrib.auth.models import User
from django.test import Client, TestCase
from rest_framework.reverse import reverse
from access.models.organization import Organization
from access.viewsets.team_notes import ViewSet
from api.tests.abstract.viewsets import ViewSetModel
class ViewsetCommon(
ViewSetModel,
):
viewset = ViewSet
route_name = 'v2:_api_v2_team_note'
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create an organization
3. create super user
"""
organization = Organization.objects.create(name='test_org')
self.organization = organization
self.view_user = User.objects.create_user(username="test_view_user", password="password", is_superuser=True)
class TeamNotesViewsetList(
ViewsetCommon,
TestCase,
):
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create object that is to be tested against
2. add kwargs
3. make list request
"""
super().setUpTestData()
self.note_model = self.viewset.model.model.field.related_model.objects.create(
organization = self.organization,
name = 'note model'
)
self.kwargs = {
'organization_id': self.organization.id,
'model_id': self.note_model.pk,
}
client = Client()
url = reverse(
self.route_name + '-list',
kwargs = self.kwargs
)
client.force_login(self.view_user)
self.http_options_response_list = client.options(url)

View File

@ -2,22 +2,13 @@ import pytest
import unittest
from django.test import TestCase, Client
from django.contrib.auth.models import Permission, User
from django.contrib.auth.models import User
from access.models.organization import Organization
from access.models.team import Team
from access.models.team_user import TeamUsers
from app.tests.abstract.models import BaseModel
from core.mixin.history_save import SaveHistory
from access.models import Organization, Team, TeamUsers, Permission
class TeamUsersModel(
TestCase,
BaseModel
):
class TeamUsersModel(TestCase):
model = TeamUsers
@ -63,14 +54,3 @@ class TeamUsersModel(
"""
assert self.item.parent_object == self.parent_item
@pytest.mark.skip( reason = 'TeamUsers is not a tenancy object' )
def test_class_inherits_tenancy_objecy(self):
""" Confirm class inheritence
TenancyObject must inherit TenancyObject
"""
pass

View File

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

View File

@ -0,0 +1,92 @@
import pytest
import unittest
import requests
from django.contrib.auth.models import User
from django.test import TestCase, Client
from access.models import Organization
from core.models.history import History
from core.tests.abstract.history_entry import HistoryEntry
from core.tests.abstract.history_entry_child_model import HistoryEntryChildItem
from access.models import Team, TeamUsers
class TeamUsersHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
model = TeamUsers
model_name = 'teamusers'
@classmethod
def setUpTestData(self):
""" Setup Test """
organization = Organization.objects.create(name='test_org')
self.organization = organization
self.item_parent = Team.objects.create(
team_name = 'test_item_' + self.model._meta.model_name,
organization = self.organization
)
self.user = User.objects.create(
username = 'test_item_' + self.model._meta.model_name,
password = 'a random password'
)
self.item_create = self.model.objects.create(
user = self.user,
team = self.item_parent
)
self.history_create = History.objects.get(
action = History.Actions.ADD[0],
item_pk = self.item_create.pk,
item_class = self.model._meta.model_name,
)
self.item_change = self.item_create
self.item_change.manager = True
self.item_change.save()
self.field_after_expected_value = '{"manager": true}'
self.history_change = History.objects.get(
action = History.Actions.UPDATE[0],
item_pk = self.item_change.pk,
item_class = self.model._meta.model_name,
)
self.user_delete = User.objects.create(
username = 'test_item_delete' + self.model._meta.model_name,
password = 'a random password'
)
self.item_delete = self.model.objects.create(
user = self.user_delete,
team = self.item_parent
)
self.deleted_pk = self.item_delete.pk
self.item_delete.delete()
self.history_delete = History.objects.get(
action = History.Actions.DELETE[0],
item_pk = self.deleted_pk,
item_class = self.model._meta.model_name,
)
self.history_delete_children = History.objects.filter(
item_parent_pk = self.deleted_pk,
item_parent_class = self.item_parent._meta.model_name,
)

View File

@ -6,9 +6,7 @@ import unittest
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from access.models.organization import Organization
from access.models.team import Team
from access.models import Organization, Team
# @pytest.fixture

View File

@ -1,6 +1,6 @@
# from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser, Permission, User
from django.contrib.auth.models import AnonymousUser, User
from django.contrib.contenttypes.models import ContentType
from django.shortcuts import reverse
from django.test import TestCase, Client
@ -9,10 +9,7 @@ import pytest
import unittest
import requests
from access.models.organization import Organization
from access.models.team import Team
from access.models.team_user import TeamUsers
from access.models import Organization, Team, TeamUsers, Permission
from access.tests.abstract.model_permissions_organization_manager import OrganizationManagerModelPermissionAdd, OrganizationManagerModelPermissionDelete
from app.tests.abstract.model_permissions import ModelPermissionsAdd, ModelPermissionsChange, ModelPermissionsDelete

View File

@ -1,76 +0,0 @@
import pytest
from django.contrib.auth.models import User
from django.test import Client, TestCase
from rest_framework.reverse import reverse
from access.models.organization import Organization
from access.models.team import Team
from access.viewsets.team_user import ViewSet
from api.tests.abstract.viewsets import ViewSetModel
class ViewsetCommon(
ViewSetModel,
):
viewset = ViewSet
route_name = 'API:_api_v2_organization_team_user'
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create an organization
3. create super user
"""
organization = Organization.objects.create(name='test_org')
self.organization = organization
self.team = Team.objects.create(
organization = self.organization,
name = 'team'
)
self.view_user = User.objects.create_user(username="test_view_user", password="password", is_superuser=True)
self.kwargs = {
'organization_id': self.organization.id,
'team_id': self.team.id
}
class TeamUserViewsetList(
ViewsetCommon,
TestCase,
):
@classmethod
def setUpTestData(self):
"""Setup Test
1. make list request
"""
super().setUpTestData()
client = Client()
url = reverse(
self.route_name + '-list',
kwargs = self.kwargs
)
client.force_login(self.view_user)
self.http_options_response_list = client.options(url)

View File

@ -3,8 +3,7 @@ import unittest
from django.test import TestCase
from access.models.tenancy import TenancyManager
from access.models.tenancy import TenancyObject
from access.models import TenancyObject, TenancyManager
from core.mixin.history_save import SaveHistory
@ -92,13 +91,3 @@ class TenancyObjectTests(TestCase):
"""
assert self.item.objects is not None
@pytest.mark.skip(reason="write test")
def test_field_not_none_organzation(self):
""" Ensure field is set
Field organization must be defined for all tenancy objects
"""
assert self.item.objects is not None

View File

@ -1,60 +0,0 @@
from django.contrib.auth.models import User
from django.shortcuts import reverse
from django.test import Client, TestCase
from rest_framework.permissions import IsAuthenticated
from access.models.organization import Organization
from api.tests.abstract.viewsets import ViewSetCommon
from access.viewsets.index import Index
class AccessViewset(
TestCase,
ViewSetCommon
):
viewset = Index
route_name = 'API:_api_v2_access_home'
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create an organization for user
3. create super user
"""
organization = Organization.objects.create(name='test_org')
self.organization = organization
self.view_user = User.objects.create_user(username="test_user_add", password="password", is_superuser=True)
client = Client()
url = reverse(self.route_name + '-list')
client.force_login(self.view_user)
self.http_options_response_list = client.options(url)
self.kwargs = {}
def test_view_attr_permission_classes_value(self):
"""Attribute Test
Attribute `permission_classes` must be metadata class `ReactUIMetadata`
"""
view_set = self.viewset()
assert view_set.permission_classes[0] is IsAuthenticated
assert len(view_set.permission_classes) == 1

View File

@ -4,7 +4,7 @@ from django.utils.decorators import method_decorator
from django.views import generic
from access.mixin import *
from access.models.organization import Organization
from access.models import *
from access.forms.organization import OrganizationForm

View File

@ -4,10 +4,8 @@ from django.utils.decorators import method_decorator
from django.urls import reverse
from access.forms.team import TeamForm, TeamFormAdd
from access.models import Team, TeamUsers, Organization
from access.mixin import *
from access.models.organization import Organization
from access.models.team import Team
from access.models.team_user import TeamUsers
from core.views.common import AddView, ChangeView, DeleteView

View File

@ -2,13 +2,11 @@ from django.contrib.auth import decorators as auth_decorator
from django.urls import reverse
from access.forms.team_users import TeamUsersForm
from access.models.team import Team
from access.models.team_user import TeamUsers
from access.models import Team, TeamUsers
from core.views.common import AddView, DeleteView
class Add(AddView):
context_object_name = "teamuser"

View File

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

View File

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

View File

@ -1,60 +0,0 @@
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse
from access.serializers.organization_notes import (
OrganizationNotes,
OrganizationNoteModelSerializer,
OrganizationNoteViewSerializer,
)
from core.viewsets.model_notes import ModelNoteViewSet
@extend_schema_view(
create=extend_schema(
summary = 'Add a note to an organization',
description = '',
responses = {
201: OpenApiResponse(description='created', response=OrganizationNoteViewSerializer),
400: OpenApiResponse(description='Validation failed.'),
403: OpenApiResponse(description='User is missing create permissions'),
}
),
destroy = extend_schema(
summary = 'Delete an organization note',
description = ''
),
list = extend_schema(
summary = 'Fetch all organization notes',
description='',
),
retrieve = extend_schema(
summary = 'Fetch a single organization note',
description='',
),
update = extend_schema(exclude = True),
partial_update = extend_schema(
summary = 'Update an organization note',
description = ''
),
)
class ViewSet(ModelNoteViewSet):
model = OrganizationNotes
def get_serializer_class(self):
if (
self.action == 'list'
or self.action == 'retrieve'
):
self.serializer_class = OrganizationNoteViewSerializer
else:
self.serializer_class = OrganizationNoteModelSerializer
return self.serializer_class

View File

@ -1,192 +0,0 @@
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiResponse
from access.models.organization import Organization
# THis import only exists so that the migrations can be created
from access.models.team_history import TeamHistory # pylint: disable=W0611:unused-import
from access.serializers.teams import (
Team,
TeamModelSerializer,
TeamViewSerializer
)
from api.viewsets.common import ModelViewSet
# @extend_schema(tags=['access'])
@extend_schema_view(
create=extend_schema(
summary = 'Create a team within this organization',
description='',
parameters = [
OpenApiParameter(
name = 'organization_id',
location = 'path',
type = int
),
],
responses = {
200: OpenApiResponse(description='Allready exists', response=TeamViewSerializer),
201: OpenApiResponse(description='Created', response=TeamViewSerializer),
# 400: OpenApiResponse(description='Validation failed.'),
403: OpenApiResponse(description='User is missing add permissions'),
}
),
destroy = extend_schema(
summary = 'Delete a team from this organization',
description = '',
parameters = [
OpenApiParameter(
name = 'id',
location = 'path',
type = int
),
OpenApiParameter(
name = 'organization_id',
location = 'path',
type = int
),
],
responses = {
204: OpenApiResponse(description=''),
403: OpenApiResponse(description='User is missing delete permissions'),
}
),
list = extend_schema(
summary = 'Fetch all teams from this organization',
description='',
parameters = [
OpenApiParameter(
name = 'organization_id',
location = 'path',
type = int
),
],
responses = {
200: OpenApiResponse(description='', response=TeamViewSerializer),
403: OpenApiResponse(description='User is missing view permissions'),
}
),
retrieve = extend_schema(
summary = 'Fetch a single team from this organization',
description='',
parameters = [
OpenApiParameter(
name = 'id',
location = 'path',
type = int
),
OpenApiParameter(
name = 'organization_id',
location = 'path',
type = int
),
],
responses = {
200: OpenApiResponse(description='', response=TeamViewSerializer),
403: OpenApiResponse(description='User is missing view permissions'),
}
),
update = extend_schema(exclude = True),
partial_update = extend_schema(
summary = 'Update a team within this organization',
description = '',
parameters = [
OpenApiParameter(
name = 'id',
location = 'path',
type = int
),
OpenApiParameter(
name = 'organization_id',
location = 'path',
type = int
),
],
responses = {
200: OpenApiResponse(description='', response=TeamViewSerializer),
# 201: OpenApiResponse(description='Created', response=OrganizationViewSerializer),
# # 400: OpenApiResponse(description='Validation failed.'),
403: OpenApiResponse(description='User is missing change permissions'),
}
),
)
class ViewSet( ModelViewSet ):
filterset_fields = [
'team_name',
]
search_fields = [
'team_name',
]
model = Team
view_description = 'Teams belonging to a single organization'
def get_back_url(self) -> str:
if(
getattr(self, '_back_url', None) is None
):
return_model = Organization.objects.get(
pk = self.kwargs['organization_id']
)
self._back_url = str(
return_model.get_url( self.request )
)
return self._back_url
def get_queryset(self):
if self.queryset is not None:
return self.queryset
queryset = super().get_queryset()
queryset = queryset.filter(organization_id=self.kwargs['organization_id'])
self.queryset = queryset
return self.queryset
def get_serializer_class(self):
if (
self.action == 'list'
or self.action == 'retrieve'
):
self.serializer_class = globals()[str( self.model._meta.verbose_name) + 'ViewSerializer']
else:
self.serializer_class = globals()[str( self.model._meta.verbose_name) + 'ModelSerializer']
return self.serializer_class
def get_return_url(self) -> str:
if getattr(self, '_get_return_url', None):
return self._get_return_url
if self.kwargs.get('pk', None) is None:
return_model = Organization.objects.get(
pk = self.kwargs['organization_id']
)
self._get_return_url = return_model.get_url( self.request )
return self._get_return_url
return None

View File

@ -1,60 +0,0 @@
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse
from access.serializers.team_notes import (
TeamNotes,
TeamNoteModelSerializer,
TeamNoteViewSerializer,
)
from core.viewsets.model_notes import ModelNoteViewSet
@extend_schema_view(
create=extend_schema(
summary = 'Add a note to a Team',
description = '',
responses = {
201: OpenApiResponse(description='created', response=TeamNoteViewSerializer),
400: OpenApiResponse(description='Validation failed.'),
403: OpenApiResponse(description='User is missing create permissions'),
}
),
destroy = extend_schema(
summary = 'Delete a team note',
description = ''
),
list = extend_schema(
summary = 'Fetch all team notes',
description='',
),
retrieve = extend_schema(
summary = 'Fetch a single team note',
description='',
),
update = extend_schema(exclude = True),
partial_update = extend_schema(
summary = 'Update a team note',
description = ''
),
)
class ViewSet(ModelNoteViewSet):
model = TeamNotes
def get_serializer_class(self):
if (
self.action == 'list'
or self.action == 'retrieve'
):
self.serializer_class = TeamNoteViewSerializer
else:
self.serializer_class = TeamNoteModelSerializer
return self.serializer_class

View File

@ -1,220 +0,0 @@
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiResponse
from access.models.team import Team
from access.serializers.team_user import (
TeamUsers,
TeamUserModelSerializer,
TeamUserViewSerializer
)
from api.viewsets.common import ModelViewSet
@extend_schema_view(
create=extend_schema(
summary = 'Create a user within this team',
description='',
parameters = [
OpenApiParameter(
name = 'organization_id',
location = 'path',
type = int
),
OpenApiParameter(
name = 'team_id',
location = 'path',
type = int
),
],
responses = {
# 200: OpenApiResponse(description='Allready exists', response=TeamUserViewSerializer),
201: OpenApiResponse(description='Created', response=TeamUserViewSerializer),
# 400: OpenApiResponse(description='Validation failed.'),
403: OpenApiResponse(description='User is missing add permissions'),
}
),
destroy = extend_schema(
summary = 'Delete a user from this team',
description = '',
parameters = [
OpenApiParameter(
name = 'id',
location = 'path',
type = int
),
OpenApiParameter(
name = 'organization_id',
location = 'path',
type = int
),
OpenApiParameter(
name = 'team_id',
location = 'path',
type = int
),
],
responses = {
204: OpenApiResponse(description=''),
403: OpenApiResponse(description='User is missing delete permissions'),
}
),
list = extend_schema(
summary = 'Fetch all users from this team',
description='',
parameters = [
OpenApiParameter(
name = 'organization_id',
location = 'path',
type = int
),
OpenApiParameter(
name = 'team_id',
location = 'path',
type = int
),
],
responses = {
200: OpenApiResponse(description='', response=TeamUserViewSerializer),
403: OpenApiResponse(description='User is missing view permissions'),
}
),
retrieve = extend_schema(
summary = 'Fetch a single user from this team',
description='',
parameters = [
OpenApiParameter(
name = 'id',
location = 'path',
type = int
),
OpenApiParameter(
name = 'organization_id',
location = 'path',
type = int
),
OpenApiParameter(
name = 'team_id',
location = 'path',
type = int
),
],
responses = {
200: OpenApiResponse(description='', response=TeamUserViewSerializer),
403: OpenApiResponse(description='User is missing view permissions'),
}
),
update = extend_schema(exclude = True),
partial_update = extend_schema(
summary = 'Update a user within this team',
description = '',
parameters = [
OpenApiParameter(
name = 'id',
location = 'path',
type = int
),
OpenApiParameter(
name = 'organization_id',
location = 'path',
type = int
),
OpenApiParameter(
name = 'team_id',
location = 'path',
type = int
),
],
responses = {
200: OpenApiResponse(description='', response=TeamUserViewSerializer),
# 201: OpenApiResponse(description='Created', response=OrganizationViewSerializer),
# # 400: OpenApiResponse(description='Validation failed.'),
403: OpenApiResponse(description='User is missing change permissions'),
}
),
)
class ViewSet( ModelViewSet ):
filterset_fields = [
'manager',
'team__organization',
]
search_fields = []
model = TeamUsers
parent_model = Team
parent_model_pk_kwarg = 'team_id'
view_description = 'Users belonging to a single team'
def get_back_url(self) -> str:
if(
getattr(self, '_back_url', None) is None
):
return_model =Team.objects.get(
pk = self.kwargs['team_id']
)
self._back_url = str(
return_model.get_url( self.request )
)
return self._back_url
def get_queryset(self):
if self.queryset is not None:
return self.queryset
self.queryset = super().get_queryset()
self.queryset = self.queryset.filter(
team_id = self.kwargs['team_id']
)
return self.queryset
def get_serializer_class(self):
if (
self.action == 'list'
or self.action == 'retrieve'
):
self.serializer_class = globals()[str( self.model._meta.verbose_name).replace(' ' , '') + 'ViewSerializer']
else:
self.serializer_class = globals()[str( self.model._meta.verbose_name).replace(' ' , '') + 'ModelSerializer']
return self.serializer_class
def get_return_url(self):
if getattr(self, '_get_return_url', None):
return self._get_return_url
if self.kwargs.get('pk', None) is None:
return_model = Team.objects.get(
pk = self.kwargs['team_id']
)
self._get_return_url = return_model.get_url( self.request )
return self._get_return_url
return None

View File

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

View File

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

View File

@ -32,7 +32,7 @@ class AuthTokenForm(CommonModelForm):
if self.prefix + '-gen_token' not in self.data:
generated_token = self.instance.generate
generated_token = self.instance.generate()
else:

View File

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

View File

@ -1,17 +0,0 @@
# Generated by Django 5.1.5 on 2025-02-28 15:12
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('api', '0002_alter_authtoken_expires_alter_authtoken_id_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='authtoken',
options={'ordering': ['expires'], 'verbose_name': 'Auth Token', 'verbose_name_plural': 'Auth Tokens'},
),
]

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