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
1019 changed files with 3382 additions and 122068 deletions

View File

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

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

6
.gitignore vendored
View File

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

View File

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

40
.vscode/launch.json vendored
View File

@ -5,7 +5,7 @@
"version": "0.2.0",
"configurations": [
{
"name": "Centurion",
"name": "Debug: Django",
"type": "debugpy",
"request": "launch",
"args": [
@ -16,45 +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": "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,5 +17,4 @@
"ITSM"
],
"cSpell.language": "en-AU",
"jest.enable": false,
}

File diff suppressed because it is too large Load Diff

View File

@ -1,103 +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
```
# 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,62 +1,7 @@
## 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,6 +1,5 @@
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 .models import *
@ -26,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

@ -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 import Team
from access.functions import permissions
from app import settings
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

@ -1,46 +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',
'django_celery_results',
'itam',
'itim',
'project_management',
'settings',
]
exclude_models = [
'appsettings',
'chordcounter',
'comment',
'groupresult',
'modelnotes',
'usersettings',
]
exclude_permissions = [
'add_organization',
'add_taskresult',
'change_organization',
'change_taskresult',
'delete_organization',
'delete_taskresult',
]
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,158 +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 import Organization, 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

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

@ -10,19 +10,6 @@ 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 = []
@ -39,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
@ -78,10 +61,6 @@ class OrganizationMixin():
id = 0
if hasattr(self, 'instance') and id is None: # Form Instance
id = self.instance.get_organization()
except AttributeError:
@ -105,10 +84,6 @@ class OrganizationMixin():
pass
if id is not None:
self._object_organization = id
return id
@ -124,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
@ -140,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 "
@ -180,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():
@ -194,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)
@ -242,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
@ -302,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:
@ -395,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,270 +0,0 @@
from django.contrib.auth.models import User, Group
from django.db import models
from access.models import Organization, 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,304 +0,0 @@
import traceback
from django.core.exceptions import ObjectDoesNotExist
from rest_framework import exceptions
from rest_framework.permissions import DjangoObjectPermissions
from access.models import 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))
):
return True
elif (
view.model.__name__ == 'UserSettings'
and request._user.id != int(view.kwargs.get('pk', 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))
):
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

View File

@ -1,21 +1,17 @@
from django.conf import settings
from django.db import models
from django.contrib.auth.models import User, Group, Permission
from rest_framework.reverse import reverse
from django.forms import ValidationError
from .fields import *
from core import exceptions as centurion_exceptions
from core.middleware.get_request import get_request
from core.mixin.history_save import SaveHistory
class Organization(SaveHistory):
class Meta:
verbose_name = "Organization"
verbose_name_plural = "Organizations"
ordering = ['name']
@ -27,34 +23,28 @@ class Organization(SaveHistory):
super().save(*args, **kwargs)
id = models.AutoField(
blank=False,
help_text = 'ID of this item',
primary_key=True,
unique=True,
verbose_name = 'ID'
blank=False
)
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'
blank = False,
null = True,
help_text = 'Organization Manager'
)
model_notes = models.TextField(
blank = True,
default = None,
help_text = 'Tid bits of information',
null= True,
verbose_name = 'Notes',
)
@ -69,76 +59,9 @@ class Organization(SaveHistory):
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})
class TenancyManager(models.Manager):
@ -183,44 +106,31 @@ class TenancyManager(models.Manager):
if request:
if request.app_settings.global_organization:
user_organizations += [ request.app_settings.global_organization.id ]
user = request.user
user = request.user._wrapped if hasattr(request.user,'_wrapped') else request.user
if user.is_authenticated:
for team in request.tenancy._user_teams:
for team_user in TeamUsers.objects.filter(user=user):
if team.organization.id not in user_organizations:
if team_user.team.organization.name not in user_organizations:
if not user_organizations:
self.user_organizations = []
user_organizations += [ team.organization.id ]
user_organizations += [ team_user.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().filter(
models.Q(organization__in=user_organizations)
|
models.Q(is_global = True)
)
return super().get_queryset()
@ -255,37 +165,23 @@ class TenancyObject(SaveHistory):
raise 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,
on_delete=models.CASCADE,
blank = False,
help_text = 'Organization this belongs to',
null = False,
on_delete = models.CASCADE,
related_name = '+',
null = True,
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'
blank = False
)
model_notes = models.TextField(
blank = True,
default = None,
help_text = 'Tid bits of information',
null = True,
null= True,
verbose_name = 'Notes',
)
@ -293,66 +189,12 @@ class TenancyObject(SaveHistory):
return self.organization
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:_api_v2_{model_name}-detail", request=request, kwargs = self.get_url_kwargs() )
return reverse(f"v2:_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 save(self, force_insert=False, force_update=False, using=None, update_fields=None):
self.clean()
if not getattr(self, 'organization', None):
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)
class Team(Group, TenancyObject):
class Meta:
ordering = [ 'team_name' ]
verbose_name = 'Team'
# proxy = True
verbose_name_plural = "Teams"
ordering = ['team_name']
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
@ -362,112 +204,18 @@ class Team(Group, TenancyObject):
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 ValidationError('You must provide an organization')
team_name = models.CharField(
verbose_name = 'Name',
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'
default = ''
)
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:
model_name = str(self._meta.verbose_name.lower()).replace(' ', '_')
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
}
@property
def parent_object(self):
@ -491,66 +239,43 @@ class Team(Group, TenancyObject):
def __str__(self):
return self.organization.name + ', ' + self.team_name
return self.team_name
class TeamUsers(SaveHistory):
class Meta:
# proxy = True
verbose_name_plural = "Team Users"
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'
blank=False
)
team = models.ForeignKey(
Team,
blank = False,
help_text = 'Team user belongs to',
null = False,
on_delete=models.CASCADE,
related_name="team",
verbose_name = 'Team'
)
on_delete=models.CASCADE)
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'
on_delete=models.CASCADE
)
manager = models.BooleanField(
blank=True,
default=False,
help_text = 'Is this user to be a manager of this team',
verbose_name='manager',
default=False,
blank=True
)
created = AutoCreatedField()
modified = AutoLastModifiedField()
page_layout: list = []
table_fields: list = [
'user',
'manager'
]
def delete(self, using=None, keep_parents=False):
""" Delete Team
@ -572,24 +297,6 @@ class TeamUsers(SaveHistory):
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 save(self, *args, **kwargs):
""" Save Team
@ -615,92 +322,3 @@ class TeamUsers(SaveHistory):
def __str__(self):
return self.user.username
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
}
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_organization_team_note-detail", request=request, kwargs = kwargs )
return reverse("v2:_api_v2_organization_team_note-detail", kwargs = kwargs )

View File

@ -1,104 +0,0 @@
from rest_framework.reverse import reverse
from rest_framework import serializers
from access.models 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 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 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,99 +0,0 @@
from rest_framework.reverse import reverse
from rest_framework import serializers
from access.models 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:
return {
'_self': item.get_url( request = self._context['view'].request )
}
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,144 +0,0 @@
from rest_framework.reverse import reverse
from rest_framework import serializers
from access.models 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:
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_team_note-list",
request=self._context['view'].request,
kwargs={
'organization_id': item.organization.pk,
'model_id': item.pk
}
),
'users': reverse(
'v2:_api_v2_organization_team_user-list',
request=self.context['view'].request,
kwargs={
'organization_id': item.organization.id,
'team_id': item.pk
}
)
}
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

@ -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,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,316 +0,0 @@
import pytest
import unittest
import requests
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 Client, TestCase
from access.models import Organization, Team, TeamUsers, Permission
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 import Organization, 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,311 +0,0 @@
import pytest
import unittest
import requests
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.test import Client, TestCase
from rest_framework.reverse import reverse
from access.models import Organization, Team, TeamUsers, Permission
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,207 +0,0 @@
import pytest
from django.contrib.auth.models import 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 import Organization, Permission
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_organization_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,55 +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 import Team, TeamNotes
class TeamNotesAPI(
ModelNotesNotesAPIFields,
TestCase,
):
model = TeamNotes
view_name: str = '_api_v2_organization_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,324 +0,0 @@
import pytest
import unittest
import requests
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.test import Client, TestCase
from rest_framework.reverse import reverse
from access.models import Organization, Team, TeamUsers, Permission
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,186 +0,0 @@
import pytest
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from rest_framework.exceptions import ValidationError
from access.models import Organization, Permission, 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

@ -17,7 +17,7 @@ class OrganizationAPI(TestCase):
model = Organization
app_namespace = 'v1'
app_namespace = 'API'
url_name = '_api_organization'

View File

@ -1,192 +0,0 @@
import pytest
import unittest
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 Client, TestCase
from rest_framework.relations import Hyperlink
from access.models import Organization, Team, TeamUsers, Permission
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

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

@ -34,7 +34,7 @@ class OrganizationHistory(TestCase):
self.history_create = History.objects.get(
action = int(History.Actions.ADD),
action = History.Actions.ADD[0],
item_pk = self.item_create.pk,
item_class = self.model._meta.model_name,
)
@ -44,7 +44,7 @@ class OrganizationHistory(TestCase):
self.item_change.save()
self.history_change = History.objects.get(
action = int(History.Actions.UPDATE),
action = History.Actions.UPDATE[0],
item_pk = self.item_change.pk,
item_class = self.model._meta.model_name,
)
@ -72,7 +72,7 @@ class OrganizationHistory(TestCase):
history = self.history_create.__dict__
assert history['action'] == int(History.Actions.ADD)
assert history['action'] == int(History.Actions.ADD[0])
# assert type(history['action']) is int
@ -125,7 +125,7 @@ class OrganizationHistory(TestCase):
history = self.history_change.__dict__
assert history['action'] == int(History.Actions.UPDATE)
assert history['action'] == int(History.Actions.UPDATE[0])
# assert type(history['action']) is int

View File

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

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

@ -21,7 +21,7 @@ class TeamAPI(TestCase):
model = Team
app_namespace = 'v1'
app_namespace = 'API'
url_name = '_api_team'

View File

@ -1,174 +0,0 @@
import pytest
from django.contrib.auth.models import 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 import Organization, Team, TeamUsers, Permission
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

@ -39,7 +39,7 @@ class TeamHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
self.history_create = History.objects.get(
action = int(History.Actions.ADD),
action = History.Actions.ADD[0],
item_pk = self.item_create.pk,
item_class = self.model._meta.model_name,
)
@ -51,7 +51,7 @@ class TeamHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
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 = int(History.Actions.UPDATE),
action = History.Actions.UPDATE[0],
item_pk = self.item_change.pk,
item_class = self.model._meta.model_name,
)
@ -68,7 +68,7 @@ class TeamHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
self.item_delete.delete()
self.history_delete = History.objects.get(
action = int(History.Actions.DELETE),
action = History.Actions.DELETE[0],
item_pk = self.deleted_pk,
item_class = self.model._meta.model_name,
)

View File

@ -18,7 +18,7 @@ class TeamPermissionsAPI(TestCase, APIPermissions):
model = Team
app_namespace = 'v1'
app_namespace = 'API'
url_name = '_api_team'

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

@ -6,16 +6,9 @@ from django.contrib.auth.models import User
from access.models import Organization, Team, TeamUsers, Permission
from app.tests.abstract.models import BaseModel
from core.mixin.history_save import SaveHistory
class TeamUsersModel(
TestCase,
BaseModel
):
class TeamUsersModel(TestCase):
model = TeamUsers
@ -61,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,214 +0,0 @@
import pytest
from django.contrib.auth.models import 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 import Organization, Team, TeamUsers, Permission
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

@ -48,7 +48,7 @@ class TeamUsersHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
self.history_create = History.objects.get(
action = int(History.Actions.ADD),
action = History.Actions.ADD[0],
item_pk = self.item_create.pk,
item_class = self.model._meta.model_name,
)
@ -60,7 +60,7 @@ class TeamUsersHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
self.field_after_expected_value = '{"manager": true}'
self.history_change = History.objects.get(
action = int(History.Actions.UPDATE),
action = History.Actions.UPDATE[0],
item_pk = self.item_change.pk,
item_class = self.model._meta.model_name,
)
@ -81,7 +81,7 @@ class TeamUsersHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
self.item_delete.delete()
self.history_delete = History.objects.get(
action = int(History.Actions.DELETE),
action = History.Actions.DELETE[0],
item_pk = self.deleted_pk,
item_class = self.model._meta.model_name,
)

View File

@ -1,75 +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 import Organization, 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

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

@ -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,96 +0,0 @@
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse
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.serializer_class is not None:
return self.serializer_class
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,65 +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.serializer_class is not None:
return self.serializer_class
if (
self.action == 'list'
or self.action == 'retrieve'
):
self.serializer_class = OrganizationNoteViewSerializer
else:
self.serializer_class = OrganizationNoteModelSerializer
return self.serializer_class

View File

@ -1,195 +0,0 @@
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiResponse
from access.models import Organization
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.serializer_class is not None:
return self.serializer_class
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,65 +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.serializer_class is not None:
return self.serializer_class
if (
self.action == 'list'
or self.action == 'retrieve'
):
self.serializer_class = TeamNoteViewSerializer
else:
self.serializer_class = TeamNoteModelSerializer
return self.serializer_class

View File

@ -1,225 +0,0 @@
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiResponse
from access.models 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.serializer_class is not None:
return self.serializer_class
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

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

@ -48,45 +48,37 @@ class AuthToken(models.Model):
id = models.AutoField(
blank=False,
help_text = 'ID of this token',
primary_key=True,
unique=True,
verbose_name = 'ID'
blank=False
)
note = models.CharField(
blank = True,
default = None,
help_text = 'A note about this token',
max_length = 50,
default = None,
null= True,
verbose_name = 'Note'
)
token = models.CharField(
blank = False,
verbose_name = 'Auth Token',
db_index=True,
help_text = 'The authorization token',
max_length = 64,
null = False,
blank = False,
unique = True,
verbose_name = 'Auth Token',
)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
help_text = 'User this token belongs to',
on_delete=models.CASCADE,
verbose_name = 'Owner'
on_delete=models.CASCADE
)
expires = models.DateTimeField(
blank = False,
help_text = 'When this token expires',
null = False,
verbose_name = 'Expiry Date',
null = False,
blank = False
)

View File

@ -1,618 +0,0 @@
import re
from django.conf import settings
from django.utils.encoding import force_str
from django.contrib.auth.models import ContentType, Permission
from rest_framework import serializers
from rest_framework_json_api.metadata import JSONAPIMetadata
from rest_framework.request import clone_request
from rest_framework.reverse import reverse
from rest_framework.utils.field_mapping import ClassLookupDict
from rest_framework_json_api.utils import get_related_resource_type
from access.models import Organization
from app.serializers.user import User, UserBaseSerializer
from core import fields as centurion_field
from core.fields.badge import BadgeField
from core.fields.icon import IconField
class OverRideJSONAPIMetadata(JSONAPIMetadata):
type_lookup = ClassLookupDict(
{
serializers.Field: "GenericField",
serializers.RelatedField: "Relationship",
serializers.BooleanField: "Boolean",
serializers.CharField: "String",
serializers.URLField: "URL",
serializers.EmailField: "Email",
serializers.RegexField: "Regex",
serializers.SlugField: "Slug",
serializers.IntegerField: "Integer",
serializers.FloatField: "Float",
serializers.DecimalField: "Decimal",
serializers.DateField: "Date",
serializers.DateTimeField: "DateTime",
serializers.TimeField: "Time",
serializers.ChoiceField: "Choice",
serializers.MultipleChoiceField: "MultipleChoice",
serializers.FileField: "File",
serializers.ImageField: "Image",
serializers.ListField: "List",
serializers.DictField: "Dict",
serializers.Serializer: "Serializer",
serializers.JSONField: "JSON", # New. Does not exist in base class
BadgeField: 'Badge',
IconField: 'Icon',
User: 'Relationship',
UserBaseSerializer: 'Relationship',
centurion_field.CharField: 'String',
centurion_field.MarkdownField: 'Markdown'
}
)
class ReactUIMetadata(OverRideJSONAPIMetadata):
def determine_metadata(self, request, view):
metadata = {}
metadata["name"] = view.get_view_name()
metadata["description"] = view.get_view_description()
if hasattr(view, 'get_model_documentation'):
if view.get_model_documentation():
metadata['documentation'] = str(settings.DOCS_ROOT) + str(view.get_model_documentation())
metadata['urls']: dict = {}
url_self = None
if view.kwargs.get('pk', None) is not None:
qs = view.get_queryset()[0]
if hasattr(qs, 'get_url'):
url_self = qs.get_url( request=request )
elif view.kwargs:
url_self = reverse('v2:' + view.basename + '-list', request = view.request, kwargs = view.kwargs )
else:
url_self = reverse('v2:' + view.basename + '-list', request = view.request )
if url_self:
metadata['urls'].update({'self': url_self})
if view.get_back_url():
metadata['urls'].update({'back': view.get_back_url()})
if view.get_return_url():
metadata['urls'].update({'return_url': view.get_return_url()})
metadata["renders"] = [
renderer.media_type for renderer in view.renderer_classes
]
metadata["parses"] = [parser.media_type for parser in view.parser_classes]
metadata["allowed_methods"] = view.allowed_methods
if hasattr(view, 'get_serializer'):
serializer = view.get_serializer()
metadata['fields'] = self.get_serializer_info(serializer)
if view.suffix == 'Instance':
metadata['layout'] = view.get_page_layout()
elif view.suffix == 'List':
if hasattr(view, 'table_fields'):
metadata['table_fields'] = view.get_table_fields()
if hasattr(view, 'page_layout'):
metadata['layout'] = view.get_page_layout()
build_repo: str = None
if settings.BUILD_REPO:
build_repo = settings.BUILD_REPO
build_sha: str = None
if settings.BUILD_SHA:
build_sha = settings.BUILD_SHA
build_version: str = 'development'
if settings.BUILD_VERSION:
build_version = settings.BUILD_VERSION
metadata['version']: dict = {
'project_url': build_repo,
'sha': build_sha,
'version': build_version,
}
metadata['navigation'] = self.get_navigation(request.user)
return metadata
def get_field_info(self, field):
""" Custom from `rest_framewarok_json_api.metadata.py`
Require that read-only fields have their choices added to the
metadata.
Given an instance of a serializer field, return a dictionary
of metadata about it.
"""
field_info = {}
serializer = field.parent
if hasattr(field, 'textarea'):
if field.textarea:
field_info["multi_line"] = True
if isinstance(field, serializers.ManyRelatedField):
field_info["type"] = self.type_lookup[field.child_relation]
else:
field_info["type"] = self.type_lookup[field]
try:
serializer_model = serializer.Meta.model
field_info["relationship_type"] = self.relation_type_lookup[
getattr(serializer_model, field.field_name)
]
except KeyError:
pass
except AttributeError:
pass
else:
field_info["relationship_resource"] = get_related_resource_type(field)
if hasattr(field, 'autolink'):
if field.autolink:
field_info['autolink'] = field.autolink
field_info["required"] = getattr(field, "required", False)
if hasattr(field, 'style_class'):
field_info["style"]: dict = {
'class': field.style_class
}
attrs = [
"read_only",
"write_only",
"label",
"help_text",
"min_length",
"max_length",
"min_value",
"max_value",
"initial",
]
for attr in attrs:
value = getattr(field, attr, None)
if value is not None and value != "":
field_info[attr] = force_str(value, strings_only=True)
if getattr(field, "child", None):
field_info["child"] = self.get_field_info(field.child)
elif getattr(field, "fields", None):
field_info["children"] = self.get_serializer_info(field)
if (
hasattr(field, "choices")
):
field_info["choices"] = [
{
"value": choice_value,
"display_name": force_str(choice_name, strings_only=True),
}
for choice_value, choice_name in field.choices.items()
]
if (
hasattr(serializer, "included_serializers")
and "relationship_resource" in field_info
):
field_info["allows_include"] = (
field.field_name in serializer.included_serializers
)
if field_info["type"] == 'Markdown':
linked_models = []
linked_tickets = []
field_info["render"] = {
'models': {},
'tickets': {},
}
if(
field.context['view'].kwargs.get('pk', None)
or field.context['view'].metadata_markdown
):
queryset = field.context['view'].get_queryset()
from core.lib.slash_commands.linked_model import CommandLinkedModel
from core.models.ticket.ticket import Ticket
for obj in queryset:
value = getattr(obj, field.source, None)
if field.source == 'display_name':
value = str(obj)
if value:
linked_models = re.findall(r'\s\$(?P<model_type>[a-z_]+)-(?P<model_id>\d+)[\s|\n]?', ' ' + str(value) + ' ')
linked_tickets = re.findall(r'(?P<ticket>#(?P<number>\d+))', str(value))
if(getattr(obj, 'from_ticket_id_id', None)):
linked_tickets += re.findall(r'(?P<ticket>#(?P<number>\d+))', '#' + str(obj.to_ticket_id_id))
for ticket, number in linked_tickets:
try:
item = Ticket.objects.get( pk = number )
field_info["render"]['tickets'].update({
number: {
'status': Ticket.TicketStatus.All(item.status).label,
'ticket_type': Ticket.TicketType(item.ticket_type).label,
'title': str(item),
'url': str(item.get_url()).replace('/api/v2', '')
}
})
except Ticket.DoesNotExist as e:
pass
for model_type, model_id in linked_models:
try:
model, item_type = CommandLinkedModel().get_model( model_type )
if model:
item = model.objects.get( pk = model_id )
item_meta = {
model_id: {
'title': str(item),
'url': str(item.get_url()).replace('/api/v2', ''),
}
}
if not field_info["render"]['models'].get(model_type, None):
field_info["render"]['models'].update({
model_type: item_meta
})
else:
field_info["render"]['models'][model_type].update( item_meta )
except model.DoesNotExist as e:
pass
return field_info
_nav = {
'access': {
"display_name": "Access",
"name": "access",
"pages": {
'view_organization': {
"display_name": "Organization",
"name": "organization",
"link": "/access/organization"
}
}
},
'assistance': {
"display_name": "Assistance",
"name": "assistance",
"pages": {
'core.view_ticket_request': {
"display_name": "Requests",
"name": "request",
"icon": "ticket_request",
"link": "/assistance/ticket/request"
},
'view_knowledgebase': {
"display_name": "Knowledge Base",
"name": "knowledge_base",
"icon": "information",
"link": "/assistance/knowledge_base"
}
}
},
'itam': {
"display_name": "ITAM",
"name": "itam",
"pages": {
'view_device': {
"display_name": "Devices",
"name": "device",
"icon": "device",
"link": "/itam/device"
},
'view_operatingsystem': {
"display_name": "Operating System",
"name": "operating_system",
"link": "/itam/operating_system"
},
'view_software': {
"display_name": "Software",
"name": "software",
"link": "/itam/software"
}
}
},
'itim': {
"display_name": "ITIM",
"name": "itim",
"pages": {
'core.view_ticket_change': {
"display_name": "Changes",
"name": "ticket_change",
"link": "/itim/ticket/change"
},
'view_cluster': {
"display_name": "Clusters",
"name": "cluster",
"link": "/itim/cluster"
},
'core.view_ticket_incident': {
"display_name": "Incidents",
"name": "ticket_incident",
"link": "/itim/ticket/incident"
},
'core.view_ticket_problem': {
"display_name": "Problems",
"name": "ticket_problem",
"link": "/itim/ticket/problem"
},
'view_service': {
"display_name": "Services",
"name": "service",
"link": "/itim/service"
},
}
},
'config_management': {
"display_name": "Config Management",
"name": "config_management",
"icon": "ansible",
"pages": {
'view_configgroups': {
"display_name": "Groups",
"name": "group",
"icon": 'config_management',
"link": "/config_management/group"
}
}
},
'project_management': {
"display_name": "Project Management",
"name": "project_management",
"icon": 'project',
"pages": {
'view_project': {
"display_name": "Projects",
"name": "project",
"icon": 'kanban',
"link": "/project_management/project"
}
}
},
'settings': {
"display_name": "Settings",
"name": "settings",
"pages": {
'all_settings': {
"display_name": "System",
"name": "setting",
"icon": "system",
"link": "/settings"
},
'django_celery_results.view_taskresult': {
"display_name": "Task Log",
"name": "celery_log",
# "icon": "settings",
"link": "/settings/celery_log"
}
}
}
}
def get_navigation(self, user) -> list(dict()):
"""Render the navigation menu
Check the users permissions agains `_nav`. if they have the permission, add the
menu entry to the navigation to be rendered,
**No** Menu is to be rendered that contains no menu entries.
Args:
user (User): User object from the request.
Returns:
list(dict()): Rendered navigation menu in the format the UI requires it to be.
"""
nav: list = []
processed_permissions: dict = {}
for group in user.groups.all():
for permission in group.permissions.all():
if str(permission.codename).startswith('view_'):
if not processed_permissions.get(permission.content_type.app_label, None):
processed_permissions.update({permission.content_type.app_label: {}})
if permission.codename not in processed_permissions[permission.content_type.app_label]:
processed_permissions[permission.content_type.app_label].update({str(permission.codename): '_'})
view_settings: list = [
'assistance.view_knowledgebasecategory',
'core.view_manufacturer',
'core.view_ticketcategory',
'core.view_ticketcommentcategory',
'itam.view_devicemodel',
'itam.view_devicetype',
'itam.view_softwarecategory',
'itim.view_clustertype',
'project_management.view_projectstate',
'project_management.view_projecttype',
'settings.view_appsettings',
]
# user = view.request.user
user_orgainzations = Organization.objects.filter(
manager = user
)
for app, entry in self._nav.items():
new_menu_entry: dict = {}
new_pages: list = []
# if processed_permissions.get(app, None): # doesn't cater for `.` in perm
for permission, page in entry['pages'].items():
if permission == 'all_settings':
for setting_permission in view_settings:
app_permission = str(setting_permission).split('.')
if processed_permissions.get(app_permission[0], None):
if processed_permissions[app_permission[0]].get(app_permission[1], None):
new_pages += [ page ]
break
elif '.' in permission:
app_permission = str(permission).split('.')
if processed_permissions.get(app_permission[0], None):
if processed_permissions[app_permission[0]].get(app_permission[1], None):
new_pages += [ page ]
else:
if processed_permissions.get(app, None):
if processed_permissions[app].get(permission, None):
new_pages += [ page ]
if(
app == 'access'
and permission == 'view_organization'
and len(user_orgainzations) > 0
):
if page not in new_pages:
new_pages += [ page ]
if len(new_pages) > 0:
new_menu_entry = entry.copy()
new_menu_entry.update({ 'pages': new_pages })
nav += [ new_menu_entry ]
return nav

View File

@ -25,7 +25,7 @@ class TeamSerializerBase(serializers.ModelSerializer):
request = self.context.get('request')
return request.build_absolute_uri(reverse("v1:_api_team", args=[obj.organization.id,obj.pk]))
return request.build_absolute_uri(reverse("API:_api_team", args=[obj.organization.id,obj.pk]))
@ -48,7 +48,7 @@ class TeamSerializer(TeamSerializerBase):
team = Team.objects.get(pk=obj.id)
return request.build_absolute_uri(reverse('v1:_api_team_permission', args=[team.organization_id,team.id]))
return request.build_absolute_uri(reverse('API:_api_team_permission', args=[team.organization_id,team.id]))
def validate(self, data):
@ -67,7 +67,7 @@ class TeamSerializer(TeamSerializerBase):
request = self.context.get('request')
return request.build_absolute_uri(reverse('v1:_api_team', args=[obj.organization_id,obj.id]))
return request.build_absolute_uri(reverse('API:_api_team', args=[obj.organization_id,obj.id]))
class Meta:
@ -93,7 +93,7 @@ class TeamSerializer(TeamSerializerBase):
class OrganizationListSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name="v1:_api_organization", format="html"
view_name="API:_api_organization", format="html"
)
@ -110,7 +110,7 @@ class OrganizationListSerializer(serializers.ModelSerializer):
class OrganizationSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name="v1:_api_organization", format="html"
view_name="API:_api_organization", format="html"
)
team_url = serializers.SerializerMethodField('get_url')
@ -121,11 +121,11 @@ class OrganizationSerializer(serializers.ModelSerializer):
team = Team.objects.filter(pk=obj.id)
return request.build_absolute_uri(reverse('v1:_api_organization_teams', args=[obj.id]))
return request.build_absolute_uri(reverse('API:_api_organization_teams', args=[obj.id]))
teams = TeamSerializer(source='team_set', many=True, read_only=False)
view_name="v1:_api_organization"
view_name="API:_api_organization"
class Meta:

View File

@ -1,63 +0,0 @@
from rest_framework.fields import empty
from api.serializers.core.ticket import TicketSerializer
from core.models.ticket.ticket import Ticket
class RequestTicketSerializer(
TicketSerializer,
):
class Meta:
model = Ticket
fields = [
'id',
'assigned_teams',
'assigned_users',
'category',
'created',
'modified',
'status',
'title',
'description',
'estimate',
'urgency',
'impact',
'priority',
'external_ref',
'external_system',
'ticket_type',
'is_deleted',
'date_closed',
# 'planned_start_date',
# 'planned_finish_date',
# 'real_start_date',
# 'real_finish_date',
'opened_by',
'organization',
'project',
'milestone',
'subscribed_teams',
'subscribed_users',
'ticket_comments',
'url',
]
read_only_fields = [
'id',
'ticket_type',
'url',
]
def __init__(self, instance=None, data=empty, **kwargs):
super().__init__(instance=instance, data=data, **kwargs)
self.fields.fields['category'].queryset = self.fields.fields['category'].queryset.filter(
request = True
)

View File

@ -1,53 +0,0 @@
from rest_framework import serializers
from access.serializers.organization import Organization, OrganizationBaseSerializer
from core import fields as centurion_field
from settings.models.app_settings import AppSettings
class OrganizationField(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
""" Queryset Override
Override the base serializer and filter out the `global_organization`
if defined.
"""
queryset = Organization.objects.all()
if self.context.get('request', None):
if getattr(self.context['request'].app_settings, 'global_organization', None):
queryset = queryset.exclude(id=self.context['request'].app_settings.global_organization.id)
return queryset
class CommonBaseSerializer(serializers.ModelSerializer):
pass
class CommonModelSerializer(CommonBaseSerializer):
"""Common Model Serializer
_**Note:** This serializer is not inherited by the organization Serializer_
_`access.serializers.organization`, this is by design_
This serializer is included within ALL model (Tenancy Model) serilaizers and is intended to be used
to add objects that ALL model serializers will require.
Args:
CommonBaseSerializer (Class): Common base serializer
"""
model_notes = centurion_field.MarkdownField( required = False )
organization = OrganizationField(required = False)

View File

@ -28,7 +28,7 @@ class ParentGroupSerializer(serializers.ModelSerializer):
request = self.context.get('request')
return request.build_absolute_uri(reverse("v1:_api_config_group", args=[obj.pk]))
return request.build_absolute_uri(reverse("API:_api_config_group", args=[obj.pk]))
@ -59,7 +59,7 @@ class ConfigGroupsSerializerBase(serializers.ModelSerializer):
request = self.context.get('request')
return request.build_absolute_uri(reverse("v1:_api_config_group", args=[obj.pk]))
return request.build_absolute_uri(reverse("API:_api_config_group", args=[obj.pk]))
@ -74,7 +74,6 @@ class ConfigGroupsSerializer(ConfigGroupsSerializerBase):
'parent',
'name',
'config',
'hosts',
'url',
]
read_only_fields = [

View File

@ -1,196 +0,0 @@
from django.urls import reverse
from rest_framework import serializers
from rest_framework.fields import empty
from api.serializers.core.ticket_comment import TicketCommentSerializer
from core import exceptions as centurion_exception
from core.forms.validate_ticket import TicketValidation
from core.models.ticket.ticket import Ticket
class TicketSerializer(
serializers.ModelSerializer,
TicketValidation,
):
url = serializers.SerializerMethodField('get_url_ticket')
def get_url_ticket(self, item):
request = self.context.get('request')
kwargs: dict = {
'pk': item.id
}
if item.ticket_type == self.Meta.model.TicketType.CHANGE.value:
view_name = '_api_itim_change'
elif item.ticket_type == self.Meta.model.TicketType.INCIDENT.value:
view_name = '_api_itim_incident'
elif item.ticket_type == self.Meta.model.TicketType.PROBLEM.value:
view_name = '_api_itim_problem'
elif item.ticket_type == self.Meta.model.TicketType.REQUEST.value:
view_name = '_api_assistance_request'
elif item.ticket_type == self.Meta.model.TicketType.PROJECT_TASK.value:
view_name = '_api_project_tasks'
kwargs.update({'project_id': item.project.id})
else:
raise ValueError('Serializer unable to obtain ticket type')
return request.build_absolute_uri(
reverse(
'v1:' + view_name + '-detail',
kwargs = kwargs
)
)
ticket_comments = serializers.SerializerMethodField('get_url_ticket_comments')
def get_url_ticket_comments(self, item):
request = self.context.get('request')
kwargs: dict = {
'ticket_id': item.id
}
if item.ticket_type == self.Meta.model.TicketType.CHANGE.value:
view_name = '_api_itim_change_ticket_comments'
elif item.ticket_type == self.Meta.model.TicketType.INCIDENT.value:
view_name = '_api_itim_incident_ticket_comments'
elif item.ticket_type == self.Meta.model.TicketType.PROBLEM.value:
view_name = '_api_itim_problem_ticket_comments'
elif item.ticket_type == self.Meta.model.TicketType.REQUEST.value:
view_name = '_api_assistance_request_ticket_comments'
elif item.ticket_type == self.Meta.model.TicketType.PROJECT_TASK.value:
view_name = '_api_project_tasks_comments'
kwargs.update({'project_id': item.project.id})
else:
raise ValueError('Serializer unable to obtain ticket type')
return request.build_absolute_uri(
reverse(
'v1:' + view_name + '-list',
kwargs = kwargs
)
)
class Meta:
model = Ticket
fields = [
'id',
'assigned_teams',
'assigned_users',
'category',
'created',
'modified',
'status',
'title',
'description',
'urgency',
'impact',
'priority',
'external_ref',
'external_system',
'ticket_type',
'is_deleted',
'date_closed',
'planned_start_date',
'planned_finish_date',
'real_start_date',
'real_finish_date',
'opened_by',
'organization',
'project',
'subscribed_teams',
'subscribed_users',
'ticket_comments',
'url',
]
read_only_fields = [
'id',
'url',
]
def __init__(self, instance=None, data=empty, **kwargs):
self.fields.fields['status'].initial = Ticket.TicketStatus.All.NEW
self.fields.fields['status'].default = Ticket.TicketStatus.All.NEW
self.ticket_type_fields = self.Meta.fields
super().__init__(instance=instance, data=data, **kwargs)
self.fields['organization'].required = True
def is_valid(self, *, raise_exception=True) -> bool:
is_valid = False
try:
self.request = self._context['request']
is_valid = super().is_valid(raise_exception=raise_exception)
self._ticket_type = str(self.fields['ticket_type'].choices[self._context['view']._ticket_type_value]).lower().replace(' ', '_')
self.validated_data['ticket_type'] = int(self._context['view']._ticket_type_value)
is_valid = self.validate_ticket()
if self.instance is None:
subscribed_users: list = []
if 'subscribed_users' in self.validated_data:
subscribed_users = self.validated_data['subscribed_users']
self.validated_data['subscribed_users'] = subscribed_users + [ self.validated_data['opened_by'] ]
except Exception as unhandled_exception:
centurion_exception.ParseError(
detail=f"Server encountered an error during validation, Traceback: {unhandled_exception.with_traceback}"
)
return is_valid

View File

@ -1,44 +0,0 @@
from django.urls import reverse
from rest_framework import serializers
from rest_framework.fields import empty
from api.serializers.core.ticket_comment import TicketCommentSerializer
from core.forms.validate_ticket import TicketValidation
from core.models.ticket.ticket_category import TicketCategory
class TicketCategorySerializer(
serializers.ModelSerializer,
):
url = serializers.HyperlinkedIdentityField(
view_name="v1:_api_ticket_category-detail", format="html"
)
class Meta:
model = TicketCategory
fields = '__all__'
read_only_fields = [
'id',
'url',
]
def __init__(self, instance=None, data=empty, **kwargs):
if instance is not None:
if hasattr(instance, 'id'):
self.fields.fields['parent'].queryset = self.fields.fields['parent'].queryset.exclude(
id=instance.id
)
super().__init__(instance=instance, data=data, **kwargs)

View File

@ -1,74 +0,0 @@
from django.urls import reverse
from rest_framework import serializers
from rest_framework.fields import empty
from core.models.ticket.ticket_comment import Ticket, TicketComment
class TicketCommentSerializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField('get_url_ticket_comment')
def get_url_ticket_comment(self, item):
request = self.context.get('request')
if item.ticket.ticket_type == item.ticket.__class__.TicketType.CHANGE:
view_name = '_api_itim_change_ticket_comments'
elif item.ticket.ticket_type == item.ticket.__class__.TicketType.INCIDENT:
view_name = '_api_itim_incident_ticket_comments'
elif item.ticket.ticket_type == item.ticket.__class__.TicketType.PROBLEM:
view_name = '_api_itim_problem_ticket_comments'
elif item.ticket.ticket_type == item.ticket.__class__.TicketType.REQUEST:
view_name = '_api_assistance_request_ticket_comments'
else:
raise ValueError('Serializer unable to obtain ticket type')
return request.build_absolute_uri(
reverse('v1:' + view_name + '-detail',
kwargs={
'ticket_id': item.ticket.id,
'pk': item.id
}
)
)
class Meta:
model = TicketComment
fields = '__all__'
def __init__(self, instance=None, data=empty, **kwargs):
if 'context' in self._kwargs:
if 'view' in self._kwargs['context']:
if 'ticket_id' in self._kwargs['context']['view'].kwargs:
ticket = Ticket.objects.get(pk=int(self._kwargs['context']['view'].kwargs['ticket_id']))
self.fields.fields['organization'].initial = ticket.organization.id
self.fields.fields['ticket'].initial = int(self._kwargs['context']['view'].kwargs['ticket_id'])
self.fields.fields['comment_type'].initial = TicketComment.CommentType.COMMENT
self.fields.fields['user'].initial = kwargs['context']['request']._user.id
super().__init__(instance=instance, data=data, **kwargs)

View File

@ -1,42 +0,0 @@
from django.urls import reverse
from rest_framework import serializers
from rest_framework.fields import empty
from core.models.ticket.ticket_comment_category import TicketCommentCategory
class TicketCommentCategorySerializer(
serializers.ModelSerializer,
):
url = serializers.HyperlinkedIdentityField(
view_name="v1:_api_ticket_comment_category-detail", format="html"
)
class Meta:
model = TicketCommentCategory
fields = '__all__'
read_only_fields = [
'id',
'url',
]
def __init__(self, instance=None, data=empty, **kwargs):
if instance is not None:
if hasattr(instance, 'id'):
self.fields.fields['parent'].queryset = self.fields.fields['parent'].queryset.exclude(
id=instance.id
)
super().__init__(instance=instance, data=data, **kwargs)

View File

@ -1,9 +1,6 @@
from django.core.exceptions import ValidationError
from django.utils.html import escape
from rest_framework.exceptions import ValidationError
class Inventory:
""" Inventory Object

View File

@ -4,7 +4,7 @@ from rest_framework import serializers
from api.serializers.config import ParentGroupSerializer
from config_management.models.groups import ConfigGroups
from config_management.models.groups import ConfigGroupHosts
from itam.models.device import Device
@ -12,13 +12,15 @@ from itam.models.device import Device
class DeviceConfigGroupsSerializer(serializers.ModelSerializer):
name = serializers.CharField(source='group.name', read_only=True)
url = serializers.HyperlinkedIdentityField(
view_name="v1:_api_config_group", format="html"
view_name="API:_api_config_group", format="html"
)
class Meta:
model = ConfigGroups
model = ConfigGroupHosts
fields = [
'id',
@ -36,21 +38,22 @@ class DeviceConfigGroupsSerializer(serializers.ModelSerializer):
class DeviceSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name="v1:device-detail", format="html"
view_name="API:device-detail", format="html"
)
config = serializers.SerializerMethodField('get_device_config')
groups = DeviceConfigGroupsSerializer(source='configgroups_set', many=True, read_only=True)
groups = DeviceConfigGroupsSerializer(source='configgrouphosts_set', many=True, read_only=True)
def get_device_config(self, device):
request = self.context.get('request')
return request.build_absolute_uri(reverse('v1:_api_device_config', args=[device.slug]))
return request.build_absolute_uri(reverse('API:_api_device_config', args=[device.slug]))
class Meta:
model = Device
depth = 1
fields = [
'id',
'is_global',

View File

@ -7,7 +7,7 @@ from itam.models.device import Software
class SoftwareSerializer(serializers.ModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name="v1:software-detail", format="html"
view_name="API:software-detail", format="html"
)
class Meta:

View File

@ -1,63 +0,0 @@
from rest_framework.fields import empty
from api.serializers.core.ticket import TicketSerializer
from core.models.ticket.ticket import Ticket
class ChangeTicketSerializer(
TicketSerializer,
):
class Meta:
model = Ticket
fields = [
'id',
'assigned_teams',
'assigned_users',
'category',
'created',
'modified',
'status',
'title',
'description',
'estimate',
'urgency',
'impact',
'priority',
'external_ref',
'external_system',
'ticket_type',
'is_deleted',
'date_closed',
# 'planned_start_date',
# 'planned_finish_date',
# 'real_start_date',
# 'real_finish_date',
'opened_by',
'organization',
'project',
'milestone',
'subscribed_teams',
'subscribed_users',
'ticket_comments',
'url',
]
read_only_fields = [
'id',
'ticket_type',
'url',
]
def __init__(self, instance=None, data=empty, **kwargs):
super().__init__(instance=instance, data=data, **kwargs)
self.fields.fields['category'].queryset = self.fields.fields['category'].queryset.filter(
project_task = True
)

View File

@ -1,63 +0,0 @@
from rest_framework.fields import empty
from api.serializers.core.ticket import TicketSerializer
from core.models.ticket.ticket import Ticket
class IncidentTicketSerializer(
TicketSerializer,
):
class Meta:
model = Ticket
fields = [
'id',
'assigned_teams',
'assigned_users',
'category',
'created',
'modified',
'status',
'title',
'description',
'estimate',
'urgency',
'impact',
'priority',
'external_ref',
'external_system',
'ticket_type',
'is_deleted',
'date_closed',
# 'planned_start_date',
# 'planned_finish_date',
# 'real_start_date',
# 'real_finish_date',
'opened_by',
'organization',
'project',
'milestone',
'subscribed_teams',
'subscribed_users',
'ticket_comments',
'url',
]
read_only_fields = [
'id',
'ticket_type',
'url',
]
def __init__(self, instance=None, data=empty, **kwargs):
super().__init__(instance=instance, data=data, **kwargs)
self.fields.fields['category'].queryset = self.fields.fields['category'].queryset.filter(
incident = True
)

View File

@ -1,63 +0,0 @@
from rest_framework.fields import empty
from api.serializers.core.ticket import TicketSerializer
from core.models.ticket.ticket import Ticket
class ProblemTicketSerializer(
TicketSerializer,
):
class Meta:
model = Ticket
fields = [
'id',
'assigned_teams',
'assigned_users',
'category',
'created',
'modified',
'status',
'title',
'description',
'estimate',
'urgency',
'impact',
'priority',
'external_ref',
'external_system',
'ticket_type',
'is_deleted',
'date_closed',
# 'planned_start_date',
# 'planned_finish_date',
# 'real_start_date',
# 'real_finish_date',
'opened_by',
'organization',
'project',
'milestone',
'subscribed_teams',
'subscribed_users',
'ticket_comments',
'url',
]
read_only_fields = [
'id',
'ticket_type',
'url',
]
def __init__(self, instance=None, data=empty, **kwargs):
super().__init__(instance=instance, data=data, **kwargs)
self.fields.fields['category'].queryset = self.fields.fields['category'].queryset.filter(
problem = True
)

View File

@ -1,74 +0,0 @@
from django.urls import reverse
from rest_framework import serializers
from rest_framework.fields import empty
from project_management.models.projects import Project
from project_management.models.project_milestone import ProjectMilestone
class ProjectMilestoneSerializer(
serializers.ModelSerializer,
):
url = serializers.SerializerMethodField('get_url_project_milestone')
def get_url_project_milestone(self, item):
request = self.context.get('request')
return request.build_absolute_uri(
reverse('v1:_api_project_milestone-detail',
kwargs={
'project_id': item.project.id,
'pk': item.id
}
)
)
class Meta:
model = ProjectMilestone
fields = [
'name',
'description',
'organization',
'project',
'start_date',
'finish_date',
'created',
'modified',
'url',
]
read_only_fields = [
'id',
'url',
]
def __init__(self, instance=None, data=empty, **kwargs):
self.fields.fields['organization'].read_only = True
self.fields.fields['project'].read_only = True
super().__init__(instance=instance, data=data, **kwargs)
def is_valid(self, *, raise_exception=False):
is_valid = super().is_valid(raise_exception=raise_exception)
project = Project.objects.get(
pk = int(self._kwargs['context']['view'].kwargs['project_id'])
)
self._validated_data.update({
'organization': project.organization,
'project': project
})
return is_valid

View File

@ -1,33 +0,0 @@
from django.urls import reverse
from rest_framework import serializers
from rest_framework.fields import empty
from project_management.models.project_states import ProjectState
class ProjectStateSerializer(
serializers.ModelSerializer,
):
url = serializers.HyperlinkedIdentityField(
view_name="v1:_api_project_state-detail", format="html"
)
class Meta:
model = ProjectState
fields = '__all__'
read_only_fields = [
'id',
'url',
]
def __init__(self, instance=None, data=empty, **kwargs):
super().__init__(instance=instance, data=data, **kwargs)

View File

@ -1,63 +0,0 @@
from rest_framework.fields import empty
from api.serializers.core.ticket import TicketSerializer
from core.models.ticket.ticket import Ticket
class ProjectTaskSerializer(
TicketSerializer,
):
class Meta:
model = Ticket
fields = [
'id',
'assigned_teams',
'assigned_users',
'category',
'created',
'modified',
'status',
'title',
'description',
'estimate',
'urgency',
'impact',
'priority',
'external_ref',
'external_system',
'ticket_type',
'is_deleted',
'date_closed',
'planned_start_date',
'planned_finish_date',
'real_start_date',
'real_finish_date',
'opened_by',
'organization',
'project',
'milestone',
'subscribed_teams',
'subscribed_users',
'ticket_comments',
'url',
]
read_only_fields = [
'id',
'ticket_type',
'url',
]
def __init__(self, instance=None, data=empty, **kwargs):
super().__init__(instance=instance, data=data, **kwargs)
self.fields.fields['category'].queryset = self.fields.fields['category'].queryset.filter(
project_task = True
)

View File

@ -1,33 +0,0 @@
from django.urls import reverse
from rest_framework import serializers
from rest_framework.fields import empty
from project_management.models.project_types import ProjectType
class ProjectTypeSerializer(
serializers.ModelSerializer,
):
url = serializers.HyperlinkedIdentityField(
view_name="v1:_api_project_state-detail", format="html"
)
class Meta:
model = ProjectType
fields = '__all__'
read_only_fields = [
'id',
'url',
]
def __init__(self, instance=None, data=empty, **kwargs):
super().__init__(instance=instance, data=data, **kwargs)

View File

@ -1,134 +0,0 @@
from django.urls import reverse
from rest_framework import serializers
from rest_framework.fields import empty
from project_management.models.projects import Project
class ProjectSerializer(
serializers.ModelSerializer,
):
percent_completed = serializers.CharField(
read_only = True,
)
url = serializers.SerializerMethodField('get_url')
def get_url(self, item):
request = self.context.get('request')
return request.build_absolute_uri(reverse("v1:_api_projects-detail", args=[item.pk]))
project_tasks_url = serializers.SerializerMethodField('get_url_project_tasks')
def get_url_project_tasks(self, item):
request = self.context.get('request')
return request.build_absolute_uri(
reverse(
'v1:_api_project_tasks-list',
kwargs={
'project_id': item.id
}
)
)
project_milestone_url = serializers.SerializerMethodField('get_url_project_milestone')
def get_url_project_milestone(self, item):
request = self.context.get('request')
return request.build_absolute_uri(
reverse(
'v1:_api_project_milestone-list',
kwargs={
'project_id': item.id
}
)
)
class Meta:
model = Project
fields = [
'id',
'organization',
'state',
'project_type',
'priority',
'name',
'description',
'code',
'planned_start_date',
'planned_finish_date',
'real_start_date',
'real_finish_date',
'manager_user',
'manager_team',
'team_members',
'project_tasks_url',
'project_milestone_url',
'percent_completed',
'created',
'modified',
'url',
]
read_only_fields = [
'id',
'url',
'created',
'modified',
]
class ProjectImportSerializer(ProjectSerializer):
class Meta:
model = Project
fields = [
'id',
'organization',
'state',
'project_type',
'priority',
'name',
'description',
'code',
'planned_start_date',
'planned_finish_date',
'real_start_date',
'real_finish_date',
'manager_user',
'manager_team',
'team_members',
'project_tasks_url',
'project_milestone_url',
'percent_completed',
'created',
'modified',
'external_ref',
'external_system',
'is_deleted',
'url',
]
read_only_fields = [
'id',
'url',
]

View File

@ -9,7 +9,7 @@ from celery import states
from access.models import Organization
from itam.serializers.inventory import InventorySerializer
from api.serializers.inventory import Inventory
from itam.models.device import Device, DeviceType, DeviceOperatingSystem, DeviceSoftware
from itam.models.operating_system import OperatingSystem, OperatingSystemVersion
@ -32,15 +32,8 @@ def process_inventory(self, data, organization: int):
logger.info('Begin Processing Inventory')
if type(data) is str:
data = json.loads(data)
data = InventorySerializer(
data = data
)
data.is_valid()
data = json.loads(data)
data = Inventory(data)
organization = Organization.objects.get(id=organization)
@ -49,13 +42,13 @@ def process_inventory(self, data, organization: int):
device_serial_number = None
device_uuid = None
if data.validated_data['details']['serial_number'] and str(data.validated_data['details']['serial_number']).lower() != 'na':
if data.details.serial_number and str(data.details.serial_number).lower() != 'na':
device_serial_number = str(data.validated_data['details']['serial_number'])
device_serial_number = str(data.details.serial_number)
if data.validated_data['details']['uuid'] and str(data.validated_data['details']['uuid']).lower() != 'na':
if data.details.uuid and str(data.details.uuid).lower() != 'na':
device_uuid = str(data.validated_data['details']['uuid'])
device_uuid = str(data.details.uuid)
if device_serial_number: # Search for device by serial number.
@ -95,13 +88,13 @@ def process_inventory(self, data, organization: int):
if not device: # Search for device by Name.
device = Device.objects.filter(
name__iexact=str(data.validated_data['details']['name']).lower()
name__iexact=str(data.details.name).lower()
)
if device.exists():
device = Device.objects.get(
name__iexact=str(data.validated_data['details']['name']).lower()
name__iexact=str(data.details.name).lower()
)
else:
@ -114,7 +107,7 @@ def process_inventory(self, data, organization: int):
if not device: # Create the device
device = Device.objects.create(
name = data.validated_data['details']['name'],
name = data.details.name,
device_type = None,
serial_number = device_serial_number,
uuid = device_uuid,
@ -138,14 +131,14 @@ def process_inventory(self, data, organization: int):
if not device.serial_number and device_serial_number:
device.serial_number = data.validated_data['details']['serial_number']
device.serial_number = data.details.serial_number
device_edited = True
if str(device.name).lower() != str(data.validated_data['details']['name']).lower(): # Update device Name
if str(device.name).lower() != str(data.details.name).lower(): # Update device Name
device.name = data.validated_data['details']['name']
device.name = data.details.name
device_edited = True
@ -156,14 +149,14 @@ def process_inventory(self, data, organization: int):
operating_system = OperatingSystem.objects.filter(
name = data.validated_data['os']['name'],
name=data.operating_system.name,
is_global = True
)
if operating_system.exists():
operating_system = OperatingSystem.objects.get(
name = data.validated_data['os']['name'],
name=data.operating_system.name,
is_global = True
)
@ -177,7 +170,7 @@ def process_inventory(self, data, organization: int):
if not operating_system:
operating_system = OperatingSystem.objects.filter(
name = data.validated_data['os']['name'],
name=data.operating_system.name,
organization = organization
)
@ -185,7 +178,7 @@ def process_inventory(self, data, organization: int):
if operating_system.exists():
operating_system = OperatingSystem.objects.get(
name = data.validated_data['os']['name'],
name=data.operating_system.name,
organization = organization
)
@ -197,22 +190,22 @@ def process_inventory(self, data, organization: int):
if not operating_system:
operating_system = OperatingSystem.objects.create(
name = data.validated_data['os']['name'],
name = data.operating_system.name,
organization = organization,
is_global = True
)
operating_system_version = OperatingSystemVersion.objects.filter(
name = data.validated_data['os']['version_major'],
operating_system = operating_system
name=data.operating_system.version_major,
operating_system=operating_system
)
if operating_system_version.exists():
operating_system_version = OperatingSystemVersion.objects.get(
name = data.validated_data['os']['version_major'],
operating_system = operating_system
name=data.operating_system.version_major,
operating_system=operating_system
)
else:
@ -225,7 +218,7 @@ def process_inventory(self, data, organization: int):
operating_system_version = OperatingSystemVersion.objects.create(
organization = organization,
is_global = True,
name = data.validated_data['os']['version_major'],
name = data.operating_system.version_major,
operating_system = operating_system,
)
@ -248,8 +241,8 @@ def process_inventory(self, data, organization: int):
device_operating_system = DeviceOperatingSystem.objects.create(
organization = organization,
device = device,
version = data.validated_data['os']['version'],
device=device,
version = data.operating_system.version,
operating_system_version = operating_system_version,
installdate = timezone.now()
)
@ -268,9 +261,9 @@ def process_inventory(self, data, organization: int):
device_operating_system.save()
if device_operating_system.version != data.validated_data['os']['version']:
if device_operating_system.version != data.operating_system.version:
device_operating_system.version = data.validated_data['os']['version']
device_operating_system.version = data.operating_system.version
device_operating_system.save()
@ -294,7 +287,7 @@ def process_inventory(self, data, organization: int):
inventoried_software: list = []
for inventory in list(data.validated_data['software']):
for inventory in list(data.software):
software = None
software_category = None
@ -302,13 +295,13 @@ def process_inventory(self, data, organization: int):
device_software = None
software_category = SoftwareCategory.objects.filter( name = inventory['category'] )
software_category = SoftwareCategory.objects.filter( name = inventory.category )
if software_category.exists():
software_category = SoftwareCategory.objects.get(
name = inventory['category']
name = inventory.category
)
else: # Create Software Category
@ -316,16 +309,16 @@ def process_inventory(self, data, organization: int):
software_category = SoftwareCategory.objects.create(
organization = software_category_organization,
is_global = True,
name = inventory['category'],
name = inventory.category,
)
if software_category.name == inventory['category']:
if software_category.name == inventory.category:
if Software.objects.filter( name = inventory['name'] ).exists():
if Software.objects.filter( name = inventory.name ).exists():
software = Software.objects.get(
name = inventory['name']
name = inventory.name
)
if not software.category:
@ -338,16 +331,16 @@ def process_inventory(self, data, organization: int):
software = Software.objects.create(
organization = software_organization,
is_global = True,
name = inventory['name'],
name = inventory.name,
category = software_category,
)
if software.name == inventory['name']:
if software.name == inventory.name:
pattern = r"^(\d+:)?(?P<semver>\d+\.\d+(\.\d+)?)"
semver = re.search(pattern, str(inventory['version']), re.DOTALL)
semver = re.search(pattern, str(inventory.version), re.DOTALL)
if semver:
@ -355,7 +348,7 @@ def process_inventory(self, data, organization: int):
semver = semver['semver']
else:
semver = inventory['version']
semver = inventory.version
if SoftwareVersion.objects.filter( name = semver, software = software ).exists():

View File

@ -1,249 +0,0 @@
from rest_framework.relations import Hyperlink
class APICommonFields:
"""Test Cases for fields common to All API responses
Must contain:
- id
- display_name
- _urls
- _urls._self
"""
api_data: object
""" API Response data """
def test_api_field_exists_id(self):
""" Test for existance of API Field
id field must exist
"""
assert 'id' in self.api_data
def test_api_field_type_id(self):
""" Test for type for API Field
id field must be int
"""
assert type(self.api_data['id']) is int
def test_api_field_exists_display_name(self):
""" Test for existance of API Field
display_name field must exist
"""
assert 'display_name' in self.api_data
def test_api_field_type_display_name(self):
""" Test for type for API Field
display_name field must be str
"""
assert type(self.api_data['display_name']) is str
def test_api_field_exists_urls(self):
""" Test for existance of API Field
_urls field must exist
"""
assert '_urls' in self.api_data
def test_api_field_type_urls(self):
""" Test for type for API Field
_urls field must be str
"""
assert type(self.api_data['_urls']) is dict
def test_api_field_exists_urls_self(self):
""" Test for existance of API Field
_urls._self field must exist
"""
assert '_self' in self.api_data['_urls']
def test_api_field_type_urls(self):
""" Test for type for API Field
_urls._self field must be str
"""
assert type(self.api_data['_urls']['_self']) is str
class APIModelFields(
APICommonFields
):
"""Test Cases for fields common to All API Model responses
Must contain:
- id
- display_name
- _urls
- _urls._self
"""
api_data: object
""" API Response data """
def test_api_field_exists_model_notes(self):
""" Test for existance of API Field
model_notes field must exist
"""
assert 'model_notes' in self.api_data
def test_api_field_type_model_notes(self):
""" Test for type for API Field
model_notes field must be str
"""
assert type(self.api_data['model_notes']) is str
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
class APITenancyObject(
APIModelFields
):
api_data: object
""" API Response data """
def test_api_field_exists_organization(self):
""" Test for existance of API Field
organization field must exist
"""
assert 'organization' in self.api_data
def test_api_field_type_organization(self):
""" Test for type for API Field
organization field must be dict
"""
assert type(self.api_data['organization']) is dict
def test_api_field_exists_organization_id(self):
""" Test for existance of API Field
organization.id field must exist
"""
assert 'id' in self.api_data['organization']
def test_api_field_type_organization_id(self):
""" Test for type for API Field
organization.id field must be dict
"""
assert type(self.api_data['organization']['id']) is int
def test_api_field_exists_organization_display_name(self):
""" Test for existance of API Field
organization.display_name field must exist
"""
assert 'display_name' in self.api_data['organization']
def test_api_field_type_organization_display_name(self):
""" Test for type for API Field
organization.display_name field must be str
"""
assert type(self.api_data['organization']['display_name']) is str
def test_api_field_exists_organization_url(self):
""" Test for existance of API Field
organization.url field must exist
"""
assert 'url' in self.api_data['organization']
def test_api_field_type_organization_url(self):
""" Test for type for API Field
organization.url field must be str
"""
assert type(self.api_data['organization']['url']) is Hyperlink

View File

@ -194,7 +194,7 @@ class APIPermissionAdd:
def test_add_has_permission(self):
""" Check correct permission for add
Attempt to add as user with permission
Attempt to add as user with no permission
"""
client = Client()

View File

@ -1,568 +0,0 @@
import pytest
import unittest
from django.shortcuts import reverse
from django.test import TestCase, Client
class APIPermissionView:
model: object
""" Item Model to test """
app_namespace: str = None
""" URL namespace """
url_name: str
""" URL name of the view to test """
url_view_kwargs: dict = None
""" URL kwargs of the item page """
def test_view_user_anon_denied(self):
""" Check correct permission for view
Attempt to view as anon user
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
response = client.get(url)
assert response.status_code == 401
def test_view_no_permission_denied(self):
""" Check correct permission for view
Attempt to view with user missing permission
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.no_permissions_user)
response = client.get(url)
assert response.status_code == 403
def test_view_different_organizaiton_denied(self):
""" Check correct permission for view
Attempt to view with user from different organization
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.different_organization_user)
response = client.get(url)
assert response.status_code == 403
def test_view_has_permission(self):
""" Check correct permission for view
Attempt to view as user with view permission
"""
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)
assert response.status_code == 200
def test_returned_results_only_user_orgs(self):
"""Returned results check
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')
viewable_organizations = [
self.organization.id,
]
if getattr(self, 'global_organization', None): # Cater for above test that also has global org
viewable_organizations += [ self.global_organization.id ]
client.force_login(self.view_user)
response = client.get(url)
contains_different_org: bool = False
for item in response.data['results']:
if int(item['organization']['id']) not in viewable_organizations:
contains_different_org = True
print(f'Failed returned row was: {item}')
assert not contains_different_org
def test_returned_data_from_user_and_global_organizations_only(self):
"""Check items returned
Items returned from the query Must be from the users organization and
global ONLY!
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs=self.url_kwargs)
only_from_user_org: bool = True
viewable_organizations = [
self.organization.id,
self.global_organization.id
]
assert getattr(self.global_organization, 'id', False) # fail if no global org set
assert getattr(self.global_org_item, 'id', False) # fail if no global item set
client.force_login(self.view_user)
response = client.get(url)
assert len(response.data['results']) >= 2 # fail if only one item extist.
for row in response.data['results']:
if row['organization']['id'] not in viewable_organizations:
only_from_user_org = False
print(f'Users org: {self.organization.id}')
print(f'global org: {self.global_organization.id}')
print(f'Failed returned row was: {row}')
assert only_from_user_org
class APIPermissionAdd:
model: object
""" Item Model to test """
app_namespace: str = None
""" URL namespace """
url_list: str
""" URL view name of the item list page """
url_kwargs: dict = None
""" URL view kwargs for the item list page """
add_data: dict = None
def test_add_user_anon_denied(self):
""" Check correct permission for add
Attempt to add as anon user
"""
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')
response = client.put(url, data=self.add_data)
assert response.status_code == 401
# @pytest.mark.skip(reason="ToDO: figure out why fails")
def test_add_no_permission_denied(self):
""" Check correct permission for add
Attempt to add as user with no permissions
"""
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.no_permissions_user)
response = client.post(url, data=self.add_data)
assert response.status_code == 403
# @pytest.mark.skip(reason="ToDO: figure out why fails")
def test_add_different_organization_denied(self):
""" Check correct permission for add
attempt to add as user from different organization
"""
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.different_organization_user)
response = client.post(url, data=self.add_data)
assert response.status_code == 403
def test_add_permission_view_denied(self):
""" Check correct permission for add
Attempt to add a user with view 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.view_user)
response = client.post(url, data=self.add_data)
assert response.status_code == 403
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
class APIPermissionChange:
model: object
""" Item Model to test """
app_namespace: str = None
""" URL namespace """
url_name: str
""" URL name of the view to test """
url_view_kwargs: dict = None
""" URL kwargs of the item page """
change_data: dict = None
def test_change_user_anon_denied(self):
""" Check correct permission for change
Attempt to change as anon
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
response = client.patch(url, data=self.change_data, content_type='application/json')
assert response.status_code == 401
def test_change_no_permission_denied(self):
""" Ensure permission view cant make change
Attempt to make change as user without permissions
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.no_permissions_user)
response = client.patch(url, data=self.change_data, content_type='application/json')
assert response.status_code == 403
def test_change_different_organization_denied(self):
""" Ensure permission view cant make change
Attempt to make change as user from different organization
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.different_organization_user)
response = client.patch(url, data=self.change_data, content_type='application/json')
assert response.status_code == 403
def test_change_permission_view_denied(self):
""" Ensure permission view cant make change
Attempt to make change as user with view permission
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.view_user)
response = client.patch(url, data=self.change_data, content_type='application/json')
assert response.status_code == 403
def test_change_permission_add_denied(self):
""" Ensure permission view cant make change
Attempt to make change as user with add permission
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.add_user)
response = client.patch(url, data=self.change_data, content_type='application/json')
assert response.status_code == 403
def test_change_has_permission(self):
""" Check correct permission for change
Make change with user who has change permission
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.change_user)
response = client.patch(url, data=self.change_data, content_type='application/json')
assert response.status_code == 200
class APIPermissionDelete:
model: object
""" Item Model to test """
app_namespace: str = None
""" URL namespace """
url_name: str
""" URL name of the view to test """
url_view_kwargs: dict = None
""" URL kwargs of the item page """
delete_data: dict = None
def test_delete_user_anon_denied(self):
""" Check correct permission for delete
Attempt to delete item as anon user
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 401
def test_delete_no_permission_denied(self):
""" Check correct permission for delete
Attempt to delete as user with no permissons
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.no_permissions_user)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 403
def test_delete_different_organization_denied(self):
""" Check correct permission for delete
Attempt to delete as user from different organization
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.different_organization_user)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 403
def test_delete_permission_view_denied(self):
""" Check correct permission for delete
Attempt to delete as user with veiw permission only
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.view_user)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 403
def test_delete_permission_add_denied(self):
""" Check correct permission for delete
Attempt to delete as user with add permission only
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.add_user)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 403
def test_delete_has_permission(self):
""" Check correct permission for delete
Delete item as user with delete permission
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.delete_user)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 204
def test_delete_permission_change_denied(self):
""" Check correct permission for delete
Attempt to delete as user with change permission only
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.change_user)
response = client.delete(url, data=self.delete_data)
assert response.status_code == 403
class APIPermissions(
APIPermissionAdd,
APIPermissionChange,
APIPermissionDelete,
APIPermissionView
):
""" Abstract class containing all API Permission test cases """
model: object
""" Item Model to test """

View File

@ -1,163 +0,0 @@
import pytest
import unittest
from django.shortcuts import reverse
from django.test import TestCase, Client
class SerializerView:
model: object
""" Item Model to test """
app_namespace: str = None
""" URL namespace """
url_name: str
""" URL name of the view to test """
url_view_kwargs: dict = None
""" URL kwargs of the item page """
def test_returned_serializer_user_view(self):
""" Check correct Serializer is returned
View action for view user must return `ViewSerializer`
"""
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)
assert str(response.renderer_context['view'].get_serializer().__class__.__name__).endswith('ViewSerializer')
class SerializerAdd:
model: object
""" Item Model to test """
app_namespace: str = None
""" URL namespace """
url_list: str
""" URL view name of the item list page """
url_kwargs: dict = None
""" URL view kwargs for the item list page """
add_data: dict = None
def test_returned_serializer_user_add(self):
""" Check correct Serializer is returned
Add action for add user must return `ModelSerializer`
"""
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 str(response.renderer_context['view'].get_serializer().__class__.__name__).endswith('ModelSerializer')
class SerializerChange:
model: object
""" Item Model to test """
app_namespace: str = None
""" URL namespace """
url_name: str
""" URL name of the view to test """
url_view_kwargs: dict = None
""" URL kwargs of the item page """
change_data: dict = None
def test_returned_serializer_user_change(self):
""" Check correct Serializer is returned
Change action for change user must return `ModelSerializer`
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.change_user)
response = client.patch(url, data=self.change_data, content_type='application/json')
assert str(response.renderer_context['view'].get_serializer().__class__.__name__).endswith('ModelSerializer')
class SerializerDelete:
model: object
""" Item Model to test """
app_namespace: str = None
""" URL namespace """
url_name: str
""" URL name of the view to test """
url_view_kwargs: dict = None
""" URL kwargs of the item page """
delete_data: dict = None
def test_returned_serializer_user_delete(self):
""" Check correct Serializer is returned
Delete action for delete user must return `ModelSerializer`
"""
client = Client()
url = reverse(self.app_namespace + ':' + self.url_name + '-detail', kwargs=self.url_view_kwargs)
client.force_login(self.delete_user)
response = client.delete(url)
assert str(response.renderer_context['view'].get_serializer().__class__.__name__).endswith('ModelSerializer')
class SerializersTestCases(
SerializerAdd,
SerializerChange,
SerializerDelete,
SerializerView
):
""" Abstract class containing all ViewSet test cases """
model: object
""" Item Model to test """

View File

@ -1,829 +0,0 @@
import pytest
from django.conf import settings
from django.test import Client
from rest_framework.reverse import reverse
class MetadataAttributesFunctionalBase:
""" Functional Tests for API, HTTP/Options Method
These tests ensure that **ALL** serializers include the metaclass that adds the required
data to the HTTP Options method.
Metaclass adds data required for the UI to function correctly.
"""
app_namespace: str = None
url_name: str = None
viewset_type: str = 'list'
def test_method_options_request_list_ok(self):
"""Test HTTP/Options Method
Ensure the request returns `OK`.
"""
client = Client()
client.force_login(self.view_user)
if getattr(self, 'url_kwargs', None):
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type)
response = client.options( url, content_type='application/json' )
assert response.status_code == 200
def test_method_options_request_list_data_returned(self):
"""Test HTTP/Options Method
Ensure the request returns data.
"""
client = Client()
client.force_login(self.view_user)
if getattr(self, 'url_kwargs', None):
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type)
response = client.options( url, content_type='application/json' )
assert response.data is not None
def test_method_options_request_list_data_type(self):
"""Test HTTP/Options Method
Ensure the request data returned is of type `dict`
"""
client = Client()
client.force_login(self.view_user)
if getattr(self, 'url_kwargs', None):
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type)
response = client.options( url, content_type='application/json' )
assert type(response.data) is dict
def test_method_options_request_detail_ok(self):
"""Test HTTP/Options Method
Ensure the request returns `OK`.
"""
client = Client()
client.force_login(self.view_user)
if getattr(self, 'url_kwargs', None):
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type)
response = client.options( url, content_type='application/json' )
assert response.status_code == 200
def test_method_options_request_detail_data_returned(self):
"""Test HTTP/Options Method
Ensure the request returns data.
"""
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 response.data is not None
def test_method_options_request_detail_data_type(self):
"""Test HTTP/Options Method
Ensure the request data returned is of type `dict`
"""
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) is dict
def test_method_options_request_detail_data_has_key_urls(self):
"""Test HTTP/Options Method
Ensure the request data returned has key `urls`
"""
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 'urls' in response.data
def test_method_options_request_detail_data_key_urls_is_dict(self):
"""Test HTTP/Options Method
Ensure the request data key `urls` is dict
"""
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']) is dict
def test_method_options_request_detail_data_has_key_urls_self(self):
"""Test HTTP/Options Method
Ensure the request data returned has key `urls.self`
"""
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 'urls' in response.data
def test_method_options_request_detail_data_key_urls_self_is_str(self):
"""Test HTTP/Options Method
Ensure the request data key `urls.self` is a string
"""
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']['self']) is str
@pytest.mark.skip(reason='to be written')
def test_method_options_no_field_is_generic(self):
"""Test HTTP/Options Method
Fields are used for the UI to setup inputs correctly.
Ensure all fields at path `.actions.<METHOD>.<name>.type` do not have `GenericField` as the value.
"""
pass
def test_method_options_request_detail_data_has_key_documentation(self):
"""Test HTTP/Options Method
Ensure the request data returned has key `documentation`
"""
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 'documentation' in response.data
def test_method_options_request_detail_data_key_documentation_is_str(self):
"""Test HTTP/Options Method
Ensure the request data key `documentation` 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['documentation']) is str
def test_method_options_request_detail_data_key_documentation_is_url(self):
"""Test HTTP/Options Method
Ensure the request data key `documentation` is prefixed with settings.DOC_ROOT
"""
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 str(response.data['documentation']).startswith( str(settings.DOCS_ROOT) )
class MetadataAttributesFunctionalTable:
"""Test cases for Metadata
These test cases are for models that are expected to
be rendered in a table.
"""
def test_method_options_request_list_data_has_key_table_fields(self):
"""Test HTTP/Options Method
Ensure the request data returned has key `table_fields`
"""
client = Client()
client.force_login(self.view_user)
if getattr(self, 'url_kwargs', None):
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type)
response = client.options( url, content_type='application/json' )
assert 'table_fields' in response.data
def test_method_options_request_list_data_key_table_fields_is_list(self):
"""Test HTTP/Options Method
Ensure the request data['table_fields'] is of type `list`
"""
client = Client()
client.force_login(self.view_user)
if getattr(self, 'url_kwargs', None):
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type)
response = client.options( url, content_type='application/json' )
assert type(response.data['table_fields']) is list
def test_method_options_request_list_data_key_table_fields_is_list_of_str(self):
"""Test HTTP/Options Method
Ensure the request data['table_fields'] list is of `str`
"""
client = Client()
client.force_login(self.view_user)
if getattr(self, 'url_kwargs', None):
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs)
else:
url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type)
response = client.options( url, content_type='application/json' )
all_string = True
for item in response.data['table_fields']:
if type(item) is not str:
all_string = False
assert all_string
class MetadataAttributesFunctionalEndpoint:
"""Test cases for Metadata
These test cases are for models that will have an
endpoint. i.e. A Detail view
"""
def test_method_options_request_detail_data_has_key_page_layout(self):
"""Test HTTP/Options Method
Ensure the request data returned has key `layout`
"""
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 'layout' in response.data
def test_method_options_request_detail_data_key_page_layout_is_list(self):
"""Test HTTP/Options Method
Ensure the request data['layout'] is of type `list`
"""
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['layout']) is list
def test_method_options_request_detail_data_key_page_layout_is_list_of_dict(self):
"""Test HTTP/Options Method
Ensure the request data['layout'] list is of `dict`
"""
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'
)
all_dict = True
for item in response.data['layout']:
if type(item) is not dict:
all_dict = False
assert all_dict
def test_method_options_request_detail_data_key_page_layout_dicts_key_exists_name(self):
"""Test HTTP/Options Method
Ensure the request data['layout'].x has key `name`
"""
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'
)
has_key = True
for item in response.data['layout']:
if 'name' not in item:
has_key = False
assert has_key
def test_method_options_request_detail_data_key_page_layout_dicts_key_type_name(self):
"""Test HTTP/Options Method
Ensure the request data['layout'].x.[name] is of type `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'
)
all_are_str = True
for item in response.data['layout']:
if type(item['name']) is not str:
all_are_str = False
assert all_are_str
def test_method_options_request_detail_data_key_page_layout_dicts_key_exists_sections(self):
"""Test HTTP/Options Method
Ensure the request data['layout'].x has key `sections`
"""
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'
)
has_key = True
for item in response.data['layout']:
if 'sections' not in item:
has_key = False
assert has_key
def test_method_options_request_detail_data_key_page_layout_dicts_key_type_sections(self):
"""Test HTTP/Options Method
Ensure the request data['layout'].x.[sections] is of type `list`
"""
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'
)
all_are_str = True
for item in response.data['layout']:
if type(item['sections']) is not list:
all_are_str = False
assert all_are_str
class MetadataAttributesFunctional(
MetadataAttributesFunctionalEndpoint,
MetadataAttributesFunctionalTable,
MetadataAttributesFunctionalBase,
):
pass
class MetaDataNavigationEntriesFunctional:
""" Test cases for the Navigation menu
Navigation menu is rendered as part of the API when a HTTP/OPTIONS
request has been made. Each menu entry requires that a user has View
permissions for that entry to be visible.
**No** menu entry is to be returned for **any** user whom does not
have the corresponding view permission.
These test cases are for any model that has a navigation menu entry.
## Tests
- Ensure add user does not have menu entry
- Ensure change user does not have menu entry
- Ensure delete user does not have menu entry
- Ensure the view user has menu entry
- No menu to return without pages for add user
- No menu to return without pages for change user
- No menu to return without pages for delete user
- No menu to return without pages for view user
"""
menu_id: str = None
""" Name of the Menu entry
Match for .navigation[i][name]
"""
menu_entry_id: str = None
"""Name of the menu entry
Match for .navigation[i][pages][i][name]
"""
app_namespace:str = None
"""application namespace"""
url_name: str = None
"""url name"""
url_kwargs: dict = None
"""View URL kwargs"""
add_user = None
""" User with add permission"""
change_user = None
""" User with change permission"""
delete_user = None
""" User with delete permission"""
view_user = None
""" User with view permission"""
def test_navigation_entry_add_user(self):
"""Test HTTP/Options Method Navigation Entry
Ensure that a user with add permission, does not
have the menu entry within navigation
"""
client = Client()
client.force_login(self.add_user)
if getattr(self, 'url_kwargs', None):
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 response.status_code == 403
def test_navigation_entry_change_user(self):
"""Test HTTP/Options Method Navigation Entry
Ensure that a user with change permission, does not
have the menu entry within navigation
"""
client = Client()
client.force_login(self.change_user)
if getattr(self, 'url_kwargs', None):
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 response.status_code == 403
def test_navigation_entry_delete_user(self):
"""Test HTTP/Options Method Navigation Entry
Ensure that a user with delete permission, does not
have the menu entry within navigation
"""
client = Client()
client.force_login(self.delete_user)
if getattr(self, 'url_kwargs', None):
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 response.status_code == 403
def test_navigation_entry_view_user(self):
"""Test HTTP/Options Method Navigation Entry
Ensure that a user with view permission,
has the menu entry within navigation
"""
client = Client()
client.force_login(self.view_user)
if getattr(self, 'url_kwargs', None):
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'
)
menu_entry_found: bool = False
for nav_menu in response.data['navigation']:
if nav_menu['name'] == self.menu_id:
for menu_entry in nav_menu['pages']:
if menu_entry['name'] == self.menu_entry_id:
menu_entry_found = True
assert menu_entry_found
def test_navigation_no_empty_menu_view_user(self):
"""Test HTTP/Options Method Navigation Entry
Ensure that a user with view permission, does not
have any nave menu without pages
"""
client = Client()
client.force_login(self.view_user)
if getattr(self, 'url_kwargs', None):
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'
)
no_empty_menu_found: bool = True
for nav_menu in response.data['navigation']:
if len(nav_menu['pages']) == 0:
no_empty_menu_found = False
assert no_empty_menu_found

View File

@ -1,817 +0,0 @@
from django.contrib.auth.models import ContentType, User
from unittest.mock import patch, PropertyMock
from access.mixins.permissions import OrganizationPermissionMixin
from api.react_ui_metadata import ReactUIMetadata
from access.middleware.request import Tenancy
from access.models import Organization, Team, TeamUsers, Permission
from settings.models.app_settings import AppSettings
class MockRequest:
"""Fake Request
contains the user and tenancy object for permission checks
Some ViewSets rely upon the request object for obtaining the user and
fetching the tenacy object for permission checking.
"""
data = {}
kwargs = {}
tenancy: Tenancy = None
user: User = None
def __init__(self, user: User, organization: Organization, viewset):
self.user = user
view_permission = Permission.objects.get(
codename = 'view_' + viewset.model._meta.model_name,
content_type = ContentType.objects.get(
app_label = viewset.model._meta.app_label,
model = viewset.model._meta.model_name,
)
)
view_team = Team.objects.create(
team_name = 'view_team',
organization = organization,
)
view_team.permissions.set([view_permission])
teamuser = TeamUsers.objects.create(
team = view_team,
user = user
)
self.app_settings = AppSettings.objects.select_related('global_organization').get(
owner_organization = None
)
self.tenancy = Tenancy(
user = user,
app_settings = self.app_settings
)
class AllViewSet:
"""Tests specific to the Viewset
**Dont include these tests directly, see below for correct class**
Tests are for ALL viewsets.
"""
viewset = None
"""ViewSet to Test"""
def test_view_attr_allowed_methods_exists(self):
"""Attribute Test
Attribute `allowed_methods` must exist
"""
assert hasattr(self.viewset, 'allowed_methods')
def test_view_attr_allowed_methods_not_empty(self):
"""Attribute Test
Attribute `allowed_methods` must return a value
"""
view_set = self.viewset()
view_set.kwargs = self.kwargs
assert view_set.allowed_methods is not None
def test_view_attr_allowed_methods_type(self):
"""Attribute Test
Attribute `allowed_methods` must be of type list
"""
view_set = self.viewset()
view_set.kwargs = self.kwargs
assert type(view_set.allowed_methods) is list
def test_view_attr_allowed_methods_values(self):
"""Attribute Test
Attribute `allowed_methods` only contains valid values
"""
# Values valid for index views
valid_values: list = [
'GET',
'HEAD',
'OPTIONS',
]
all_valid: bool = True
view_set = self.viewset()
for method in list(view_set.allowed_methods):
if method not in valid_values:
all_valid = False
assert all_valid
def test_view_attr_metadata_class_exists(self):
"""Attribute Test
Attribute `metadata_class` must exist
"""
assert hasattr(self.viewset, 'metadata_class')
def test_view_attr_metadata_class_not_empty(self):
"""Attribute Test
Attribute `metadata_class` must return a value
"""
view_set = self.viewset()
assert view_set.metadata_class is not None
def test_view_attr_metadata_class_type(self):
"""Attribute Test
Attribute `metadata_class` must be metadata class `ReactUIMetadata`
"""
view_set = self.viewset()
assert view_set.metadata_class is ReactUIMetadata
def test_view_attr_permission_classes_exists(self):
"""Attribute Test
Attribute `permission_classes` must exist
"""
assert hasattr(self.viewset, 'permission_classes')
def test_view_attr_permission_classes_not_empty(self):
"""Attribute Test
Attribute `permission_classes` must return a value
"""
view_set = self.viewset()
assert view_set.permission_classes is not None
def test_view_attr_permission_classes_type(self):
"""Attribute Test
Attribute `permission_classes` must be list
"""
view_set = self.viewset()
assert type(view_set.permission_classes) is list
def test_view_attr_permission_classes_value(self):
"""Attribute Test
Attribute `permission_classes` must be metadata class `ReactUIMetadata`
"""
view_set = self.viewset()
assert view_set.permission_classes[0] is OrganizationPermissionMixin
assert len(view_set.permission_classes) == 1
def test_view_attr_view_description_exists(self):
"""Attribute Test
Attribute `view_description` must exist
"""
assert hasattr(self.viewset, 'view_description')
def test_view_attr_view_description_not_empty(self):
"""Attribute Test
Attribute `view_description` must return a value
"""
assert self.viewset.view_description is not None
def test_view_attr_view_description_type(self):
"""Attribute Test
Attribute `view_description` must be of type str
"""
assert type(self.viewset.view_description) is str
def test_view_attr_view_name_exists(self):
"""Attribute Test
Attribute `view_name` must exist
"""
assert hasattr(self.viewset, 'view_name')
def test_view_attr_view_name_not_empty(self):
"""Attribute Test
Attribute `view_name` must return a value
"""
assert self.viewset.view_name is not None
def test_view_attr_view_name_type(self):
"""Attribute Test
Attribute `view_name` must be of type str
"""
view_set = self.viewset()
assert (
type(view_set.view_name) is str
)
class APIRenderViewSet:
"""Function ViewSet test
**Dont include these tests directly, see below for correct class**
These tests ensure that the data from the ViewSet is present for a
HTTP Request
"""
http_options_response_list: dict = None
"""The HTTP/Options Response for the ViewSet"""
def test_api_render_field_allowed_methods_exists(self):
"""Attribute Test
Attribute `allowed_methods` must exist
"""
assert 'allowed_methods' in self.http_options_response_list.data
def test_api_render_field_allowed_methods_not_empty(self):
"""Attribute Test
Attribute `allowed_methods` must return a value
"""
assert len(self.http_options_response_list.data['allowed_methods']) > 0
def test_api_render_field_allowed_methods_type(self):
"""Attribute Test
Attribute `allowed_methods` must be of type list
"""
assert type(self.http_options_response_list.data['allowed_methods']) is list
def test_api_render_field_allowed_methods_values(self):
"""Attribute Test
Attribute `allowed_methods` only contains valid values
"""
# Values valid for index views
valid_values: list = [
'GET',
'HEAD',
'OPTIONS',
]
all_valid: bool = True
for method in list(self.http_options_response_list.data['allowed_methods']):
if method not in valid_values:
all_valid = False
assert all_valid
def test_api_render_field_view_description_exists(self):
"""Attribute Test
Attribute `description` must exist
"""
assert 'description' in self.http_options_response_list.data
def test_api_render_field_view_description_not_empty(self):
"""Attribute Test
Attribute `view_description` must return a value
"""
assert self.http_options_response_list.data['description'] is not None
def test_api_render_field_view_description_type(self):
"""Attribute Test
Attribute `view_description` must be of type str
"""
assert type(self.http_options_response_list.data['description']) is str
def test_api_render_field_view_name_exists(self):
"""Attribute Test
Attribute `view_name` must exist
"""
assert 'name' in self.http_options_response_list.data
def test_api_render_field_view_name_not_empty(self):
"""Attribute Test
Attribute `view_name` must return a value
"""
assert self.http_options_response_list.data['name'] is not None
def test_api_render_field_view_name_type(self):
"""Attribute Test
Attribute `view_name` must be of type str
"""
assert type(self.http_options_response_list.data['name']) is str
class ModelViewSet(AllViewSet):
"""Tests for Model Viewsets
**Dont include these tests directly, see below for correct class**
"""
viewset = None
"""ViewSet to Test"""
def test_view_attr_documentation_exists(self):
"""Attribute Test
Attribute `documentation` must exist
"""
assert hasattr(self.viewset, 'documentation')
def test_view_attr_documentation_type(self):
"""Attribute Test
Attribute `documentation` must be of type str or None.
this attribute is optional.
"""
view_set = self.viewset()
assert (
type(view_set.documentation) is str
or view_set.documentation is None
)
def test_view_attr_filterset_fields_exists(self):
"""Attribute Test
Attribute `filterset_fields` must exist
"""
assert hasattr(self.viewset, 'filterset_fields')
def test_view_attr_filterset_fields_not_empty(self):
"""Attribute Test
Attribute `filterset_fields` must return a value
"""
assert self.viewset.filterset_fields is not None
def test_view_attr_filterset_fields_type(self):
"""Attribute Test
Attribute `filterset_fields` must be of type list
"""
view_set = self.viewset()
assert (
type(view_set.filterset_fields) is list
)
def test_view_attr_allowed_methods_values(self):
"""Attribute Test
Attribute `allowed_methods` only contains valid values
"""
# Values valid for model views
valid_values: list = [
'DELETE',
'GET',
'HEAD',
'OPTIONS',
'PATCH',
'POST',
'PUT',
]
all_valid: bool = True
view_set = self.viewset()
view_set.kwargs = self.kwargs
for method in list(view_set.allowed_methods):
if method not in valid_values:
all_valid = False
assert all_valid
def test_view_attr_model_exists(self):
"""Attribute Test
Attribute `model` must exist
"""
assert hasattr(self.viewset, 'model')
def test_view_attr_model_not_empty(self):
"""Attribute Test
Attribute `model` must return a value
"""
view_set = self.viewset()
assert view_set.model is not None
def test_view_attr_search_fields_exists(self):
"""Attribute Test
Attribute `search_fields` must exist
"""
assert hasattr(self.viewset, 'search_fields')
def test_view_attr_search_fields_not_empty(self):
"""Attribute Test
Attribute `search_fields` must return a value
"""
assert self.viewset.search_fields is not None
def test_view_attr_search_fields_type(self):
"""Attribute Test
Attribute `search_fields` must be of type list
"""
view_set = self.viewset()
assert (
type(view_set.search_fields) is list
)
def test_view_attr_view_name_not_empty(self):
"""Attribute Test
Attribute `view_name` must return a value
"""
view_set = self.viewset()
assert (
view_set.view_name is not None
or view_set.get_view_name() is not None
)
def test_view_attr_view_name_type(self):
"""Attribute Test
Attribute `view_name` must be of type str
"""
view_set = self.viewset()
assert (
type(view_set.view_name) is str
or type(view_set.get_view_name()) is str
)
class APIRenderModelViewSet(APIRenderViewSet):
"""Tests for Model Viewsets
**Dont include these tests directly, see below for correct class**
"""
viewset = None
"""ViewSet to Test"""
def test_api_render_field_allowed_methods_values(self):
"""Attribute Test
Attribute `allowed_methods` only contains valid values
"""
# Values valid for model views
valid_values: list = [
'DELETE',
'GET',
'HEAD',
'OPTIONS',
'PATCH',
'POST',
'PUT',
]
all_valid: bool = True
for method in list(self.http_options_response_list.data['allowed_methods']):
if method not in valid_values:
all_valid = False
assert all_valid
class ViewSetCommon(
AllViewSet,
APIRenderViewSet
):
""" Tests for Non-Model Viewsets
**Include this class directly into Non-Model ViewSets**
Args:
AllViewSet (class): Tests for all Viewsets.
APIRenderViewSet (class): Tests to check API Rendering to ensure data present.
"""
pass
class ViewSetModel(
ModelViewSet,
APIRenderModelViewSet
):
"""Tests for model ViewSets
**Include this class directly into Model ViewSets**
Args:
ModelViewSet (class): Tests for Model Viewsets, includes `AllViewSet` tests.
APIRenderModelViewSet (class): Tests to check API rendering to ensure data is present, includes `APIRenderViewSet` tests.
"""
def test_view_func_get_queryset_cache_result(self):
"""Viewset Test
Ensure that the `get_queryset` function caches the result under
attribute `<viewset>.queryset`
"""
view_set = self.viewset()
view_set.request = MockRequest(
user = self.view_user,
organization = self.organization,
viewset = self.viewset
)
view_set.kwargs = self.kwargs
view_set.action = 'list'
view_set.detail = False
assert view_set.queryset is None # Must be empty before init
q = view_set.get_queryset()
assert view_set.queryset is not None # Must not be empty after init
assert q == view_set.queryset
def test_view_func_get_queryset_cache_result_used(self):
"""Viewset Test
Ensure that the `get_queryset` function caches the result under
attribute `<viewset>.queryset`
"""
view_set = self.viewset()
view_set.request = MockRequest(
user = self.view_user,
organization = self.organization,
viewset = self.viewset
)
view_set.kwargs = self.kwargs
view_set.action = 'list'
view_set.detail = False
mock_return = view_set.get_queryset() # Real item to be used as mock return Some
# functions use `Queryset` for additional filtering
setter_not_called = True
with patch.object(self.viewset, 'queryset', new_callable=PropertyMock) as qs:
qs.return_value = mock_return
mocked_view_set = self.viewset()
mocked_view_set.kwargs = self.kwargs
mocked_view_set.action = 'list'
mocked_view_set.detail = False
qs.reset_mock() # Just in case
mocked_setup = mocked_view_set.get_queryset() # should only add two calls, if exists and the return
for mock_call in list(qs.mock_calls): # mock_calls with args means setter was called
if len(mock_call.args) > 0:
setter_not_called = False
assert setter_not_called
assert qs.call_count == 2
def test_view_func_get_serializer_class_cache_result(self):
"""Viewset Test
Ensure that the `get_serializer_class` function caches the result under
attribute `<viewset>.serializer_class`
"""
view_set = self.viewset()
view_set.request = MockRequest(
user = self.view_user,
organization = self.organization,
viewset = self.viewset
)
view_set.kwargs = self.kwargs
view_set.action = 'list'
view_set.detail = False
assert view_set.serializer_class is None # Must be empty before init
q = view_set.get_serializer_class()
assert view_set.serializer_class is not None # Must not be empty after init
assert q == view_set.serializer_class
def test_view_func_get_serializer_class_cache_result_used(self):
"""Viewset Test
Ensure that the `get_serializer_class` function caches the result under
attribute `<viewset>.serializer_class`
"""
view_set = self.viewset()
view_set.request = MockRequest(
user = self.view_user,
organization = self.organization,
viewset = self.viewset
)
view_set.kwargs = self.kwargs
view_set.action = 'list'
view_set.detail = False
mock_return = view_set.get_serializer_class() # Real item to be used as mock return Some
# functions use `Queryset` for additional filtering
setter_not_called = True
with patch.object(self.viewset, 'serializer_class', new_callable=PropertyMock) as qs:
qs.return_value = mock_return
mocked_view_set = self.viewset()
mocked_view_set.kwargs = self.kwargs
mocked_view_set.action = 'list'
mocked_view_set.detail = False
qs.reset_mock() # Just in case
mocked_setup = mocked_view_set.get_serializer_class() # should only add two calls, if exists and the return
for mock_call in list(qs.mock_calls): # mock_calls with args means setter was called
if len(mock_call.args) > 0:
setter_not_called = False
assert setter_not_called
assert qs.call_count == 2

View File

@ -16,10 +16,11 @@ from access.models import Organization, Team, TeamUsers, Permission
from api.views.mixin import OrganizationPermissionAPI
from api.serializers.inventory import Inventory
from api.tasks import process_inventory
from itam.models.device import Device, DeviceOperatingSystem, DeviceSoftware
from itam.models.operating_system import OperatingSystem, OperatingSystemVersion
from itam.models.software import Software, SoftwareCategory, SoftwareVersion
from itam.tasks.inventory import process_inventory
from settings.models.user_settings import UserSettings
@ -159,7 +160,7 @@ class InventoryAPI(TestCase):
"""
client = Client()
url = reverse('v1:_api_device_inventory')
url = reverse('API:_api_device_inventory')
client.force_login(self.add_user)
response = client.post(url, data=self.inventory, content_type='application/json')
@ -181,7 +182,7 @@ class InventoryAPI(TestCase):
"""
client = Client()
url = reverse('v1:_api_device_inventory')
url = reverse('API:_api_device_inventory')
client.force_login(self.add_user)
response = client.post(url, data=self.inventory, content_type='application/json')
@ -200,7 +201,7 @@ class InventoryAPI(TestCase):
"""
client = Client()
url = reverse('v1:_api_device_inventory')
url = reverse('API:_api_device_inventory')
client.force_login(self.add_user)
response = client.post(url, data=self.inventory, content_type='application/json')
@ -219,7 +220,7 @@ class InventoryAPI(TestCase):
"""
client = Client()
url = reverse('v1:_api_device_inventory')
url = reverse('API:_api_device_inventory')
client.force_login(self.add_user)
response = client.post(url, data=self.inventory, content_type='application/json')
@ -238,7 +239,7 @@ class InventoryAPI(TestCase):
"""
client = Client()
url = reverse('v1:_api_device_inventory')
url = reverse('API:_api_device_inventory')
client.force_login(self.add_user)
response = client.post(url, data=self.inventory, content_type='application/json')
@ -394,7 +395,7 @@ class InventoryAPI(TestCase):
""" Successful inventory upload returns 200 for existing device"""
client = Client()
url = reverse('v1:_api_device_inventory')
url = reverse('API:_api_device_inventory')
client.force_login(self.add_user)
response = client.post(url, data=self.inventory, content_type='application/json')
@ -408,7 +409,7 @@ class InventoryAPI(TestCase):
""" Incorrectly formated inventory upload returns 400 """
client = Client()
url = reverse('v1:_api_device_inventory')
url = reverse('API:_api_device_inventory')
mod_inventory = self.inventory.copy()
@ -501,8 +502,7 @@ class InventoryAPIDifferentNameSerialNumberMatch(TestCase):
Device.objects.create(
name='random device name',
serial_number='serial_number_123',
organization = organization,
serial_number='serial_number_123'
)
add_permissions = Permission.objects.get(
@ -537,7 +537,7 @@ class InventoryAPIDifferentNameSerialNumberMatch(TestCase):
process_inventory(json.dumps(self.inventory), organization.id)
self.device = Device.objects.get(name=self.inventory['details']['name'], organization = organization)
self.device = Device.objects.get(name=self.inventory['details']['name'])
self.operating_system = OperatingSystem.objects.get(name=self.inventory['os']['name'])
@ -778,8 +778,7 @@ class InventoryAPIDifferentNameUUIDMatch(TestCase):
Device.objects.create(
name='random device name',
uuid='123-456-789',
organization = organization,
uuid='123-456-789'
)
add_permissions = Permission.objects.get(

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