Merge pull request #344 from nofusscomputing/feature-v1-3
This commit is contained in:
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@ -32,6 +32,8 @@
|
||||
|
||||
- [ ] :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_
|
||||
|
17
.vscode/launch.json
vendored
17
.vscode/launch.json
vendored
@ -5,7 +5,7 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug: Django",
|
||||
"name": "Centurion",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
@ -17,6 +17,7 @@
|
||||
"program": "${workspaceFolder}/app/manage.py"
|
||||
},
|
||||
{
|
||||
|
||||
"name": "Debug: Gunicorn",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
@ -35,9 +36,21 @@
|
||||
"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": "python",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"module": "celery",
|
||||
"console": "integratedTerminal",
|
||||
|
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -17,4 +17,5 @@
|
||||
"ITSM"
|
||||
],
|
||||
"cSpell.language": "en-AU",
|
||||
"jest.enable": false,
|
||||
}
|
@ -68,6 +68,14 @@ Included within the root of the repository is a makefile that can be used during
|
||||
> 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
|
||||
|
||||
|
||||
|
@ -1,3 +1,27 @@
|
||||
## 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"
|
||||
@ -14,10 +38,12 @@ This release updates the docker container to be a production setup for deploymen
|
||||
- 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
|
||||
|
@ -11,12 +11,20 @@ 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)
|
||||
|
||||
|
||||
@ -28,6 +36,18 @@ 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()
|
||||
@ -45,6 +65,20 @@ 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 == '_':
|
||||
|
17
app/access/migrations/0002_alter_team_options.py
Normal file
17
app/access/migrations/0002_alter_team_options.py
Normal file
@ -0,0 +1,17 @@
|
||||
# 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'},
|
||||
),
|
||||
]
|
@ -0,0 +1,57 @@
|
||||
# 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'),
|
||||
),
|
||||
]
|
@ -0,0 +1,44 @@
|
||||
# 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'),
|
||||
),
|
||||
]
|
18
app/access/migrations/0005_alter_team_team_name.py
Normal file
18
app/access/migrations/0005_alter_team_team_name.py
Normal file
@ -0,0 +1,18 @@
|
||||
# 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'),
|
||||
),
|
||||
]
|
20
app/access/migrations/0006_alter_team_organization.py
Normal file
20
app/access/migrations/0006_alter_team_organization.py
Normal file
@ -0,0 +1,20 @@
|
||||
# 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'),
|
||||
),
|
||||
]
|
@ -48,7 +48,7 @@ class OrganizationMixin():
|
||||
|
||||
if hasattr(self, '_object_organization'):
|
||||
|
||||
return self._object_organization
|
||||
return int(self._object_organization)
|
||||
|
||||
try:
|
||||
|
||||
@ -124,9 +124,13 @@ class OrganizationMixin():
|
||||
|
||||
is_member = False
|
||||
|
||||
if organization in self.user_organizations():
|
||||
if organization is None:
|
||||
|
||||
return True
|
||||
return False
|
||||
|
||||
if int(organization) in self.user_organizations():
|
||||
|
||||
is_member = True
|
||||
|
||||
return is_member
|
||||
|
||||
@ -136,6 +140,10 @@ 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 "
|
||||
@ -216,6 +224,11 @@ class OrganizationMixin():
|
||||
|
||||
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)
|
||||
|
@ -3,6 +3,8 @@ 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
|
||||
@ -12,6 +14,7 @@ from core.mixin.history_save import SaveHistory
|
||||
class Organization(SaveHistory):
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Organization"
|
||||
verbose_name_plural = "Organizations"
|
||||
ordering = ['name']
|
||||
|
||||
@ -23,28 +26,34 @@ class Organization(SaveHistory):
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
id = models.AutoField(
|
||||
blank=False,
|
||||
help_text = 'ID of this item',
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
blank=False
|
||||
verbose_name = 'ID'
|
||||
)
|
||||
|
||||
name = models.CharField(
|
||||
blank = False,
|
||||
help_text = 'Name of this Organization',
|
||||
max_length = 50,
|
||||
unique = True,
|
||||
verbose_name = 'Name'
|
||||
)
|
||||
|
||||
manager = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.SET_NULL,
|
||||
blank = False,
|
||||
help_text = 'Manager for this organization',
|
||||
null = True,
|
||||
help_text = 'Organization Manager'
|
||||
on_delete=models.SET_NULL,
|
||||
verbose_name = 'Manager'
|
||||
)
|
||||
|
||||
model_notes = models.TextField(
|
||||
blank = True,
|
||||
default = None,
|
||||
help_text = 'Tid bits of information',
|
||||
null= True,
|
||||
verbose_name = 'Notes',
|
||||
)
|
||||
@ -62,6 +71,59 @@ 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):
|
||||
@ -176,30 +238,77 @@ 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,
|
||||
null = True,
|
||||
help_text = 'Organization this belongs to',
|
||||
null = False,
|
||||
on_delete = models.CASCADE,
|
||||
validators = [validatate_organization_exists],
|
||||
verbose_name = 'Organization'
|
||||
)
|
||||
|
||||
is_global = models.BooleanField(
|
||||
blank = False,
|
||||
default = False,
|
||||
blank = False
|
||||
help_text = 'Is this a global object?',
|
||||
verbose_name = 'Global Object'
|
||||
)
|
||||
|
||||
model_notes = models.TextField(
|
||||
blank = True,
|
||||
default = None,
|
||||
null= True,
|
||||
help_text = 'Tid bits of information',
|
||||
null = True,
|
||||
verbose_name = 'Notes',
|
||||
)
|
||||
|
||||
def get_organization(self) -> Organization:
|
||||
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:
|
||||
@ -211,10 +320,14 @@ class TenancyObject(SaveHistory):
|
||||
|
||||
|
||||
class Team(Group, TenancyObject):
|
||||
|
||||
class Meta:
|
||||
# proxy = True
|
||||
|
||||
ordering = [ 'team_name' ]
|
||||
|
||||
verbose_name = 'Team'
|
||||
|
||||
verbose_name_plural = "Teams"
|
||||
ordering = ['team_name']
|
||||
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
@ -225,17 +338,79 @@ 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,
|
||||
default = ''
|
||||
verbose_name = 'Name',
|
||||
)
|
||||
|
||||
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):
|
||||
@ -266,36 +441,56 @@ 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,
|
||||
blank=False
|
||||
verbose_name = 'ID'
|
||||
)
|
||||
|
||||
team = models.ForeignKey(
|
||||
Team,
|
||||
blank = False,
|
||||
help_text = 'Team user belongs to',
|
||||
null = False,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="team",
|
||||
on_delete=models.CASCADE)
|
||||
verbose_name = 'Team'
|
||||
)
|
||||
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE
|
||||
blank = False,
|
||||
help_text = 'User who will be added to the team',
|
||||
null = False,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name = 'User'
|
||||
)
|
||||
|
||||
manager = models.BooleanField(
|
||||
verbose_name='manager',
|
||||
blank=True,
|
||||
default=False,
|
||||
blank=True
|
||||
help_text = 'Is this user to be a manager of this team',
|
||||
verbose_name='manager',
|
||||
)
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
page_layout: list = []
|
||||
|
||||
table_fields: list = []
|
||||
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
""" Delete Team
|
||||
@ -317,6 +512,24 @@ 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
|
||||
|
||||
|
89
app/access/serializers/organization.py
Normal file
89
app/access/serializers/organization.py
Normal file
@ -0,0 +1,89 @@
|
||||
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)
|
99
app/access/serializers/team_user.py
Normal file
99
app/access/serializers/team_user.py
Normal file
@ -0,0 +1,99 @@
|
||||
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)
|
126
app/access/serializers/teams.py
Normal file
126
app/access/serializers/teams.py
Normal file
@ -0,0 +1,126 @@
|
||||
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)
|
@ -1,14 +1,14 @@
|
||||
import pytest
|
||||
|
||||
# from django.contrib.auth.models import User
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
|
||||
# from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
# from access.serializers.organization import (
|
||||
# Organization,
|
||||
# OrganizationModelSerializer
|
||||
# )
|
||||
from access.serializers.organization import (
|
||||
Organization,
|
||||
OrganizationModelSerializer
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -16,8 +16,77 @@ class OrganizationValidationAPI(
|
||||
TestCase,
|
||||
):
|
||||
|
||||
model = Organization
|
||||
|
||||
@pytest.mark.skip( reason = 'tests to be written' )
|
||||
def test_dummy(self):
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
pass
|
||||
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)
|
||||
|
@ -0,0 +1,280 @@
|
||||
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
|
201
app/access/tests/functional/team/test_team_permission_viewset.py
Normal file
201
app/access/tests/functional/team/test_team_permission_viewset.py
Normal file
@ -0,0 +1,201 @@
|
||||
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
|
180
app/access/tests/functional/team/test_team_serializer.py
Normal file
180
app/access/tests/functional/team/test_team_serializer.py
Normal file
@ -0,0 +1,180 @@
|
||||
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)
|
@ -0,0 +1,211 @@
|
||||
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
|
@ -0,0 +1,186 @@
|
||||
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'
|
@ -17,7 +17,7 @@ class OrganizationAPI(TestCase):
|
||||
|
||||
model = Organization
|
||||
|
||||
app_namespace = 'API'
|
||||
app_namespace = 'v1'
|
||||
|
||||
url_name = '_api_organization'
|
||||
|
||||
|
192
app/access/tests/unit/organization/test_organizaiton_api_v2.py
Normal file
192
app/access/tests/unit/organization/test_organizaiton_api_v2.py
Normal file
@ -0,0 +1,192 @@
|
||||
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
|
@ -20,7 +20,7 @@ class OrganizationPermissionsAPI(TestCase, APIPermissionChange, APIPermissionVie
|
||||
model_name = 'organization'
|
||||
app_label = 'access'
|
||||
|
||||
app_namespace = 'API'
|
||||
app_namespace = 'v1'
|
||||
|
||||
url_name = '_api_organization'
|
||||
|
||||
|
@ -34,7 +34,7 @@ class OrganizationHistory(TestCase):
|
||||
|
||||
|
||||
self.history_create = History.objects.get(
|
||||
action = History.Actions.ADD[0],
|
||||
action = int(History.Actions.ADD),
|
||||
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 = History.Actions.UPDATE[0],
|
||||
action = int(History.Actions.UPDATE),
|
||||
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[0])
|
||||
assert history['action'] == int(History.Actions.ADD)
|
||||
# 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[0])
|
||||
assert history['action'] == int(History.Actions.UPDATE)
|
||||
# assert type(history['action']) is int
|
||||
|
||||
|
||||
|
@ -67,4 +67,74 @@ class TeamModel(
|
||||
|
||||
@pytest.mark.skip(reason="uses Django group manager")
|
||||
def test_model_class_tenancy_manager_function_get_queryset_called(self):
|
||||
pass
|
||||
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
|
||||
|
@ -21,7 +21,7 @@ class TeamAPI(TestCase):
|
||||
|
||||
model = Team
|
||||
|
||||
app_namespace = 'API'
|
||||
app_namespace = 'v1'
|
||||
|
||||
url_name = '_api_team'
|
||||
|
||||
|
174
app/access/tests/unit/team/test_team_api_v2.py
Normal file
174
app/access/tests/unit/team/test_team_api_v2.py
Normal file
@ -0,0 +1,174 @@
|
||||
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
|
@ -39,7 +39,7 @@ class TeamHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
|
||||
|
||||
|
||||
self.history_create = History.objects.get(
|
||||
action = History.Actions.ADD[0],
|
||||
action = int(History.Actions.ADD),
|
||||
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 = History.Actions.UPDATE[0],
|
||||
action = int(History.Actions.UPDATE),
|
||||
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 = History.Actions.DELETE[0],
|
||||
action = int(History.Actions.DELETE),
|
||||
item_pk = self.deleted_pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
@ -18,7 +18,7 @@ class TeamPermissionsAPI(TestCase, APIPermissions):
|
||||
|
||||
model = Team
|
||||
|
||||
app_namespace = 'API'
|
||||
app_namespace = 'v1'
|
||||
|
||||
url_name = '_api_team'
|
||||
|
||||
|
@ -6,9 +6,13 @@ 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):
|
||||
class TeamUsersModel(
|
||||
TestCase,
|
||||
BaseModel
|
||||
):
|
||||
|
||||
model = TeamUsers
|
||||
|
||||
|
214
app/access/tests/unit/team_user/test_team_user_api_v2.py
Normal file
214
app/access/tests/unit/team_user/test_team_user_api_v2.py
Normal file
@ -0,0 +1,214 @@
|
||||
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
|
@ -48,7 +48,7 @@ class TeamUsersHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
|
||||
|
||||
|
||||
self.history_create = History.objects.get(
|
||||
action = History.Actions.ADD[0],
|
||||
action = int(History.Actions.ADD),
|
||||
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 = History.Actions.UPDATE[0],
|
||||
action = int(History.Actions.UPDATE),
|
||||
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 = History.Actions.DELETE[0],
|
||||
action = int(History.Actions.DELETE),
|
||||
item_pk = self.deleted_pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
42
app/access/tests/unit/test_access_viewset.py
Normal file
42
app/access/tests/unit/test_access_viewset.py
Normal file
@ -0,0 +1,42 @@
|
||||
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)
|
30
app/access/viewsets/index.py
Normal file
30
app/access/viewsets/index.py
Normal file
@ -0,0 +1,30 @@
|
||||
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)
|
||||
}
|
||||
)
|
89
app/access/viewsets/organization.py
Normal file
89
app/access/viewsets/organization.py
Normal file
@ -0,0 +1,89 @@
|
||||
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']
|
||||
|
148
app/access/viewsets/team.py
Normal file
148
app/access/viewsets/team.py
Normal file
@ -0,0 +1,148 @@
|
||||
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']
|
||||
|
174
app/access/viewsets/team_user.py
Normal file
174
app/access/viewsets/team_user.py
Normal file
@ -0,0 +1,174 @@
|
||||
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']
|
||||
|
@ -5,6 +5,20 @@ 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):
|
||||
|
8
app/api/exceptions.py
Normal file
8
app/api/exceptions.py
Normal file
@ -0,0 +1,8 @@
|
||||
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'
|
@ -0,0 +1,41 @@
|
||||
# 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'),
|
||||
),
|
||||
]
|
@ -48,37 +48,45 @@ class AuthToken(models.Model):
|
||||
|
||||
|
||||
id = models.AutoField(
|
||||
blank=False,
|
||||
help_text = 'ID of this token',
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
blank=False
|
||||
verbose_name = 'ID'
|
||||
)
|
||||
|
||||
note = models.CharField(
|
||||
blank = True,
|
||||
max_length = 50,
|
||||
default = None,
|
||||
help_text = 'A note about this token',
|
||||
max_length = 50,
|
||||
null= True,
|
||||
verbose_name = 'Note'
|
||||
)
|
||||
|
||||
token = models.CharField(
|
||||
verbose_name = 'Auth Token',
|
||||
blank = False,
|
||||
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,
|
||||
on_delete=models.CASCADE
|
||||
help_text = 'User this token belongs to',
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name = 'Owner'
|
||||
)
|
||||
|
||||
expires = models.DateTimeField(
|
||||
verbose_name = 'Expiry Date',
|
||||
blank = False,
|
||||
help_text = 'When this token expires',
|
||||
null = False,
|
||||
blank = False
|
||||
verbose_name = 'Expiry Date',
|
||||
)
|
||||
|
||||
|
||||
|
356
app/api/react_ui_metadata.py
Normal file
356
app/api/react_ui_metadata.py
Normal file
@ -0,0 +1,356 @@
|
||||
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
|
@ -25,7 +25,7 @@ class TeamSerializerBase(serializers.ModelSerializer):
|
||||
|
||||
request = self.context.get('request')
|
||||
|
||||
return request.build_absolute_uri(reverse("API:_api_team", args=[obj.organization.id,obj.pk]))
|
||||
return request.build_absolute_uri(reverse("v1:_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('API:_api_team_permission', args=[team.organization_id,team.id]))
|
||||
return request.build_absolute_uri(reverse('v1:_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('API:_api_team', args=[obj.organization_id,obj.id]))
|
||||
return request.build_absolute_uri(reverse('v1:_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="API:_api_organization", format="html"
|
||||
view_name="v1:_api_organization", format="html"
|
||||
)
|
||||
|
||||
|
||||
@ -110,7 +110,7 @@ class OrganizationListSerializer(serializers.ModelSerializer):
|
||||
class OrganizationSerializer(serializers.ModelSerializer):
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="API:_api_organization", format="html"
|
||||
view_name="v1:_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('API:_api_organization_teams', args=[obj.id]))
|
||||
return request.build_absolute_uri(reverse('v1:_api_organization_teams', args=[obj.id]))
|
||||
|
||||
teams = TeamSerializer(source='team_set', many=True, read_only=False)
|
||||
|
||||
view_name="API:_api_organization"
|
||||
view_name="v1:_api_organization"
|
||||
|
||||
|
||||
class Meta:
|
||||
|
16
app/api/serializers/common.py
Normal file
16
app/api/serializers/common.py
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
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 )
|
@ -28,7 +28,7 @@ class ParentGroupSerializer(serializers.ModelSerializer):
|
||||
|
||||
request = self.context.get('request')
|
||||
|
||||
return request.build_absolute_uri(reverse("API:_api_config_group", args=[obj.pk]))
|
||||
return request.build_absolute_uri(reverse("v1:_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("API:_api_config_group", args=[obj.pk]))
|
||||
return request.build_absolute_uri(reverse("v1:_api_config_group", args=[obj.pk]))
|
||||
|
||||
|
||||
|
||||
@ -74,6 +74,7 @@ class ConfigGroupsSerializer(ConfigGroupsSerializerBase):
|
||||
'parent',
|
||||
'name',
|
||||
'config',
|
||||
'hosts',
|
||||
'url',
|
||||
]
|
||||
read_only_fields = [
|
||||
|
@ -5,6 +5,7 @@ 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
|
||||
|
||||
@ -54,7 +55,7 @@ class TicketSerializer(
|
||||
|
||||
return request.build_absolute_uri(
|
||||
reverse(
|
||||
'API:' + view_name + '-detail',
|
||||
'v1:' + view_name + '-detail',
|
||||
kwargs = kwargs
|
||||
)
|
||||
)
|
||||
@ -100,7 +101,7 @@ class TicketSerializer(
|
||||
|
||||
return request.build_absolute_uri(
|
||||
reverse(
|
||||
'API:' + view_name + '-list',
|
||||
'v1:' + view_name + '-list',
|
||||
kwargs = kwargs
|
||||
)
|
||||
)
|
||||
@ -172,10 +173,10 @@ class TicketSerializer(
|
||||
|
||||
self._ticket_type = str(self.fields['ticket_type'].choices[self._context['view']._ticket_type_value]).lower().replace(' ', '_')
|
||||
|
||||
is_valid = self.validate_ticket()
|
||||
|
||||
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 = []
|
||||
@ -188,7 +189,7 @@ class TicketSerializer(
|
||||
|
||||
except Exception as unhandled_exception:
|
||||
|
||||
serializers.ParseError(
|
||||
centurion_exception.ParseError(
|
||||
detail=f"Server encountered an error during validation, Traceback: {unhandled_exception.with_traceback}"
|
||||
)
|
||||
|
||||
|
@ -15,7 +15,7 @@ class TicketCategorySerializer(
|
||||
):
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="API:_api_ticket_category-detail", format="html"
|
||||
view_name="v1:_api_ticket_category-detail", format="html"
|
||||
)
|
||||
|
||||
|
||||
|
@ -38,7 +38,7 @@ class TicketCommentSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
return request.build_absolute_uri(
|
||||
reverse('API:' + view_name + '-detail',
|
||||
reverse('v1:' + view_name + '-detail',
|
||||
kwargs={
|
||||
'ticket_id': item.ticket.id,
|
||||
'pk': item.id
|
||||
|
@ -13,7 +13,7 @@ class TicketCommentCategorySerializer(
|
||||
):
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="API:_api_ticket_comment_category-detail", format="html"
|
||||
view_name="v1:_api_ticket_comment_category-detail", format="html"
|
||||
)
|
||||
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.html import escape
|
||||
|
||||
from rest_framework.exceptions import ValidationError
|
||||
|
||||
|
||||
|
||||
class Inventory:
|
||||
""" Inventory Object
|
||||
|
||||
|
@ -4,7 +4,7 @@ from rest_framework import serializers
|
||||
|
||||
from api.serializers.config import ParentGroupSerializer
|
||||
|
||||
from config_management.models.groups import ConfigGroupHosts
|
||||
from config_management.models.groups import ConfigGroups
|
||||
|
||||
from itam.models.device import Device
|
||||
|
||||
@ -12,15 +12,13 @@ from itam.models.device import Device
|
||||
|
||||
class DeviceConfigGroupsSerializer(serializers.ModelSerializer):
|
||||
|
||||
name = serializers.CharField(source='group.name', read_only=True)
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="API:_api_config_group", format="html"
|
||||
view_name="v1:_api_config_group", format="html"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
||||
model = ConfigGroupHosts
|
||||
model = ConfigGroups
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
@ -38,17 +36,17 @@ class DeviceConfigGroupsSerializer(serializers.ModelSerializer):
|
||||
class DeviceSerializer(serializers.ModelSerializer):
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="API:device-detail", format="html"
|
||||
view_name="v1:device-detail", format="html"
|
||||
)
|
||||
|
||||
config = serializers.SerializerMethodField('get_device_config')
|
||||
|
||||
groups = DeviceConfigGroupsSerializer(source='configgrouphosts_set', many=True, read_only=True)
|
||||
groups = DeviceConfigGroupsSerializer(source='configgroups_set', many=True, read_only=True)
|
||||
|
||||
def get_device_config(self, device):
|
||||
|
||||
request = self.context.get('request')
|
||||
return request.build_absolute_uri(reverse('API:_api_device_config', args=[device.slug]))
|
||||
return request.build_absolute_uri(reverse('v1:_api_device_config', args=[device.slug]))
|
||||
|
||||
|
||||
class Meta:
|
||||
|
@ -7,7 +7,7 @@ from itam.models.device import Software
|
||||
class SoftwareSerializer(serializers.ModelSerializer):
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="API:software-detail", format="html"
|
||||
view_name="v1:software-detail", format="html"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -19,7 +19,7 @@ class ProjectMilestoneSerializer(
|
||||
request = self.context.get('request')
|
||||
|
||||
return request.build_absolute_uri(
|
||||
reverse('API:_api_project_milestone-detail',
|
||||
reverse('v1:_api_project_milestone-detail',
|
||||
kwargs={
|
||||
'project_id': item.project.id,
|
||||
'pk': item.id
|
||||
|
@ -12,7 +12,7 @@ class ProjectStateSerializer(
|
||||
):
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="API:_api_project_state-detail", format="html"
|
||||
view_name="v1:_api_project_state-detail", format="html"
|
||||
)
|
||||
|
||||
|
||||
|
@ -12,7 +12,7 @@ class ProjectTypeSerializer(
|
||||
):
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="API:_api_project_state-detail", format="html"
|
||||
view_name="v1:_api_project_state-detail", format="html"
|
||||
)
|
||||
|
||||
|
||||
|
@ -22,7 +22,7 @@ class ProjectSerializer(
|
||||
|
||||
request = self.context.get('request')
|
||||
|
||||
return request.build_absolute_uri(reverse("API:_api_projects-detail", args=[item.pk]))
|
||||
return request.build_absolute_uri(reverse("v1:_api_projects-detail", args=[item.pk]))
|
||||
|
||||
|
||||
project_tasks_url = serializers.SerializerMethodField('get_url_project_tasks')
|
||||
@ -34,7 +34,7 @@ class ProjectSerializer(
|
||||
|
||||
return request.build_absolute_uri(
|
||||
reverse(
|
||||
'API:_api_project_tasks-list',
|
||||
'v1:_api_project_tasks-list',
|
||||
kwargs={
|
||||
'project_id': item.id
|
||||
}
|
||||
@ -50,7 +50,7 @@ class ProjectSerializer(
|
||||
|
||||
return request.build_absolute_uri(
|
||||
reverse(
|
||||
'API:_api_project_milestone-list',
|
||||
'v1:_api_project_milestone-list',
|
||||
kwargs={
|
||||
'project_id': item.id
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ from celery import states
|
||||
|
||||
from access.models import Organization
|
||||
|
||||
from api.serializers.inventory import Inventory
|
||||
from itam.serializers.inventory import InventorySerializer
|
||||
|
||||
from itam.models.device import Device, DeviceType, DeviceOperatingSystem, DeviceSoftware
|
||||
from itam.models.operating_system import OperatingSystem, OperatingSystemVersion
|
||||
@ -32,8 +32,15 @@ def process_inventory(self, data, organization: int):
|
||||
|
||||
logger.info('Begin Processing Inventory')
|
||||
|
||||
data = json.loads(data)
|
||||
data = Inventory(data)
|
||||
if type(data) is str:
|
||||
|
||||
data = json.loads(data)
|
||||
|
||||
data = InventorySerializer(
|
||||
data = data
|
||||
)
|
||||
|
||||
data.is_valid()
|
||||
|
||||
organization = Organization.objects.get(id=organization)
|
||||
|
||||
@ -42,13 +49,13 @@ def process_inventory(self, data, organization: int):
|
||||
device_serial_number = None
|
||||
device_uuid = None
|
||||
|
||||
if data.details.serial_number and str(data.details.serial_number).lower() != 'na':
|
||||
if data.validated_data['details']['serial_number'] and str(data.validated_data['details']['serial_number']).lower() != 'na':
|
||||
|
||||
device_serial_number = str(data.details.serial_number)
|
||||
device_serial_number = str(data.validated_data['details']['serial_number'])
|
||||
|
||||
if data.details.uuid and str(data.details.uuid).lower() != 'na':
|
||||
if data.validated_data['details']['uuid'] and str(data.validated_data['details']['uuid']).lower() != 'na':
|
||||
|
||||
device_uuid = str(data.details.uuid)
|
||||
device_uuid = str(data.validated_data['details']['uuid'])
|
||||
|
||||
|
||||
if device_serial_number: # Search for device by serial number.
|
||||
@ -88,13 +95,13 @@ def process_inventory(self, data, organization: int):
|
||||
if not device: # Search for device by Name.
|
||||
|
||||
device = Device.objects.filter(
|
||||
name__iexact=str(data.details.name).lower()
|
||||
name__iexact=str(data.validated_data['details']['name']).lower()
|
||||
)
|
||||
|
||||
if device.exists():
|
||||
|
||||
device = Device.objects.get(
|
||||
name__iexact=str(data.details.name).lower()
|
||||
name__iexact=str(data.validated_data['details']['name']).lower()
|
||||
)
|
||||
|
||||
else:
|
||||
@ -107,7 +114,7 @@ def process_inventory(self, data, organization: int):
|
||||
if not device: # Create the device
|
||||
|
||||
device = Device.objects.create(
|
||||
name = data.details.name,
|
||||
name = data.validated_data['details']['name'],
|
||||
device_type = None,
|
||||
serial_number = device_serial_number,
|
||||
uuid = device_uuid,
|
||||
@ -131,14 +138,14 @@ def process_inventory(self, data, organization: int):
|
||||
|
||||
if not device.serial_number and device_serial_number:
|
||||
|
||||
device.serial_number = data.details.serial_number
|
||||
device.serial_number = data.validated_data['details']['serial_number']
|
||||
|
||||
device_edited = True
|
||||
|
||||
|
||||
if str(device.name).lower() != str(data.details.name).lower(): # Update device Name
|
||||
if str(device.name).lower() != str(data.validated_data['details']['name']).lower(): # Update device Name
|
||||
|
||||
device.name = data.details.name
|
||||
device.name = data.validated_data['details']['name']
|
||||
|
||||
device_edited = True
|
||||
|
||||
@ -149,14 +156,14 @@ def process_inventory(self, data, organization: int):
|
||||
|
||||
|
||||
operating_system = OperatingSystem.objects.filter(
|
||||
name=data.operating_system.name,
|
||||
name = data.validated_data['os']['name'],
|
||||
is_global = True
|
||||
)
|
||||
|
||||
if operating_system.exists():
|
||||
|
||||
operating_system = OperatingSystem.objects.get(
|
||||
name=data.operating_system.name,
|
||||
name = data.validated_data['os']['name'],
|
||||
is_global = True
|
||||
)
|
||||
|
||||
@ -170,7 +177,7 @@ def process_inventory(self, data, organization: int):
|
||||
if not operating_system:
|
||||
|
||||
operating_system = OperatingSystem.objects.filter(
|
||||
name=data.operating_system.name,
|
||||
name = data.validated_data['os']['name'],
|
||||
organization = organization
|
||||
)
|
||||
|
||||
@ -178,7 +185,7 @@ def process_inventory(self, data, organization: int):
|
||||
if operating_system.exists():
|
||||
|
||||
operating_system = OperatingSystem.objects.get(
|
||||
name=data.operating_system.name,
|
||||
name = data.validated_data['os']['name'],
|
||||
organization = organization
|
||||
)
|
||||
|
||||
@ -190,22 +197,22 @@ def process_inventory(self, data, organization: int):
|
||||
if not operating_system:
|
||||
|
||||
operating_system = OperatingSystem.objects.create(
|
||||
name = data.operating_system.name,
|
||||
name = data.validated_data['os']['name'],
|
||||
organization = organization,
|
||||
is_global = True
|
||||
)
|
||||
|
||||
|
||||
operating_system_version = OperatingSystemVersion.objects.filter(
|
||||
name=data.operating_system.version_major,
|
||||
operating_system=operating_system
|
||||
name = data.validated_data['os']['version_major'],
|
||||
operating_system = operating_system
|
||||
)
|
||||
|
||||
if operating_system_version.exists():
|
||||
|
||||
operating_system_version = OperatingSystemVersion.objects.get(
|
||||
name=data.operating_system.version_major,
|
||||
operating_system=operating_system
|
||||
name = data.validated_data['os']['version_major'],
|
||||
operating_system = operating_system
|
||||
)
|
||||
|
||||
else:
|
||||
@ -218,7 +225,7 @@ def process_inventory(self, data, organization: int):
|
||||
operating_system_version = OperatingSystemVersion.objects.create(
|
||||
organization = organization,
|
||||
is_global = True,
|
||||
name = data.operating_system.version_major,
|
||||
name = data.validated_data['os']['version_major'],
|
||||
operating_system = operating_system,
|
||||
)
|
||||
|
||||
@ -241,8 +248,8 @@ def process_inventory(self, data, organization: int):
|
||||
|
||||
device_operating_system = DeviceOperatingSystem.objects.create(
|
||||
organization = organization,
|
||||
device=device,
|
||||
version = data.operating_system.version,
|
||||
device = device,
|
||||
version = data.validated_data['os']['version'],
|
||||
operating_system_version = operating_system_version,
|
||||
installdate = timezone.now()
|
||||
)
|
||||
@ -261,9 +268,9 @@ def process_inventory(self, data, organization: int):
|
||||
device_operating_system.save()
|
||||
|
||||
|
||||
if device_operating_system.version != data.operating_system.version:
|
||||
if device_operating_system.version != data.validated_data['os']['version']:
|
||||
|
||||
device_operating_system.version = data.operating_system.version
|
||||
device_operating_system.version = data.validated_data['os']['version']
|
||||
|
||||
device_operating_system.save()
|
||||
|
||||
@ -287,7 +294,7 @@ def process_inventory(self, data, organization: int):
|
||||
|
||||
inventoried_software: list = []
|
||||
|
||||
for inventory in list(data.software):
|
||||
for inventory in list(data.validated_data['software']):
|
||||
|
||||
software = None
|
||||
software_category = None
|
||||
@ -295,13 +302,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
|
||||
@ -309,16 +316,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:
|
||||
@ -331,16 +338,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:
|
||||
@ -348,7 +355,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():
|
||||
|
249
app/api/tests/abstract/api_fields.py
Normal file
249
app/api/tests/abstract/api_fields.py
Normal file
@ -0,0 +1,249 @@
|
||||
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
|
513
app/api/tests/abstract/api_permissions_viewset.py
Normal file
513
app/api/tests/abstract/api_permissions_viewset.py
Normal file
@ -0,0 +1,513 @@
|
||||
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 """
|
163
app/api/tests/abstract/api_serializer_viewset.py
Normal file
163
app/api/tests/abstract/api_serializer_viewset.py
Normal file
@ -0,0 +1,163 @@
|
||||
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 """
|
586
app/api/tests/abstract/viewsets.py
Normal file
586
app/api/tests/abstract/viewsets.py
Normal file
@ -0,0 +1,586 @@
|
||||
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
|
@ -160,7 +160,7 @@ class InventoryAPI(TestCase):
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_device_inventory')
|
||||
url = reverse('v1:_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('API:_api_device_inventory')
|
||||
url = reverse('v1:_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('API:_api_device_inventory')
|
||||
url = reverse('v1:_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('API:_api_device_inventory')
|
||||
url = reverse('v1:_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('API:_api_device_inventory')
|
||||
url = reverse('v1:_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('API:_api_device_inventory')
|
||||
url = reverse('v1:_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('API:_api_device_inventory')
|
||||
url = reverse('v1:_api_device_inventory')
|
||||
|
||||
mod_inventory = self.inventory.copy()
|
||||
|
||||
|
@ -201,7 +201,7 @@ class InventoryPermissionsAPI(TestCase):
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_device_inventory')
|
||||
url = reverse('v1:_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('API:_api_device_inventory')
|
||||
url = reverse('v1:_api_device_inventory')
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
@ -236,7 +236,7 @@ class InventoryPermissionsAPI(TestCase):
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_device_inventory')
|
||||
url = reverse('v1:_api_device_inventory')
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
@ -254,7 +254,7 @@ class InventoryPermissionsAPI(TestCase):
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_device_inventory')
|
||||
url = reverse('v1:_api_device_inventory')
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
@ -272,7 +272,7 @@ class InventoryPermissionsAPI(TestCase):
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_device_inventory')
|
||||
url = reverse('v1:_api_device_inventory')
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
|
42
app/api/tests/unit/test_index_viewset.py
Normal file
42
app/api/tests/unit/test_index_viewset.py
Normal file
@ -0,0 +1,42 @@
|
||||
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)
|
@ -64,15 +64,10 @@ router.register('settings/ticket_comment_categories', ticket_comment_categories.
|
||||
router.register('software', software.SoftwareViewSet, basename='software')
|
||||
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
path("assistance", assistance.index.Index.as_view(), name="_api_assistance"),
|
||||
|
||||
#
|
||||
# Sof Old Paths to be refactored
|
||||
#
|
||||
|
||||
path("config/<slug:slug>/", itam_config.View.as_view(), name="_api_device_config"),
|
||||
|
||||
path("configuration/", config.ConfigGroupsList.as_view(), name='_api_config_groups'),
|
||||
|
196
app/api/urls_v2.py
Normal file
196
app/api/urls_v2.py
Normal file
@ -0,0 +1,196 @@
|
||||
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
|
47
app/api/v2/tests/unit/abstract/test_view_set_unit.py
Normal file
47
app/api/v2/tests/unit/abstract/test_view_set_unit.py
Normal file
@ -0,0 +1,47 @@
|
||||
|
||||
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`
|
@ -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",
|
||||
|
@ -1,5 +1,7 @@
|
||||
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
|
||||
@ -7,7 +9,7 @@ from rest_framework.response import Response
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
|
||||
|
||||
@extend_schema(deprecated=True)
|
||||
class Index(views.APIView):
|
||||
|
||||
permission_classes = [
|
||||
@ -29,7 +31,7 @@ class Index(views.APIView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
||||
body: dict = {
|
||||
'requests': reverse('API:_api_assistance_request-list', request=request)
|
||||
'requests': reverse('v1:_api_assistance_request-list', request=request)
|
||||
}
|
||||
|
||||
return Response(body)
|
||||
|
@ -4,6 +4,7 @@ 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'
|
||||
|
@ -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,6 +31,7 @@ class ConfigGroupsList(generics.ListAPIView):
|
||||
|
||||
|
||||
|
||||
@extend_schema( deprecated = True )
|
||||
@extend_schema_view(
|
||||
get=extend_schema(
|
||||
summary = "Get A Config Group",
|
||||
|
@ -10,7 +10,7 @@ from api.serializers.core.ticket_category import TicketCategory, TicketCategoryS
|
||||
from api.views.mixin import OrganizationPermissionAPI
|
||||
|
||||
|
||||
|
||||
@extend_schema(deprecated=True)
|
||||
class View(OrganizationMixin, viewsets.ModelViewSet):
|
||||
|
||||
permission_classes = [
|
||||
|
@ -10,7 +10,7 @@ from api.serializers.core.ticket_comment_category import TicketCommentCategory,
|
||||
from api.views.mixin import OrganizationPermissionAPI
|
||||
|
||||
|
||||
|
||||
@extend_schema(deprecated=True)
|
||||
class View(OrganizationMixin, viewsets.ModelViewSet):
|
||||
|
||||
permission_classes = [
|
||||
|
@ -12,7 +12,7 @@ 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 = [
|
||||
|
@ -1,3 +1,5 @@
|
||||
from django.conf import settings as django_settings
|
||||
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from rest_framework import generics, permissions, routers, viewsets
|
||||
@ -16,10 +18,10 @@ class Index(viewsets.ViewSet):
|
||||
|
||||
|
||||
def get_view_name(self):
|
||||
return "API Index"
|
||||
return "API"
|
||||
|
||||
def get_view_description(self, html=False) -> str:
|
||||
text = "My REST API"
|
||||
text = "Centurion ERP Rest API"
|
||||
if html:
|
||||
return mark_safe(f"<p>{text}</p>")
|
||||
else:
|
||||
@ -27,16 +29,18 @@ class Index(viewsets.ViewSet):
|
||||
|
||||
|
||||
def list(self, request, pk=None):
|
||||
return Response(
|
||||
{
|
||||
|
||||
API: dict = {
|
||||
# "teams": reverse("_api_teams", request=request),
|
||||
'assistance': reverse("API:_api_assistance", request=request),
|
||||
"devices": reverse("API:device-list", request=request),
|
||||
"config_groups": reverse("API:_api_config_groups", request=request),
|
||||
'itim': reverse("API:_api_itim", request=request),
|
||||
"organizations": reverse("API:_api_orgs", request=request),
|
||||
'project_management': reverse("API:_api_project_management", request=request),
|
||||
"settings": reverse('API:_settings', request=request),
|
||||
"software": reverse("API:software-list", 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)
|
||||
}
|
||||
)
|
||||
|
||||
return Response( API )
|
||||
|
@ -1,11 +1,14 @@
|
||||
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):
|
||||
|
@ -14,7 +14,7 @@ from api.views.mixin import OrganizationPermissionAPI
|
||||
from itam.models.device import Device
|
||||
|
||||
|
||||
|
||||
@extend_schema( deprecated = True )
|
||||
class DeviceViewSet(OrganizationMixin, viewsets.ModelViewSet):
|
||||
|
||||
permission_classes = [
|
||||
|
@ -1,11 +1,10 @@
|
||||
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
|
||||
@ -34,6 +33,7 @@ class InventoryPermissions(OrganizationPermissionAPI):
|
||||
|
||||
|
||||
|
||||
@extend_schema( deprecated = True )
|
||||
class Collect(OrganizationPermissionAPI, views.APIView):
|
||||
|
||||
queryset = Device.objects.all()
|
||||
@ -91,12 +91,13 @@ 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 Http404
|
||||
raise PermissionDenied()
|
||||
|
||||
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
|
||||
@ -105,7 +106,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.message
|
||||
response_data = e.detail
|
||||
|
||||
except Exception as e:
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
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
|
||||
@ -11,7 +13,7 @@ from api.views.mixin import OrganizationPermissionAPI
|
||||
from itam.models.software import Software
|
||||
|
||||
|
||||
|
||||
@extend_schema(deprecated = True)
|
||||
class SoftwareViewSet(OrganizationMixin, viewsets.ModelViewSet):
|
||||
|
||||
permission_classes = [
|
||||
|
@ -5,7 +5,7 @@ from api.serializers.itim.change import ChangeTicketSerializer
|
||||
from api.views.core.tickets import View
|
||||
|
||||
|
||||
|
||||
@extend_schema(deprecated=True)
|
||||
class View(View):
|
||||
|
||||
_ticket_type:str = 'change'
|
||||
|
@ -5,6 +5,7 @@ from api.views.core.tickets import View
|
||||
|
||||
|
||||
|
||||
@extend_schema(deprecated=True)
|
||||
class View(View):
|
||||
|
||||
_ticket_type:str = 'incident'
|
||||
|
@ -1,5 +1,7 @@
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from drf_spectacular.utils import extend_schema
|
||||
|
||||
from rest_framework import views
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
@ -7,6 +9,7 @@ from rest_framework.reverse import reverse
|
||||
|
||||
|
||||
|
||||
@extend_schema(deprecated=True)
|
||||
class Index(views.APIView):
|
||||
|
||||
permission_classes = [
|
||||
@ -28,9 +31,9 @@ class Index(views.APIView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
||||
body: dict = {
|
||||
'changes': reverse('API:_api_itim_change-list', request=request),
|
||||
'incidents': reverse('API:_api_itim_incident-list', request=request),
|
||||
'problems': reverse('API:_api_itim_problem-list', request=request),
|
||||
'changes': reverse('v1:_api_itim_change-list', request=request),
|
||||
'incidents': reverse('v1:_api_itim_incident-list', request=request),
|
||||
'problems': reverse('v1:_api_itim_problem-list', request=request),
|
||||
}
|
||||
|
||||
return Response(body)
|
||||
|
@ -5,6 +5,7 @@ from api.views.core.tickets import View
|
||||
|
||||
|
||||
|
||||
@extend_schema(deprecated=True)
|
||||
class View(View):
|
||||
|
||||
_ticket_type:str = 'problem'
|
||||
|
@ -6,6 +6,8 @@ from rest_framework.permissions import DjangoObjectPermissions
|
||||
|
||||
from access.mixin import OrganizationMixin
|
||||
|
||||
from core import exceptions as centurion_exceptions
|
||||
|
||||
|
||||
|
||||
class OrganizationPermissionAPI(DjangoObjectPermissions, OrganizationMixin):
|
||||
@ -27,104 +29,111 @@ class OrganizationPermissionAPI(DjangoObjectPermissions, OrganizationMixin):
|
||||
|
||||
return False
|
||||
|
||||
self.request = request
|
||||
try:
|
||||
|
||||
method = self.request._request.method.lower()
|
||||
self.request = request
|
||||
|
||||
if method.upper() not in view.allowed_methods:
|
||||
method = self.request._request.method.lower()
|
||||
|
||||
view.http_method_not_allowed(request._request)
|
||||
if method.upper() not in view.allowed_methods:
|
||||
|
||||
if hasattr(view, 'get_queryset'):
|
||||
view.http_method_not_allowed(request._request)
|
||||
|
||||
queryset = view.get_queryset()
|
||||
if request.user.is_authenticated and method == 'options':
|
||||
|
||||
self.obj = queryset.model
|
||||
return True
|
||||
|
||||
elif hasattr(view, 'queryset'):
|
||||
if hasattr(view, 'get_queryset'):
|
||||
|
||||
if view.queryset.model._meta:
|
||||
self.obj = view.queryset.model
|
||||
queryset = view.get_queryset()
|
||||
|
||||
object_organization = None
|
||||
self.obj = queryset.model
|
||||
|
||||
if method == 'get':
|
||||
elif hasattr(view, 'queryset'):
|
||||
|
||||
action = 'view'
|
||||
|
||||
elif method == 'post':
|
||||
if view.queryset.model._meta:
|
||||
|
||||
action = 'add'
|
||||
self.obj = view.queryset.model
|
||||
|
||||
if 'organization' in request.data:
|
||||
object_organization = None
|
||||
|
||||
if not request.data['organization']:
|
||||
raise ValidationError('you must provide an organization')
|
||||
if method == 'get':
|
||||
|
||||
object_organization = int(request.data['organization'])
|
||||
elif method == 'patch':
|
||||
action = 'view'
|
||||
|
||||
elif method == 'post':
|
||||
|
||||
action = 'change'
|
||||
action = 'add'
|
||||
|
||||
elif method == 'put':
|
||||
if 'organization' in request.data:
|
||||
|
||||
action = 'change'
|
||||
if not request.data['organization']:
|
||||
raise centurion_exceptions.ValidationError('you must provide an organization')
|
||||
|
||||
elif method == 'delete':
|
||||
object_organization = int(request.data['organization'])
|
||||
elif method == 'patch':
|
||||
|
||||
action = 'delete'
|
||||
action = 'change'
|
||||
|
||||
else:
|
||||
elif method == 'put':
|
||||
|
||||
action = 'view'
|
||||
action = 'change'
|
||||
|
||||
permission = self.obj._meta.app_label + '.' + action + '_' + self.obj._meta.model_name
|
||||
elif method == 'delete':
|
||||
|
||||
self.permission_required = [ permission ]
|
||||
action = 'delete'
|
||||
|
||||
if hasattr(view, 'get_dynamic_permissions'):
|
||||
else:
|
||||
|
||||
self.permission_required = view.get_dynamic_permissions()
|
||||
action = 'view'
|
||||
|
||||
if hasattr(self, 'obj'):
|
||||
|
||||
permission = self.obj._meta.app_label + '.' + action + '_' + self.obj._meta.model_name
|
||||
|
||||
self.permission_required = [ permission ]
|
||||
|
||||
if hasattr(view, 'get_dynamic_permissions'):
|
||||
|
||||
self.permission_required = view.get_dynamic_permissions()
|
||||
|
||||
|
||||
if view:
|
||||
if 'organization_id' in view.kwargs:
|
||||
|
||||
if view.kwargs['organization_id']:
|
||||
|
||||
object_organization = view.kwargs['organization_id']
|
||||
|
||||
if object_organization is None and 'pk' in view.kwargs:
|
||||
|
||||
try:
|
||||
|
||||
self.obj = view.queryset.get(pk=view.kwargs['pk']) # Here
|
||||
|
||||
except ObjectDoesNotExist:
|
||||
|
||||
return False
|
||||
|
||||
|
||||
if view:
|
||||
if 'organization_id' in view.kwargs:
|
||||
if obj:
|
||||
|
||||
if view.kwargs['organization_id']:
|
||||
if obj.get_organization():
|
||||
|
||||
object_organization = view.kwargs['organization_id']
|
||||
object_organization = obj.get_organization().id
|
||||
|
||||
if object_organization is None and 'pk' in view.kwargs:
|
||||
if hasattr(self.obj, 'is_global'):
|
||||
|
||||
if obj.is_global:
|
||||
|
||||
try:
|
||||
|
||||
self.obj = view.queryset.get(pk=view.kwargs['pk']) # Here
|
||||
|
||||
except ObjectDoesNotExist:
|
||||
|
||||
return False
|
||||
object_organization = 0
|
||||
|
||||
|
||||
if obj:
|
||||
if 'pk' in view.kwargs:
|
||||
|
||||
if obj.get_organization():
|
||||
if object_organization is None and view.queryset.model._meta.model_name == 'organization' and view.kwargs['pk']:
|
||||
|
||||
object_organization = obj.get_organization().id
|
||||
|
||||
if hasattr(self.obj, 'is_global'):
|
||||
|
||||
if obj.is_global:
|
||||
|
||||
object_organization = 0
|
||||
|
||||
|
||||
if 'pk' in view.kwargs:
|
||||
|
||||
if object_organization is None and view.queryset.model._meta.model_name == 'organization' and view.kwargs['pk']:
|
||||
|
||||
object_organization = view.kwargs['pk']
|
||||
object_organization = view.kwargs['pk']
|
||||
|
||||
if object_organization is None:
|
||||
|
||||
@ -137,35 +146,43 @@ class OrganizationPermissionAPI(DjangoObjectPermissions, OrganizationMixin):
|
||||
return False
|
||||
|
||||
|
||||
if hasattr(self, 'obj') and object_organization is None and 'pk' in view.kwargs:
|
||||
if hasattr(self, 'obj') and object_organization is None and 'pk' in view.kwargs:
|
||||
|
||||
if self.obj.get_organization():
|
||||
if self.obj.get_organization():
|
||||
|
||||
object_organization = self.obj.get_organization().id
|
||||
object_organization = self.obj.get_organization().id
|
||||
|
||||
if hasattr(self.obj, 'is_global'):
|
||||
if hasattr(self.obj, 'is_global'):
|
||||
|
||||
if self.obj.is_global:
|
||||
if self.obj.is_global:
|
||||
|
||||
object_organization = 0
|
||||
object_organization = 0
|
||||
|
||||
|
||||
# ToDo: implement proper checking of listview as this if allows ALL.
|
||||
if 'pk' not in view.kwargs and method == 'get' and object_organization is None:
|
||||
# ToDo: implement proper checking of listview as this if allows ALL.
|
||||
if 'pk' not in view.kwargs and method == 'get' and object_organization is None:
|
||||
|
||||
return True
|
||||
return True
|
||||
|
||||
if hasattr(self, 'default_organization'):
|
||||
object_organization = self.default_organization
|
||||
if hasattr(self, 'default_organization'):
|
||||
object_organization = self.default_organization
|
||||
|
||||
if method == 'post' and hasattr(self, 'default_organization'):
|
||||
if method == 'post' and hasattr(self, 'default_organization'):
|
||||
|
||||
if self.default_organization:
|
||||
if self.default_organization:
|
||||
|
||||
object_organization = self.default_organization.id
|
||||
object_organization = self.default_organization.id
|
||||
|
||||
if not self.has_organization_permission(object_organization) and not request.user.is_superuser:
|
||||
if not self.has_organization_permission(object_organization) and not request.user.is_superuser:
|
||||
|
||||
raise PermissionDenied('You are not part of this organization')
|
||||
raise PermissionDenied('You are not part of this organization')
|
||||
|
||||
except centurion_exceptions.MethodNotAllowed as e:
|
||||
|
||||
raise centurion_exceptions.MethodNotAllowed( str(method).upper() )
|
||||
|
||||
except Exception as e:
|
||||
|
||||
return False
|
||||
|
||||
return True
|
||||
|
@ -1,12 +1,14 @@
|
||||
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.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 = [
|
||||
@ -28,7 +30,7 @@ class Index(views.APIView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
||||
body: dict = {
|
||||
'projects': reverse('API:_api_projects-list', request=request)
|
||||
'projects': reverse('v1:_api_projects-list', request=request)
|
||||
}
|
||||
|
||||
return Response(body)
|
||||
|
@ -9,7 +9,7 @@ from api.serializers.project_management.project_milestone import ProjectMileston
|
||||
from api.views.mixin import OrganizationPermissionAPI
|
||||
|
||||
|
||||
|
||||
@extend_schema(deprecated = True )
|
||||
class View(OrganizationMixin, viewsets.ModelViewSet):
|
||||
|
||||
permission_classes = [
|
||||
|
@ -9,7 +9,7 @@ from api.views.core.tickets import View
|
||||
from api.views.mixin import OrganizationPermissionAPI
|
||||
|
||||
|
||||
|
||||
@extend_schema(deprecated = True )
|
||||
class View(OrganizationMixin, viewsets.ModelViewSet):
|
||||
|
||||
permission_classes = [
|
||||
|
@ -6,6 +6,7 @@ from api.views.core.tickets import View
|
||||
|
||||
|
||||
|
||||
@extend_schema(deprecated=True)
|
||||
class View(View):
|
||||
|
||||
_ticket_type:str = 'project_task'
|
||||
|
@ -8,7 +8,7 @@ from api.serializers.project_management.project_type import ProjectType, Project
|
||||
from api.views.mixin import OrganizationPermissionAPI
|
||||
|
||||
|
||||
|
||||
@extend_schema(deprecated = True )
|
||||
class View(OrganizationMixin, viewsets.ModelViewSet):
|
||||
|
||||
permission_classes = [
|
||||
|
@ -16,7 +16,7 @@ from project_management.models.projects import Project
|
||||
from settings.models.user_settings import UserSettings
|
||||
|
||||
|
||||
|
||||
@extend_schema(deprecated = True )
|
||||
class View(OrganizationMixin, viewsets.ModelViewSet):
|
||||
|
||||
filterset_fields = [
|
||||
@ -39,12 +39,18 @@ class View(OrganizationMixin, viewsets.ModelViewSet):
|
||||
|
||||
def get_serializer_class(self):
|
||||
|
||||
if self.has_organization_permission(
|
||||
organization = UserSettings.objects.get(user = self.request.user).default_organization,
|
||||
permissions_required = ['project_management.import_project']
|
||||
) or self.request.user.is_superuser:
|
||||
user_default_organization = UserSettings.objects.get(user = self.request.user).default_organization
|
||||
|
||||
return ProjectImportSerializer
|
||||
if user_default_organization:
|
||||
|
||||
if hasattr(user_default_organization, 'default_organization'):
|
||||
|
||||
if self.has_organization_permission(
|
||||
organization = user_default_organization.default_organization.id,
|
||||
permissions_required = ['project_management.import_project']
|
||||
) or self.request.user.is_superuser:
|
||||
|
||||
return ProjectImportSerializer
|
||||
|
||||
return ProjectSerializer
|
||||
|
||||
|
@ -10,7 +10,7 @@ from rest_framework.reverse import reverse
|
||||
from core.http.common import Http
|
||||
|
||||
|
||||
|
||||
@extend_schema( deprecated = True )
|
||||
class View(views.APIView):
|
||||
|
||||
permission_classes = [
|
||||
@ -37,11 +37,11 @@ class View(views.APIView):
|
||||
status = Http.Status.OK
|
||||
|
||||
response_data: dict = {
|
||||
"permissions": reverse('API:_settings_permissions', request=request),
|
||||
"project_state": reverse('API:_api_project_state-list', request=request),
|
||||
"project_type": reverse('API:_api_project_type-list', request=request),
|
||||
"ticket_categories": reverse('API:_api_ticket_category-list', request=request),
|
||||
"ticket_comment_categories": reverse('API:_api_ticket_comment_category-list', request=request)
|
||||
"permissions": reverse('v1:_settings_permissions', request=request),
|
||||
"project_state": reverse('v1:_api_project_state-list', request=request),
|
||||
"project_type": reverse('v1:_api_project_type-list', request=request),
|
||||
"ticket_categories": reverse('v1:_api_ticket_category-list', request=request),
|
||||
"ticket_comment_categories": reverse('v1:_api_ticket_comment_category-list', request=request)
|
||||
}
|
||||
|
||||
return Response(data=response_data,status=status)
|
||||
|
@ -9,7 +9,7 @@ from access.functions import permissions
|
||||
from core.http.common import Http
|
||||
|
||||
|
||||
|
||||
@extend_schema(deprecated=True)
|
||||
class View(views.APIView):
|
||||
|
||||
permission_classes = [
|
||||
|
602
app/api/viewsets/common.py
Normal file
602
app/api/viewsets/common.py
Normal file
@ -0,0 +1,602 @@
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.exceptions import APIException
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
|
||||
from access.mixin import OrganizationMixin
|
||||
|
||||
from api.auth import TokenScheme
|
||||
from api.react_ui_metadata import ReactUIMetadata
|
||||
from api.views.mixin import OrganizationPermissionAPI
|
||||
|
||||
|
||||
|
||||
class CommonViewSet(
|
||||
OrganizationMixin,
|
||||
viewsets.ViewSet
|
||||
):
|
||||
"""Common ViewSet class
|
||||
|
||||
This class is to be inherited by ALL viewsets.
|
||||
|
||||
Args:
|
||||
OrganizationMixin (class): Contains the Authorization checks.
|
||||
viewsets (class): Django Rest Framework base class.
|
||||
"""
|
||||
|
||||
@property
|
||||
def allowed_methods(self):
|
||||
"""Allowed HTTP Methods
|
||||
|
||||
_Optional_, HTTP Methods allowed for the `viewSet`.
|
||||
|
||||
Returns:
|
||||
list: Allowed HTTP Methods
|
||||
"""
|
||||
|
||||
return super().allowed_methods
|
||||
|
||||
|
||||
documentation: str = None
|
||||
""" Viewset Documentation URL
|
||||
|
||||
_Optional_, if specified will be add to list view metadata
|
||||
"""
|
||||
|
||||
|
||||
metadata_class = ReactUIMetadata
|
||||
""" Metadata Class
|
||||
|
||||
_Mandatory_, required so that the HTTP/Options method is populated with the data
|
||||
required to generate the UI.
|
||||
"""
|
||||
|
||||
model_documentation: str = None
|
||||
"""Model Documentation URL
|
||||
|
||||
_Optional_, if specified will be add to detail view metadata"""
|
||||
|
||||
page_layout: list = []
|
||||
""" Page layout class
|
||||
|
||||
_Optional_, used by metadata to add the page layout to the HTTP/Options method
|
||||
for detail view, Enables the UI can setup the page layout.
|
||||
"""
|
||||
|
||||
permission_classes = [ OrganizationPermissionAPI ]
|
||||
"""Permission Class
|
||||
|
||||
_Mandatory_, Permission check class
|
||||
"""
|
||||
|
||||
table_fields: list = []
|
||||
""" Table layout list
|
||||
|
||||
_Optional_, used by metadata for the table fields and added to the HTTP/Options
|
||||
method for detail view, Enables the UI can setup the table.
|
||||
"""
|
||||
|
||||
view_description: str = None
|
||||
|
||||
view_name: str = None
|
||||
|
||||
|
||||
def get_model_documentation(self):
|
||||
|
||||
if not self.model_documentation:
|
||||
|
||||
if hasattr(self.model, 'documentataion'):
|
||||
|
||||
self.model_documentation = self.model.documentation
|
||||
|
||||
else:
|
||||
|
||||
self.model_documentation = ''
|
||||
|
||||
return self.model_documentation
|
||||
|
||||
|
||||
def get_page_layout(self):
|
||||
|
||||
if len(self.page_layout) < 1:
|
||||
|
||||
if hasattr(self, 'model'):
|
||||
|
||||
if hasattr(self.model, 'page_layout'):
|
||||
|
||||
self.page_layout = self.model.page_layout
|
||||
|
||||
else:
|
||||
|
||||
self.page_layout = []
|
||||
|
||||
return self.page_layout
|
||||
|
||||
|
||||
def get_table_fields(self):
|
||||
|
||||
if len(self.table_fields) < 1:
|
||||
|
||||
if hasattr(self, 'model'):
|
||||
|
||||
if hasattr(self.model, 'table_fields'):
|
||||
|
||||
self.table_fields = self.model.table_fields
|
||||
|
||||
else:
|
||||
|
||||
self.table_fields = []
|
||||
|
||||
return self.table_fields
|
||||
|
||||
|
||||
def get_view_description(self, html=False) -> str:
|
||||
|
||||
if not self.view_description:
|
||||
|
||||
self.view_description = ""
|
||||
|
||||
if html:
|
||||
|
||||
return mark_safe(f"<p>{self.view_description}</p>")
|
||||
|
||||
else:
|
||||
|
||||
return self.view_description
|
||||
|
||||
|
||||
def get_view_name(self):
|
||||
|
||||
if hasattr(self, 'model'):
|
||||
|
||||
if self.detail:
|
||||
|
||||
return self.model._meta.verbose_name
|
||||
|
||||
return self.model._meta.verbose_name_plural
|
||||
|
||||
if not self.view_name:
|
||||
|
||||
return 'Error'
|
||||
|
||||
return self.view_name
|
||||
|
||||
|
||||
|
||||
|
||||
class ModelViewSetBase(
|
||||
CommonViewSet
|
||||
):
|
||||
|
||||
|
||||
filterset_fields: list = []
|
||||
"""Fields to use for filtering the query
|
||||
|
||||
_Optional_, if specified, these fields can be used to filter the API response
|
||||
"""
|
||||
|
||||
model: object = None
|
||||
"""Django Model
|
||||
_Mandatory_, Django model used for this view.
|
||||
"""
|
||||
|
||||
queryset: object = None
|
||||
"""View Queryset
|
||||
|
||||
_Optional_, View model Query
|
||||
"""
|
||||
|
||||
search_fields:list = []
|
||||
""" Search Fields
|
||||
|
||||
_Optional_, Used by API text search as the fields to search.
|
||||
"""
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
if not self.queryset:
|
||||
|
||||
queryset = self.model.objects.all()
|
||||
|
||||
if 'pk' in self.kwargs:
|
||||
|
||||
if self.kwargs['pk']:
|
||||
|
||||
queryset = queryset.filter( pk = int( self.kwargs['pk'] ) )
|
||||
|
||||
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']
|
||||
|
||||
|
||||
|
||||
class ModelViewSet(
|
||||
ModelViewSetBase,
|
||||
viewsets.ModelViewSet,
|
||||
):
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
"""Sainty override
|
||||
|
||||
This function overrides the function of the same name
|
||||
in the parent class for the purpose of ensuring a
|
||||
non-api exception will not have the API return a HTTP
|
||||
500 error.
|
||||
|
||||
This function is a sanity check that if it triggers,
|
||||
(an exception occured), the user will be presented with
|
||||
a stack trace that they will hopefully report as a bug.
|
||||
|
||||
HTTP status set to HTTP/501 so it's distinguishable from
|
||||
a HTTP/500 which is generally a random error that has not
|
||||
been planned for. i.e. uncaught exception
|
||||
"""
|
||||
|
||||
response = None
|
||||
|
||||
try:
|
||||
|
||||
response = super().retrieve(request = request, *args, **kwargs)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
if not isinstance(e, APIException):
|
||||
|
||||
response = Response(
|
||||
data = {
|
||||
'server_error': str(e)
|
||||
},
|
||||
status = 501
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
|
||||
class ModelCreateViewSet(
|
||||
ModelViewSetBase,
|
||||
viewsets.mixins.CreateModelMixin,
|
||||
):
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
"""Sainty override
|
||||
|
||||
This function overrides the function of the same name
|
||||
in the parent class for the purpose of ensuring a
|
||||
non-api exception will not have the API return a HTTP
|
||||
500 error.
|
||||
|
||||
This function is a sanity check that if it triggers,
|
||||
(an exception occured), the user will be presented with
|
||||
a stack trace that they will hopefully report as a bug.
|
||||
|
||||
HTTP status set to HTTP/501 so it's distinguishable from
|
||||
a HTTP/500 which is generally a random error that has not
|
||||
been planned for. i.e. uncaught exception
|
||||
"""
|
||||
|
||||
response = None
|
||||
|
||||
try:
|
||||
|
||||
response = super().create(request = request, *args, **kwargs)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
if not isinstance(e, APIException):
|
||||
|
||||
response = Response(
|
||||
data = {
|
||||
'server_error': str(e)
|
||||
},
|
||||
status = 501
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
|
||||
class ModelListRetrieveDeleteViewSet(
|
||||
viewsets.mixins.ListModelMixin,
|
||||
viewsets.mixins.RetrieveModelMixin,
|
||||
viewsets.mixins.DestroyModelMixin,
|
||||
viewsets.GenericViewSet,
|
||||
ModelViewSetBase
|
||||
):
|
||||
""" Use for models that you wish to delete and view ONLY!"""
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
"""Sainty override
|
||||
|
||||
This function overrides the function of the same name
|
||||
in the parent class for the purpose of ensuring a
|
||||
non-api exception will not have the API return a HTTP
|
||||
500 error.
|
||||
|
||||
This function is a sanity check that if it triggers,
|
||||
(an exception occured), the user will be presented with
|
||||
a stack trace that they will hopefully report as a bug.
|
||||
|
||||
HTTP status set to HTTP/501 so it's distinguishable from
|
||||
a HTTP/500 which is generally a random error that has not
|
||||
been planned for. i.e. uncaught exception
|
||||
"""
|
||||
|
||||
response = None
|
||||
|
||||
try:
|
||||
|
||||
response = super().list(request = request, *args, **kwargs)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
if not isinstance(e, APIException):
|
||||
|
||||
response = Response(
|
||||
data = {
|
||||
'server_error': str(e)
|
||||
},
|
||||
status = 501
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
"""Sainty override
|
||||
|
||||
This function overrides the function of the same name
|
||||
in the parent class for the purpose of ensuring a
|
||||
non-api exception will not have the API return a HTTP
|
||||
500 error.
|
||||
|
||||
This function is a sanity check that if it triggers,
|
||||
(an exception occured), the user will be presented with
|
||||
a stack trace that they will hopefully report as a bug.
|
||||
|
||||
HTTP status set to HTTP/501 so it's distinguishable from
|
||||
a HTTP/500 which is generally a random error that has not
|
||||
been planned for. i.e. uncaught exception
|
||||
"""
|
||||
|
||||
response = None
|
||||
|
||||
try:
|
||||
|
||||
response = super().retrieve(request = request, *args, **kwargs)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
if not isinstance(e, APIException):
|
||||
|
||||
response = Response(
|
||||
data = {
|
||||
'server_error': str(e)
|
||||
},
|
||||
status = 501
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
"""Sainty override
|
||||
|
||||
This function overrides the function of the same name
|
||||
in the parent class for the purpose of ensuring a
|
||||
non-api exception will not have the API return a HTTP
|
||||
500 error.
|
||||
|
||||
This function is a sanity check that if it triggers,
|
||||
(an exception occured), the user will be presented with
|
||||
a stack trace that they will hopefully report as a bug.
|
||||
|
||||
HTTP status set to HTTP/501 so it's distinguishable from
|
||||
a HTTP/500 which is generally a random error that has not
|
||||
been planned for. i.e. uncaught exception
|
||||
"""
|
||||
|
||||
response = None
|
||||
|
||||
try:
|
||||
|
||||
response = super().destroy(request = request, *args, **kwargs)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
if not isinstance(e, APIException):
|
||||
|
||||
response = Response(
|
||||
data = {
|
||||
'server_error': str(e)
|
||||
},
|
||||
status = 501
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
|
||||
class ModelRetrieveUpdateViewSet(
|
||||
viewsets.mixins.RetrieveModelMixin,
|
||||
viewsets.mixins.UpdateModelMixin,
|
||||
viewsets.GenericViewSet,
|
||||
ModelViewSetBase
|
||||
):
|
||||
""" Use for models that you wish to update and view ONLY!"""
|
||||
|
||||
|
||||
def partial_update(self, request, *args, **kwargs):
|
||||
"""Sainty override
|
||||
|
||||
This function overrides the function of the same name
|
||||
in the parent class for the purpose of ensuring a
|
||||
non-api exception will not have the API return a HTTP
|
||||
500 error.
|
||||
|
||||
This function is a sanity check that if it triggers,
|
||||
(an exception occured), the user will be presented with
|
||||
a stack trace that they will hopefully report as a bug.
|
||||
|
||||
HTTP status set to HTTP/501 so it's distinguishable from
|
||||
a HTTP/500 which is generally a random error that has not
|
||||
been planned for. i.e. uncaught exception
|
||||
"""
|
||||
|
||||
response = None
|
||||
|
||||
try:
|
||||
|
||||
response = super().partial_update(request = request, *args, **kwargs)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
if not isinstance(e, APIException):
|
||||
|
||||
response = Response(
|
||||
data = {
|
||||
'server_error': str(e)
|
||||
},
|
||||
status = 501
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
"""Sainty override
|
||||
|
||||
This function overrides the function of the same name
|
||||
in the parent class for the purpose of ensuring a
|
||||
non-api exception will not have the API return a HTTP
|
||||
500 error.
|
||||
|
||||
This function is a sanity check that if it triggers,
|
||||
(an exception occured), the user will be presented with
|
||||
a stack trace that they will hopefully report as a bug.
|
||||
|
||||
HTTP status set to HTTP/501 so it's distinguishable from
|
||||
a HTTP/500 which is generally a random error that has not
|
||||
been planned for. i.e. uncaught exception
|
||||
"""
|
||||
|
||||
response = None
|
||||
|
||||
try:
|
||||
|
||||
response = super().update(request = request, *args, **kwargs)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
if not isinstance(e, APIException):
|
||||
|
||||
response = Response(
|
||||
data = {
|
||||
'server_error': str(e)
|
||||
},
|
||||
status = 501
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
|
||||
|
||||
class ReadOnlyModelViewSet(
|
||||
viewsets.ReadOnlyModelViewSet,
|
||||
ModelViewSetBase
|
||||
):
|
||||
|
||||
permission_classes = [
|
||||
IsAuthenticated,
|
||||
]
|
||||
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
"""Sainty override
|
||||
|
||||
This function overrides the function of the same name
|
||||
in the parent class for the purpose of ensuring a
|
||||
non-api exception will not have the API return a HTTP
|
||||
500 error.
|
||||
|
||||
This function is a sanity check that if it triggers,
|
||||
(an exception occured), the user will be presented with
|
||||
a stack trace that they will hopefully report as a bug.
|
||||
|
||||
HTTP status set to HTTP/501 so it's distinguishable from
|
||||
a HTTP/500 which is generally a random error that has not
|
||||
been planned for. i.e. uncaught exception
|
||||
"""
|
||||
|
||||
response = None
|
||||
|
||||
try:
|
||||
|
||||
response = super().retrieve(request = request, *args, **kwargs)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
if not isinstance(e, APIException):
|
||||
|
||||
response = Response(
|
||||
data = {
|
||||
'server_error': str(e)
|
||||
},
|
||||
status = 501
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
"""Sainty override
|
||||
|
||||
This function overrides the function of the same name
|
||||
in the parent class for the purpose of ensuring a
|
||||
non-api exception will not have the API return a HTTP
|
||||
500 error.
|
||||
|
||||
This function is a sanity check that if it triggers,
|
||||
(an exception occured), the user will be presented with
|
||||
a stack trace that they will hopefully report as a bug.
|
||||
|
||||
HTTP status set to HTTP/501 so it's distinguishable from
|
||||
a HTTP/500 which is generally a random error that has not
|
||||
been planned for. i.e. uncaught exception
|
||||
"""
|
||||
|
||||
response = None
|
||||
|
||||
try:
|
||||
|
||||
response = super().list(request = request, *args, **kwargs)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
if not isinstance(e, APIException):
|
||||
|
||||
response = Response(
|
||||
data = {
|
||||
'server_error': str(e)
|
||||
},
|
||||
status = 501
|
||||
)
|
||||
|
||||
return response
|
38
app/api/viewsets/index.py
Normal file
38
app/api/viewsets/index.py
Normal file
@ -0,0 +1,38 @@
|
||||
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 = 'Centurion ERP API V2.'
|
||||
|
||||
view_name = "v2"
|
||||
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
|
||||
return Response(
|
||||
{
|
||||
"access": reverse('v2:_api_v2_access_home-list', request=request),
|
||||
"assistance": reverse('v2:_api_v2_assistance_home-list', request=request),
|
||||
"docs": reverse('v2:_api_v2_docs', request=request),
|
||||
"base": reverse('v2:_api_v2_base_home-list', request=request),
|
||||
"itam": reverse('v2:_api_v2_itam_home-list', request=request),
|
||||
"itim": reverse('v2:_api_v2_itim_home-list', request=request),
|
||||
"config_management": reverse('v2:_api_v2_config_management_home-list', request=request),
|
||||
"project_management": reverse('v2:_api_v2_project_management_home-list', request=request),
|
||||
"settings": reverse('v2:_api_v2_settings_home-list', request=request)
|
||||
}
|
||||
)
|
38
app/app/middleware/timezone.py
Normal file
38
app/app/middleware/timezone.py
Normal file
@ -0,0 +1,38 @@
|
||||
import zoneinfo
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class TimezoneMiddleware:
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
|
||||
def _activate_tz(tz):
|
||||
timezone.activate(zoneinfo.ZoneInfo(tz))
|
||||
|
||||
# tzname = request.session.get("django_timezone", None)
|
||||
|
||||
# if tzname:
|
||||
|
||||
# _activate_tz(tzname)
|
||||
|
||||
# else:
|
||||
|
||||
user = request.user
|
||||
|
||||
if hasattr(user, 'user_settings'):
|
||||
|
||||
tzname = user.user_settings.all()[0].timezone
|
||||
|
||||
# set the cookie
|
||||
# request.session['django_timezone'] = tzname
|
||||
|
||||
_activate_tz(tzname)
|
||||
|
||||
else:
|
||||
|
||||
timezone.deactivate()
|
||||
|
||||
return self.get_response(request)
|
69
app/app/serializers/content_type.py
Normal file
69
app/app/serializers/content_type.py
Normal file
@ -0,0 +1,69 @@
|
||||
from django.contrib.auth.models import ContentType
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
|
||||
|
||||
class ContentTypeBaseSerializer(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_content_type-detail", format="html"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
||||
model = ContentType
|
||||
|
||||
fields = '__all__'
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'url'
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'url'
|
||||
]
|
||||
|
||||
|
||||
|
||||
class ContentTypeViewSerializer(ContentTypeBaseSerializer):
|
||||
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> dict:
|
||||
|
||||
return {
|
||||
'_self': reverse("v2:_api_v2_content_type-detail", request=self._context['view'].request, kwargs={'pk': item.pk}),
|
||||
}
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = ContentType
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'app_label',
|
||||
'model',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'app_label',
|
||||
'model',
|
||||
'_urls',
|
||||
]
|
77
app/app/serializers/permission.py
Normal file
77
app/app/serializers/permission.py
Normal file
@ -0,0 +1,77 @@
|
||||
from django.contrib.auth.models import Permission
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from app.serializers.content_type import ContentTypeBaseSerializer
|
||||
|
||||
|
||||
class PermissionBaseSerializer(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_permission-detail", format="html"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Permission
|
||||
|
||||
fields = '__all__'
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'url'
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'url'
|
||||
]
|
||||
|
||||
|
||||
|
||||
class PermissionViewSerializer(PermissionBaseSerializer):
|
||||
|
||||
|
||||
content_type = ContentTypeBaseSerializer()
|
||||
|
||||
_urls = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, item) -> dict:
|
||||
|
||||
return {
|
||||
'_self': reverse("v2:_api_v2_permission-detail", request=self._context['view'].request, kwargs={'pk': item.pk}),
|
||||
}
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
model = Permission
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'name',
|
||||
'display_name',
|
||||
'codename',
|
||||
'content_type',
|
||||
'_urls',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'name',
|
||||
'display_name',
|
||||
'codename',
|
||||
'content_type',
|
||||
'_urls',
|
||||
]
|
||||
|
44
app/app/serializers/user.py
Normal file
44
app/app/serializers/user.py
Normal file
@ -0,0 +1,44 @@
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
|
||||
class UserBaseSerializer(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_user-detail", format="html"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
||||
model = User
|
||||
|
||||
fields = '__all__'
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'username',
|
||||
'is_active',
|
||||
'url'
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'display_name',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'username',
|
||||
'is_active',
|
||||
'url'
|
||||
]
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user