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
771 changed files with 2558 additions and 86199 deletions

View File

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

View File

@ -32,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",
]
}

36
.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,41 +16,9 @@
"autoStartBrowser": false,
"program": "${workspaceFolder}/app/manage.py"
},
{
"name": "Debug: Gunicorn",
"type": "debugpy",
"request": "launch",
"module": "gunicorn",
"args": [
"--access-logfile",
"-",
"--workers",
"3",
"--bind",
"0.0.0.0:8002",
"app.wsgi:application",
],
"django": true,
"autoStartBrowser": false,
"cwd": "${workspaceFolder}/app"
},
{
"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

@ -17,5 +17,4 @@
"ITSM"
],
"cSpell.language": "en-AU",
"jest.enable": false,
}

File diff suppressed because it is too large Load Diff

View File

@ -1,84 +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`
# 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,49 +1,7 @@
## 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

@ -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',
'settings',
]
exclude_models = [
'appsettings',
'chordcounter',
'comment',
'groupresult',
'organization'
'settings',
'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,17 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-13 06:42
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('access', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='team',
options={'ordering': ['team_name'], 'verbose_name': 'Team', 'verbose_name_plural': 'Teams'},
),
]

View File

@ -1,57 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-13 15:27
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', '0002_alter_team_options'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
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', null=True, 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(default='', help_text='Name to give this team', max_length=50, verbose_name='Name'),
),
]

View File

@ -1,44 +0,0 @@
# Generated by Django 5.1.2 on 2024-10-16 06:54
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0003_alter_organization_id_alter_organization_manager_and_more'),
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='teamusers',
options={'ordering': ['user'], 'verbose_name': 'Team User', 'verbose_name_plural': 'Team Users'},
),
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,18 +0,0 @@
# Generated by Django 5.1.2 on 2024-11-07 06:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0004_alter_organization_options_alter_teamusers_options_and_more'),
]
operations = [
migrations.AlterField(
model_name='team',
name='team_name',
field=models.CharField(help_text='Name to give this team', max_length=50, verbose_name='Name'),
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 5.1.2 on 2024-11-20 02:41
import access.models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0005_alter_team_team_name'),
]
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.TenancyObject.validatate_organization_exists], verbose_name='Organization'),
),
]

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

@ -3,8 +3,6 @@ from django.db import models
from django.contrib.auth.models import User, Group, Permission
from django.forms import ValidationError
from rest_framework.reverse import reverse
from .fields import *
from core.middleware.get_request import get_request
@ -14,7 +12,6 @@ from core.mixin.history_save import SaveHistory
class Organization(SaveHistory):
class Meta:
verbose_name = "Organization"
verbose_name_plural = "Organizations"
ordering = ['name']
@ -26,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',
)
@ -71,59 +62,6 @@ class Organization(SaveHistory):
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": "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):
@ -168,9 +106,7 @@ class TenancyManager(models.Manager):
if request:
# user = request.user._wrapped if hasattr(request.user,'_wrapped') else request.user
user = request.user
user = request.user._wrapped if hasattr(request.user,'_wrapped') else request.user
if user.is_authenticated:
@ -188,22 +124,13 @@ class TenancyManager(models.Manager):
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()
@ -238,36 +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,
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',
)
@ -275,59 +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):
if self.organization is None:
raise ValidationError('Organization not defined')
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):
@ -338,79 +205,17 @@ class Team(Group, TenancyObject):
team_name = models.CharField(
verbose_name = 'Name',
blank = False,
help_text = 'Name to give this team',
max_length = 50,
unique = False,
verbose_name = 'Name',
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": "user",
},
]
},
{
"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):
@ -441,56 +246,36 @@ class Team(Group, TenancyObject):
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 = []
def delete(self, using=None, keep_parents=False):
""" Delete Team
@ -512,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

View File

@ -1,89 +0,0 @@
from rest_framework.reverse import reverse
from rest_framework import serializers
from access.models import Organization
from api.serializers import common
from app.serializers.user import UserBaseSerializer
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(
common.CommonModelSerializer,
OrganizationBaseSerializer
):
_urls = serializers.SerializerMethodField('get_url')
def get_url(self, item) -> dict:
return {
'_self': item.get_url( request = self._context['view'].request ),
'teams': reverse("v2:_api_v2_organization_team-list", request=self._context['view'].request, kwargs={'organization_id': item.pk}),
}
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,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,126 +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.serializers.organization import OrganizationBaseSerializer
from app.serializers.permission import PermissionBaseSerializer
from core import fields as centurion_field
class TeamBaseSerializer(serializers.ModelSerializer):
display_name = serializers.SerializerMethodField('get_display_name')
def get_display_name(self, item) -> str:
return str( item )
url = serializers.SerializerMethodField('get_url')
def get_url(self, item) -> str:
return item.get_url( request = self.context['view'].request )
class Meta:
model = Team
fields = [
'id',
'display_name',
'team_name',
'url',
]
read_only_fields = [
'id',
'display_name',
'team_name',
'url',
]
class TeamModelSerializer(
common.CommonModelSerializer,
TeamBaseSerializer
):
_urls = serializers.SerializerMethodField('get_url')
def get_url(self, item) -> dict:
return {
'_self': item.get_url( request = self._context['view'].request ),
'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 )
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,280 +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
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_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.super_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
class OrganizationViewSet(
ViewSetBase,
SerializersTestCases,
TestCase
):
pass

View File

@ -1,201 +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 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
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,
):
pass
class TeamViewSet(
ViewSetBase,
SerializersTestCases,
TestCase,
):
pass

View File

@ -1,180 +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
from access.serializers.teams import (
Team,
TeamModelSerializer
)
class MockView:
action: str = None
kwargs: dict = {}
class MockRequest:
user = None
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()
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_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()
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_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()
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_request,
'view': mock_view,
},
data = data
)
assert serializer.is_valid(raise_exception = True)

View File

@ -1,211 +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 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
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
class TeamUserViewSet(
ViewSetBase,
SerializersTestCases,
TestCase
):
pass

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'

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

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

@ -6,13 +6,9 @@ from django.contrib.auth.models import User
from access.models import Organization, Team, TeamUsers, Permission
from app.tests.abstract.models import BaseModel
class TeamUsersModel(
TestCase,
BaseModel
):
class TeamUsersModel(TestCase):
model = TeamUsers

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

@ -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,42 +0,0 @@
from django.contrib.auth.models import User
from django.shortcuts import reverse
from django.test import Client, TestCase
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)

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 CommonViewSet
@extend_schema(exclude = True)
class Index(CommonViewSet):
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,89 +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
documentation: str = ''
view_description = 'Centurion Organizations'
def get_serializer_class(self):
if (
self.action == 'list'
or self.action == 'retrieve'
):
return globals()[str( self.model._meta.verbose_name) + 'ViewSerializer']
return globals()[str( self.model._meta.verbose_name) + 'ModelSerializer']

View File

@ -1,148 +0,0 @@
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiResponse
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
documentation: str = ''
view_description = 'Teams belonging to a single organization'
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.filter(organization_id=self.kwargs['organization_id'])
self.queryset = queryset
return self.queryset
def get_serializer_class(self):
if (
self.action == 'list'
or self.action == 'retrieve'
):
return globals()[str( self.model._meta.verbose_name) + 'ViewSerializer']
return globals()[str( self.model._meta.verbose_name) + 'ModelSerializer']

View File

@ -1,174 +0,0 @@
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiResponse
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
documentation: str = ''
view_description = 'Users belonging to a single team'
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.filter(
team_id = self.kwargs['team_id']
)
self.queryset = queryset
return self.queryset
def get_serializer_class(self):
if (
self.action == 'list'
or self.action == 'retrieve'
):
return globals()[str( self.model._meta.verbose_name).replace(' ', '') + 'ViewSerializer']
return globals()[str( self.model._meta.verbose_name).replace(' ', '') + 'ModelSerializer']

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-10-13 15:27
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,356 +0,0 @@
from django.utils.encoding import force_str
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 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 'pk' in view.kwargs:
if view.kwargs['pk']:
qs = view.get_queryset()[0]
if hasattr(qs, 'get_url'):
metadata['return_url'] = qs.get_url( request )
elif view.kwargs:
metadata['return_url'] = reverse('v2:' + view.basename + '-list', request = view.request, kwargs = view.kwargs )
else:
metadata['return_url'] = reverse('v2:' + view.basename + '-list', request = view.request )
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()
if hasattr(view, 'get_model_documentation'):
if view.get_model_documentation():
metadata['documentation'] = view.get_model_documentation()
elif view.suffix == 'List':
if hasattr(view, 'table_fields'):
metadata['table_fields'] = view.get_table_fields()
if view.documentation:
metadata['documentation'] = view.documentation
if hasattr(view, 'page_layout'):
metadata['layout'] = view.get_page_layout()
metadata['navigation'] = [
{
"display_name": "Access",
"name": "access",
"pages": [
{
"display_name": "Organization",
"name": "organization",
"link": "/access/organization"
}
]
},
{
"display_name": "Assistance",
"name": "assistance",
"pages": [
{
"display_name": "Requests",
"name": "request",
"icon": "ticket_request",
"link": "/assistance/ticket/request"
},
{
"display_name": "Knowledge Base",
"name": "knowledge_base",
"icon": "information",
"link": "/assistance/knowledge_base"
}
]
},
{
"display_name": "ITAM",
"name": "itam",
"pages": [
{
"display_name": "Devices",
"name": "device",
"icon": "device",
"link": "/itam/device"
},
{
"display_name": "Operating System",
"name": "operating_system",
"link": "/itam/operating_system"
},
{
"display_name": "Software",
"name": "software",
"link": "/itam/software"
}
]
},
{
"display_name": "ITIM",
"name": "itim",
"pages": [
{
"display_name": "Changes",
"name": "ticket_change",
"link": "/itim/ticket/change"
},
{
"display_name": "Clusters",
"name": "cluster",
"link": "/itim/cluster"
},
{
"display_name": "Incidents",
"name": "ticket_incident",
"link": "/itim/ticket/incident"
},
{
"display_name": "Problems",
"name": "ticket_problem",
"link": "/itim/ticket/problem"
},
{
"display_name": "Services",
"name": "service",
"link": "/itim/service"
},
]
},
{
"display_name": "Config Management",
"name": "config_management",
"icon": "ansible",
"pages": [
{
"display_name": "Groups",
"name": "group",
"icon": 'config_management',
"link": "/config_management/group"
}
]
},
{
"display_name": "Project Management",
"name": "project_management",
"icon": 'project',
"pages": [
{
"display_name": "Projects",
"name": "project",
"icon": 'kanban',
"link": "/project_management/project"
}
]
},
{
"display_name": "Settings",
"name": "settings",
"pages": [
{
"display_name": "System",
"name": "setting",
"icon": "system",
"link": "/settings"
},
{
"display_name": "Task Log",
"name": "celery_log",
# "icon": "settings",
"link": "/settings/celery_log"
}
]
}
]
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 (
# not field_info.get("read_only")
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
)
return field_info

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,16 +0,0 @@
from rest_framework import serializers
from core import fields as centurion_field
class CommonBaseSerializer(serializers.ModelSerializer):
pass
class CommonModelSerializer(CommonBaseSerializer):
model_notes = centurion_field.MarkdownField( 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,513 +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')
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']) != self.organization.id:
contains_different_org = True
assert not contains_different_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_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
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
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,586 +0,0 @@
from api.react_ui_metadata import ReactUIMetadata
from api.views.mixin import OrganizationPermissionAPI
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()
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()
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 OrganizationPermissionAPI
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 type(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()
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.
"""
pass

View File

@ -160,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')
@ -182,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')
@ -201,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')
@ -220,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')
@ -239,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')
@ -395,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')
@ -409,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()
@ -502,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(
@ -538,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'])
@ -779,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(

View File

@ -201,7 +201,7 @@ class InventoryPermissionsAPI(TestCase):
"""
client = Client()
url = reverse('v1:_api_device_inventory')
url = reverse('API:_api_device_inventory')
response = client.put(url, data=self.inventory, content_type='application/json')
@ -218,7 +218,7 @@ class InventoryPermissionsAPI(TestCase):
"""
client = Client()
url = reverse('v1:_api_device_inventory')
url = reverse('API:_api_device_inventory')
client.force_login(self.no_permissions_user)
@ -236,7 +236,7 @@ class InventoryPermissionsAPI(TestCase):
"""
client = Client()
url = reverse('v1:_api_device_inventory')
url = reverse('API:_api_device_inventory')
client.force_login(self.different_organization_user)
@ -254,7 +254,7 @@ class InventoryPermissionsAPI(TestCase):
"""
client = Client()
url = reverse('v1:_api_device_inventory')
url = reverse('API:_api_device_inventory')
client.force_login(self.view_user)
@ -272,7 +272,7 @@ class InventoryPermissionsAPI(TestCase):
"""
client = Client()
url = reverse('v1:_api_device_inventory')
url = reverse('API:_api_device_inventory')
client.force_login(self.add_user)

View File

@ -1,42 +0,0 @@
from django.contrib.auth.models import User
from django.shortcuts import reverse
from django.test import Client, TestCase
from access.models import Organization
from api.tests.abstract.viewsets import ViewSetCommon
from api.viewsets.index import Index
class HomeViewset(
TestCase,
ViewSetCommon
):
viewset = Index
route_name = 'API:_api_v2_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)

View File

@ -5,25 +5,6 @@ from rest_framework.urlpatterns import format_suffix_patterns
from .views import access, config, index
from api.views.settings import permissions
from api.views.settings import index as settings
from api.views import assistance, itim, project_management
from api.views.assistance import request_ticket
from api.views.core import (
ticket_categories,
ticket_comment_categories,
ticket_comments as core_ticket_comments
)
from api.views.itim import change_ticket, incident_ticket, problem_ticket
from api.views.project_management import (
projects,
project_milestone,
project_state,
project_type,
project_task
)
from .views.itam import software, config as itam_config
from .views.itam.device import DeviceViewSet
from .views.itam import inventory
@ -32,42 +13,15 @@ from .views.itam import inventory
app_name = "API"
router = DefaultRouter(trailing_slash=False)
router = DefaultRouter()
router.register('', index.Index, basename='_api_home')
router.register('assistance/request', request_ticket.View, basename='_api_assistance_request')
router.register('assistance/request/(?P<ticket_id>[0-9]+)/comments', core_ticket_comments.View, basename='_api_assistance_request_ticket_comments')
router.register('device', DeviceViewSet, basename='device')
router.register('itim/change', change_ticket.View, basename='_api_itim_change')
router.register('itim/change/(?P<ticket_id>[0-9]+)/comments', core_ticket_comments.View, basename='_api_itim_change_ticket_comments')
router.register('itim/incident', incident_ticket.View, basename='_api_itim_incident')
router.register('itim/incident/(?P<ticket_id>[0-9]+)/comments', core_ticket_comments.View, basename='_api_itim_incident_ticket_comments')
router.register('itim/problem', problem_ticket.View, basename='_api_itim_problem')
router.register('itim/problem/(?P<ticket_id>[0-9]+)/comments', core_ticket_comments.View, basename='_api_itim_problem_ticket_comments')
router.register('project_management/projects', projects.View, basename='_api_projects')
router.register('project_management/projects/(?P<project_id>[0-9]+)/milestones', project_milestone.View, basename='_api_project_milestone')
router.register('project_management/projects/(?P<project_id>[0-9]+)/tasks', project_task.View, basename='_api_project_tasks')
router.register('project_management/projects/(?P<project_id>[0-9]+)/tasks/(?P<ticket_id>[0-9]+)/comments', core_ticket_comments.View, basename='_api_project_tasks_comments')
router.register('settings/ticket_categories', ticket_categories.View, basename='_api_ticket_category')
router.register('settings/project_state', project_state.View, basename='_api_project_state')
router.register('settings/project_type', project_type.View, basename='_api_project_type')
router.register('settings/ticket_comment_categories', ticket_comment_categories.View, basename='_api_ticket_comment_category')
router.register('software', software.SoftwareViewSet, basename='software')
urlpatterns = [
path("assistance", assistance.index.Index.as_view(), name="_api_assistance"),
path("config/<slug:slug>/", itam_config.View.as_view(), name="_api_device_config"),
path("configuration/", config.ConfigGroupsList.as_view(), name='_api_config_groups'),
@ -75,8 +29,6 @@ urlpatterns = [
path("device/inventory", inventory.Collect.as_view(), name="_api_device_inventory"),
path("itim", itim.index.Index.as_view(), name="_api_itim"),
path("organization/", access.OrganizationList.as_view(), name='_api_orgs'),
path("organization/<int:pk>/", access.OrganizationDetail.as_view(), name='_api_organization'),
path("organization/<int:organization_id>/team", access.TeamList.as_view(), name='_api_organization_teams'),
@ -84,11 +36,6 @@ urlpatterns = [
path("organization/<int:organization_id>/team/<int:group_ptr_id>/permissions", access.TeamPermissionDetail.as_view(), name='_api_team_permission'),
path("organization/team/", access.TeamList.as_view(), name='_api_teams'),
path("project_management", project_management.index.Index.as_view(), name="_api_project_management"),
path("settings", settings.View.as_view(), name='_settings'),
path("settings/permissions", permissions.View.as_view(), name='_settings_permissions'),
]
urlpatterns = format_suffix_patterns(urlpatterns)

View File

@ -1,196 +0,0 @@
from django.urls import path
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
from rest_framework.routers import DefaultRouter
from api.viewsets import (
index as v2
)
from app.viewsets.base import (
index as base_index_v2,
content_type as content_type_v2,
permisson as permission_v2,
user as user_v2
)
from access.viewsets import (
index as access_v2,
organization as organization_v2,
team as team_v2,
team_user as team_user_v2
)
from assistance.viewsets import (
index as assistance_index_v2,
knowledge_base as knowledge_base_v2,
knowledge_base_category as knowledge_base_category_v2,
request as request_ticket_v2,
)
from config_management.viewsets import (
index as config_management_v2,
config_group as config_group_v2,
config_group_software as config_group_software_v2
)
from core.viewsets import (
celery_log as celery_log_v2,
history as history_v2,
manufacturer as manufacturer_v2,
notes as notes_v2,
ticket_category,
ticket_comment,
ticket_comment_category,
ticket_linked_item,
related_ticket,
)
from itam.viewsets import (
index as itam_index_v2,
device as device_v2,
device_model as device_model_v2,
device_type as device_type_v2,
device_software as device_software_v2,
device_operating_system,
inventory,
operating_system as operating_system_v2,
operating_system_version as operating_system_version_v2,
software as software_v2,
software_category as software_category_v2,
software_version as software_version_v2,
)
from itim.viewsets import (
index as itim_v2,
change,
cluster as cluster_v2,
cluster_type as cluster_type_v2,
incident,
port as port_v2,
problem,
service as service_v2,
service_device as service_device_v2
)
from project_management.viewsets import (
index as project_management_v2,
project as project_v2,
project_milestone as project_milestone_v2,
project_state as project_state_v2,
project_task,
project_type as project_type_v2,
)
from settings.viewsets import (
app_settings as app_settings_v2,
external_link as external_link_v2,
index as settings_index_v2,
user_settings as user_settings_v2
)
app_name = "API"
router = DefaultRouter(trailing_slash=False)
router.register('', v2.Index, basename='_api_v2_home')
router.register('access', access_v2.Index, basename='_api_v2_access_home')
router.register('access/organization', organization_v2.ViewSet, basename='_api_v2_organization')
router.register('access/organization/(?P<organization_id>[0-9]+)/team', team_v2.ViewSet, basename='_api_v2_organization_team')
router.register('access/organization/(?P<organization_id>[0-9]+)/team/(?P<team_id>[0-9]+)/user', team_user_v2.ViewSet, basename='_api_v2_organization_team_user')
router.register('assistance', assistance_index_v2.Index, basename='_api_v2_assistance_home')
router.register('assistance/knowledge_base', knowledge_base_v2.ViewSet, basename='_api_v2_knowledge_base')
router.register('assistance/ticket/request', request_ticket_v2.ViewSet, basename='_api_v2_ticket_request')
router.register('base', base_index_v2.Index, basename='_api_v2_base_home')
router.register('base/content_type', content_type_v2.ViewSet, basename='_api_v2_content_type')
router.register('base/permission', permission_v2.ViewSet, basename='_api_v2_permission')
router.register('base/user', user_v2.ViewSet, basename='_api_v2_user')
router.register('config_management', config_management_v2.Index, basename='_api_v2_config_management_home')
router.register('config_management/group', config_group_v2.ViewSet, basename='_api_v2_config_group')
router.register('config_management/group/(?P<parent_group>[0-9]+)/child_group', config_group_v2.ViewSet, basename='_api_v2_config_group_child')
router.register('config_management/group/(?P<config_group_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_config_group_notes')
router.register('config_management/group/(?P<config_group_id>[0-9]+)/software', config_group_software_v2.ViewSet, basename='_api_v2_config_group_software')
router.register('core/(?P<model_class>.+)/(?P<model_id>[0-9]+)/history', history_v2.ViewSet, basename='_api_v2_model_history')
router.register('core/ticket/(?P<ticket_id>[0-9]+)/comments', ticket_comment.ViewSet, basename='_api_v2_ticket_comment')
router.register('core/ticket/(?P<ticket_id>[0-9]+)/comments/(?P<parent_id>[0-9]+)/threads', ticket_comment.ViewSet, basename='_api_v2_ticket_comment_threads')
router.register('core/ticket/(?P<ticket_id>[0-9]+)/linked_item', ticket_linked_item.ViewSet, basename='_api_v2_ticket_linked_item')
router.register('core/ticket/(?P<ticket_id>[0-9]+)/related_ticket', related_ticket.ViewSet, basename='_api_v2_ticket_related')
router.register('core/(?P<item_class>[a-z_]+)/(?P<item_id>[0-9]+)/item_ticket', ticket_linked_item.ViewSet, basename='_api_v2_item_tickets')
router.register('itam', itam_index_v2.Index, basename='_api_v2_itam_home')
router.register('itam/device', device_v2.ViewSet, basename='_api_v2_device')
router.register('itam/device/(?P<device_id>[0-9]+)/operating_system', device_operating_system.ViewSet, basename='_api_v2_device_operating_system')
router.register('itam/device/(?P<device_id>[0-9]+)/software', device_software_v2.ViewSet, basename='_api_v2_device_software')
router.register('itam/device/(?P<device_id>[0-9]+)/service', service_device_v2.ViewSet, basename='_api_v2_service_device')
router.register('itam/device/(?P<device_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_device_notes')
router.register('itam/inventory', inventory.ViewSet, basename='_api_v2_inventory')
router.register('itam/operating_system', operating_system_v2.ViewSet, basename='_api_v2_operating_system')
router.register('itam/operating_system/(?P<operating_system_id>[0-9]+)/installs', device_operating_system.ViewSet, basename='_api_v2_operating_system_installs')
router.register('itam/operating_system/(?P<operating_system_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_operating_system_notes')
router.register('itam/operating_system/(?P<operating_system_id>[0-9]+)/version', operating_system_version_v2.ViewSet, basename='_api_v2_operating_system_version')
router.register('itam/software', software_v2.ViewSet, basename='_api_v2_software')
router.register('itam/software/(?P<software_id>[0-9]+)/installs', device_software_v2.ViewSet, basename='_api_v2_software_installs')
router.register('itam/software/(?P<software_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_software_notes')
router.register('itam/software/(?P<software_id>[0-9]+)/version', software_version_v2.ViewSet, basename='_api_v2_software_version')
router.register('itim', itim_v2.Index, basename='_api_v2_itim_home')
router.register('itim/ticket/change', change.ViewSet, basename='_api_v2_ticket_change')
router.register('itim/cluster', cluster_v2.ViewSet, basename='_api_v2_cluster')
router.register('itim/cluster/(?P<cluster_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_cluster_notes')
router.register('itim/ticket/incident', incident.ViewSet, basename='_api_v2_ticket_incident')
router.register('itim/ticket/problem', problem.ViewSet, basename='_api_v2_ticket_problem')
router.register('itim/service', service_v2.ViewSet, basename='_api_v2_service')
router.register('itim/service/(?P<service_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_service_notes')
router.register('project_management', project_management_v2.Index, basename='_api_v2_project_management_home')
router.register('project_management/project', project_v2.ViewSet, basename='_api_v2_project')
router.register('project_management/project/(?P<project_id>[0-9]+)/milestone', project_milestone_v2.ViewSet, basename='_api_v2_project_milestone')
router.register('project_management/project/(?P<project_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_project_notes')
router.register('project_management/project/(?P<project_id>[0-9]+)/project_task', project_task.ViewSet, basename='_api_v2_ticket_project_task')
router.register('settings', settings_index_v2.Index, basename='_api_v2_settings_home')
router.register('settings/app_settings', app_settings_v2.ViewSet, basename='_api_v2_app_settings')
router.register('settings/celery_log', celery_log_v2.ViewSet, basename='_api_v2_celery_log')
router.register('settings/cluster_type', cluster_type_v2.ViewSet, basename='_api_v2_cluster_type')
router.register('settings/cluster_type/(?P<cluster_type_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_cluster_type_notes')
router.register('settings/device_model', device_model_v2.ViewSet, basename='_api_v2_device_model')
router.register('settings/device_type', device_type_v2.ViewSet, basename='_api_v2_device_type')
router.register('settings/external_link', external_link_v2.ViewSet, basename='_api_v2_external_link')
router.register('settings/knowledge_base_category', knowledge_base_category_v2.ViewSet, basename='_api_v2_knowledge_base_category')
router.register('settings/manufacturer', manufacturer_v2.ViewSet, basename='_api_v2_manufacturer')
router.register('settings/manufacturer/(?P<manufacturer_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_manufacturer_notes')
router.register('settings/port', port_v2.ViewSet, basename='_api_v2_port')
router.register('settings/port/(?P<port_id>[0-9]+)/notes', notes_v2.ViewSet, basename='_api_v2_port_notes')
router.register('settings/project_state', project_state_v2.ViewSet, basename='_api_v2_project_state')
router.register('settings/project_type', project_type_v2.ViewSet, basename='_api_v2_project_type')
router.register('settings/software_category', software_category_v2.ViewSet, basename='_api_v2_software_category')
router.register('settings/ticket_category', ticket_category.ViewSet, basename='_api_v2_ticket_category')
router.register('settings/ticket_comment_category', ticket_comment_category.ViewSet, basename='_api_v2_ticket_comment_category')
router.register('settings/user_settings', user_settings_v2.ViewSet, basename='_api_v2_user_settings')
urlpatterns = [
path('schema', SpectacularAPIView.as_view(api_version='v2'), name='schema-v2',),
path('docs', SpectacularSwaggerView.as_view(url_name='schema-v2'), name='_api_v2_docs'),
]
urlpatterns += router.urls

View File

@ -1,47 +0,0 @@
from django.test import TestCase
class ViewSetAttributesUnit:
""" Unit Tests For View Set attributes.
These tests ensure that View sets contian the required attributesthat are
used by the API .
"""
def test_attribute_exists_page_layout(self):
"""Attrribute Test, Exists
Ensure attribute `page_layout` exists
"""
pass
def test_attribute_type_page_layout(self):
"""Attrribute Test, Type
Ensure attribute `page_layout` is of type `list`
"""
pass
def test_attribute_not_callable_page_layout(self):
"""Attrribute Test, Not Callable
Attribute must be a property
Ensure attribute `page_layout` is not callable.
"""
pass
# other tests required
# - filterset_fields
# - metadata_class
# - search_fields
# - documentation
# - model_documentation or is in `model.documentation`

View File

@ -12,7 +12,7 @@ from access.models import Organization, Team
from api.serializers.access import OrganizationSerializer, OrganizationListSerializer, TeamSerializer, TeamPermissionSerializer
from api.views.mixin import OrganizationPermissionAPI
@extend_schema(deprecated=True)
@extend_schema_view(
get=extend_schema(
summary = "Fetch Organizations",
@ -34,7 +34,7 @@ class OrganizationList(generics.ListAPIView):
return "Organizations"
@extend_schema(deprecated=True)
@extend_schema_view(
get=extend_schema(
summary = "Get An Organization",
@ -61,7 +61,7 @@ class OrganizationDetail(generics.RetrieveUpdateAPIView):
return "Organization"
@extend_schema(deprecated=True)
@extend_schema_view(
post=extend_schema(
summary = "Create a Team",
@ -97,7 +97,7 @@ class TeamList(generics.ListCreateAPIView):
return "Organization Teams"
@extend_schema(deprecated=True)
@extend_schema_view(
get=extend_schema(
summary = "Fetch a Team",
@ -149,7 +149,7 @@ class TeamDetail(generics.RetrieveUpdateDestroyAPIView):
lookup_field = 'group_ptr_id'
@extend_schema(deprecated=True)
@extend_schema_view(
get=extend_schema(
summary = "Fetch a teams permissions",

View File

@ -1 +0,0 @@
from .index import *

View File

@ -1,37 +0,0 @@
from django.utils.safestring import mark_safe
from drf_spectacular.utils import extend_schema
from rest_framework import generics, permissions, routers, views
# from rest_framework.decorators import api_view
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.reverse import reverse
@extend_schema(deprecated=True)
class Index(views.APIView):
permission_classes = [
IsAuthenticated,
]
def get_view_name(self):
return "Assistance"
def get_view_description(self, html=False) -> str:
text = "Assistance Module"
if html:
return mark_safe(f"<p>{text}</p>")
else:
return text
def get(self, request, *args, **kwargs):
body: dict = {
'requests': reverse('v1:_api_assistance_request-list', request=request)
}
return Response(body)

View File

@ -1,78 +0,0 @@
from drf_spectacular.utils import extend_schema, OpenApiResponse
from api.serializers.assistance.request import RequestTicketSerializer
from api.views.core.tickets import View
@extend_schema(deprecated=True)
class View(View):
_ticket_type:str = 'request'
@extend_schema(
summary='Create a ticket',
description = """This model includes all of the ticket types.
Due to this not all fields will be available and what fields are available
depends upon the comment type. see
[administration docs](https://nofusscomputing.com/projects/centurion_erp/administration/core/ticketing/index.html) for more info.
""",
request = RequestTicketSerializer,
responses = {
201: OpenApiResponse(
response = RequestTicketSerializer,
),
}
)
def create(self, request, *args, **kwargs):
return super().create(request, *args, **kwargs)
@extend_schema(
summary='Fetch all tickets',
description = """This model includes all of the ticket comment types.
Due to this not all fields will be available and what fields are available
depends upon the comment type. see
[administration docs](https://nofusscomputing.com/projects/centurion_erp/administration/core/ticketing/index.html) for more info.
""",
methods=["GET"],
responses = {
200: OpenApiResponse(
description='Success',
response = RequestTicketSerializer
)
}
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
@extend_schema(
summary='Fetch the selected ticket',
description = """This model includes all of the ticket comment types.
Due to this not all fields will be available and what fields are available
depends upon the comment type. see
[administration docs](https://nofusscomputing.com/projects/centurion_erp/administration/core/ticketing/index.html) for more info.
""",
methods=["GET"],
responses = {
200: OpenApiResponse(
description='Success',
response = RequestTicketSerializer
)
}
)
def retrieve(self, request, *args, **kwargs):
return super().retrieve(request, *args, **kwargs)
def get_view_name(self):
if self.detail:
return "Request Ticket"
return 'Request Tickets'

View File

@ -8,7 +8,7 @@ from api.views.mixin import OrganizationPermissionAPI
from config_management.models.groups import ConfigGroups
@extend_schema( deprecated = True )
@extend_schema_view(
get=extend_schema(
summary = "Fetch Config groups",
@ -31,7 +31,6 @@ class ConfigGroupsList(generics.ListAPIView):
@extend_schema( deprecated = True )
@extend_schema_view(
get=extend_schema(
summary = "Get A Config Group",

View File

@ -1,79 +0,0 @@
from django.shortcuts import get_object_or_404
from drf_spectacular.utils import extend_schema, OpenApiResponse
from rest_framework import generics, viewsets
from access.mixin import OrganizationMixin
from api.serializers.core.ticket_category import TicketCategory, TicketCategorySerializer
from api.views.mixin import OrganizationPermissionAPI
@extend_schema(deprecated=True)
class View(OrganizationMixin, viewsets.ModelViewSet):
permission_classes = [
OrganizationPermissionAPI
]
queryset = TicketCategory.objects.all()
serializer_class = TicketCategorySerializer
@extend_schema(
summary='Create a ticket category',
request = TicketCategorySerializer,
responses = {
201: OpenApiResponse(description='Ticket category created', response=TicketCategorySerializer),
403: OpenApiResponse(description='User tried to edit field they dont have access to'),
}
)
def create(self, request, *args, **kwargs):
return super().create(request, *args, **kwargs)
@extend_schema(
summary='Fetch all of a tickets category',
methods=["GET"],
responses = {
200: OpenApiResponse(description='Success', response=TicketCategorySerializer),
}
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
@extend_schema(
summary='Fetch the selected ticket category',
methods=["GET"],
responses = {
200: OpenApiResponse(description='Success', response=TicketCategorySerializer),
}
)
def retrieve(self, request, *args, **kwargs):
return super().retrieve(request, *args, **kwargs)
@extend_schema(
summary='Update a ticket category',
methods=["PUT"],
responses = {
200: OpenApiResponse(description='Ticket comment updated', response=TicketCategorySerializer),
403: OpenApiResponse(description='User tried to edit field they dont have access to'),
}
)
def update(self, request, *args, **kwargs):
return super().update(request, *args, **kwargs)
def get_view_name(self):
if self.detail:
return "Ticket Category"
return 'Ticket Categories'

View File

@ -1,79 +0,0 @@
from django.shortcuts import get_object_or_404
from drf_spectacular.utils import extend_schema, OpenApiResponse
from rest_framework import generics, viewsets
from access.mixin import OrganizationMixin
from api.serializers.core.ticket_comment_category import TicketCommentCategory, TicketCommentCategorySerializer
from api.views.mixin import OrganizationPermissionAPI
@extend_schema(deprecated=True)
class View(OrganizationMixin, viewsets.ModelViewSet):
permission_classes = [
OrganizationPermissionAPI
]
queryset = TicketCommentCategory.objects.all()
serializer_class = TicketCommentCategorySerializer
@extend_schema(
summary='Create a ticket comment category',
request = TicketCommentCategorySerializer,
responses = {
201: OpenApiResponse(description='Ticket category created', response=TicketCommentCategorySerializer),
403: OpenApiResponse(description='User tried to edit field they dont have access to'),
}
)
def create(self, request, *args, **kwargs):
return super().create(request, *args, **kwargs)
@extend_schema(
summary='Fetch all of the ticket comment categories',
methods=["GET"],
responses = {
200: OpenApiResponse(description='Success', response=TicketCommentCategorySerializer),
}
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
@extend_schema(
summary='Fetch the selected ticket comment category',
methods=["GET"],
responses = {
200: OpenApiResponse(description='Success', response=TicketCommentCategorySerializer),
}
)
def retrieve(self, request, *args, **kwargs):
return super().retrieve(request, *args, **kwargs)
@extend_schema(
summary='Update a ticket comment category',
methods=["PUT"],
responses = {
200: OpenApiResponse(description='Ticket comment updated', response=TicketCommentCategorySerializer),
403: OpenApiResponse(description='User tried to edit field they dont have access to'),
}
)
def update(self, request, *args, **kwargs):
return super().update(request, *args, **kwargs)
def get_view_name(self):
if self.detail:
return "Ticket Comment Category"
return 'Ticket Comment Categories'

View File

@ -1,102 +0,0 @@
from django.shortcuts import get_object_or_404
from drf_spectacular.utils import extend_schema, OpenApiResponse
from rest_framework import generics, viewsets
from access.mixin import OrganizationMixin
from api.serializers.core.ticket_comment import TicketCommentSerializer
from api.views.mixin import OrganizationPermissionAPI
from core.models.ticket.ticket_comment import TicketComment
@extend_schema(deprecated=True)
class View(OrganizationMixin, viewsets.ModelViewSet):
permission_classes = [
OrganizationPermissionAPI
]
queryset = TicketComment.objects.all()
serializer_class = TicketCommentSerializer
@extend_schema(
summary='Create a ticket comment',
description = """This model includes all of the ticket comment types.
Due to this not all fields will be available and what fields are available
depends upon the comment type.
""",
request = TicketCommentSerializer,
responses = {
201: OpenApiResponse(description='Ticket comment created', response=TicketCommentSerializer),
403: OpenApiResponse(description='User tried to edit field they dont have access to'),
}
)
def create(self, request, *args, **kwargs):
return super().create(request, *args, **kwargs)
@extend_schema(
summary='Fetch all of a tickets comments',
methods=["GET"],
responses = {
200: OpenApiResponse(description='Success', response=TicketCommentSerializer),
}
)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
@extend_schema(
summary='Fetch the selected ticket Comment',
methods=["GET"],
responses = {
200: OpenApiResponse(description='Success', response=TicketCommentSerializer),
}
)
def retrieve(self, request, *args, **kwargs):
return super().retrieve(request, *args, **kwargs)
@extend_schema(
summary='Update a ticket Comment',
description = """This model includes all of the ticket comment types.
Due to this not all fields will be available and what fields are available
depends upon the comment type.
""",
methods=["PUT"],
responses = {
200: OpenApiResponse(description='Ticket comment updated', response=TicketCommentSerializer),
403: OpenApiResponse(description='User tried to edit field they dont have access to'),
}
)
def update(self, request, *args, **kwargs):
return super().update(request, *args, **kwargs)
def get_queryset(self):
if 'ticket_id' in self.kwargs:
self.queryset = self.queryset.filter(ticket=self.kwargs['ticket_id']).order_by('created')
if 'pk' in self.kwargs:
self.queryset = self.queryset.filter(pk = self.kwargs['pk'])
return self.queryset
def get_view_name(self):
if self.detail:
return "Ticket Comment"
return 'Ticket Comments'

View File

@ -1,164 +0,0 @@
from django.db.models import Q
from rest_framework import generics, viewsets
from access.mixin import OrganizationMixin
from api.serializers.assistance.request import RequestTicketSerializer
from api.serializers.itim.change import ChangeTicketSerializer
from api.serializers.itim.incident import IncidentTicketSerializer
from api.serializers.itim.problem import ProblemTicketSerializer
from api.serializers.project_management.project_task import ProjectTaskSerializer
from api.views.mixin import OrganizationPermissionAPI
from core.models.ticket.ticket import Ticket
class View(OrganizationMixin, viewsets.ModelViewSet):
filterset_fields = [
'external_system',
'external_ref',
]
search_fields = [
'title',
'description',
]
permission_classes = [
OrganizationPermissionAPI
]
def get_dynamic_permissions(self):
if self.action == 'create':
action_keyword = 'add'
elif self.action == 'destroy':
action_keyword = 'delete'
elif self.action == 'list':
action_keyword = 'view'
elif self.action == 'partial_update':
action_keyword = 'change'
elif self.action == 'retrieve':
action_keyword = 'view'
elif self.action == 'update':
action_keyword = 'change'
elif self.action is None:
action_keyword = 'view'
else:
raise ValueError('unable to determin the action_keyword')
self.permission_required = [
'core.' + action_keyword + '_ticket_' + self._ticket_type,
]
return super().get_permission_required()
# queryset = Ticket.objects.all()
queryset = None
model = Ticket
def get_serializer(self, *args, **kwargs):
if self._ticket_type == 'change':
self.serializer_class = ChangeTicketSerializer
self._ticket_type_value = Ticket.TicketType.CHANGE.value
elif self._ticket_type == 'incident':
self.serializer_class = IncidentTicketSerializer
self._ticket_type_value = Ticket.TicketType.INCIDENT.value
elif self._ticket_type == 'problem':
self.serializer_class = ProblemTicketSerializer
self._ticket_type_value = Ticket.TicketType.PROBLEM.value
elif self._ticket_type == 'request':
self.serializer_class = RequestTicketSerializer
self._ticket_type_value = Ticket.TicketType.REQUEST.value
elif self._ticket_type == 'project_task':
self.serializer_class = ProjectTaskSerializer
self._ticket_type_value = Ticket.TicketType.PROJECT_TASK.value
else:
raise ValueError('unable to determin the serializer_class')
return super().get_serializer(*args, **kwargs)
def get_queryset(self):
if self._ticket_type == 'change':
ticket_type = self.model.TicketType.CHANGE.value
elif self._ticket_type == 'incident':
ticket_type = self.model.TicketType.INCIDENT.value
elif self._ticket_type == 'problem':
ticket_type = self.model.TicketType.PROBLEM.value
elif self._ticket_type == 'request':
ticket_type = self.model.TicketType.REQUEST.value
elif self._ticket_type == 'project_task':
ticket_type = self.model.TicketType.REQUEST.value
# return self.queryset.filter(
# project = self.kwargs['project_id']
# )
else:
raise ValueError('Unknown ticket type. kwarg `ticket_type` must be set')
if not self.queryset:
queryset = Ticket.objects.all()
queryset = queryset.filter(
ticket_type = ticket_type
)
if self._ticket_type == 'project_task':
queryset = queryset.filter(
project = self.kwargs['project_id']
)
self.queryset = queryset
return self.queryset

View File

@ -1,27 +1,21 @@
from django.conf import settings as django_settings
# from django.contrib.auth.mixins import PermissionRequiredMixin, LoginRequiredMixin
from django.contrib.auth.models import User
from django.utils.safestring import mark_safe
from rest_framework import generics, permissions, routers, viewsets
from rest_framework.decorators import api_view
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.reverse import reverse
class Index(viewsets.ViewSet):
permission_classes = [
IsAuthenticated,
]
# permission_required = 'access.view_organization'
def get_view_name(self):
return "API"
return "API Index"
def get_view_description(self, html=False) -> str:
text = "Centurion ERP Rest API"
text = "My REST API"
if html:
return mark_safe(f"<p>{text}</p>")
else:
@ -29,18 +23,12 @@ class Index(viewsets.ViewSet):
def list(self, request, pk=None):
API: dict = {
return Response(
{
# "teams": reverse("_api_teams", request=request),
'assistance': reverse("v1:_api_assistance", request=request),
"devices": reverse("v1:device-list", request=request),
"config_groups": reverse("v1:_api_config_groups", request=request),
'itim': reverse("v1:_api_itim", request=request),
"organizations": reverse("v1:_api_orgs", request=request),
'project_management': reverse("v1:_api_project_management", request=request),
"settings": reverse('v1:_settings', request=request),
"software": reverse("v1:software-list", request=request),
'v2': reverse("v2:_api_v2_home-list", request=request)
"devices": reverse("API:device-list", request=request),
"config_groups": reverse("API:_api_config_groups", request=request),
"organizations": reverse("API:_api_orgs", request=request),
"software": reverse("API:software-list", request=request),
}
return Response( API )
)

View File

@ -1,14 +1,11 @@
from django.contrib.auth.mixins import PermissionRequiredMixin, LoginRequiredMixin
from drf_spectacular.utils import extend_schema
from itam.models.device import Device
from rest_framework import views
from rest_framework.response import Response
@extend_schema( deprecated = True )
class View(views.APIView):
def get(self, request, slug):

View File

@ -1,10 +1,9 @@
from django.db.models import Q
from django.shortcuts import get_object_or_404
from drf_spectacular.utils import extend_schema, OpenApiResponse
from drf_spectacular.utils import extend_schema
from rest_framework import generics, viewsets
from rest_framework.response import Response
from access.mixin import OrganizationMixin
@ -14,7 +13,7 @@ from api.views.mixin import OrganizationPermissionAPI
from itam.models.device import Device
@extend_schema( deprecated = True )
class DeviceViewSet(OrganizationMixin, viewsets.ModelViewSet):
permission_classes = [
@ -25,46 +24,6 @@ class DeviceViewSet(OrganizationMixin, viewsets.ModelViewSet):
serializer_class = DeviceSerializer
@extend_schema(
summary = 'Create a device',
description="""Add a new device to the ITAM database.
If you attempt to create a device and a device with a matching name and uuid or name and serial number
is found within the database, it will not re-create it. The device will be returned within the message body.
""",
methods=["POST"],
responses = {
200: OpenApiResponse(description='Device allready exists', response=DeviceSerializer),
201: OpenApiResponse(description='Device created', response=DeviceSerializer),
400: OpenApiResponse(description='Validation failed.'),
403: OpenApiResponse(description='User is missing create permissions'),
}
)
def create(self, request, *args, **kwargs):
current_device = []
if 'uuid' in self.request.POST:
current_device = self.serializer_class.Meta.model.objects.filter(
organization = int(self.request.POST['organization']),
uuid = str(self.request.POST['uuid'])
)
if 'serial_number' in self.request.POST and len(current_device) == 0:
current_device = self.serializer_class.Meta.model.objects.filter(
organization = int(self.request.POST['organization']),
serial_number = str(self.request.POST['serial_number'])
)
if len(current_device) == 1:
instance = current_device.get()
serializer = self.get_serializer(instance)
return Response(serializer.data)
return super().create(request, *args, **kwargs)
@extend_schema( description='Fetch devices that are from the users assigned organization(s)', methods=["GET"])
def list(self, request):

View File

@ -1,10 +1,11 @@
import json
import re
from django.core.exceptions import ValidationError, PermissionDenied
from drf_spectacular.utils import extend_schema, OpenApiResponse
from rest_framework import generics, views
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.response import Response
from api.views.mixin import OrganizationPermissionAPI
@ -33,7 +34,6 @@ class InventoryPermissions(OrganizationPermissionAPI):
@extend_schema( deprecated = True )
class Collect(OrganizationPermissionAPI, views.APIView):
queryset = Device.objects.all()
@ -91,13 +91,12 @@ this setting populated, no device will be created and the endpoint will return H
if not self.permission_check(request=request, view=self, obj=device):
raise PermissionDenied()
raise Http404
task = process_inventory.delay(request.body, self.default_organization.id)
response_data: dict = {"task_id": f"{task.id}"}
except PermissionDenied as e:
status = Http.Status.FORBIDDEN
@ -106,7 +105,7 @@ this setting populated, no device will be created and the endpoint will return H
except ValidationError as e:
status = Http.Status.BAD_REQUEST
response_data = e.detail
response_data = e.message
except Exception as e:

View File

@ -1,8 +1,6 @@
from django.db.models import Q
from django.shortcuts import get_object_or_404
from drf_spectacular.utils import extend_schema
from rest_framework import generics, viewsets
from access.mixin import OrganizationMixin
@ -13,7 +11,7 @@ from api.views.mixin import OrganizationPermissionAPI
from itam.models.software import Software
@extend_schema(deprecated = True)
class SoftwareViewSet(OrganizationMixin, viewsets.ModelViewSet):
permission_classes = [

View File

@ -1 +0,0 @@
from .index import *

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