Merge branch 'development' into 'master'
chore: release 0.5.0 See merge request nofusscomputing/projects/django_template!21
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@ -6,3 +6,5 @@ __pycache__
|
||||
artifacts/
|
||||
**.tmp.*
|
||||
volumes/
|
||||
build/
|
||||
pages/
|
@ -73,6 +73,12 @@ Docker Container:
|
||||
# - '{dockerfile,dockerfile.j2}'
|
||||
# when: always
|
||||
|
||||
- if:
|
||||
$CI_COMMIT_AUTHOR =='nfc_bot <helpdesk@nofusscomputing.com>'
|
||||
&&
|
||||
$CI_COMMIT_BRANCH == "development"
|
||||
when: never
|
||||
|
||||
- if: # condition_not_master_or_dev_push
|
||||
$CI_COMMIT_BRANCH != "master" &&
|
||||
$CI_COMMIT_BRANCH != "development" &&
|
||||
|
35
.gitlab/merge_request_templates/default.md
Normal file
35
.gitlab/merge_request_templates/default.md
Normal file
@ -0,0 +1,35 @@
|
||||
### :books: Summary
|
||||
<!-- your summary here emojis ref: https://github.com/yodamad/gitlab-emoji -->
|
||||
|
||||
|
||||
|
||||
### :link: Links / References
|
||||
<!--
|
||||
|
||||
using a list as any links to other references or links as required. if relevent, describe the link/reference
|
||||
|
||||
Include any issues or related merge requests. Note: dependent MR's also to be added to "Merge request dependencies"
|
||||
|
||||
-->
|
||||
|
||||
|
||||
|
||||
### :construction_worker: Tasks
|
||||
|
||||
- [ ] Add your tasks here if required (delete)
|
||||
|
||||
<!-- dont remove tasks below strike through including the checkbox by enclosing in double tidle '~~' -->
|
||||
|
||||
- [ ] ~"breaking-change" Any Breaking change(s)
|
||||
|
||||
_Breaking Change must also be notated in the commit that introduces it and in [Conventional Commit Format](https://www.conventionalcommits.org/en/v1.0.0/)._
|
||||
|
||||
- [ ] ~Documentation Documentation written
|
||||
|
||||
_All features to be documented within the correct section(s). Administration, Development and/or User_
|
||||
|
||||
- [ ] Milestone assigned
|
||||
|
||||
- [ ] [Unit Test(s) Written](https://nofusscomputing.com/projects/django-template/development/testing/)
|
||||
|
||||
_ensure test coverage delta is not less than zero_
|
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@ -5,8 +5,12 @@
|
||||
"!python"
|
||||
],
|
||||
"python.testing.pytestArgs": [
|
||||
// "-v",
|
||||
// "--cov",
|
||||
// "--cov-report xml",
|
||||
"app"
|
||||
],
|
||||
"python.testing.unittestEnabled": true,
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestEnabled": true,
|
||||
"testing.coverageToolbarEnabled": true,
|
||||
}
|
@ -41,46 +41,7 @@ Updates to python modules will need to be captured with SCM. This can be done by
|
||||
!!! danger "Requirement"
|
||||
All models **are** to have tests written for them, Including testing between dependent models.
|
||||
|
||||
To ensure consistency and reliability of this application, tests are to be written. Each test is to test one item ONLY and no more. Each module is to contain a tests directory of the model being tested with a single file for grouping of what is being tested. for items that depend upon a parent model, the test file is to be within the child-models test directory named with format `test_<model>_<parent app>_<parent model name>`
|
||||
|
||||
_example structure for the device model that relies upon access app model organization, core app model history and model notes._
|
||||
|
||||
``` text
|
||||
|
||||
├── tests
|
||||
│ ├── device
|
||||
│ │ ├── test_device_access_organization.py
|
||||
│ │ ├── test_device_api_permission.py
|
||||
│ │ ├── test_device_core_history.py
|
||||
│ │ ├── test_device_core_notes.py
|
||||
│ │ ├── test_device_permission.py
|
||||
│ │ └── test_device.py
|
||||
|
||||
|
||||
```
|
||||
|
||||
Items to test include but are not limited to:
|
||||
|
||||
- CRUD permissions admin site
|
||||
|
||||
- CRUD permissions api site
|
||||
|
||||
- CRUD permissions main site
|
||||
|
||||
- can only access organization object
|
||||
|
||||
- can access global object (still to require model CRUD permission)
|
||||
|
||||
- parent models
|
||||
|
||||
|
||||
### Running Tests
|
||||
|
||||
test can be run by running the following:
|
||||
|
||||
1. `pip install -r requirements_test.txt -r requirements.txt`
|
||||
|
||||
1. `pytest --cov --cov-report html --cov=./`
|
||||
See [Documentation](https://nofusscomputing.com/projects/django-template/development/testing/) for further information
|
||||
|
||||
|
||||
## Docker Container
|
||||
|
13
README.md
13
README.md
@ -6,3 +6,16 @@
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
artifacts
|
||||
|
||||
|
||||
dont work to file
|
||||
https://gitlab.com/nofusscomputing/projects/django_template/-/jobs/artifacts/master/browse/artifacts/coverage/index.html?job=Unit
|
||||
|
||||
works to dir
|
||||
https://gitlab.com/nofusscomputing/projects/django_template/-/jobs/artifacts/master/browse/artifacts/coverage/?job=Unit
|
||||
|
||||
|
||||
|
@ -17,7 +17,7 @@ class TeamInline(admin.TabularInline):
|
||||
|
||||
class OrganizationAdmin(admin.ModelAdmin):
|
||||
fieldsets = [
|
||||
(None, {"fields": ["name", "slug"]}),
|
||||
(None, {"fields": ["name", 'manager', "slug"]}),
|
||||
#("Date information", {"fields": ["slug"], "classes": ["collapse"]}),
|
||||
]
|
||||
inlines = [TeamInline]
|
||||
|
37
app/access/forms/organization.py
Normal file
37
app/access/forms/organization.py
Normal file
@ -0,0 +1,37 @@
|
||||
from django import forms
|
||||
from django.db.models import Q
|
||||
|
||||
from app import settings
|
||||
|
||||
from access.models import Organization
|
||||
|
||||
|
||||
class OrganizationForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Organization
|
||||
fields = [
|
||||
'name',
|
||||
'manager',
|
||||
'model_notes',
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['created'] = forms.DateTimeField(
|
||||
label="Created",
|
||||
input_formats=settings.DATETIME_FORMAT,
|
||||
initial=kwargs['instance'].created,
|
||||
disabled=True,
|
||||
required=False,
|
||||
)
|
||||
|
||||
self.fields['modified'] = forms.DateTimeField(
|
||||
label="Modified",
|
||||
input_formats=settings.DATETIME_FORMAT,
|
||||
initial=kwargs['instance'].modified,
|
||||
disabled=True,
|
||||
required=False,
|
||||
)
|
81
app/access/forms/team.py
Normal file
81
app/access/forms/team.py
Normal file
@ -0,0 +1,81 @@
|
||||
from django import forms
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.db.models import Q
|
||||
from django.forms import inlineformset_factory
|
||||
|
||||
from app import settings
|
||||
|
||||
from .team_users import TeamUsersForm, TeamUsers
|
||||
from access.models import Team
|
||||
|
||||
|
||||
TeamUserFormSet = inlineformset_factory(
|
||||
model=TeamUsers,
|
||||
parent_model= Team,
|
||||
extra = 1,
|
||||
fields=[
|
||||
'user',
|
||||
'manager'
|
||||
]
|
||||
)
|
||||
|
||||
class TeamForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Team
|
||||
fields = [
|
||||
'name',
|
||||
'permissions',
|
||||
'model_notes',
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['created'] = forms.DateTimeField(
|
||||
label="Created",
|
||||
input_formats=settings.DATETIME_FORMAT,
|
||||
initial=kwargs['instance'].created,
|
||||
disabled=True,
|
||||
required=False,
|
||||
)
|
||||
|
||||
self.fields['modified'] = forms.DateTimeField(
|
||||
label="Modified",
|
||||
input_formats=settings.DATETIME_FORMAT,
|
||||
initial=kwargs['instance'].modified,
|
||||
disabled=True,
|
||||
required=False,
|
||||
)
|
||||
|
||||
self.fields['permissions'].widget.attrs = {'style': "height: 200px;"}
|
||||
|
||||
apps = [
|
||||
'access',
|
||||
'config_management',
|
||||
'core',
|
||||
'itam',
|
||||
'settings',
|
||||
]
|
||||
|
||||
exclude_models = [
|
||||
'appsettings',
|
||||
'organization'
|
||||
'settings',
|
||||
'usersettings',
|
||||
]
|
||||
|
||||
exclude_permissions = [
|
||||
'add_organization',
|
||||
'change_organization',
|
||||
'delete_organization',
|
||||
]
|
||||
|
||||
self.fields['permissions'].queryset = Permission.objects.filter(
|
||||
content_type__app_label__in=apps,
|
||||
).exclude(
|
||||
content_type__model__in=exclude_models
|
||||
).exclude(
|
||||
codename__in = exclude_permissions
|
||||
)
|
16
app/access/forms/team_users.py
Normal file
16
app/access/forms/team_users.py
Normal file
@ -0,0 +1,16 @@
|
||||
from django import forms
|
||||
from django.db.models import Q
|
||||
|
||||
from app import settings
|
||||
|
||||
from access.models import TeamUsers
|
||||
|
||||
|
||||
class TeamUsersForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = TeamUsers
|
||||
fields = [
|
||||
'user',
|
||||
'manager',
|
||||
]
|
18
app/access/migrations/0004_team_model_notes.py
Normal file
18
app/access/migrations/0004_team_model_notes.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-11 20:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0003_alter_team_organization'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='team',
|
||||
name='model_notes',
|
||||
field=models.TextField(blank=True, default=None, null=True),
|
||||
),
|
||||
]
|
@ -0,0 +1,26 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-17 10:03
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0004_team_model_notes'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='organization',
|
||||
name='manager',
|
||||
field=models.ForeignKey(help_text='Organization Manager', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='organization',
|
||||
name='model_notes',
|
||||
field=models.TextField(blank=True, default=None, null=True),
|
||||
),
|
||||
]
|
@ -6,7 +6,7 @@ from django.utils.functional import cached_property
|
||||
|
||||
|
||||
|
||||
from .models import Team
|
||||
from .models import Organization, Team
|
||||
|
||||
|
||||
class OrganizationMixin():
|
||||
@ -16,6 +16,21 @@ class OrganizationMixin():
|
||||
|
||||
user_groups = []
|
||||
|
||||
|
||||
def get_parent_obj(self):
|
||||
""" Get the Parent Model Object
|
||||
|
||||
Use in views where the the model has no organization and the organization should be fetched from the parent model.
|
||||
|
||||
Requires attribute `parent_model` within the view with the value of the parent's model class
|
||||
|
||||
Returns:
|
||||
parent_model (Model): with PK from kwargs['pk']
|
||||
"""
|
||||
|
||||
return self.parent_model.objects.get(pk=self.kwargs['pk'])
|
||||
|
||||
|
||||
def object_organization(self) -> int:
|
||||
|
||||
id = None
|
||||
@ -26,7 +41,17 @@ class OrganizationMixin():
|
||||
self.get_queryset()
|
||||
|
||||
|
||||
if hasattr(self, 'get_object'):
|
||||
if hasattr(self, 'parent_model'):
|
||||
obj = self.get_parent_obj()
|
||||
|
||||
id = obj.get_organization().id
|
||||
|
||||
if obj.is_global:
|
||||
|
||||
id = 0
|
||||
|
||||
|
||||
if hasattr(self, 'get_object') and id is None:
|
||||
|
||||
obj = self.get_object()
|
||||
|
||||
@ -45,6 +70,7 @@ class OrganizationMixin():
|
||||
|
||||
id = int(self.request.POST.get("organization", ""))
|
||||
|
||||
|
||||
return id
|
||||
|
||||
|
||||
@ -101,11 +127,8 @@ class OrganizationMixin():
|
||||
Get All groups the user is part of, fetch the associated team,
|
||||
iterate over the results adding the organization ID to a list to be returned.
|
||||
|
||||
Args:
|
||||
request (_type_): Current http request
|
||||
|
||||
Returns:
|
||||
_type_: _description_
|
||||
_type_: User Organizations.
|
||||
"""
|
||||
|
||||
user_organizations = []
|
||||
@ -124,7 +147,7 @@ class OrganizationMixin():
|
||||
|
||||
|
||||
# ToDo: Ensure that the group has access to item
|
||||
def has_organization_permission(self, organization=None) -> bool:
|
||||
def has_organization_permission(self, organization: int=None) -> bool:
|
||||
|
||||
has_permission = False
|
||||
|
||||
@ -152,20 +175,108 @@ class OrganizationMixin():
|
||||
return has_permission
|
||||
|
||||
|
||||
def permission_check(self, request, permissions_required: list = None) -> bool:
|
||||
|
||||
class OrganizationPermission(AccessMixin, OrganizationMixin):
|
||||
"""checking organization membership"""
|
||||
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.request = request
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
return self.handle_no_permission()
|
||||
if permissions_required:
|
||||
|
||||
self.permission_required = permissions_required
|
||||
|
||||
organization_manager_models = [
|
||||
'access.organization',
|
||||
'access.team',
|
||||
'access.teamusers',
|
||||
]
|
||||
|
||||
is_organization_manager = False
|
||||
|
||||
if hasattr(self, 'get_object'):
|
||||
|
||||
if not self.has_organization_permission() and not request.user.is_superuser:
|
||||
raise PermissionDenied('You are not part of this organization')
|
||||
if hasattr(self, 'model'):
|
||||
|
||||
if self.model._meta.label_lower in organization_manager_models:
|
||||
|
||||
organization = Organization.objects.get(pk=self.object_organization())
|
||||
|
||||
if organization.manager == request.user:
|
||||
|
||||
is_organization_manager = True
|
||||
|
||||
if not self.has_organization_permission() and not request.user.is_superuser and not is_organization_manager:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
class OrganizationPermission(AccessMixin, OrganizationMixin):
|
||||
"""## Permission Checking
|
||||
|
||||
The base django permissions have not been modified with this app providing Multi-Tenancy. This is done by a mixin, that checks if the item is apart of an organization, if it is; confirmation is made that the user is part of the same organization and as long as they have the correct permission within the organization, access is granted.
|
||||
|
||||
|
||||
### How it works
|
||||
|
||||
The overall permissions system of django has not been modified with it remaining fully functional. The multi-tenancy has been setup based off of an organization with teams. A team to the underlying django system is an extension of the django auth group and for every team created a django auth group is created. THe group name is set using the following format: `<organization>_<team name>` and contains underscores `_` instead of spaces.
|
||||
|
||||
A User who is added to an team as a "Manager" can modify the team members or if they have permission `access.change_team` which also allows the changing of team permissions. Modification of an organization can be done by the django administrator (super user) or any user with permission `access._change_organization`.
|
||||
|
||||
Items can be set as `Global`, meaning that all users who have the correct permission regardless of organization will be able to take action against the object.
|
||||
|
||||
Permissions that can be modified for a team have been limited to application permissions only unless adjust the permissions from the django admin site.
|
||||
|
||||
|
||||
### Multi-Tenancy workflow
|
||||
|
||||
The workflow is conducted as part of the view and has the following flow:
|
||||
|
||||
1. Checks if user is member of organization the object the action is being performed on. Will also return true if the object has field `is_global` set to `true`.
|
||||
|
||||
1. Fetches all teams the user is part of.
|
||||
|
||||
1. obtains all permissions that are linked to the team.
|
||||
|
||||
1. checks if user has the required permission for the action.
|
||||
|
||||
1. confirms that the team the permission came from is part of the same organization as the object the action is being conducted on.
|
||||
|
||||
1. ONLY on success of the above items, grants access.
|
||||
"""
|
||||
|
||||
permission_required: list = []
|
||||
""" Permission required for the view
|
||||
|
||||
Not specifying this property adjusts the permission check logic so that you can
|
||||
use the `permission_check()` function directly.
|
||||
|
||||
An example of a get request....
|
||||
|
||||
``` py
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
|
||||
return self.handle_no_permission()
|
||||
|
||||
if not self.permission_check(request, [ 'access.view_organization' ]):
|
||||
|
||||
raise PermissionDenied('You are not part of this organization')
|
||||
|
||||
return super().get(request, *args, **kwargs)
|
||||
```
|
||||
this example details manual usage of the `permission_check()` function for a get request.
|
||||
"""
|
||||
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
return self.handle_no_permission()
|
||||
|
||||
if len(self.permission_required) > 0:
|
||||
|
||||
if not self.permission_check(request):
|
||||
raise PermissionDenied('You are not part of this organization')
|
||||
|
||||
return super().dispatch(self.request, *args, **kwargs)
|
||||
|
@ -37,6 +37,20 @@ class Organization(SaveHistory):
|
||||
unique = True,
|
||||
)
|
||||
|
||||
manager = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.SET_NULL,
|
||||
blank = False,
|
||||
null = True,
|
||||
help_text = 'Organization Manager'
|
||||
)
|
||||
|
||||
model_notes = models.TextField(
|
||||
blank = True,
|
||||
default = None,
|
||||
null= True,
|
||||
)
|
||||
|
||||
slug = AutoSlugField()
|
||||
|
||||
created = AutoCreatedField()
|
||||
@ -73,6 +87,12 @@ class TenancyObject(models.Model):
|
||||
blank = False
|
||||
)
|
||||
|
||||
model_notes = models.TextField(
|
||||
blank = True,
|
||||
default = None,
|
||||
null= True,
|
||||
)
|
||||
|
||||
def get_organization(self) -> Organization:
|
||||
return self.organization
|
||||
|
||||
@ -86,11 +106,12 @@ class Team(Group, TenancyObject, SaveHistory):
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
|
||||
self.name = self.organization.name.lower().replace(' ', '_') + '_' + self.team_name.lower().replace(' ', '_')
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
super().save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
|
||||
|
||||
|
||||
team_name = models.CharField(
|
||||
@ -106,6 +127,13 @@ class Team(Group, TenancyObject, SaveHistory):
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
|
||||
@property
|
||||
def parent_object(self):
|
||||
""" Fetch the parent object """
|
||||
|
||||
return self.organization
|
||||
|
||||
|
||||
def permission_list(self) -> list:
|
||||
|
||||
permission_list = []
|
||||
@ -190,3 +218,10 @@ class TeamUsers(SaveHistory):
|
||||
|
||||
user.groups.add(group)
|
||||
|
||||
|
||||
@property
|
||||
def parent_object(self):
|
||||
""" Fetch the parent object """
|
||||
|
||||
return self.team
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
{% block title %}Organizations{% endblock %}
|
||||
{% block content_header_icon %}{% endblock %}
|
||||
|
||||
{% block body%}
|
||||
{% block content %}
|
||||
|
||||
<table class="data">
|
||||
<tr>
|
||||
|
@ -1,19 +1,89 @@
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% load markdown %}
|
||||
|
||||
{% block title %}Organization - {{ organization.name }}{% endblock %}
|
||||
|
||||
{% block body%}
|
||||
{% block content %}
|
||||
<style>
|
||||
form div .helptext {
|
||||
background-color: rgb(0, 140, 255);
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
<section class="content-header">
|
||||
<fieldset><label>Name</label><!-- <input type="text" value="{{ organization.name }}" /> -->{{form.name}}</fieldset>
|
||||
<fieldset><label>Created</label><input type="text" value="{{ organization.created }}" readonly /></fieldset>
|
||||
<fieldset><label>Modified</label><input type="text" value="{{ organization.modified }}" readonly /></fieldset>
|
||||
</section>
|
||||
{% include 'icons/issue_link.html.j2' with issue=13 %}<br>
|
||||
|
||||
<input type="button" value="<< Back" onclick="window.location='{% url 'Access:Organizations' %}';">
|
||||
<input type="button" value="New Team" onclick="window.location='{% url 'Access:_team_add' organization.id %}';">
|
||||
|
||||
.detail-view-field {
|
||||
display:unset;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 0px 20px 40px 20px;
|
||||
|
||||
}
|
||||
|
||||
.detail-view-field label {
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
width: 200px;
|
||||
margin: 10px;
|
||||
/*padding: 10px;*/
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
|
||||
}
|
||||
|
||||
.detail-view-field span {
|
||||
display: inline-block;
|
||||
width: 340px;
|
||||
margin: 10px;
|
||||
/*padding: 10px;*/
|
||||
border-bottom: 1px solid #ccc;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<div style="align-items:flex-start; align-content: center; display: flexbox; width: 100%">
|
||||
<div style="display: inline; width: 40%; margin: 30px;">
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.name.label }}</label>
|
||||
<span>{{ form.name.value }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.manager.label }}</label>
|
||||
<span>{{ form.manager.value }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.created.label }}</label>
|
||||
<span>{{ form.created.value }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.modified.label }}</label>
|
||||
<span>{{ form.modified.value }}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div style="display: inline; width: 40%; margin: 30px; text-align: left;">
|
||||
<div>
|
||||
<label style="font-weight: bold; width: 100%; border-bottom: 1px solid #ccc; display: block; text-align: inherit;">{{ form.model_notes.label }}</label>
|
||||
|
||||
<div style="display: inline-block; text-align: left;">{{ form.model_notes.value | markdown | safe }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: block;">
|
||||
<input type="button" value="<< Back" onclick="window.location='{% url 'Access:Organizations' %}';">
|
||||
<input type="button" value="New Team" onclick="window.location='{% url 'Access:_team_add' organization.id %}';">
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<table>
|
||||
|
@ -2,26 +2,12 @@
|
||||
|
||||
{% block title %}Team - {{ team.team_name }}{% endblock %}
|
||||
|
||||
{% block body%}
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div>
|
||||
<input name="organization" id="id_organization" type="hidden" value="{{ organization.id }}">
|
||||
<section class="content-header">
|
||||
<fieldset><label>Name</label><input name="name" required id="id_name" type="text" value="{{ team.team_name }}" /></fieldset>
|
||||
<fieldset><label>Created</label><input name="created" type="text" value="{{ team.created }}" readonly /></fieldset>
|
||||
<fieldset><label>Modified</label><input name="modified" type="text" value="{{ team.modified }}" readonly /></fieldset>
|
||||
<fieldset><label>Permissions</label>
|
||||
<select name="permissions" id="id_permissions" style="height: 200px;" multiple>
|
||||
{% for permission in permissions %}
|
||||
{% if 'administration' not in permission.content_type|lower and 'authorization' not in permission.content_type|lower and 'content types' not in permission.content_type|lower and 'session' not in permission.content_type|lower and 'python social auth' not in permission.content_type|lower and 'add_organization' not in permission.codename|lower and 'delete_organization' not in permission.codename|lower %}
|
||||
<option value="{{ permission.id }}" {% for team_permission in team.permissions.all %}{% if permission.id == team_permission.id %}selected{% endif %}{% endfor %}>{{ permission.content_type }} | {{ permission.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</fieldset>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{{ form.as_div }}
|
||||
|
||||
{% include 'icons/issue_link.html.j2' with issue=13 %}<br>
|
||||
<input style="display:unset;" type="submit" value="Submit">
|
||||
</form>
|
||||
|
0
app/access/tests/__init__.py
Normal file
0
app/access/tests/__init__.py
Normal file
0
app/access/tests/abstract/__init__.py
Normal file
0
app/access/tests/abstract/__init__.py
Normal file
@ -0,0 +1,251 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from django.test import Client
|
||||
from django.shortcuts import reverse
|
||||
|
||||
|
||||
|
||||
class OrganizationManagerModelPermissionView:
|
||||
""" Tests for checking Organization Manager model permissions """
|
||||
|
||||
|
||||
app_namespace: str = None
|
||||
""" Application namespace of the model being tested """
|
||||
|
||||
different_organization_is_manager: object
|
||||
""" User whom is organization Manager of different organization than object """
|
||||
|
||||
url_name_view: str
|
||||
""" url name of the model view to be tested """
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
""" View URL kwargs for model being tested """
|
||||
|
||||
user_is_organization_manager: object
|
||||
""" User whom is organization Manager of the object"""
|
||||
|
||||
|
||||
|
||||
def test_model_view_different_organizaiton_is_organization_manager_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view with user from different organization whom is an organization Manager.
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
if self.app_namespace:
|
||||
|
||||
url = reverse(self.app_namespace + ':' + self.url_name_view, kwargs=self.url_view_kwargs)
|
||||
|
||||
else:
|
||||
|
||||
url = reverse(self.url_name_view, kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.different_organization_is_manager)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_model_view_has_no_permission_is_organization_manager(self):
|
||||
""" Confirm that an organization manager can view the model
|
||||
|
||||
Attempt to view as user who is an organization manager and has no permissions assigned.
|
||||
Object to be within same organization the user is a manager of.
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
if self.app_namespace:
|
||||
|
||||
url = reverse(self.app_namespace + ':' + self.url_name_view, kwargs=self.url_view_kwargs)
|
||||
|
||||
else:
|
||||
|
||||
url = reverse(self.url_name_view, kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.user_is_organization_manager)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
class OrganizationManagerModelPermissionAdd:
|
||||
""" Tests for checking model Add permissions """
|
||||
|
||||
|
||||
app_namespace: str = None
|
||||
""" Application namespace of the model being tested """
|
||||
|
||||
different_organization_is_manager: object
|
||||
""" User whom is organization Manager of different organization than object """
|
||||
|
||||
url_name_view: str
|
||||
""" url name of the model view to be tested """
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
""" View URL kwargs for model being tested """
|
||||
|
||||
user_is_organization_manager: object
|
||||
""" User whom is organization Manager of the object"""
|
||||
|
||||
|
||||
|
||||
def test_model_add_different_organization_is_organization_manager_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
attempt to add as user from different organization whom is an organization Manager.
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name_add, kwargs=self.url_add_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.different_organization_is_manager)
|
||||
response = client.post(url, data=self.add_data)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_model_add_has_no_permission_is_organization_manager(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user who is an organization manager and has no permissions assigned.
|
||||
Object to be within same organization the user is a manager of.
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name_add, kwargs=self.url_add_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.user_is_organization_manager)
|
||||
response = client.post(url, data=self.add_data)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
class OrganizationManagerModelPermissionChange:
|
||||
""" Tests for checking model change permissions """
|
||||
|
||||
|
||||
app_namespace: str = None
|
||||
""" Application namespace of the model being tested """
|
||||
|
||||
different_organization_is_manager: object
|
||||
""" User whom is organization Manager of different organization than object """
|
||||
|
||||
url_name_change: str
|
||||
""" url name of the model view to be tested """
|
||||
|
||||
url_change_kwargs: dict = None
|
||||
""" View URL kwargs for model being tested """
|
||||
|
||||
user_is_organization_manager: object
|
||||
""" User whom is organization Manager of the object"""
|
||||
|
||||
|
||||
|
||||
def test_model_change_different_organization_is_organization_manager_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user from different organization whom is an organization Manager.
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name_change, kwargs=self.url_change_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.different_organization_is_manager)
|
||||
response = client.post(url, data=self.change_data)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_model_change_has_no_permission_is_organization_manager(self):
|
||||
""" Check correct permission for change
|
||||
|
||||
Make change as user who is an organization manager and has no permissions assigned.
|
||||
Object to be within same organization the user is a manager of.
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name_change, kwargs=self.url_change_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.user_is_organization_manager)
|
||||
response = client.post(url, data=self.change_data)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
class OrganizationManagerModelPermissionDelete:
|
||||
""" Tests for checking model delete permissions """
|
||||
|
||||
|
||||
app_namespace: str = None
|
||||
""" Application namespace of the model being tested """
|
||||
|
||||
different_organization_is_manager: object
|
||||
""" User whom is organization Manager of different organization than object """
|
||||
|
||||
url_name_view: str
|
||||
""" url name of the model view to be tested """
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
""" View URL kwargs for model being tested """
|
||||
|
||||
user_is_organization_manager: object
|
||||
""" User whom is organization Manager of the object"""
|
||||
|
||||
|
||||
|
||||
def test_model_delete_different_organization_is_organization_manager_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user from different organization whom is an organization Manager.
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name_delete, kwargs=self.url_delete_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.different_organization_is_manager)
|
||||
response = client.delete(url, data=self.delete_data)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_model_delete_has_no_permission_is_organization_manager(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Delete item as user who is an organization manager and has no permissions assigned.
|
||||
Object to be within same organization the user is a manager of.
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name_delete, kwargs=self.url_delete_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.user_is_organization_manager)
|
||||
response = client.delete(url, data=self.delete_data)
|
||||
|
||||
assert response.status_code == 302 and response.url == self.url_delete_response
|
||||
|
||||
|
||||
class OrganizationManagerModelPermissions(
|
||||
OrganizationManagerModelPermissionView,
|
||||
OrganizationManagerModelPermissionAdd,
|
||||
OrganizationManagerModelPermissionChange,
|
||||
OrganizationManagerModelPermissionDelete
|
||||
):
|
||||
""" Tests for checking Organization Manager model permissions
|
||||
|
||||
This class includes all test cases for: Add, Change, Delete and View.
|
||||
"""
|
||||
|
||||
app_namespace: str = None
|
@ -10,14 +10,32 @@ import unittest
|
||||
import requests
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
from access.tests.abstract.model_permissions_organization_manager import OrganizationManagerModelPermissionChange, OrganizationManagerModelPermissionView
|
||||
|
||||
from app.tests.abstract.model_permissions import ModelPermissionsView, ModelPermissionsChange
|
||||
|
||||
|
||||
class OrganizationPermissions(TestCase):
|
||||
class OrganizationPermissions(
|
||||
TestCase,
|
||||
ModelPermissionsView,
|
||||
ModelPermissionsChange,
|
||||
OrganizationManagerModelPermissionChange,
|
||||
OrganizationManagerModelPermissionView,
|
||||
):
|
||||
|
||||
model = Organization
|
||||
|
||||
model_name = 'organization'
|
||||
app_label = 'access'
|
||||
app_namespace = 'Access'
|
||||
|
||||
url_name_view = '_organization_view'
|
||||
|
||||
# url_name_add = '_organization_add'
|
||||
|
||||
url_name_change = '_organization_view'
|
||||
|
||||
# url_name_delete = '_organization_delete'
|
||||
|
||||
# url_delete_response = reverse('ITAM:Operating Systems')
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
@ -34,7 +52,11 @@ class OrganizationPermissions(TestCase):
|
||||
|
||||
self.organization = organization
|
||||
|
||||
different_organization = Organization.objects.create(name='test_different_organization')
|
||||
different_organization = Organization.objects.create(
|
||||
name='test_different_organization'
|
||||
)
|
||||
|
||||
self.different_organization = different_organization
|
||||
|
||||
|
||||
# self.item = self.model.objects.create(
|
||||
@ -44,11 +66,27 @@ class OrganizationPermissions(TestCase):
|
||||
|
||||
self.item = organization
|
||||
|
||||
|
||||
self.url_view_kwargs = {'pk': self.item.id}
|
||||
|
||||
# self.url_add_kwargs = {'pk': self.item.id}
|
||||
|
||||
# self.add_data = {'operating_system': 'operating_system', 'organization': self.organization.id}
|
||||
|
||||
self.url_change_kwargs = {'pk': self.item.id}
|
||||
|
||||
self.change_data = {'operating_system': 'operating_system', 'organization': self.organization.id}
|
||||
|
||||
# self.url_delete_kwargs = {'pk': self.item.id}
|
||||
|
||||
# self.delete_data = {'operating_system': 'operating_system', 'organization': self.organization.id}
|
||||
|
||||
|
||||
view_permissions = Permission.objects.get(
|
||||
codename = 'view_' + self.model_name,
|
||||
codename = 'view_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -62,10 +100,10 @@ class OrganizationPermissions(TestCase):
|
||||
|
||||
|
||||
add_permissions = Permission.objects.get(
|
||||
codename = 'add_' + self.model_name,
|
||||
codename = 'add_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -79,10 +117,10 @@ class OrganizationPermissions(TestCase):
|
||||
|
||||
|
||||
change_permissions = Permission.objects.get(
|
||||
codename = 'change_' + self.model_name,
|
||||
codename = 'change_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -96,10 +134,10 @@ class OrganizationPermissions(TestCase):
|
||||
|
||||
|
||||
delete_permissions = Permission.objects.get(
|
||||
codename = 'delete_' + self.model_name,
|
||||
codename = 'delete_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -159,374 +197,18 @@ class OrganizationPermissions(TestCase):
|
||||
user = self.different_organization_user
|
||||
)
|
||||
|
||||
|
||||
|
||||
def test_organization_auth_view_user_anon_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_organization_view', kwargs={'pk': self.item.id})
|
||||
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 302 and response.url.startswith('/account/login')
|
||||
|
||||
|
||||
def test_organization_auth_view_no_permission_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view with user missing permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_organization_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_organization_auth_view_different_organizaiton_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view with user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_organization_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_organization_auth_view_has_permission(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view as user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_organization_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="No Add view exists")
|
||||
def test_organization_auth_add_user_anon_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_organization_add')
|
||||
|
||||
|
||||
response = client.put(url, data={'device': 'device'})
|
||||
|
||||
assert (
|
||||
response.status_code == 302
|
||||
or
|
||||
response.status_code == 403
|
||||
self.user_is_organization_manager = User.objects.create_user(
|
||||
username="test_org_manager",
|
||||
password="password"
|
||||
)
|
||||
|
||||
self.organization.manager = self.user_is_organization_manager
|
||||
self.organization.save()
|
||||
|
||||
@pytest.mark.skip(reason="No Add view exists")
|
||||
def test_organization_auth_add_no_permission_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user with no permissions
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_organization_add')
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.post(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="No Add view exists")
|
||||
def test_organization_auth_add_different_organization_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
attempt to add as user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_organization_add')
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.post(url, data={'name': 'device', 'organization': self.organization.id})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="No Add view exists")
|
||||
def test_organization_auth_add_permission_view_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add a user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_organization_add')
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.post(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="No Add view exists")
|
||||
def test_organization_auth_add_has_permission(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user with no permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_organization_add')
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.post(url, data={'device': 'device', 'organization': self.organization.id})
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
def test_organization_auth_change_user_anon_denied(self):
|
||||
""" Check correct permission for change
|
||||
|
||||
Attempt to change as anon
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_organization_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
response = client.patch(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 302 and response.url.startswith('/account/login')
|
||||
|
||||
|
||||
def test_organization_auth_change_no_permission_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user without permissions
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_organization_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.post(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_organization_auth_change_different_organization_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_organization_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.post(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_organization_auth_change_permission_view_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_organization_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.post(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_organization_auth_change_permission_add_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user with add permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_organization_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.post(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_organization_auth_change_has_permission(self):
|
||||
""" Check correct permission for change
|
||||
|
||||
Make change with user who has change permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_organization_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.change_user)
|
||||
response = client.post(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="No Delete view exists")
|
||||
def test_organization_auth_delete_user_anon_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete item as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_organization_delete', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
assert (
|
||||
response.status_code == 302
|
||||
or
|
||||
response.status_code == 403
|
||||
self.different_organization_is_manager = User.objects.create_user(
|
||||
username="test_org_manager_different_org",
|
||||
password="password"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="No Delete view exists")
|
||||
def test_organization_auth_delete_no_permission_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with no permissons
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_organization_delete', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="No Delete view exists")
|
||||
def test_organization_auth_delete_different_organization_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_organization_delete', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="No Delete view exists")
|
||||
def test_organization_auth_delete_permission_view_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with veiw permission only
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_organization_delete', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="No Delete view exists")
|
||||
def test_organization_auth_delete_permission_add_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with add permission only
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_organization_delete', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="No Delete view exists")
|
||||
def test_organization_auth_delete_permission_change_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with change permission only
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_organization_delete', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.change_user)
|
||||
response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="No Delete view exists")
|
||||
def test_organization_auth_delete_has_permission(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Delete item as user with delete permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_organization_delete', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.delete_user)
|
||||
response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 302 and response.url == reverse('Access:Devices')
|
||||
self.different_organization.manager = self.different_organization_is_manager
|
||||
self.different_organization.save()
|
||||
|
@ -1,23 +1,35 @@
|
||||
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 TestCase
|
||||
from rest_framework.test import APIClient as Client
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
|
||||
class OrganizationPermissionsAPI(TestCase):
|
||||
from api.tests.abstract.api_permissions import APIPermissionChange, APIPermissionView
|
||||
|
||||
|
||||
|
||||
class OrganizationPermissionsAPI(TestCase, APIPermissionChange, APIPermissionView):
|
||||
|
||||
model = Organization
|
||||
|
||||
model_name = 'organization'
|
||||
app_label = 'access'
|
||||
|
||||
app_namespace = 'API'
|
||||
|
||||
url_name = '_api_organization'
|
||||
|
||||
url_list = 'device-list'
|
||||
|
||||
change_data = {'name': 'device'}
|
||||
|
||||
# delete_data = {'device': 'device'}
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
@ -38,11 +50,18 @@ class OrganizationPermissionsAPI(TestCase):
|
||||
|
||||
self.item = organization
|
||||
|
||||
self.url_view_kwargs = {'pk': self.item.id}
|
||||
|
||||
self.url_kwargs = {'pk': self.item.id}
|
||||
|
||||
# self.add_data = {'name': 'device', 'organization': self.organization.id}
|
||||
|
||||
|
||||
view_permissions = Permission.objects.get(
|
||||
codename = 'view_' + self.model_name,
|
||||
codename = 'view_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -56,10 +75,10 @@ class OrganizationPermissionsAPI(TestCase):
|
||||
|
||||
|
||||
add_permissions = Permission.objects.get(
|
||||
codename = 'add_' + self.model_name,
|
||||
codename = 'add_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -73,10 +92,10 @@ class OrganizationPermissionsAPI(TestCase):
|
||||
|
||||
|
||||
change_permissions = Permission.objects.get(
|
||||
codename = 'change_' + self.model_name,
|
||||
codename = 'change_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -90,10 +109,10 @@ class OrganizationPermissionsAPI(TestCase):
|
||||
|
||||
|
||||
delete_permissions = Permission.objects.get(
|
||||
codename = 'delete_' + self.model_name,
|
||||
codename = 'delete_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -152,375 +171,3 @@ class OrganizationPermissionsAPI(TestCase):
|
||||
team = different_organization_team,
|
||||
user = self.different_organization_user
|
||||
)
|
||||
|
||||
|
||||
|
||||
def test_organization_auth_view_user_anon_denied_api(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_organization', kwargs={'pk': self.item.id})
|
||||
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_organization_auth_view_no_permission_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view with user missing permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_organization', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_organization_auth_view_different_organizaiton_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view with user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_organization', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_organization_auth_view_has_permission(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view as user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_organization', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="currently only able to add via admin interface")
|
||||
# def test_organization_auth_add_user_anon_denied(self):
|
||||
# """ Check correct permission for add
|
||||
|
||||
# Attempt to add as anon user
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# url = reverse('API:_api_orgs')
|
||||
|
||||
|
||||
# response = client.post(url, data={'device': 'device'})
|
||||
|
||||
# assert (
|
||||
# response.status_code == 302
|
||||
# or
|
||||
# response.status_code == 403
|
||||
# )
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="currently only able to add via admin interface")
|
||||
# def test_organization_auth_add_no_permission_denied(self):
|
||||
# """ Check correct permission for add
|
||||
|
||||
# Attempt to add as user with no permissions
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# url = reverse('API:_api_orgs')
|
||||
|
||||
|
||||
# client.force_login(self.no_permissions_user)
|
||||
# response = client.post(url, data={'device': 'device'})
|
||||
|
||||
# assert response.status_code == 403
|
||||
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="currently only able to add via admin interface")
|
||||
# def test_organization_auth_add_different_organization_denied(self):
|
||||
# """ Check correct permission for add
|
||||
|
||||
# attempt to add as user from different organization
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# url = reverse('API:_api_orgs')
|
||||
|
||||
|
||||
# client.force_login(self.different_organization_user)
|
||||
# response = client.post(url, data={'name': 'device', 'organization': self.organization.id})
|
||||
|
||||
# assert response.status_code == 403
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="currently only able to add via admin interface")
|
||||
# def test_organization_auth_add_permission_view_denied(self):
|
||||
# """ Check correct permission for add
|
||||
|
||||
# Attempt to add a user with view permission
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# url = reverse('API:_api_orgs')
|
||||
|
||||
|
||||
# client.force_login(self.view_user)
|
||||
# response = client.post(url, data={'device': 'device'})
|
||||
|
||||
# assert response.status_code == 403
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="currently only able to add via admin interface")
|
||||
# def test_organization_auth_add_has_permission(self):
|
||||
# """ Check correct permission for add
|
||||
|
||||
# Attempt to add as user with no permission
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# url = reverse('API:_api_orgs')
|
||||
|
||||
|
||||
# client.force_login(self.add_user)
|
||||
# response = client.post(url, data={'device': 'device', 'organization': self.organization.id})
|
||||
|
||||
# assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
def test_organization_auth_change_user_anon_denied(self):
|
||||
""" Check correct permission for change
|
||||
|
||||
Attempt to change as anon
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_organization', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
response = client.patch(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_organization_auth_change_no_permission_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user without permissions
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_organization', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.patch(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_organization_auth_change_different_organization_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_organization', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.patch(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_organization_auth_change_permission_view_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_organization', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.patch(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_organization_auth_change_permission_add_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user with add permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_organization', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.patch(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_organization_auth_change_has_permission(self):
|
||||
""" Check correct permission for change
|
||||
|
||||
Make change with user who has change permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_organization', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.change_user)
|
||||
response = client.patch(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="currently only able to add via admin interface")
|
||||
# def test_organization_auth_delete_user_anon_denied(self):
|
||||
# """ Check correct permission for delete
|
||||
|
||||
# Attempt to delete item as anon user
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# url = reverse('API:_api_orgs', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
# response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
# assert (
|
||||
# response.status_code == 302
|
||||
# or
|
||||
# response.status_code == 403
|
||||
# )
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="currently only able to add via admin interface")
|
||||
# def test_organization_auth_delete_no_permission_denied(self):
|
||||
# """ Check correct permission for delete
|
||||
|
||||
# Attempt to delete as user with no permissons
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# url = reverse('API:_api_organization', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
# client.force_login(self.no_permissions_user)
|
||||
# response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
# assert response.status_code == 403
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="currently only able to add via admin interface")
|
||||
# def test_organization_auth_delete_different_organization_denied(self):
|
||||
# """ Check correct permission for delete
|
||||
|
||||
# Attempt to delete as user from different organization
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# url = reverse('API:_api_organization', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
# client.force_login(self.different_organization_user)
|
||||
# response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
# assert response.status_code == 403
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="currently only able to add via admin interface")
|
||||
# def test_organization_auth_delete_permission_view_denied(self):
|
||||
# """ Check correct permission for delete
|
||||
|
||||
# Attempt to delete as user with veiw permission only
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# url = reverse('API:_api_organization', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
# client.force_login(self.view_user)
|
||||
# response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
# assert response.status_code == 403
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="currently only able to add via admin interface")
|
||||
# def test_organization_auth_delete_permission_add_denied(self):
|
||||
# """ Check correct permission for delete
|
||||
|
||||
# Attempt to delete as user with add permission only
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# url = reverse('API:_api_organization', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
# client.force_login(self.add_user)
|
||||
# response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
# assert response.status_code == 403
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="currently only able to add via admin interface")
|
||||
# def test_organization_auth_delete_permission_change_denied(self):
|
||||
# """ Check correct permission for delete
|
||||
|
||||
# Attempt to delete as user with change permission only
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# url = reverse('API:_api_organization', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
# client.force_login(self.change_user)
|
||||
# response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
# assert response.status_code == 403
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="currently only able to add via admin interface")
|
||||
# def test_organization_auth_delete_has_permission(self):
|
||||
# """ Check correct permission for delete
|
||||
|
||||
# Delete item as user with delete permission
|
||||
# """
|
||||
|
||||
# client = Client()
|
||||
# url = reverse('API:_api_organization', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
# client.force_login(self.delete_user)
|
||||
# response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
# assert response.status_code == 302 and response.url == reverse('API:_api_orgs')
|
||||
|
@ -12,87 +12,6 @@ from core.models.history import History
|
||||
from access.models import Organization
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_auth_view():
|
||||
# """ User requires Permission view_history """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_create():
|
||||
# """ History row must be added to history table on create """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_update():
|
||||
# """ History row must be added to history table on updatej """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_delete():
|
||||
# """ History row must be added to history table on delete """
|
||||
# pass
|
||||
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_operating_system_create():
|
||||
# """ History row must be added to history table on create
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_operating_system_update():
|
||||
# """ History row must be added to history table on update
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_operating_system_delete():
|
||||
# """ History row must be added to history table on delete
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_software_create():
|
||||
# """ History row must be added to history table on create
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_software_update():
|
||||
# """ History row must be added to history table on update
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_software_delete():
|
||||
# """ History row must be added to history table on delete
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
|
||||
|
||||
class OrganizationHistory(TestCase):
|
||||
|
||||
@ -130,8 +49,24 @@ class OrganizationHistory(TestCase):
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
||||
self.item_delete = self.model.objects.create(
|
||||
name = 'test_item_delete_' + self.model_name,
|
||||
)
|
||||
|
||||
self.item_delete.delete()
|
||||
|
||||
self.history_delete = History.objects.filter(
|
||||
item_pk = self.item_delete.pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
||||
self.history_delete_children = History.objects.filter(
|
||||
item_parent_pk = self.item_delete.pk,
|
||||
item_parent_class = self.model._meta.model_name,
|
||||
)
|
||||
|
||||
|
||||
|
||||
# field type testing to be done as part of model testing
|
||||
def test_history_entry_item_add_field_action(self):
|
||||
""" Ensure action is "add" for item creation """
|
||||
|
||||
@ -185,7 +120,6 @@ class OrganizationHistory(TestCase):
|
||||
|
||||
|
||||
|
||||
# field type testing to be done as part of model testing
|
||||
def test_history_entry_item_change_field_action(self):
|
||||
""" Ensure action is "add" for item creation """
|
||||
|
||||
@ -232,3 +166,22 @@ class OrganizationHistory(TestCase):
|
||||
# assert type(history['item_class']) is str
|
||||
|
||||
|
||||
|
||||
|
||||
################################## Delete ##################################
|
||||
|
||||
|
||||
|
||||
|
||||
def test_device_history_entry_delete(self):
|
||||
""" When an item is deleted, it's history entries must be removed """
|
||||
|
||||
assert self.history_delete.exists() is False
|
||||
|
||||
|
||||
def test_device_history_entry_children_delete(self):
|
||||
""" When an item is deleted, it's history entries must be removed """
|
||||
|
||||
assert self.history_delete_children.exists() is False
|
||||
|
||||
|
||||
|
@ -14,7 +14,7 @@ from access.models import Organization, Team, TeamUsers, Permission
|
||||
from core.models.history import History
|
||||
|
||||
|
||||
class DeviceHistoryPermissions(TestCase):
|
||||
class OrganizationHistoryPermissions(TestCase):
|
||||
|
||||
|
||||
item_model = Organization
|
||||
|
56
app/access/tests/team/test_team.py
Normal file
56
app/access/tests/team/test_team.py
Normal file
@ -0,0 +1,56 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from django.test import TestCase, Client
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
|
||||
|
||||
|
||||
class TeamModel(TestCase):
|
||||
|
||||
model = Team
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
""" Setup Test
|
||||
|
||||
"""
|
||||
|
||||
self.parent_item = Organization.objects.create(name='test_org')
|
||||
|
||||
different_organization = Organization.objects.create(name='test_different_organization')
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization=self.parent_item,
|
||||
name = 'teamone'
|
||||
)
|
||||
|
||||
|
||||
def test_model_has_property_parent_object(self):
|
||||
""" Check if model contains 'parent_object'
|
||||
|
||||
This is a required property for all models that have a parent
|
||||
"""
|
||||
|
||||
assert hasattr(self.model, 'parent_object')
|
||||
|
||||
|
||||
def test_model_property_parent_object_returns_object(self):
|
||||
""" Check if model contains 'parent_object'
|
||||
|
||||
This is a required property for all models that have a parent
|
||||
"""
|
||||
|
||||
assert self.item.parent_object is self.parent_item
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_function_save_attributes():
|
||||
""" Ensure save Attributes function match django default
|
||||
|
||||
the save method is overridden. the function attributes must match default django method
|
||||
"""
|
||||
pass
|
@ -8,98 +8,19 @@ from django.test import TestCase, Client
|
||||
from access.models import Organization
|
||||
|
||||
from core.models.history import History
|
||||
from core.tests.abstract.history_entry import HistoryEntry
|
||||
from core.tests.abstract.history_entry_child_model import HistoryEntryChildItem
|
||||
|
||||
from access.models import Team
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_auth_view():
|
||||
# """ User requires Permission view_history """
|
||||
# pass
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_create():
|
||||
# """ History row must be added to history table on create """
|
||||
# pass
|
||||
class TeamHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_update():
|
||||
# """ History row must be added to history table on updatej """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_delete():
|
||||
# """ History row must be added to history table on delete """
|
||||
# pass
|
||||
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_operating_system_create():
|
||||
# """ History row must be added to history table on create
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_operating_system_update():
|
||||
# """ History row must be added to history table on update
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_operating_system_delete():
|
||||
# """ History row must be added to history table on delete
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_software_create():
|
||||
# """ History row must be added to history table on create
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_software_update():
|
||||
# """ History row must be added to history table on update
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_software_delete():
|
||||
# """ History row must be added to history table on delete
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
|
||||
|
||||
class TeamHistory(TestCase):
|
||||
|
||||
model = Team
|
||||
|
||||
model_name = 'team'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
@ -109,8 +30,10 @@ class TeamHistory(TestCase):
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.item_parent = organization
|
||||
|
||||
self.item_create = self.model.objects.create(
|
||||
name = 'test_item_' + self.model_name,
|
||||
team_name = 'test_item_' + self.model._meta.model_name,
|
||||
organization = self.organization
|
||||
)
|
||||
|
||||
@ -122,122 +45,35 @@ class TeamHistory(TestCase):
|
||||
)
|
||||
|
||||
self.item_change = self.item_create
|
||||
self.item_change.name = 'test_item_' + self.model_name + '_changed'
|
||||
self.item_change.team_name = 'test_item_' + self.model._meta.model_name + '_changed'
|
||||
self.item_change.save()
|
||||
|
||||
self.field_after_expected_value = '{"name": "test_org_' + self.item_change.team_name + '", "team_name": "' + self.item_change.team_name + '"}'
|
||||
|
||||
self.history_change = History.objects.get(
|
||||
action = History.Actions.UPDATE[0],
|
||||
item_pk = self.item_change.pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
||||
debug = Group.objects.all()
|
||||
|
||||
@pytest.mark.skip(reason="fails, fixme see #46")
|
||||
# field type testing to be done as part of model testing
|
||||
def test_history_entry_item_add_field_action(self):
|
||||
""" Ensure action is "add" for item creation """
|
||||
self.item_delete = self.model.objects.create(
|
||||
team_name = 'test_item_delete_' + self.model._meta.model_name,
|
||||
organization = self.organization
|
||||
)
|
||||
|
||||
history = self.history_create.__dict__
|
||||
self.deleted_pk = self.item_delete.pk
|
||||
|
||||
assert history['action'] == int(History.Actions.ADD[0])
|
||||
# assert type(history['action']) is int
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_entry_item_add_field_after(self):
|
||||
""" Ensure after field contains correct value """
|
||||
|
||||
history = self.history_create.__dict__
|
||||
|
||||
assert history['after'] == str('{}')
|
||||
# assert type(history['after']) is str
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="fails, fixme see #46")
|
||||
def test_history_entry_item_add_field_before(self):
|
||||
""" Ensure before field is an empty JSON string for create """
|
||||
|
||||
history = self.history_create.__dict__
|
||||
|
||||
assert history['before'] == str('{}')
|
||||
# assert type(history['before']) is str
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="fails, fixme see #46")
|
||||
def test_history_entry_item_add_field_item_pk(self):
|
||||
""" Ensure history entry field item_pk is the created items pk """
|
||||
|
||||
history = self.history_create.__dict__
|
||||
|
||||
assert history['item_pk'] == self.item_create.pk
|
||||
# assert type(history['item_pk']) is int
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="fails, fixme see #46")
|
||||
def test_history_entry_item_add_field_item_class(self):
|
||||
""" Ensure history entry field item_class is the model name """
|
||||
|
||||
history = self.history_create.__dict__
|
||||
|
||||
assert history['item_class'] == self.model._meta.model_name
|
||||
# assert type(history['item_class']) is str
|
||||
|
||||
|
||||
|
||||
|
||||
################################## Change ##################################
|
||||
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="fails, fixme see #46")
|
||||
# field type testing to be done as part of model testing
|
||||
def test_history_entry_item_change_field_action(self):
|
||||
""" Ensure action is "add" for item creation """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['action'] == int(History.Actions.UPDATE[0])
|
||||
# assert type(history['action']) is int
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="fails, fixme see #46")
|
||||
def test_history_entry_item_change_field_after(self):
|
||||
""" Ensure after field contains correct value """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['after'] == str('{"name": "test_item_' + self.model_name + '_changed"}')
|
||||
# assert type(history['after']) is str
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_entry_item_change_field_before(self):
|
||||
""" Ensure before field is an empty JSON string for create """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['before'] == str('{}')
|
||||
# assert type(history['before']) is str
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="fails, fixme see #46")
|
||||
def test_history_entry_item_change_field_item_pk(self):
|
||||
""" Ensure history entry field item_pk is the created items pk """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['item_pk'] == self.item_create.pk
|
||||
# assert type(history['item_pk']) is int
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="fails, fixme see #46")
|
||||
def test_history_entry_item_change_field_item_class(self):
|
||||
""" Ensure history entry field item_class is the model name """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['item_class'] == self.model._meta.model_name
|
||||
# assert type(history['item_class']) is str
|
||||
self.item_delete.delete()
|
||||
|
||||
self.history_delete = History.objects.get(
|
||||
action = History.Actions.DELETE[0],
|
||||
item_pk = self.deleted_pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
||||
self.history_delete_children = History.objects.filter(
|
||||
item_parent_pk = self.deleted_pk,
|
||||
item_parent_class = self.item_parent._meta.model_name,
|
||||
)
|
||||
|
@ -10,15 +10,30 @@ import unittest
|
||||
import requests
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
from access.tests.abstract.model_permissions_organization_manager import OrganizationManagerModelPermissions
|
||||
|
||||
from app.tests.abstract.model_permissions import ModelPermissions
|
||||
|
||||
|
||||
|
||||
class TeamPermissions(TestCase):
|
||||
class TeamPermissions(
|
||||
TestCase,
|
||||
ModelPermissions,
|
||||
OrganizationManagerModelPermissions,
|
||||
):
|
||||
|
||||
model = Team
|
||||
|
||||
model_name = 'team'
|
||||
app_label = 'access'
|
||||
app_namespace = 'Access'
|
||||
|
||||
url_name_view = '_team_view'
|
||||
|
||||
url_name_add = '_team_add'
|
||||
|
||||
url_name_change = '_team_view'
|
||||
|
||||
url_name_delete = '_team_delete'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
@ -37,17 +52,37 @@ class TeamPermissions(TestCase):
|
||||
|
||||
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.url_view_kwargs = {'organization_id': self.organization.id, 'pk': self.item.id}
|
||||
|
||||
self.url_add_kwargs = {'pk': self.organization.id}
|
||||
|
||||
self.add_data = {'team': 'team'}
|
||||
|
||||
self.url_change_kwargs = {'organization_id': self.organization.id, 'pk': self.item.id}
|
||||
|
||||
self.change_data = {'team': 'team'}
|
||||
|
||||
self.url_delete_kwargs = {'organization_id': self.organization.id, 'pk': self.item.id}
|
||||
|
||||
self.delete_data = {'team': 'team'}
|
||||
|
||||
self.url_delete_response = reverse('Access:_organization_view', kwargs={'pk': self.organization.id})
|
||||
|
||||
|
||||
view_permissions = Permission.objects.get(
|
||||
codename = 'view_' + self.model_name,
|
||||
codename = 'view_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -61,10 +96,10 @@ class TeamPermissions(TestCase):
|
||||
|
||||
|
||||
add_permissions = Permission.objects.get(
|
||||
codename = 'add_' + self.model_name,
|
||||
codename = 'add_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -78,10 +113,10 @@ class TeamPermissions(TestCase):
|
||||
|
||||
|
||||
change_permissions = Permission.objects.get(
|
||||
codename = 'change_' + self.model_name,
|
||||
codename = 'change_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -95,10 +130,10 @@ class TeamPermissions(TestCase):
|
||||
|
||||
|
||||
delete_permissions = Permission.objects.get(
|
||||
codename = 'delete_' + self.model_name,
|
||||
codename = 'delete_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -158,353 +193,18 @@ class TeamPermissions(TestCase):
|
||||
user = self.different_organization_user
|
||||
)
|
||||
|
||||
self.user_is_organization_manager = User.objects.create_user(
|
||||
username="test_org_manager",
|
||||
password="password"
|
||||
)
|
||||
|
||||
self.organization.manager = self.user_is_organization_manager
|
||||
self.organization.save()
|
||||
|
||||
def test_team_auth_view_user_anon_denied(self):
|
||||
""" Check correct permission for view
|
||||
self.different_organization_is_manager = User.objects.create_user(
|
||||
username="test_org_manager_different_org",
|
||||
password="password"
|
||||
)
|
||||
|
||||
Attempt to view as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_view', kwargs={'organization_id': self.organization.id, 'pk': self.item.id})
|
||||
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 302 and response.url.startswith('/account/login')
|
||||
|
||||
|
||||
def test_team_auth_view_no_permission_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view with user missing permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_view', kwargs={'organization_id': self.organization.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_view_different_organizaiton_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view with user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_view', kwargs={'organization_id': self.organization.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_view_has_permission(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view as user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_view', kwargs={'organization_id': self.organization.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
def test_team_auth_add_user_anon_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_add', kwargs={'pk': self.organization.id})
|
||||
|
||||
|
||||
response = client.put(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 302 and response.url.startswith('/account/login')
|
||||
|
||||
|
||||
def test_team_auth_add_no_permission_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user with no permissions
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_add', kwargs={'pk': self.organization.id})
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.post(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_add_different_organization_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
attempt to add as user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_add', kwargs={'pk': self.organization.id})
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.post(url, data={'name': 'team', 'organization': self.organization.id})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_add_permission_view_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add a user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_add', kwargs={'pk': self.organization.id})
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.post(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_add_has_permission(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user with no permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_add', kwargs={'pk': self.organization.id})
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.post(url, data={'team': 'team', 'organization': self.organization.id})
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
def test_team_auth_change_user_anon_denied(self):
|
||||
""" Check correct permission for change
|
||||
|
||||
Attempt to change as anon
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_view', kwargs={'organization_id': self.organization.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
response = client.patch(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 302 and response.url.startswith('/account/login')
|
||||
|
||||
|
||||
def test_team_auth_change_no_permission_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user without permissions
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_view', kwargs={'organization_id': self.organization.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.post(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_change_different_organization_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_view', kwargs={'organization_id': self.organization.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.post(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_change_permission_view_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_view', kwargs={'organization_id': self.organization.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.post(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_change_permission_add_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user with add permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_view', kwargs={'organization_id': self.organization.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.post(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_change_has_permission(self):
|
||||
""" Check correct permission for change
|
||||
|
||||
Make change with user who has change permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_view', kwargs={'organization_id': self.organization.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.change_user)
|
||||
response = client.post(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
def test_team_auth_delete_user_anon_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete item as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_delete', kwargs={'organization_id': self.organization.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
response = client.delete(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 302 and response.url.startswith('/account/login')
|
||||
|
||||
|
||||
def test_team_auth_delete_no_permission_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with no permissons
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_delete', kwargs={'organization_id': self.organization.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.delete(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_delete_different_organization_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_delete', kwargs={'organization_id': self.organization.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.delete(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_delete_permission_view_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with veiw permission only
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_delete', kwargs={'organization_id': self.organization.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.delete(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_delete_permission_add_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with add permission only
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_delete', kwargs={'organization_id': self.organization.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.delete(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_delete_permission_change_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with change permission only
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_delete', kwargs={'organization_id': self.organization.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.change_user)
|
||||
response = client.delete(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_delete_has_permission(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Delete item as user with delete permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_delete', kwargs={'organization_id': self.organization.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.delete_user)
|
||||
response = client.delete(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 302 and response.url == reverse('Access:_organization_view', kwargs={'pk': self.organization.id})
|
||||
self.different_organization.manager = self.different_organization_is_manager
|
||||
self.different_organization.save()
|
@ -1,26 +1,32 @@
|
||||
# from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import AnonymousUser, User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.shortcuts import reverse
|
||||
from django.test import TestCase, Client as nClient
|
||||
|
||||
from rest_framework.test import APIClient as Client
|
||||
|
||||
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 import APIPermissions
|
||||
|
||||
|
||||
class TeamPermissionsAPI(TestCase):
|
||||
|
||||
class TeamPermissionsAPI(TestCase, APIPermissions):
|
||||
|
||||
model = Team
|
||||
|
||||
model_name = 'team'
|
||||
app_label = 'access'
|
||||
app_namespace = 'API'
|
||||
|
||||
url_name = '_api_team'
|
||||
|
||||
url_list = '_api_organization_teams'
|
||||
|
||||
change_data = {'name': 'device'}
|
||||
|
||||
delete_data = {'device': 'device'}
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
@ -45,11 +51,19 @@ class TeamPermissionsAPI(TestCase):
|
||||
name = 'teamone'
|
||||
)
|
||||
|
||||
|
||||
self.url_kwargs = {'organization_id': self.organization.id}
|
||||
|
||||
self.url_view_kwargs = {'organization_id': self.organization.id, 'group_ptr_id': self.item.id}
|
||||
|
||||
self.add_data = {'team_name': 'team_post'}
|
||||
|
||||
|
||||
view_permissions = Permission.objects.get(
|
||||
codename = 'view_' + self.model_name,
|
||||
codename = 'view_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -63,10 +77,10 @@ class TeamPermissionsAPI(TestCase):
|
||||
|
||||
|
||||
add_permissions = Permission.objects.get(
|
||||
codename = 'add_' + self.model_name,
|
||||
codename = 'add_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -80,10 +94,10 @@ class TeamPermissionsAPI(TestCase):
|
||||
|
||||
|
||||
change_permissions = Permission.objects.get(
|
||||
codename = 'change_' + self.model_name,
|
||||
codename = 'change_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -97,10 +111,10 @@ class TeamPermissionsAPI(TestCase):
|
||||
|
||||
|
||||
delete_permissions = Permission.objects.get(
|
||||
codename = 'delete_' + self.model_name,
|
||||
codename = 'delete_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -159,354 +173,3 @@ class TeamPermissionsAPI(TestCase):
|
||||
team = different_organization_team,
|
||||
user = self.different_organization_user
|
||||
)
|
||||
|
||||
|
||||
|
||||
def test_team_auth_view_user_anon_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_team', kwargs={'organization_id': self.organization.id, 'group_ptr_id': self.item.id})
|
||||
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_team_auth_view_no_permission_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view with user missing permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_team', kwargs={'organization_id': self.organization.id, 'group_ptr_id': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_view_different_organizaiton_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view with user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_team', kwargs={'organization_id': self.organization.id, 'group_ptr_id': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_view_has_permission(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view as user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_team', kwargs={'organization_id': self.organization.id, 'group_ptr_id': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
def test_team_auth_add_user_anon_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_organization_teams', kwargs={'organization_id': self.organization.id})
|
||||
|
||||
|
||||
response = client.post(url, data={'team_name': 'team'})
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_team_auth_add_no_permission_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user with no permissions
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_organization_teams', kwargs={'organization_id': self.organization.id})
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.post(url, data={'team_name': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_add_different_organization_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
attempt to add as user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_organization_teams', kwargs={'organization_id': self.organization.id})
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.post(url, data={'team_name': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_add_permission_view_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add a user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_organization_teams', kwargs={'organization_id': self.organization.id})
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.post(url, data={'team_name': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_add_has_permission(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user with no permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_organization_teams', kwargs={'organization_id': self.organization.id})
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.post(url, {'team_name': 'team_post'})
|
||||
|
||||
assert response.status_code == 201
|
||||
|
||||
|
||||
|
||||
def test_team_auth_change_user_anon_denied(self):
|
||||
""" Check correct permission for change
|
||||
|
||||
Attempt to change as anon
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_team', kwargs={'organization_id': self.organization.id, 'group_ptr_id': self.item.id})
|
||||
|
||||
|
||||
response = client.patch(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_team_auth_change_no_permission_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user without permissions
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_team', kwargs={'organization_id': self.organization.id, 'group_ptr_id': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.patch(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_change_different_organization_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_team', kwargs={'organization_id': self.organization.id, 'group_ptr_id': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.patch(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_change_permission_view_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_team', kwargs={'organization_id': self.organization.id, 'group_ptr_id': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.patch(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_change_permission_add_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user with add permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_team', kwargs={'organization_id': self.organization.id, 'group_ptr_id': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.patch(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_change_has_permission(self):
|
||||
""" Check correct permission for change
|
||||
|
||||
Make change with user who has change permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_team', kwargs={'organization_id': self.organization.id, 'group_ptr_id': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.change_user)
|
||||
response = client.patch(url, data={'id': self.item.id, 'team_name': 'team'})
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
def test_team_auth_delete_user_anon_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete item as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_team', kwargs={'organization_id': self.organization.id, 'group_ptr_id': self.item.id})
|
||||
|
||||
|
||||
response = client.delete(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_team_auth_delete_no_permission_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with no permissons
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_team', kwargs={'organization_id': self.organization.id, 'group_ptr_id': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.delete(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_delete_different_organization_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_team', kwargs={'organization_id': self.organization.id, 'group_ptr_id': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.delete(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_delete_permission_view_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with veiw permission only
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_team', kwargs={'organization_id': self.organization.id, 'group_ptr_id': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.delete(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_delete_permission_add_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with add permission only
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_team', kwargs={'organization_id': self.organization.id, 'group_ptr_id': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.delete(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_delete_permission_change_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with change permission only
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_team', kwargs={'organization_id': self.organization.id, 'group_ptr_id': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.change_user)
|
||||
response = client.delete(url, data={'team': 'team'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_auth_delete_has_permission(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Delete item as user with delete permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_team', kwargs={'organization_id': self.organization.id, 'group_ptr_id': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.delete_user)
|
||||
response = client.delete(url, data={'group_ptr_id': self.item.id}, content_type='application/json')
|
||||
|
||||
assert response.status_code == 204
|
||||
|
56
app/access/tests/team_user/test_team_user.py
Normal file
56
app/access/tests/team_user/test_team_user.py
Normal file
@ -0,0 +1,56 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from django.test import TestCase, Client
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
|
||||
|
||||
|
||||
class TeamUsersModel(TestCase):
|
||||
|
||||
model = TeamUsers
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
""" Setup Test
|
||||
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
different_organization = Organization.objects.create(name='test_different_organization')
|
||||
|
||||
self.parent_item = Team.objects.create(
|
||||
team_name = 'test_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
team_user = User.objects.create_user(username="test_self.team_user", password="password")
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
team = self.parent_item,
|
||||
user = team_user
|
||||
)
|
||||
|
||||
|
||||
|
||||
def test_model_has_property_parent_object(self):
|
||||
""" Check if model contains 'parent_object'
|
||||
|
||||
This is a required property for all models that have a parent
|
||||
"""
|
||||
|
||||
assert hasattr(self.model, 'parent_object')
|
||||
|
||||
|
||||
def test_model_property_parent_object_returns_object(self):
|
||||
""" Check if model contains 'parent_object'
|
||||
|
||||
This is a required property for all models that have a parent
|
||||
"""
|
||||
|
||||
assert self.item.parent_object == self.parent_item
|
@ -3,98 +3,20 @@ import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase, Client
|
||||
|
||||
from access.models import Organization
|
||||
|
||||
from core.models.history import History
|
||||
from core.tests.abstract.history_entry import HistoryEntry
|
||||
from core.tests.abstract.history_entry_child_model import HistoryEntryChildItem
|
||||
|
||||
from access.models import TeamUsers
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_auth_view():
|
||||
# """ User requires Permission view_history """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_create():
|
||||
# """ History row must be added to history table on create """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_update():
|
||||
# """ History row must be added to history table on updatej """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_delete():
|
||||
# """ History row must be added to history table on delete """
|
||||
# pass
|
||||
from access.models import Team, TeamUsers
|
||||
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_operating_system_create():
|
||||
# """ History row must be added to history table on create
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_operating_system_update():
|
||||
# """ History row must be added to history table on update
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_operating_system_delete():
|
||||
# """ History row must be added to history table on delete
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_software_create():
|
||||
# """ History row must be added to history table on create
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_software_update():
|
||||
# """ History row must be added to history table on update
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_software_delete():
|
||||
# """ History row must be added to history table on delete
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to do")
|
||||
class TeamUsersHistory(TestCase):
|
||||
class TeamUsersHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
|
||||
|
||||
model = TeamUsers
|
||||
|
||||
@ -109,11 +31,21 @@ class TeamUsersHistory(TestCase):
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.item_create = self.model.objects.create(
|
||||
name = 'test_item_' + self.model_name,
|
||||
self.item_parent = Team.objects.create(
|
||||
team_name = 'test_item_' + self.model._meta.model_name,
|
||||
organization = self.organization
|
||||
)
|
||||
|
||||
self.user = User.objects.create(
|
||||
username = 'test_item_' + self.model._meta.model_name,
|
||||
password = 'a random password'
|
||||
)
|
||||
|
||||
self.item_create = self.model.objects.create(
|
||||
user = self.user,
|
||||
team = self.item_parent
|
||||
)
|
||||
|
||||
|
||||
self.history_create = History.objects.get(
|
||||
action = History.Actions.ADD[0],
|
||||
@ -122,9 +54,11 @@ class TeamUsersHistory(TestCase):
|
||||
)
|
||||
|
||||
self.item_change = self.item_create
|
||||
self.item_change.name = 'test_item_' + self.model_name + '_changed'
|
||||
self.item_change.manager = True
|
||||
self.item_change.save()
|
||||
|
||||
self.field_after_expected_value = '{"manager": true}'
|
||||
|
||||
self.history_change = History.objects.get(
|
||||
action = History.Actions.UPDATE[0],
|
||||
item_pk = self.item_change.pk,
|
||||
@ -132,112 +66,27 @@ class TeamUsersHistory(TestCase):
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to do")
|
||||
# field type testing to be done as part of model testing
|
||||
def test_history_entry_item_add_field_action(self):
|
||||
""" Ensure action is "add" for item creation """
|
||||
self.user_delete = User.objects.create(
|
||||
username = 'test_item_delete' + self.model._meta.model_name,
|
||||
password = 'a random password'
|
||||
)
|
||||
|
||||
history = self.history_create.__dict__
|
||||
self.item_delete = self.model.objects.create(
|
||||
user = self.user_delete,
|
||||
team = self.item_parent
|
||||
)
|
||||
|
||||
assert history['action'] == int(History.Actions.ADD[0])
|
||||
# assert type(history['action']) is int
|
||||
self.deleted_pk = self.item_delete.pk
|
||||
|
||||
self.item_delete.delete()
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_entry_item_add_field_after(self):
|
||||
""" Ensure after field contains correct value """
|
||||
|
||||
history = self.history_create.__dict__
|
||||
|
||||
assert history['after'] == str('{}')
|
||||
# assert type(history['after']) is str
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to do")
|
||||
def test_history_entry_item_add_field_before(self):
|
||||
""" Ensure before field is an empty JSON string for create """
|
||||
|
||||
history = self.history_create.__dict__
|
||||
|
||||
assert history['before'] == str('{}')
|
||||
# assert type(history['before']) is str
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to do")
|
||||
def test_history_entry_item_add_field_item_pk(self):
|
||||
""" Ensure history entry field item_pk is the created items pk """
|
||||
|
||||
history = self.history_create.__dict__
|
||||
|
||||
assert history['item_pk'] == self.item_create.pk
|
||||
# assert type(history['item_pk']) is int
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to do")
|
||||
def test_history_entry_item_add_field_item_class(self):
|
||||
""" Ensure history entry field item_class is the model name """
|
||||
|
||||
history = self.history_create.__dict__
|
||||
|
||||
assert history['item_class'] == self.model._meta.model_name
|
||||
# assert type(history['item_class']) is str
|
||||
|
||||
|
||||
|
||||
|
||||
################################## Change ##################################
|
||||
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to do")
|
||||
# field type testing to be done as part of model testing
|
||||
def test_history_entry_item_change_field_action(self):
|
||||
""" Ensure action is "add" for item creation """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['action'] == int(History.Actions.UPDATE[0])
|
||||
# assert type(history['action']) is int
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to do")
|
||||
def test_history_entry_item_change_field_after(self):
|
||||
""" Ensure after field contains correct value """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['after'] == str('{"name": "test_item_' + self.model_name + '_changed"}')
|
||||
# assert type(history['after']) is str
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_entry_item_change_field_before(self):
|
||||
""" Ensure before field is an empty JSON string for create """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['before'] == str('{}')
|
||||
# assert type(history['before']) is str
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to do")
|
||||
def test_history_entry_item_change_field_item_pk(self):
|
||||
""" Ensure history entry field item_pk is the created items pk """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['item_pk'] == self.item_create.pk
|
||||
# assert type(history['item_pk']) is int
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to do")
|
||||
def test_history_entry_item_change_field_item_class(self):
|
||||
""" Ensure history entry field item_class is the model name """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['item_class'] == self.model._meta.model_name
|
||||
# assert type(history['item_class']) is str
|
||||
|
||||
self.history_delete = History.objects.get(
|
||||
action = History.Actions.DELETE[0],
|
||||
item_pk = self.deleted_pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
||||
self.history_delete_children = History.objects.filter(
|
||||
item_parent_pk = self.deleted_pk,
|
||||
item_parent_class = self.item_parent._meta.model_name,
|
||||
)
|
||||
|
@ -10,15 +10,32 @@ import unittest
|
||||
import requests
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
from access.tests.abstract.model_permissions_organization_manager import OrganizationManagerModelPermissionAdd, OrganizationManagerModelPermissionDelete
|
||||
|
||||
from app.tests.abstract.model_permissions import ModelPermissionsAdd, ModelPermissionsChange, ModelPermissionsDelete
|
||||
|
||||
|
||||
|
||||
class TeamUserPermissions(TestCase):
|
||||
class TeamUserPermissions(
|
||||
TestCase,
|
||||
ModelPermissionsAdd,
|
||||
ModelPermissionsDelete,
|
||||
OrganizationManagerModelPermissionAdd,
|
||||
OrganizationManagerModelPermissionDelete
|
||||
):
|
||||
|
||||
model = TeamUsers
|
||||
|
||||
model_name = 'teamusers'
|
||||
app_label = 'access'
|
||||
app_namespace = 'Access'
|
||||
|
||||
url_name_view = '_team_user_view'
|
||||
|
||||
url_name_add = '_team_user_add'
|
||||
|
||||
url_name_change = '_team_user_view'
|
||||
|
||||
url_name_delete = '_team_user_delete'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
@ -37,6 +54,8 @@ class TeamUserPermissions(TestCase):
|
||||
|
||||
different_organization = Organization.objects.create(name='test_different_organization')
|
||||
|
||||
self.different_organization = different_organization
|
||||
|
||||
self.test_team = Team.objects.create(
|
||||
team_name = 'test_team',
|
||||
organization = organization,
|
||||
@ -49,11 +68,34 @@ class TeamUserPermissions(TestCase):
|
||||
user = self.team_user
|
||||
)
|
||||
|
||||
|
||||
self.url_view_kwargs = {'pk': self.item.id}
|
||||
|
||||
self.url_add_kwargs = {'organization_id': self.organization.id, 'pk': self.item.id}
|
||||
|
||||
self.add_data = {'operating_system': 'operating_system', 'organization': self.organization.id}
|
||||
|
||||
self.url_change_kwargs = {'organization_id': self.organization.id, 'team_id': self.item.team.id, 'pk': self.item.id}
|
||||
|
||||
self.change_data = {'operating_system': 'operating_system', 'organization': self.organization.id}
|
||||
|
||||
self.url_delete_kwargs = {'organization_id': self.organization.id, 'team_id': self.item.team.id, 'pk': self.item.id}
|
||||
|
||||
self.delete_data = {'operating_system': 'operating_system', 'organization': self.organization.id}
|
||||
|
||||
self.url_delete_response = reverse('Access:_team_view',
|
||||
kwargs={
|
||||
'organization_id': self.organization.id,
|
||||
'pk': self.test_team.id
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
view_permissions = Permission.objects.get(
|
||||
codename = 'view_' + self.model_name,
|
||||
codename = 'view_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -67,10 +109,10 @@ class TeamUserPermissions(TestCase):
|
||||
|
||||
|
||||
add_permissions = Permission.objects.get(
|
||||
codename = 'add_' + self.model_name,
|
||||
codename = 'add_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -84,10 +126,10 @@ class TeamUserPermissions(TestCase):
|
||||
|
||||
|
||||
change_permissions = Permission.objects.get(
|
||||
codename = 'change_' + self.model_name,
|
||||
codename = 'change_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -101,10 +143,10 @@ class TeamUserPermissions(TestCase):
|
||||
|
||||
|
||||
delete_permissions = Permission.objects.get(
|
||||
codename = 'delete_' + self.model_name,
|
||||
codename = 'delete_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -164,153 +206,21 @@ class TeamUserPermissions(TestCase):
|
||||
user = self.different_organization_user
|
||||
)
|
||||
|
||||
self.user_is_organization_manager = User.objects.create_user(
|
||||
username="test_org_manager",
|
||||
password="password"
|
||||
)
|
||||
|
||||
self.organization.manager = self.user_is_organization_manager
|
||||
self.organization.save()
|
||||
|
||||
@pytest.mark.skip(reason="feature does not exist")
|
||||
def test_team_user_auth_view_user_anon_denied(self):
|
||||
""" Check correct permission for view
|
||||
self.different_organization_is_manager = User.objects.create_user(
|
||||
username="test_org_manager_different_org",
|
||||
password="password"
|
||||
)
|
||||
|
||||
Attempt to view as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_user_view', kwargs={'pk': self.item.id})
|
||||
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 302 and response.url.startswith('/account/login')
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="feature does not exist")
|
||||
def test_team_user_auth_view_no_permission_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view with user missing permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_user_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="feature does not exist")
|
||||
def test_team_user_auth_view_different_organizaiton_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view with user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_user_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="feature does not exist")
|
||||
def test_team_user_auth_view_has_permission(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view as user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_user_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
def test_team_user_auth_add_user_anon_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_user_add', kwargs={'organization_id': self.organization.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
response = client.put(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 302 and response.url.startswith('/account/login')
|
||||
|
||||
# @pytest.mark.skip(reason="ToDO: figure out why fails")
|
||||
def test_team_user_auth_add_no_permission_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user with no permissions
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_user_add', kwargs={'organization_id': self.organization.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.post(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="ToDO: figure out why fails")
|
||||
def test_team_user_auth_add_different_organization_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
attempt to add as user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_user_add', kwargs={'organization_id': self.organization.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.post(url, data={'name': 'device', 'organization': self.organization.id})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_user_auth_add_permission_view_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add a user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_user_add', kwargs={'organization_id': self.organization.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.post(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_user_auth_add_has_permission(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user with no permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_user_add', kwargs={'organization_id': self.organization.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.post(url, data={'device': 'device', 'organization': self.organization.id})
|
||||
|
||||
assert response.status_code == 200
|
||||
self.different_organization.manager = self.different_organization_is_manager
|
||||
self.different_organization.save()
|
||||
|
||||
|
||||
|
||||
@ -413,126 +323,3 @@ class TeamUserPermissions(TestCase):
|
||||
response = client.post(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
def test_team_user_auth_delete_user_anon_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete item as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_user_delete', kwargs={'organization_id': self.organization.id, 'team_id': self.item.team.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 302 and response.url.startswith('/account/login')
|
||||
|
||||
|
||||
def test_team_user_auth_delete_no_permission_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with no permissons
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_user_delete', kwargs={'organization_id': self.organization.id, 'team_id': self.item.team.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_user_auth_delete_different_organization_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_user_delete', kwargs={'organization_id': self.organization.id, 'team_id': self.item.team.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_user_auth_delete_permission_view_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with veiw permission only
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_user_delete', kwargs={'organization_id': self.organization.id, 'team_id': self.item.team.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_user_auth_delete_permission_add_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with add permission only
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_user_delete', kwargs={'organization_id': self.organization.id, 'team_id': self.item.team.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_user_auth_delete_permission_change_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with change permission only
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_user_delete', kwargs={'organization_id': self.organization.id, 'team_id': self.item.team.id, 'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.change_user)
|
||||
response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_team_user_auth_delete_has_permission(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Delete item as user with delete permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Access:_team_user_delete',
|
||||
kwargs={
|
||||
'organization_id': self.organization.id,
|
||||
'team_id': self.test_team.id,
|
||||
'pk': self.item.id
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
client.force_login(self.delete_user)
|
||||
response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 302 and response.url == reverse('Access:_team_view',
|
||||
kwargs={
|
||||
'organization_id': self.organization.id,
|
||||
'pk': self.test_team.id
|
||||
}
|
||||
)
|
||||
|
@ -1,32 +1,15 @@
|
||||
# from django.conf import settings
|
||||
# from django.shortcuts import reverse
|
||||
from django.test import TestCase, Client
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
from django.test import TestCase, Client
|
||||
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_team_user_auth_view_api(user):
|
||||
""" Check correct permission for view """
|
||||
pass
|
||||
from api.tests.abstract.api_permissions import APIPermissions
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_team_user_auth_add_api(user):
|
||||
""" Check correct permission for add """
|
||||
pass
|
||||
class TeamUsersPermissionsAPI(TestCase, APIPermissions):
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_team_user_auth_change_api(user):
|
||||
""" Check correct permission for change """
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_team_user_auth_delete_api(user):
|
||||
""" Check correct permission for delete """
|
||||
pass
|
||||
model = TeamUsers
|
||||
|
24
app/access/tests/tenancy_object/test_tenancy_object.py
Normal file
24
app/access/tests/tenancy_object/test_tenancy_object.py
Normal file
@ -0,0 +1,24 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
|
||||
|
||||
class TenancyObject(TestCase):
|
||||
|
||||
# @classmethod
|
||||
# def setUpTestData(self):
|
||||
# """ Setup Test """
|
||||
|
||||
# pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_function_save_attributes(self):
|
||||
""" Ensure save Attributes function match django default
|
||||
|
||||
the save method is overridden. the function attributes must match default django method
|
||||
"""
|
||||
pass
|
@ -5,6 +5,8 @@ from django.views import generic
|
||||
from access.mixin import *
|
||||
from access.models import *
|
||||
|
||||
from access.forms.organization import OrganizationForm
|
||||
|
||||
|
||||
|
||||
class IndexView(OrganizationPermission, generic.ListView):
|
||||
@ -28,13 +30,26 @@ class IndexView(OrganizationPermission, generic.ListView):
|
||||
|
||||
|
||||
class View(OrganizationPermission, generic.UpdateView):
|
||||
|
||||
context_object_name = "organization"
|
||||
|
||||
form_class = OrganizationForm
|
||||
|
||||
model = Organization
|
||||
permission_required = [
|
||||
'access.view_organization',
|
||||
'access.change_organization',
|
||||
]
|
||||
|
||||
template_name = "access/organization.html.j2"
|
||||
fields = ["name", 'id']
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
|
||||
return self.handle_no_permission()
|
||||
|
||||
if not self.permission_check(request, [ 'access.view_organization' ]):
|
||||
|
||||
raise PermissionDenied('You are not part of this organization')
|
||||
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
@ -48,7 +63,7 @@ class View(OrganizationPermission, generic.UpdateView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['organization'] = Organization.objects.get(pk=self.kwargs['pk'])
|
||||
context['model_docs_path'] = self.model._meta.app_label + '/' + self.model._meta.model_name + '/'
|
||||
|
||||
context['teams'] = Team.objects.filter(organization=self.kwargs['pk'])
|
||||
|
||||
@ -58,9 +73,16 @@ class View(OrganizationPermission, generic.UpdateView):
|
||||
return context
|
||||
|
||||
|
||||
@method_decorator(auth_decorator.permission_required("access.change_organization", raise_exception=True))
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
|
||||
return self.handle_no_permission()
|
||||
|
||||
if not self.permission_check(request, [ 'access.change_organization' ]):
|
||||
|
||||
raise PermissionDenied('You are not part of this organization')
|
||||
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
||||
|
||||
|
@ -4,30 +4,48 @@ from django.utils.decorators import method_decorator
|
||||
from django.urls import reverse
|
||||
from django.views import generic
|
||||
|
||||
from access.forms.team import TeamForm
|
||||
# from access.forms.team_users import TeamUsersForm
|
||||
from access.models import Team, TeamUsers, Organization
|
||||
from access.mixin import *
|
||||
|
||||
|
||||
|
||||
class View(OrganizationPermission, generic.UpdateView):
|
||||
|
||||
context_object_name = "team"
|
||||
|
||||
form_class = TeamForm
|
||||
|
||||
model = Team
|
||||
|
||||
permission_required = [
|
||||
'access.view_team',
|
||||
'access.change_team',
|
||||
]
|
||||
|
||||
template_name = 'access/team.html.j2'
|
||||
|
||||
fields = [
|
||||
"name",
|
||||
'id',
|
||||
'organization',
|
||||
'permissions'
|
||||
]
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
|
||||
return self.handle_no_permission()
|
||||
|
||||
if not self.permission_check(request, [ 'access.view_team' ]):
|
||||
|
||||
raise PermissionDenied('You are not part of this organization')
|
||||
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['model_docs_path'] = self.model._meta.app_label + '/' + self.model._meta.model_name + '/'
|
||||
|
||||
|
||||
organization = Organization.objects.get(pk=self.kwargs['organization_id'])
|
||||
|
||||
context['organization'] = organization
|
||||
@ -37,7 +55,6 @@ class View(OrganizationPermission, generic.UpdateView):
|
||||
teamusers = TeamUsers.objects.filter(team=self.kwargs['pk'])
|
||||
|
||||
context['teamusers'] = teamusers
|
||||
context['permissions'] = Permission.objects.filter()
|
||||
|
||||
context['model_pk'] = self.kwargs['pk']
|
||||
context['model_name'] = self.model._meta.verbose_name.replace(' ', '')
|
||||
@ -48,9 +65,16 @@ class View(OrganizationPermission, generic.UpdateView):
|
||||
return reverse('Access:_team_view', args=(self.kwargs['organization_id'], self.kwargs['pk'],))
|
||||
|
||||
|
||||
@method_decorator(auth_decorator.permission_required("access.change_team", raise_exception=True))
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
||||
if not request.user.is_authenticated:
|
||||
|
||||
return self.handle_no_permission()
|
||||
|
||||
if not self.permission_check(request, [ 'access.change_team' ]):
|
||||
|
||||
raise PermissionDenied('You are not part of this organization')
|
||||
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
||||
|
||||
|
@ -3,22 +3,28 @@ from django.urls import reverse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import generic
|
||||
|
||||
from access.forms.team_users import TeamUsersForm
|
||||
from access.mixin import OrganizationPermission
|
||||
from access.models import Team, TeamUsers
|
||||
|
||||
|
||||
|
||||
class Add(OrganizationPermission, generic.CreateView):
|
||||
|
||||
context_object_name = "teamuser"
|
||||
|
||||
form_class = TeamUsersForm
|
||||
|
||||
model = TeamUsers
|
||||
|
||||
parent_model = TeamUsers
|
||||
|
||||
permission_required = [
|
||||
'access.view_team',
|
||||
'access.add_teamusers'
|
||||
]
|
||||
|
||||
template_name = 'form.html.j2'
|
||||
fields = [
|
||||
'user',
|
||||
'manager'
|
||||
]
|
||||
|
||||
|
||||
def form_valid(self, form):
|
||||
|
73
app/api/serializers/itam/inventory.py
Normal file
73
app/api/serializers/itam/inventory.py
Normal file
@ -0,0 +1,73 @@
|
||||
from django.urls import reverse
|
||||
|
||||
from itam.models.device import Device
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
|
||||
|
||||
class InventorySerializer(serializers.Serializer):
|
||||
""" Serializer for Inventory Upload """
|
||||
|
||||
|
||||
class DetailsSerializer(serializers.Serializer):
|
||||
|
||||
name = serializers.CharField(
|
||||
help_text = 'Host name',
|
||||
required = True
|
||||
)
|
||||
|
||||
serial_number = serializers.CharField(
|
||||
help_text = 'Devices serial number',
|
||||
required = True
|
||||
)
|
||||
|
||||
uuid = serializers.CharField(
|
||||
help_text = 'Device system UUID',
|
||||
required = True
|
||||
)
|
||||
|
||||
|
||||
class OperatingSystemSerializer(serializers.Serializer):
|
||||
|
||||
name = serializers.CharField(
|
||||
help_text='Name of the operating system installed on the device',
|
||||
required = True,
|
||||
)
|
||||
|
||||
version_major = serializers.IntegerField(
|
||||
help_text='Major semver version number of the OS version',
|
||||
required = True,
|
||||
)
|
||||
|
||||
version = serializers.CharField(
|
||||
help_text='semver version number of the OS',
|
||||
required = True
|
||||
)
|
||||
|
||||
|
||||
class SoftwareSerializer(serializers.Serializer):
|
||||
|
||||
name = serializers.CharField(
|
||||
help_text='Name of the software',
|
||||
required = True
|
||||
)
|
||||
|
||||
category = serializers.CharField(
|
||||
help_text='Category of the software',
|
||||
default = None,
|
||||
required = False
|
||||
)
|
||||
|
||||
version = serializers.CharField(
|
||||
default = None,
|
||||
help_text='semver version number of the software',
|
||||
required = False
|
||||
)
|
||||
|
||||
|
||||
details = DetailsSerializer()
|
||||
|
||||
os = OperatingSystemSerializer()
|
||||
|
||||
software = SoftwareSerializer(many = True)
|
0
app/api/tests/__init__.py
Normal file
0
app/api/tests/__init__.py
Normal file
0
app/api/tests/abstract/__init__.py
Normal file
0
app/api/tests/abstract/__init__.py
Normal file
470
app/api/tests/abstract/api_permissions.py
Normal file
470
app/api/tests/abstract/api_permissions.py
Normal file
@ -0,0 +1,470 @@
|
||||
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, 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, 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, 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, kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
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_list, kwargs = self.url_kwargs)
|
||||
|
||||
else:
|
||||
|
||||
url = reverse(self.app_namespace + ':' + self.url_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_list, kwargs = self.url_kwargs)
|
||||
|
||||
else:
|
||||
|
||||
url = reverse(self.app_namespace + ':' + self.url_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_list, kwargs = self.url_kwargs)
|
||||
|
||||
else:
|
||||
|
||||
url = reverse(self.app_namespace + ':' + self.url_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_list, kwargs = self.url_kwargs)
|
||||
|
||||
else:
|
||||
|
||||
url = reverse(self.app_namespace + ':' + self.url_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 no permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
if self.url_kwargs:
|
||||
|
||||
url = reverse(self.app_namespace + ':' + self.url_list, kwargs = self.url_kwargs)
|
||||
|
||||
else:
|
||||
|
||||
url = reverse(self.app_namespace + ':' + self.url_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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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 """
|
@ -1,151 +1,251 @@
|
||||
from django.shortcuts import reverse
|
||||
from django.test import TestCase, Client
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.shortcuts import reverse
|
||||
from django.test import TestCase, Client
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_device_added():
|
||||
""" Device is created """
|
||||
pass
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
|
||||
from api.views.mixin import OrganizationPermissionAPI
|
||||
|
||||
from itam.models.device import Device
|
||||
|
||||
from settings.models.user_settings import UserSettings
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_operating_system_added():
|
||||
""" Operating System is created """
|
||||
pass
|
||||
class InventoryAPI(TestCase):
|
||||
|
||||
model = Device
|
||||
|
||||
model_name = 'device'
|
||||
app_label = 'itam'
|
||||
|
||||
inventory = {
|
||||
"details": {
|
||||
"name": "device_name",
|
||||
"serial_number": "a serial number",
|
||||
"uuid": "string"
|
||||
},
|
||||
"os": {
|
||||
"name": "os_name",
|
||||
"version_major": "12",
|
||||
"version": "12.1"
|
||||
},
|
||||
"software": [
|
||||
{
|
||||
"name": "software_name",
|
||||
"category": "category_name",
|
||||
"version": "1.2.3"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_operating_system_version_added():
|
||||
""" Operating System version is created """
|
||||
pass
|
||||
@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 device
|
||||
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
|
||||
|
||||
add_permissions = Permission.objects.get(
|
||||
codename = 'add_' + self.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
add_team = Team.objects.create(
|
||||
team_name = 'add_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
add_team.permissions.set([add_permissions])
|
||||
|
||||
self.add_user = User.objects.create_user(username="test_user_add", password="password")
|
||||
|
||||
add_user_settings = UserSettings.objects.get(user=self.add_user)
|
||||
|
||||
add_user_settings.default_organization = organization
|
||||
|
||||
add_user_settings.save()
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_device_has_operating_system_added():
|
||||
""" Operating System version linked to device """
|
||||
pass
|
||||
@patch.object(OrganizationPermissionAPI, 'permission_check')
|
||||
def test_inventory_function_called_permission_check(self, permission_check):
|
||||
""" Inventory Upload checks permissions
|
||||
|
||||
Function 'permission_check' is the function that checks permissions
|
||||
|
||||
As the non-established way of authentication an API permission is being done
|
||||
confimation that the permissions are still checked is required.
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_device_inventory')
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.post(url, data=self.inventory, content_type='application/json')
|
||||
|
||||
assert permission_check.called
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_device_added(self):
|
||||
""" Device is created """
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_device_operating_system_version_is_semver():
|
||||
""" Operating System version is full semver
|
||||
|
||||
Operating system versions name is the major version number of semver.
|
||||
The device version is to be full semver
|
||||
"""
|
||||
pass
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_operating_system_added(self):
|
||||
""" Operating System is created """
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_software_no_version_cleaned():
|
||||
""" Check softare cleaned up
|
||||
|
||||
As part of the inventory upload the software versions of software found on the device is set to null
|
||||
and before the processing is completed, the version=null software is supposed to be cleaned up.
|
||||
"""
|
||||
pass
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_operating_system_version_added(self):
|
||||
""" Operating System version is created """
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_software_category_added():
|
||||
""" Software category exists """
|
||||
pass
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_device_has_operating_system_added(self):
|
||||
""" Operating System version linked to device """
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_software_added():
|
||||
""" Test software exists """
|
||||
pass
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_device_operating_system_version_is_semver(self):
|
||||
""" Operating System version is full semver
|
||||
|
||||
Operating system versions name is the major version number of semver.
|
||||
The device version is to be full semver
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_software_category_linked_to_software():
|
||||
""" Software category linked to software """
|
||||
pass
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_software_no_version_cleaned(self):
|
||||
""" Check softare cleaned up
|
||||
|
||||
As part of the inventory upload the software versions of software found on the device is set to null
|
||||
and before the processing is completed, the version=null software is supposed to be cleaned up.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_software_version_added():
|
||||
""" Test software version exists """
|
||||
pass
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_software_category_added(self):
|
||||
""" Software category exists """
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_software_version_returns_semver():
|
||||
""" Software Version from inventory returns semver if within version string """
|
||||
pass
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_software_added(self):
|
||||
""" Test software exists """
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_software_version_returns_original_version():
|
||||
""" Software Version from inventory returns inventoried version if no semver found """
|
||||
pass
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_software_category_linked_to_software(self):
|
||||
""" Software category linked to software """
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_software_version_linked_to_software():
|
||||
""" Test software version linked to software it belongs too """
|
||||
pass
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_software_version_added(self):
|
||||
""" Test software version exists """
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_device_has_software_version():
|
||||
""" Inventoried software is linked to device and it's the corret one"""
|
||||
pass
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_software_version_returns_semver(self):
|
||||
""" Software Version from inventory returns semver if within version string """
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_device_software_has_installed_date():
|
||||
""" Inventoried software version has install date """
|
||||
pass
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_software_version_returns_original_version(self):
|
||||
""" Software Version from inventory returns inventoried version if no semver found """
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_device_software_blank_installed_date_is_updated():
|
||||
""" A blank installed date of software is updated if the software was already attached to the device """
|
||||
pass
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_software_version_linked_to_software(self):
|
||||
""" Test software version linked to software it belongs too """
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_valid_status_created():
|
||||
""" Successful inventory upload returns 201 """
|
||||
pass
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_device_has_software_version(self):
|
||||
""" Inventoried software is linked to device and it's the corret one"""
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_invalid_status_bad_request():
|
||||
""" Incorrectly formated inventory upload returns 400 """
|
||||
pass
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_device_software_has_installed_date(self):
|
||||
""" Inventoried software version has install date """
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_exeception_status_sever_error():
|
||||
""" if the method throws an exception 500 must be returned.
|
||||
|
||||
idea to test: add a random key to the report that is not documented
|
||||
and perform some action against it that will cause a python exception.
|
||||
"""
|
||||
pass
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_device_software_blank_installed_date_is_updated(self):
|
||||
""" A blank installed date of software is updated if the software was already attached to the device """
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_valid_status_created(self):
|
||||
""" Successful inventory upload returns 201 """
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_invalid_status_bad_request(self):
|
||||
""" Incorrectly formated inventory upload returns 400 """
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_api_inventory_exeception_status_sever_error(self):
|
||||
""" if the method throws an exception 500 must be returned.
|
||||
|
||||
idea to test: add a random key to the report that is not documented
|
||||
and perform some action against it that will cause a python exception.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
@ -1,17 +1,18 @@
|
||||
# from django.conf import settings
|
||||
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 TestCase, Client
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
|
||||
from itam.models.device import Device
|
||||
|
||||
from settings.models.user_settings import UserSettings
|
||||
|
||||
class InventoryPermissionsAPI(TestCase):
|
||||
|
||||
@ -140,6 +141,13 @@ class InventoryPermissionsAPI(TestCase):
|
||||
)
|
||||
|
||||
self.add_user = User.objects.create_user(username="test_user_add", password="password")
|
||||
|
||||
add_user_settings = UserSettings.objects.get(user=self.add_user)
|
||||
|
||||
add_user_settings.default_organization = organization
|
||||
|
||||
add_user_settings.save()
|
||||
|
||||
teamuser = TeamUsers.objects.create(
|
||||
team = add_team,
|
||||
user = self.add_user
|
||||
@ -180,7 +188,6 @@ class InventoryPermissionsAPI(TestCase):
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="test to be written")
|
||||
def test_device_auth_add_user_anon_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
@ -191,12 +198,11 @@ class InventoryPermissionsAPI(TestCase):
|
||||
url = reverse('API:_api_device_inventory')
|
||||
|
||||
|
||||
response = client.put(url, data=self.inventory)
|
||||
response = client.put(url, data=self.inventory, content_type='application/json')
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="test to be written")
|
||||
def test_device_auth_add_no_permission_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
@ -208,12 +214,11 @@ class InventoryPermissionsAPI(TestCase):
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.post(url, data=self.inventory)
|
||||
response = client.post(url, data=self.inventory, content_type='application/json')
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="test to be written")
|
||||
def test_device_auth_add_different_organization_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
@ -225,12 +230,11 @@ class InventoryPermissionsAPI(TestCase):
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.post(url, data=self.inventory)
|
||||
response = client.post(url, data=self.inventory, content_type='application/json')
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="test to be written")
|
||||
def test_device_auth_add_permission_view_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
@ -242,12 +246,11 @@ class InventoryPermissionsAPI(TestCase):
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.post(url, data=self.inventory)
|
||||
response = client.post(url, data=self.inventory, content_type='application/json')
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="test to be written")
|
||||
def test_device_auth_add_has_permission(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
@ -259,7 +262,7 @@ class InventoryPermissionsAPI(TestCase):
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.post(url, data=self.inventory)
|
||||
response = client.post(url, data=self.inventory, content_type='application/json')
|
||||
|
||||
assert response.status_code == 201
|
||||
|
||||
|
@ -2,9 +2,11 @@
|
||||
import json
|
||||
import re
|
||||
|
||||
from django.http import JsonResponse
|
||||
from django.http import Http404, JsonResponse
|
||||
from django.utils import timezone
|
||||
|
||||
from drf_spectacular.utils import extend_schema, OpenApiExample, OpenApiTypes, OpenApiResponse, OpenApiParameter
|
||||
|
||||
from rest_framework import generics, views
|
||||
from rest_framework.response import Response
|
||||
|
||||
@ -12,6 +14,7 @@ from access.mixin import OrganizationMixin
|
||||
from access.models import Organization
|
||||
|
||||
from api.views.mixin import OrganizationPermissionAPI
|
||||
from api.serializers.itam.inventory import InventorySerializer
|
||||
|
||||
from core.http.common import Http
|
||||
|
||||
@ -36,44 +39,81 @@ class InventoryPermissions(OrganizationPermissionAPI):
|
||||
|
||||
|
||||
|
||||
class Collect(OrganizationMixin, views.APIView):
|
||||
|
||||
permission_classes = [
|
||||
InventoryPermissions
|
||||
]
|
||||
class Collect(OrganizationPermissionAPI, views.APIView):
|
||||
|
||||
queryset = Device.objects.all()
|
||||
|
||||
|
||||
@extend_schema(
|
||||
summary = "Upload a device's inventory",
|
||||
description = """After inventorying a device, it's inventory file, `.json` is uploaded to this endpoint.
|
||||
If the device does not exist, it will be created. If the device does exist the existing
|
||||
device will be updated with the information within the inventory.
|
||||
|
||||
matching for an existing device is by slug which is the hostname converted to lower case
|
||||
letters. This conversion is automagic.
|
||||
|
||||
**NOTE:** _for device creation, the API user must have user setting 'Default Organization'. Without
|
||||
this setting populated, no device will be created and the endpoint will return HTTP/403_
|
||||
|
||||
## Permissions
|
||||
|
||||
- `itam.add_device` Required to upload inventory
|
||||
""",
|
||||
|
||||
methods=["POST"],
|
||||
parameters = None,
|
||||
tags = ['device', 'inventory',],
|
||||
request = InventorySerializer,
|
||||
responses = {
|
||||
200: OpenApiResponse(description='Inventory updated an existing device'),
|
||||
201: OpenApiResponse(description='Inventory created a new device'),
|
||||
400: OpenApiResponse(description='Inventory is invalid'),
|
||||
401: OpenApiResponse(description='User Not logged in'),
|
||||
403: OpenApiResponse(description='User is missing permission or in different organization'),
|
||||
500: OpenApiResponse(description='Exception occured. View server logs for the Stack Trace'),
|
||||
}
|
||||
)
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
||||
|
||||
data = json.loads(request.body)
|
||||
|
||||
status = Http.Status.BAD_REQUEST
|
||||
|
||||
device = None
|
||||
|
||||
self.default_organization = UserSettings.objects.get(user=request.user).default_organization
|
||||
|
||||
if Device.objects.filter(slug=str(data['details']['name']).lower()).exists():
|
||||
|
||||
self.obj = Device.objects.get(slug=str(data['details']['name']).lower())
|
||||
|
||||
device = self.obj
|
||||
|
||||
|
||||
if not self.permission_check(request=request, view=self, obj=device):
|
||||
|
||||
raise Http404
|
||||
|
||||
|
||||
|
||||
status = Http.Status.BAD_REQUEST
|
||||
|
||||
device_operating_system = None
|
||||
operating_system = None
|
||||
operating_system_version = None
|
||||
|
||||
try:
|
||||
|
||||
default_organization = UserSettings.objects.get(user=request.user).default_organization
|
||||
|
||||
app_settings = AppSettings.objects.get(owner_organization = None)
|
||||
|
||||
if Device.objects.filter(name=data['details']['name']).exists():
|
||||
|
||||
device = Device.objects.get(name=data['details']['name'])
|
||||
|
||||
else: # Create the device
|
||||
if not device: # Create the device
|
||||
|
||||
device = Device.objects.create(
|
||||
name = data['details']['name'],
|
||||
device_type = None,
|
||||
serial_number = data['details']['serial_number'],
|
||||
uuid = data['details']['uuid'],
|
||||
organization = default_organization,
|
||||
organization = self.default_organization,
|
||||
)
|
||||
|
||||
status = Http.Status.CREATED
|
||||
@ -87,7 +127,7 @@ class Collect(OrganizationMixin, views.APIView):
|
||||
|
||||
operating_system = OperatingSystem.objects.create(
|
||||
name = data['os']['name'],
|
||||
organization = default_organization,
|
||||
organization = self.default_organization,
|
||||
is_global = True
|
||||
)
|
||||
|
||||
@ -95,7 +135,7 @@ class Collect(OrganizationMixin, views.APIView):
|
||||
if OperatingSystemVersion.objects.filter( name=data['os']['version_major'], operating_system=operating_system ).exists():
|
||||
|
||||
operating_system_version = OperatingSystemVersion.objects.get(
|
||||
organization = default_organization,
|
||||
organization = self.default_organization,
|
||||
is_global = True,
|
||||
name = data['os']['version_major'],
|
||||
operating_system = operating_system
|
||||
@ -104,7 +144,7 @@ class Collect(OrganizationMixin, views.APIView):
|
||||
else: # Create Operating System Version
|
||||
|
||||
operating_system_version = OperatingSystemVersion.objects.create(
|
||||
organization = default_organization,
|
||||
organization = self.default_organization,
|
||||
is_global = True,
|
||||
name = data['os']['version_major'],
|
||||
operating_system = operating_system,
|
||||
@ -128,7 +168,7 @@ class Collect(OrganizationMixin, views.APIView):
|
||||
else: # Create Operating System Version
|
||||
|
||||
device_operating_system = DeviceOperatingSystem.objects.create(
|
||||
organization = default_organization,
|
||||
organization = self.default_organization,
|
||||
device=device,
|
||||
version = data['os']['version'],
|
||||
operating_system_version = operating_system_version,
|
||||
@ -223,7 +263,7 @@ class Collect(OrganizationMixin, views.APIView):
|
||||
else: # Create Software Category
|
||||
|
||||
software_version = SoftwareVersion.objects.create(
|
||||
organization = default_organization,
|
||||
organization = self.default_organization,
|
||||
is_global = True,
|
||||
name = semver,
|
||||
software = software,
|
||||
@ -240,7 +280,7 @@ class Collect(OrganizationMixin, views.APIView):
|
||||
else: # Create Software
|
||||
|
||||
device_software = DeviceSoftware.objects.create(
|
||||
organization = default_organization,
|
||||
organization = self.default_organization,
|
||||
is_global = True,
|
||||
installedversion = software_version,
|
||||
software = software,
|
||||
@ -284,7 +324,9 @@ class Collect(OrganizationMixin, views.APIView):
|
||||
|
||||
device.save()
|
||||
|
||||
status = Http.Status.OK
|
||||
if status != Http.Status.CREATED:
|
||||
|
||||
status = Http.Status.OK
|
||||
|
||||
|
||||
except Exception as e:
|
||||
|
@ -1,4 +1,4 @@
|
||||
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.forms import ValidationError
|
||||
|
||||
from rest_framework.permissions import DjangoObjectPermissions
|
||||
@ -50,7 +50,6 @@ class OrganizationPermissionAPI(DjangoObjectPermissions, OrganizationMixin):
|
||||
raise ValidationError('you must provide an organization')
|
||||
|
||||
object_organization = int(request.data['organization'])
|
||||
|
||||
elif method == 'patch':
|
||||
|
||||
action = 'change'
|
||||
@ -126,12 +125,17 @@ class OrganizationPermissionAPI(DjangoObjectPermissions, OrganizationMixin):
|
||||
|
||||
return True
|
||||
|
||||
if hasattr(self, 'default_organization'):
|
||||
object_organization = self.default_organization
|
||||
|
||||
if object_organization is None:
|
||||
if method == 'post' and hasattr(self, 'default_organization'):
|
||||
|
||||
raise Exception("unable to determine object organization")
|
||||
if self.default_organization:
|
||||
|
||||
object_organization = self.default_organization.id
|
||||
|
||||
if not self.has_organization_permission(object_organization) and not request.user.is_superuser:
|
||||
return False
|
||||
|
||||
raise PermissionDenied('You are not part of this organization')
|
||||
|
||||
return True
|
||||
|
@ -21,6 +21,34 @@ def request(request):
|
||||
return request.get_full_path()
|
||||
|
||||
|
||||
def social_backends(request):
|
||||
""" Fetch Backend Names
|
||||
|
||||
Required for use on the login page to dynamically build the social auth URLS
|
||||
|
||||
Returns:
|
||||
list(str): backend name
|
||||
"""
|
||||
from importlib import import_module
|
||||
|
||||
social_backends = []
|
||||
|
||||
if hasattr(settings, 'SSO_BACKENDS'):
|
||||
|
||||
for backend in settings.SSO_BACKENDS:
|
||||
|
||||
paths = str(backend).split('.')
|
||||
|
||||
module = import_module(paths[0] + '.' + paths[1] + '.' + paths[2])
|
||||
|
||||
backend_class = getattr(module, paths[3])
|
||||
backend = backend_class.name
|
||||
|
||||
social_backends += [ str(backend) ]
|
||||
|
||||
return social_backends
|
||||
|
||||
|
||||
def user_settings(context) -> int:
|
||||
""" Provides the settings ID for the current user.
|
||||
|
||||
@ -74,6 +102,7 @@ def nav_items(context) -> list(dict()):
|
||||
'admin',
|
||||
'djdt', # Debug application
|
||||
'api',
|
||||
'social',
|
||||
]
|
||||
|
||||
nav_items = []
|
||||
@ -136,5 +165,6 @@ def common(context):
|
||||
return {
|
||||
'build_details': build_details(context),
|
||||
'nav_items': nav_items(context),
|
||||
'social_backends': social_backends(context),
|
||||
'user_settings': user_settings(context),
|
||||
}
|
||||
|
33
app/app/helpers/merge_software.py
Normal file
33
app/app/helpers/merge_software.py
Normal file
@ -0,0 +1,33 @@
|
||||
|
||||
def merge_software(software: list, new_software: list) -> list:
|
||||
""" Merge two lists of software actions
|
||||
|
||||
Args:
|
||||
software (list(dict)): Original list to merge over
|
||||
new_software (list(dict)): new list to use to merge over
|
||||
|
||||
Returns:
|
||||
list(dict): merged list of software actions
|
||||
"""
|
||||
|
||||
merge_software = []
|
||||
|
||||
merge: dict = {}
|
||||
|
||||
for original in software:
|
||||
|
||||
merge.update({
|
||||
original['name']: original
|
||||
})
|
||||
|
||||
for new in new_software:
|
||||
|
||||
merge.update({
|
||||
new['name']: new
|
||||
})
|
||||
|
||||
for key, value in merge.items():
|
||||
|
||||
merge_software = merge_software + [ value ]
|
||||
|
||||
return merge_software
|
@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/5.0/ref/settings/
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pathlib import Path
|
||||
from split_settings.tools import optional, include
|
||||
@ -23,20 +24,33 @@ SETTINGS_DIR = '/etc/itsm' # Primary Settings Directory
|
||||
BUILD_REPO = os.getenv('CI_PROJECT_URL')
|
||||
BUILD_SHA = os.getenv('CI_COMMIT_SHA')
|
||||
BUILD_VERSION = os.getenv('CI_COMMIT_TAG')
|
||||
|
||||
DOCS_ROOT = 'https://nofusscomputing.com/projects/django-template/user/'
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'django-insecure-b*41-$afq0yl)1e#qpz^-nbt-opvjwb#avv++b9rfdxa@b55sk'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = False
|
||||
#
|
||||
# Defaults
|
||||
#
|
||||
ALLOWED_HOSTS = [ '*' ] # Site host to serve
|
||||
DEBUG = False # SECURITY WARNING: don't run with debug turned on in production!
|
||||
SITE_URL = 'http://127.0.0.1' # domain with HTTP method for the sites URL
|
||||
SECRET_KEY = None # You need to generate this
|
||||
SESSION_COOKIE_AGE = 1209600 # Age the session cookie should live for in seconds.
|
||||
SSO_ENABLED = False # Enable SSO
|
||||
SSO_LOGIN_ONLY_BACKEND = None # Use specified SSO backend as the ONLY method to login. (builting login form will not be used)
|
||||
TRUSTED_ORIGINS = [] # list of trusted domains for CSRF
|
||||
|
||||
ALLOWED_HOSTS = [ '*' ]
|
||||
|
||||
|
||||
# Application definition
|
||||
# CSRF_COOKIE_SECURE = True
|
||||
# SECURE_HSTS_SECONDS = # ToDo: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-SECURE_HSTS_SECONDS
|
||||
# SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") # ToDo: https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header
|
||||
# SECURE_SSL_REDIRECT = True
|
||||
# SECURE_SSL_HOST = # ToDo: https://docs.djangoproject.com/en/dev/ref/settings/#secure-ssl-host
|
||||
# SESSION_COOKIE_SECURE = True
|
||||
# USE_X_FORWARDED_HOST = True # ToDo: https://docs.djangoproject.com/en/dev/ref/settings/#use-x-forwarded-host
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
@ -100,7 +114,7 @@ WSGI_APPLICATION = 'app.wsgi.application'
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
'NAME': str(BASE_DIR / 'db.sqlite3'),
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,8 +223,33 @@ if API_ENABLED:
|
||||
}
|
||||
|
||||
SPECTACULAR_SETTINGS = {
|
||||
'TITLE': 'Your Project API',
|
||||
'DESCRIPTION': 'Your project description',
|
||||
'TITLE': 'ITSM API',
|
||||
'DESCRIPTION': """This UI is intended to serve as the API documentation.
|
||||
|
||||
## Authentication
|
||||
|
||||
Authentication with the api is via Token. The token is placed in header `Authorization` with a value of `Token <Your Token>`.
|
||||
|
||||
## Token Generation
|
||||
|
||||
To generate a token, run `python3 manage.py drf_create_token <username>` from the CLI.
|
||||
|
||||
## Examples
|
||||
|
||||
curl:
|
||||
- Simple API Request: `curl -X GET <url>/api/ -H 'Authorization: Token <token>'`
|
||||
|
||||
- Post an Inventory File:
|
||||
|
||||
``` bash
|
||||
curl --header "Content-Type: application/json" \\
|
||||
--header "Authorization: Token <token>" \\
|
||||
--request POST \\
|
||||
--data @<path to inventory file>/<file name>.json \\
|
||||
<url>/api/device/inventory
|
||||
```
|
||||
|
||||
""",
|
||||
'VERSION': '1.0.0',
|
||||
'SERVE_INCLUDE_SCHEMA': False,
|
||||
|
||||
@ -220,12 +259,34 @@ if API_ENABLED:
|
||||
}
|
||||
|
||||
DATETIME_FORMAT = 'j N Y H:i:s'
|
||||
#
|
||||
# Settings for unit tests
|
||||
#
|
||||
|
||||
RUNNING_TESTS = 'test' in str(sys.argv)
|
||||
|
||||
if RUNNING_TESTS:
|
||||
SECRET_KEY = 'django-insecure-tests_are_being_run'
|
||||
|
||||
#
|
||||
# Load user settings files
|
||||
#
|
||||
if os.path.isdir(SETTINGS_DIR):
|
||||
|
||||
settings_files = os.path.join(SETTINGS_DIR, '*.py')
|
||||
include(optional(settings_files))
|
||||
|
||||
#
|
||||
# Settings to reset to prevent user from over-riding
|
||||
#
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
)
|
||||
|
||||
CSRF_TRUSTED_ORIGINS = [
|
||||
SITE_URL,
|
||||
*TRUSTED_ORIGINS
|
||||
]
|
||||
|
||||
if DEBUG:
|
||||
INSTALLED_APPS += [
|
||||
@ -245,3 +306,25 @@ if DEBUG:
|
||||
'information.apps.InformationConfig',
|
||||
'project_management.apps.ProjectManagementConfig',
|
||||
]
|
||||
|
||||
|
||||
if SSO_ENABLED:
|
||||
|
||||
if SSO_LOGIN_ONLY_BACKEND:
|
||||
LOGIN_URL = f'/sso/login/{SSO_LOGIN_ONLY_BACKEND}/'
|
||||
|
||||
AUTHENTICATION_BACKENDS += (
|
||||
*SSO_BACKENDS,
|
||||
)
|
||||
|
||||
SOCIAL_AUTH_PIPELINE = (
|
||||
'social_core.pipeline.social_auth.social_details',
|
||||
'social_core.pipeline.social_auth.social_uid',
|
||||
'social_core.pipeline.social_auth.social_user',
|
||||
'social_core.pipeline.user.get_username',
|
||||
'social_core.pipeline.social_auth.associate_by_email',
|
||||
'social_core.pipeline.user.create_user',
|
||||
'social_core.pipeline.social_auth.associate_user',
|
||||
'social_core.pipeline.social_auth.load_extra_data',
|
||||
'social_core.pipeline.user.user_details',
|
||||
)
|
||||
|
0
app/app/tests/__init__.py
Normal file
0
app/app/tests/__init__.py
Normal file
0
app/app/tests/abstract/__init__.py
Normal file
0
app/app/tests/abstract/__init__.py
Normal file
442
app/app/tests/abstract/model_permissions.py
Normal file
442
app/app/tests/abstract/model_permissions.py
Normal file
@ -0,0 +1,442 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from django.test import Client
|
||||
from django.shortcuts import reverse
|
||||
|
||||
|
||||
|
||||
class ModelPermissionsView:
|
||||
""" Tests for checking model view permissions """
|
||||
|
||||
|
||||
app_namespace: str = None
|
||||
|
||||
url_name_view: str
|
||||
|
||||
url_view_kwargs: dict = None
|
||||
|
||||
|
||||
|
||||
def test_model_view_user_anon_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
|
||||
if self.app_namespace:
|
||||
|
||||
url = reverse(self.app_namespace + ':' + self.url_name_view, kwargs=self.url_view_kwargs)
|
||||
|
||||
else:
|
||||
|
||||
url = reverse(self.url_name_view, kwargs=self.url_view_kwargs)
|
||||
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 302 and response.url.startswith('/account/login')
|
||||
|
||||
|
||||
def test_model_view_no_permission_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view with user missing permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
if self.app_namespace:
|
||||
|
||||
url = reverse(self.app_namespace + ':' + self.url_name_view, kwargs=self.url_view_kwargs)
|
||||
|
||||
else:
|
||||
|
||||
url = reverse(self.url_name_view, kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_model_view_different_organizaiton_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view with user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
if self.app_namespace:
|
||||
|
||||
url = reverse(self.app_namespace + ':' + self.url_name_view, kwargs=self.url_view_kwargs)
|
||||
|
||||
else:
|
||||
|
||||
url = reverse(self.url_name_view, kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_model_view_has_permission(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view as user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
if self.app_namespace:
|
||||
|
||||
url = reverse(self.app_namespace + ':' + self.url_name_view, kwargs=self.url_view_kwargs)
|
||||
|
||||
else:
|
||||
|
||||
url = reverse(self.url_name_view, kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
class ModelPermissionsAdd:
|
||||
""" Tests for checking model Add permissions """
|
||||
|
||||
app_namespace: str = None
|
||||
|
||||
url_name_add: str
|
||||
|
||||
url_add_kwargs: dict = None
|
||||
|
||||
add_data: dict = None
|
||||
|
||||
|
||||
def test_model_add_user_anon_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name_add, kwargs=self.url_add_kwargs)
|
||||
|
||||
|
||||
response = client.put(url, data=self.add_data)
|
||||
|
||||
assert response.status_code == 302 and response.url.startswith('/account/login')
|
||||
|
||||
# @pytest.mark.skip(reason="ToDO: figure out why fails")
|
||||
def test_model_add_no_permission_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user with no permissions
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name_add, kwargs=self.url_add_kwargs)
|
||||
|
||||
|
||||
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_model_add_different_organization_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
attempt to add as user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name_add, kwargs=self.url_add_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.post(url, data=self.add_data)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_model_add_permission_view_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add a user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name_add, kwargs=self.url_add_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.post(url, data=self.add_data)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_model_add_has_permission(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user with no permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_name_add, kwargs=self.url_add_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.post(url, data=self.add_data)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
class ModelPermissionsChange:
|
||||
""" Tests for checking model change permissions """
|
||||
|
||||
app_namespace: str = None
|
||||
|
||||
url_name_change: str
|
||||
|
||||
url_change_kwargs: dict = None
|
||||
|
||||
change_data: dict = None
|
||||
|
||||
|
||||
def test_model_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_change, kwargs=self.url_change_kwargs)
|
||||
|
||||
|
||||
response = client.patch(url, data=self.change_data)
|
||||
|
||||
assert response.status_code == 302 and response.url.startswith('/account/login')
|
||||
|
||||
|
||||
def test_model_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_change, kwargs=self.url_change_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.post(url, data=self.change_data)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_model_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_change, kwargs=self.url_change_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.post(url, data=self.change_data)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_model_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_change, kwargs=self.url_change_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.post(url, data=self.change_data)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_model_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_change, kwargs=self.url_change_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.post(url, data=self.change_data)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_model_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_change, kwargs=self.url_change_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.change_user)
|
||||
response = client.post(url, data=self.change_data)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
class ModelPermissionsDelete:
|
||||
""" Tests for checking model delete permissions """
|
||||
|
||||
app_namespace: str = None
|
||||
|
||||
url_name_delete: str
|
||||
|
||||
url_delete_kwargs: dict = None
|
||||
|
||||
url_delete_response: str
|
||||
|
||||
delete_data: dict = None
|
||||
|
||||
def test_model_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_delete, kwargs=self.url_delete_kwargs)
|
||||
|
||||
|
||||
response = client.delete(url, data=self.delete_data)
|
||||
|
||||
assert response.status_code == 302 and response.url.startswith('/account/login')
|
||||
|
||||
|
||||
def test_model_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_delete, kwargs=self.url_delete_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.delete(url, data=self.delete_data)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_model_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_delete, kwargs=self.url_delete_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.delete(url, data=self.delete_data)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_model_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_delete, kwargs=self.url_delete_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.delete(url, data=self.delete_data)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_model_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_delete, kwargs=self.url_delete_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.delete(url, data=self.delete_data)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_model_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_delete, kwargs=self.url_delete_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.change_user)
|
||||
response = client.delete(url, data=self.delete_data)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_model_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_delete, kwargs=self.url_delete_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.delete_user)
|
||||
response = client.delete(url, data=self.delete_data)
|
||||
|
||||
assert response.status_code == 302 and response.url == self.url_delete_response
|
||||
|
||||
|
||||
class ModelPermissions(
|
||||
ModelPermissionsView,
|
||||
ModelPermissionsAdd,
|
||||
ModelPermissionsChange,
|
||||
ModelPermissionsDelete
|
||||
):
|
||||
""" Tests for checking model permissions """
|
||||
|
||||
app_namespace: str = None
|
88
app/app/tests/test_helpers_merge_software.py
Normal file
88
app/app/tests/test_helpers_merge_software.py
Normal file
@ -0,0 +1,88 @@
|
||||
from django.conf import settings as django_settings
|
||||
from django.shortcuts import reverse
|
||||
from django.test import TestCase, Client
|
||||
|
||||
from app.helpers.merge_software import merge_software
|
||||
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
|
||||
class MergeSoftwareHelper(TestCase):
|
||||
""" tests for function `merge_software` """
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
self.data: dict = {
|
||||
'first_list': [
|
||||
{
|
||||
'name': 'software_1',
|
||||
'state': 'install'
|
||||
},
|
||||
{
|
||||
'name': 'software_2',
|
||||
'state': 'install'
|
||||
}
|
||||
],
|
||||
'second_list': [
|
||||
{
|
||||
'name': 'software_1',
|
||||
'state': 'absent'
|
||||
},
|
||||
{
|
||||
'name': 'software_2',
|
||||
'state': 'absent'
|
||||
}
|
||||
],
|
||||
'third_list': [
|
||||
{
|
||||
'name': 'software_1',
|
||||
'state': 'other'
|
||||
},
|
||||
{
|
||||
'name': 'software_2',
|
||||
'state': 'other'
|
||||
},
|
||||
{
|
||||
'name': 'software_3',
|
||||
'state': 'install'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
self.software_list_one = merge_software(self.data['first_list'], self.data['second_list'])
|
||||
|
||||
self.software_list_two = merge_software(self.software_list_one, self.data['third_list'])
|
||||
|
||||
|
||||
def test_merging_0_0(self):
|
||||
""" ensure Second list overwrites the first app1 """
|
||||
|
||||
assert self.software_list_one[0]['state'] == 'absent'
|
||||
|
||||
|
||||
def test_merging_0_1(self):
|
||||
""" ensure Second list overwrites the first app2 """
|
||||
|
||||
assert self.software_list_one[1]['state'] == 'absent'
|
||||
|
||||
|
||||
|
||||
def test_merging_1_0(self):
|
||||
""" ensure Second list overwrites the first app1 again """
|
||||
|
||||
assert self.software_list_two[0]['state'] == 'other'
|
||||
|
||||
|
||||
def test_merging_1_1(self):
|
||||
""" ensure Second list overwrites the first app2 again """
|
||||
|
||||
assert self.software_list_two[1]['state'] == 'other'
|
||||
|
||||
|
||||
def test_merging_1_new_list_item(self):
|
||||
""" ensure Second list overwrites the first app2 again """
|
||||
|
||||
assert len(self.software_list_two) == 3
|
||||
|
@ -43,9 +43,18 @@ urlpatterns = [
|
||||
path("config_management/", include("config_management.urls")),
|
||||
|
||||
path("history/<str:model_name>/<int:model_pk>", history.View.as_view(), name='_history'),
|
||||
re_path(r'^static/(?P<path>.*)$', serve,{'document_root': settings.STATIC_ROOT})
|
||||
re_path(r'^static/(?P<path>.*)$', serve,{'document_root': settings.STATIC_ROOT}),
|
||||
|
||||
]
|
||||
|
||||
|
||||
if settings.SSO_ENABLED:
|
||||
|
||||
urlpatterns += [
|
||||
path('sso/', include('social_django.urls', namespace='social'))
|
||||
]
|
||||
|
||||
|
||||
if settings.API_ENABLED:
|
||||
urlpatterns += [
|
||||
|
||||
@ -54,12 +63,14 @@ if settings.API_ENABLED:
|
||||
path('api/swagger/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
|
||||
]
|
||||
|
||||
|
||||
if settings.DEBUG:
|
||||
|
||||
urlpatterns += [
|
||||
|
||||
path("__debug__/", include("debug_toolbar.urls"), name='_debug'),
|
||||
# Apps Under Development
|
||||
path("itim/", include("itim.urls")),
|
||||
path("information/", include("information.urls")),
|
||||
path("project_management/", include("project_management.urls")),
|
||||
]
|
||||
|
21
app/config_management/forms/group/add_software.py
Normal file
21
app/config_management/forms/group/add_software.py
Normal file
@ -0,0 +1,21 @@
|
||||
from django import forms
|
||||
from django.db.models import Q
|
||||
|
||||
from config_management.models.groups import ConfigGroupSoftware
|
||||
from itam.models.software import Software
|
||||
|
||||
|
||||
class SoftwareAdd(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = ConfigGroupSoftware
|
||||
fields = [
|
||||
'software',
|
||||
'action'
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
organizations = kwargs.pop('organizations')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['software'].queryset = Software.objects.filter(Q(organization_id__in=organizations) | Q(is_global = True))
|
22
app/config_management/forms/group/change_software.py
Normal file
22
app/config_management/forms/group/change_software.py
Normal file
@ -0,0 +1,22 @@
|
||||
from django import forms
|
||||
from django.db.models import Q
|
||||
|
||||
from config_management.models.groups import ConfigGroupSoftware
|
||||
from itam.models.software import Software, SoftwareVersion
|
||||
|
||||
|
||||
class SoftwareUpdate(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = ConfigGroupSoftware
|
||||
fields = [
|
||||
'action',
|
||||
'version',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['version'].queryset = SoftwareVersion.objects.filter(software_id=self.instance.software.id)
|
||||
|
17
app/config_management/forms/group/group.py
Normal file
17
app/config_management/forms/group/group.py
Normal file
@ -0,0 +1,17 @@
|
||||
from django import forms
|
||||
from django.db.models import Q
|
||||
|
||||
from config_management.models.groups import ConfigGroups
|
||||
from itam.models.software import Software, SoftwareVersion
|
||||
|
||||
|
||||
class ConfigGroupForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = ConfigGroups
|
||||
fields = [
|
||||
'name',
|
||||
'parent',
|
||||
'is_global',
|
||||
'config',
|
||||
]
|
36
app/config_management/migrations/0004_configgroupsoftware.py
Normal file
36
app/config_management/migrations/0004_configgroupsoftware.py
Normal file
@ -0,0 +1,36 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-07 21:43
|
||||
|
||||
import access.fields
|
||||
import access.models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0003_alter_team_organization'),
|
||||
('config_management', '0003_alter_configgrouphosts_organization_and_more'),
|
||||
('itam', '0013_alter_device_organization_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ConfigGroupSoftware',
|
||||
fields=[
|
||||
('is_global', models.BooleanField(default=False)),
|
||||
('id', models.AutoField(primary_key=True, serialize=False, unique=True)),
|
||||
('created', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False)),
|
||||
('modified', access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False)),
|
||||
('action', models.CharField(blank=True, choices=[('1', 'Install'), ('0', 'Remove')], default=None, max_length=1, null=True)),
|
||||
('config_group', models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='config_management.configgroups')),
|
||||
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists])),
|
||||
('software', models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='itam.software')),
|
||||
('version', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.softwareversion')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-action', 'software'],
|
||||
},
|
||||
),
|
||||
]
|
@ -0,0 +1,28 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-11 20:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config_management', '0004_configgroupsoftware'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='configgrouphosts',
|
||||
name='model_notes',
|
||||
field=models.TextField(blank=True, default=None, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='configgroups',
|
||||
name='model_notes',
|
||||
field=models.TextField(blank=True, default=None, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='configgroupsoftware',
|
||||
name='model_notes',
|
||||
field=models.TextField(blank=True, default=None, null=True),
|
||||
),
|
||||
]
|
@ -7,9 +7,12 @@ from django.forms import ValidationError
|
||||
from access.fields import *
|
||||
from access.models import TenancyObject
|
||||
|
||||
from app.helpers.merge_software import merge_software
|
||||
|
||||
from core.mixin.history_save import SaveHistory
|
||||
|
||||
from itam.models.device import Device
|
||||
from itam.models.device import Device, DeviceSoftware
|
||||
from itam.models.software import Software, SoftwareVersion
|
||||
|
||||
|
||||
|
||||
@ -113,6 +116,14 @@ class ConfigGroups(GroupsCommonFields, SaveHistory):
|
||||
|
||||
|
||||
|
||||
|
||||
@property
|
||||
def parent_object(self):
|
||||
""" Fetch the parent object """
|
||||
|
||||
return self.parent
|
||||
|
||||
|
||||
def render_config(self) -> str:
|
||||
|
||||
config: dict = dict()
|
||||
@ -125,14 +136,49 @@ class ConfigGroups(GroupsCommonFields, SaveHistory):
|
||||
|
||||
config.update(self.config)
|
||||
|
||||
softwares = ConfigGroupSoftware.objects.filter(config_group=self.id)
|
||||
|
||||
software_actions = {
|
||||
"software": []
|
||||
}
|
||||
|
||||
for software in softwares:
|
||||
|
||||
if software.action:
|
||||
|
||||
if int(software.action) == 1:
|
||||
|
||||
state = 'present'
|
||||
|
||||
elif int(software.action) == 0:
|
||||
|
||||
state = 'absent'
|
||||
|
||||
software_action = {
|
||||
"name": software.software.slug,
|
||||
"state": state
|
||||
}
|
||||
|
||||
|
||||
if software.version:
|
||||
software_action['version'] = software.version.name
|
||||
|
||||
software_actions['software'] = software_actions['software'] + [ software_action ]
|
||||
|
||||
if len(software_actions['software']) > 0: # don't add empty software as it prevents parent software from being added
|
||||
|
||||
if 'software' not in config.keys():
|
||||
|
||||
config['software'] = []
|
||||
|
||||
config['software'] = merge_software(config['software'], software_actions['software'])
|
||||
|
||||
return json.dumps(config)
|
||||
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
self.is_global = False
|
||||
|
||||
if self.config:
|
||||
|
||||
self.config = self.config_keys_ansible_variable(self.config)
|
||||
@ -142,8 +188,13 @@ class ConfigGroups(GroupsCommonFields, SaveHistory):
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
|
||||
if self.parent:
|
||||
|
||||
return f'{self.parent.name} > {self.name}'
|
||||
|
||||
return self.name
|
||||
|
||||
|
||||
@ -177,3 +228,66 @@ class ConfigGroupHosts(GroupsCommonFields, SaveHistory):
|
||||
null = False,
|
||||
blank= False
|
||||
)
|
||||
|
||||
|
||||
@property
|
||||
def parent_object(self):
|
||||
""" Fetch the parent object """
|
||||
|
||||
return self.group
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class ConfigGroupSoftware(GroupsCommonFields, SaveHistory):
|
||||
""" A way to configure software to install/remove per config group """
|
||||
|
||||
class Meta:
|
||||
ordering = [
|
||||
'-action',
|
||||
'software'
|
||||
]
|
||||
|
||||
|
||||
config_group = models.ForeignKey(
|
||||
ConfigGroups,
|
||||
on_delete=models.CASCADE,
|
||||
default = None,
|
||||
null = False,
|
||||
blank= False
|
||||
)
|
||||
|
||||
|
||||
software = models.ForeignKey(
|
||||
Software,
|
||||
on_delete=models.CASCADE,
|
||||
default = None,
|
||||
null = False,
|
||||
blank= False
|
||||
)
|
||||
|
||||
action = models.CharField(
|
||||
max_length=1,
|
||||
choices=DeviceSoftware.Actions,
|
||||
default=None,
|
||||
null=True,
|
||||
blank = True,
|
||||
)
|
||||
|
||||
version = models.ForeignKey(
|
||||
SoftwareVersion,
|
||||
on_delete=models.CASCADE,
|
||||
default = None,
|
||||
null = True,
|
||||
blank= True
|
||||
)
|
||||
|
||||
|
||||
@property
|
||||
def parent_object(self):
|
||||
""" Fetch the parent object """
|
||||
|
||||
return self.config_group
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% block body %}
|
||||
{% block content %}
|
||||
|
||||
<script>
|
||||
|
||||
@ -120,6 +120,45 @@
|
||||
Software
|
||||
</h3>
|
||||
|
||||
<input type="button" value="Add Software Action" onclick="window.location='{% url 'Config Management:_group_software_add' model_pk %}';">
|
||||
<table>
|
||||
<thead>
|
||||
<th>Name</th>
|
||||
<th>Category</th>
|
||||
<th>Action</th>
|
||||
<th>Desired Version</th>
|
||||
<th> </th>
|
||||
</thead>
|
||||
{% if softwares %}
|
||||
{% for software in softwares %}
|
||||
<tr>
|
||||
<td><a href="{% url 'ITAM:_software_view' pk=software.software_id %}">{{ software.software }}</a></td>
|
||||
<td>{{ software.software.category }}</td>
|
||||
<td>
|
||||
{% url 'Config Management:_group_software_change' group_id=group.id pk=software.id as icon_link %}
|
||||
{% if software.get_action_display == 'Install' %}
|
||||
{% include 'icons/success_text.html.j2' with icon_text=software.get_action_display icon_link=icon_link %}
|
||||
{% elif software.get_action_display == 'Remove'%}
|
||||
{% include 'icons/cross_text.html.j2' with icon_text=software.get_action_display %}
|
||||
{% else %}
|
||||
{% include 'icons/add_link.html.j2' with icon_text='Add' %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if software.version %}
|
||||
{{ software.version }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<td colspan="5">Nothing Found</td>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="Configuration" class="tabcontent">
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% block body %}
|
||||
{% block content %}
|
||||
|
||||
<input type="button" value="New Group" onclick="window.location='{% url 'Config Management:_group_add' %}';">
|
||||
<table class="data">
|
||||
|
@ -0,0 +1,88 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models import Organization
|
||||
|
||||
from config_management.models.groups import ConfigGroups
|
||||
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class ConfigGroupsModel(TestCase):
|
||||
|
||||
model = ConfigGroups
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization for user and item
|
||||
2. Create an item
|
||||
|
||||
"""
|
||||
|
||||
self.organization = Organization.objects.create(name='test_org')
|
||||
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
name = 'one',
|
||||
config = dict({"key": "one", "existing": "dont_over_write"})
|
||||
)
|
||||
|
||||
self.second_item = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
name = 'one_two',
|
||||
config = dict({"key": "two"}),
|
||||
parent = self.item
|
||||
)
|
||||
|
||||
|
||||
|
||||
def test_config_groups_count_child_groups(self):
|
||||
""" Test function count_children """
|
||||
|
||||
assert self.item.count_children() == 1
|
||||
|
||||
|
||||
def test_config_groups_rendered_config_not_empty(self):
|
||||
""" Rendered Config must be returned """
|
||||
|
||||
assert self.item.config is not None
|
||||
|
||||
|
||||
def test_config_groups_rendered_config_is_dict(self):
|
||||
""" Rendered Config is a string """
|
||||
|
||||
assert type(self.item.render_config()) is str
|
||||
|
||||
|
||||
def test_config_groups_rendered_config_is_correct(self):
|
||||
""" Rendered Config is correct """
|
||||
|
||||
assert self.item.config['key'] == 'one'
|
||||
|
||||
|
||||
def test_config_groups_rendered_config_inheritence_overwrite(self):
|
||||
""" rendered config from parent group merged correctly """
|
||||
|
||||
assert self.second_item.config['key'] == 'two'
|
||||
|
||||
|
||||
def test_config_groups_rendered_config_inheritence_existing_key_present(self):
|
||||
""" rendered config from parent group merge existing key present
|
||||
|
||||
during merge, a key that doesn't exist in the child group that exists in the
|
||||
parent group should be within the child groups rendered config
|
||||
"""
|
||||
|
||||
assert self.second_item.config['key'] == 'two'
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_config_groups_config_keys_valid_ansible_variable():
|
||||
""" All config keys must be valid ansible variables """
|
||||
pass
|
||||
|
@ -1,20 +0,0 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from config_management.models.groups import ConfigGroups
|
||||
|
||||
|
||||
class ConfigGroups(TestCase):
|
||||
|
||||
model = ConfigGroups
|
||||
|
||||
model_name = 'configgroups'
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_config_groups_config_keys_valid_ansible_variable():
|
||||
""" All config keys must be valid ansible variables """
|
||||
pass
|
||||
|
@ -8,98 +8,18 @@ from django.test import TestCase, Client
|
||||
from access.models import Organization
|
||||
|
||||
from core.models.history import History
|
||||
from core.tests.abstract.history_entry import HistoryEntry
|
||||
from core.tests.abstract.history_entry_child_model import HistoryEntryChildItem
|
||||
|
||||
from config_management.models.groups import ConfigGroups
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_auth_view():
|
||||
# """ User requires Permission view_history """
|
||||
# pass
|
||||
|
||||
class ConfigGroupsHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_create():
|
||||
# """ History row must be added to history table on create """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_update():
|
||||
# """ History row must be added to history table on updatej """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_delete():
|
||||
# """ History row must be added to history table on delete """
|
||||
# pass
|
||||
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_operating_system_create():
|
||||
# """ History row must be added to history table on create
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_operating_system_update():
|
||||
# """ History row must be added to history table on update
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_operating_system_delete():
|
||||
# """ History row must be added to history table on delete
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_software_create():
|
||||
# """ History row must be added to history table on create
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_software_update():
|
||||
# """ History row must be added to history table on update
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="to be written")
|
||||
# def test_history_device_software_delete():
|
||||
# """ History row must be added to history table on delete
|
||||
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
|
||||
|
||||
class ConfigGroupsHistory(TestCase):
|
||||
|
||||
model = ConfigGroups
|
||||
|
||||
model_name = 'configgroups'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
@ -109,11 +29,17 @@ class ConfigGroupsHistory(TestCase):
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.item_create = self.model.objects.create(
|
||||
name = 'test_item_' + self.model_name,
|
||||
self.item_parent = self.model.objects.create(
|
||||
name = 'test_item_parent_' + self.model._meta.model_name,
|
||||
organization = self.organization
|
||||
)
|
||||
|
||||
self.item_create = self.model.objects.create(
|
||||
name = 'test_item_' + self.model._meta.model_name,
|
||||
organization = self.organization,
|
||||
parent = self.item_parent
|
||||
)
|
||||
|
||||
|
||||
self.history_create = History.objects.get(
|
||||
action = History.Actions.ADD[0],
|
||||
@ -122,114 +48,34 @@ class ConfigGroupsHistory(TestCase):
|
||||
)
|
||||
|
||||
self.item_change = self.item_create
|
||||
self.item_change.name = 'test_item_' + self.model_name + '_changed'
|
||||
self.item_change.name = 'test_item_' + self.model._meta.model_name + '_changed'
|
||||
self.item_change.save()
|
||||
|
||||
self.field_after_expected_value = '{"name": "' + self.item_change.name + '"}'
|
||||
|
||||
self.history_change = History.objects.get(
|
||||
action = History.Actions.UPDATE[0],
|
||||
item_pk = self.item_change.pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
||||
self.item_delete = self.model.objects.create(
|
||||
name = 'test_item_delete_' + self.model._meta.model_name,
|
||||
organization = self.organization,
|
||||
parent = self.item_parent
|
||||
)
|
||||
|
||||
# field type testing to be done as part of model testing
|
||||
def test_device_history_entry_item_add_field_action(self):
|
||||
""" Ensure action is "add" for item creation """
|
||||
self.deleted_pk = self.item_delete.pk
|
||||
|
||||
history = self.history_create.__dict__
|
||||
|
||||
assert history['action'] == int(History.Actions.ADD[0])
|
||||
# assert type(history['action']) is int
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_device_history_entry_item_add_field_after(self):
|
||||
""" Ensure after field contains correct value """
|
||||
|
||||
history = self.history_create.__dict__
|
||||
|
||||
assert history['after'] == str('{}')
|
||||
# assert type(history['after']) is str
|
||||
|
||||
|
||||
def test_device_history_entry_item_add_field_before(self):
|
||||
""" Ensure before field is an empty JSON string for create """
|
||||
|
||||
history = self.history_create.__dict__
|
||||
|
||||
assert history['before'] == str('{}')
|
||||
# assert type(history['before']) is str
|
||||
|
||||
|
||||
def test_device_history_entry_item_add_field_item_pk(self):
|
||||
""" Ensure history entry field item_pk is the created items pk """
|
||||
|
||||
history = self.history_create.__dict__
|
||||
|
||||
assert history['item_pk'] == self.item_create.pk
|
||||
# assert type(history['item_pk']) is int
|
||||
|
||||
|
||||
def test_device_history_entry_item_add_field_item_class(self):
|
||||
""" Ensure history entry field item_class is the model name """
|
||||
|
||||
history = self.history_create.__dict__
|
||||
|
||||
assert history['item_class'] == self.model._meta.model_name
|
||||
# assert type(history['item_class']) is str
|
||||
|
||||
|
||||
|
||||
|
||||
################################## Change ##################################
|
||||
|
||||
|
||||
|
||||
|
||||
# field type testing to be done as part of model testing
|
||||
def test_device_history_entry_item_change_field_action(self):
|
||||
""" Ensure action is "add" for item creation """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['action'] == int(History.Actions.UPDATE[0])
|
||||
# assert type(history['action']) is int
|
||||
|
||||
|
||||
def test_device_history_entry_item_change_field_after(self):
|
||||
""" Ensure after field contains correct value """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['after'] == str('{"name": "test_item_' + self.model_name + '_changed"}')
|
||||
# assert type(history['after']) is str
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_device_history_entry_item_change_field_before(self):
|
||||
""" Ensure before field is an empty JSON string for create """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['before'] == str('{}')
|
||||
# assert type(history['before']) is str
|
||||
|
||||
|
||||
def test_device_history_entry_item_change_field_item_pk(self):
|
||||
""" Ensure history entry field item_pk is the created items pk """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['item_pk'] == self.item_create.pk
|
||||
# assert type(history['item_pk']) is int
|
||||
|
||||
|
||||
def test_device_history_entry_item_change_field_item_class(self):
|
||||
""" Ensure history entry field item_class is the model name """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['item_class'] == self.model._meta.model_name
|
||||
# assert type(history['item_class']) is str
|
||||
self.item_delete.delete()
|
||||
|
||||
self.history_delete = History.objects.get(
|
||||
action = History.Actions.DELETE[0],
|
||||
item_pk = self.deleted_pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
||||
self.history_delete_children = History.objects.filter(
|
||||
item_parent_pk = self.deleted_pk,
|
||||
item_parent_class = self.item_parent._meta.model_name,
|
||||
)
|
||||
|
@ -10,15 +10,29 @@ import unittest
|
||||
import requests
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
|
||||
from app.tests.abstract.model_permissions import ModelPermissions
|
||||
|
||||
from config_management.models.groups import ConfigGroups
|
||||
|
||||
|
||||
class ConfigGroupPermissions(TestCase):
|
||||
class ConfigGroupPermissions(TestCase, ModelPermissions):
|
||||
|
||||
|
||||
model = ConfigGroups
|
||||
|
||||
model_name = 'configgroups'
|
||||
app_label = 'config_management'
|
||||
app_namespace = 'Config Management'
|
||||
|
||||
url_name_view = '_group_view'
|
||||
|
||||
url_name_add = '_group_add'
|
||||
|
||||
url_name_change = '_group_view'
|
||||
|
||||
url_name_delete = '_group_delete'
|
||||
|
||||
url_delete_response = reverse('Config Management:Groups')
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
@ -43,11 +57,27 @@ class ConfigGroupPermissions(TestCase):
|
||||
name = 'deviceone'
|
||||
)
|
||||
|
||||
|
||||
self.url_view_kwargs = {'pk': self.item.id}
|
||||
|
||||
# self.url_add_kwargs = {'pk': self.item.id}
|
||||
|
||||
self.add_data = {'device': 'device', 'organization': self.organization.id}
|
||||
|
||||
self.url_change_kwargs = {'pk': self.item.id}
|
||||
|
||||
self.change_data = {'device': 'device', 'organization': self.organization.id}
|
||||
|
||||
self.url_delete_kwargs = {'pk': self.item.id}
|
||||
|
||||
self.delete_data = {'device': 'device', 'organization': self.organization.id}
|
||||
|
||||
|
||||
view_permissions = Permission.objects.get(
|
||||
codename = 'view_' + self.model_name,
|
||||
codename = 'view_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -61,10 +91,10 @@ class ConfigGroupPermissions(TestCase):
|
||||
|
||||
|
||||
add_permissions = Permission.objects.get(
|
||||
codename = 'add_' + self.model_name,
|
||||
codename = 'add_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -78,10 +108,10 @@ class ConfigGroupPermissions(TestCase):
|
||||
|
||||
|
||||
change_permissions = Permission.objects.get(
|
||||
codename = 'change_' + self.model_name,
|
||||
codename = 'change_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -95,10 +125,10 @@ class ConfigGroupPermissions(TestCase):
|
||||
|
||||
|
||||
delete_permissions = Permission.objects.get(
|
||||
codename = 'delete_' + self.model_name,
|
||||
codename = 'delete_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
@ -157,355 +187,3 @@ class ConfigGroupPermissions(TestCase):
|
||||
team = different_organization_team,
|
||||
user = self.different_organization_user
|
||||
)
|
||||
|
||||
|
||||
|
||||
def test_config_groups_auth_view_user_anon_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_view', kwargs={'pk': self.item.id})
|
||||
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 302 and response.url.startswith('/account/login')
|
||||
|
||||
|
||||
def test_config_groups_auth_view_no_permission_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view with user missing permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_config_groups_auth_view_different_organizaiton_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view with user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_config_groups_auth_view_has_permission(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view as user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
def test_config_groups_auth_add_user_anon_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_add')
|
||||
|
||||
|
||||
response = client.put(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 302 and response.url.startswith('/account/login')
|
||||
|
||||
# @pytest.mark.skip(reason="ToDO: figure out why fails")
|
||||
def test_config_groups_auth_add_no_permission_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user with no permissions
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_add')
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.post(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
# @pytest.mark.skip(reason="ToDO: figure out why fails")
|
||||
def test_config_groups_auth_add_different_organization_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
attempt to add as user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_add')
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.post(url, data={'name': 'device', 'organization': self.organization.id})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_config_groups_auth_add_permission_view_denied(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add a user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_add')
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.post(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_config_groups_auth_add_has_permission(self):
|
||||
""" Check correct permission for add
|
||||
|
||||
Attempt to add as user with no permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_add')
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.post(url, data={'device': 'device', 'organization': self.organization.id})
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
def test_config_groups_auth_change_user_anon_denied(self):
|
||||
""" Check correct permission for change
|
||||
|
||||
Attempt to change as anon
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
response = client.patch(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 302 and response.url.startswith('/account/login')
|
||||
|
||||
|
||||
def test_config_groups_auth_change_no_permission_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user without permissions
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.post(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_config_groups_auth_change_different_organization_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.post(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_config_groups_auth_change_permission_view_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.post(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_config_groups_auth_change_permission_add_denied(self):
|
||||
""" Ensure permission view cant make change
|
||||
|
||||
Attempt to make change as user with add permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.post(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_config_groups_auth_change_has_permission(self):
|
||||
""" Check correct permission for change
|
||||
|
||||
Make change with user who has change permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.change_user)
|
||||
response = client.post(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
def test_config_groups_auth_delete_user_anon_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete item as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_delete', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 302 and response.url.startswith('/account/login')
|
||||
|
||||
|
||||
def test_config_groups_auth_delete_no_permission_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with no permissons
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_delete', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_config_groups_auth_delete_different_organization_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_delete', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_config_groups_auth_delete_permission_view_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with veiw permission only
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_delete', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_config_groups_auth_delete_permission_add_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with add permission only
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_delete', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_config_groups_auth_delete_permission_change_denied(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Attempt to delete as user with change permission only
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_delete', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.change_user)
|
||||
response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_config_groups_auth_delete_has_permission(self):
|
||||
""" Check correct permission for delete
|
||||
|
||||
Delete item as user with delete permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_delete', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.delete_user)
|
||||
response = client.delete(url, data={'device': 'device'})
|
||||
|
||||
assert response.status_code == 302 and response.url == reverse('Config Management:Groups')
|
||||
|
@ -0,0 +1,63 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models import Organization
|
||||
|
||||
from config_management.models.groups import ConfigGroups, ConfigGroupSoftware
|
||||
|
||||
from itam.models.device import DeviceSoftware
|
||||
from itam.models.software import Software
|
||||
|
||||
|
||||
|
||||
class ConfigGroupSoftwareModel(TestCase):
|
||||
|
||||
model = ConfigGroupSoftware
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
""" Setup Test
|
||||
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
|
||||
self.parent_item = ConfigGroups.objects.create(
|
||||
organization=organization,
|
||||
name = 'group_one'
|
||||
)
|
||||
|
||||
self.software_item = Software.objects.create(
|
||||
organization=organization,
|
||||
name = 'softwareone',
|
||||
)
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = organization,
|
||||
software = self.software_item,
|
||||
config_group = self.parent_item,
|
||||
action = DeviceSoftware.Actions.INSTALL
|
||||
)
|
||||
|
||||
|
||||
|
||||
def test_model_has_property_parent_object(self):
|
||||
""" Check if model contains 'parent_object'
|
||||
|
||||
This is a required property for all models that have a parent
|
||||
"""
|
||||
|
||||
assert hasattr(self.model, 'parent_object')
|
||||
|
||||
|
||||
def test_model_property_parent_object_returns_object(self):
|
||||
""" Check if model contains 'parent_object'
|
||||
|
||||
This is a required property for all models that have a parent
|
||||
"""
|
||||
|
||||
assert self.item.parent_object == self.parent_item
|
@ -0,0 +1,100 @@
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from access.models import Organization
|
||||
|
||||
from core.models.history import History
|
||||
from core.tests.abstract.history_entry import HistoryEntry
|
||||
from core.tests.abstract.history_entry_child_model import HistoryEntryChildItem
|
||||
|
||||
from config_management.models.groups import ConfigGroups, ConfigGroupSoftware
|
||||
|
||||
from itam.models.device import DeviceSoftware
|
||||
from itam.models.software import Software
|
||||
|
||||
|
||||
|
||||
|
||||
class ConfigGroupSoftwareHistory(TestCase, HistoryEntry, HistoryEntryChildItem):
|
||||
|
||||
model = ConfigGroupSoftware
|
||||
|
||||
parent_model = ConfigGroups
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
""" Setup Test """
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.item_parent = self.parent_model.objects.create(
|
||||
name = 'test_item_' + self.model._meta.model_name,
|
||||
organization = self.organization
|
||||
)
|
||||
|
||||
software = Software.objects.create(
|
||||
name = 'test_item_' + self.model._meta.model_name,
|
||||
organization = self.organization
|
||||
)
|
||||
|
||||
self.item_create = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
config_group = self.item_parent,
|
||||
software = software,
|
||||
action = DeviceSoftware.Actions.INSTALL,
|
||||
)
|
||||
|
||||
|
||||
self.history_create = History.objects.get(
|
||||
action = History.Actions.ADD[0],
|
||||
item_pk = self.item_create.pk,
|
||||
item_class = self.model._meta.model_name
|
||||
)
|
||||
|
||||
self.item_change = self.item_create
|
||||
self.item_change.action = DeviceSoftware.Actions.REMOVE
|
||||
self.item_change.save()
|
||||
|
||||
self.field_after_expected_value = '{"action": "' + DeviceSoftware.Actions.REMOVE + '"}'
|
||||
|
||||
self.history_change = History.objects.get(
|
||||
action = History.Actions.UPDATE[0],
|
||||
item_pk = self.item_change.pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
||||
# self.item_delete = self.item_change
|
||||
#
|
||||
software_two = Software.objects.create(
|
||||
name = 'test_item_two_' + self.model._meta.model_name,
|
||||
organization = self.organization
|
||||
)
|
||||
|
||||
self.item_delete = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
config_group = self.item_parent,
|
||||
software = software_two,
|
||||
action = DeviceSoftware.Actions.INSTALL,
|
||||
)
|
||||
|
||||
self.deleted_pk = self.item_delete.pk
|
||||
|
||||
self.item_delete.delete()
|
||||
|
||||
self.history_delete = History.objects.get(
|
||||
action = History.Actions.DELETE[0],
|
||||
item_pk = self.deleted_pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
||||
self.history_delete_children = History.objects.filter(
|
||||
item_parent_pk = self.deleted_pk,
|
||||
item_parent_class = self.item_parent._meta.model_name,
|
||||
)
|
@ -11,15 +11,15 @@ import requests
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
|
||||
from config_management.models.groups import ConfigGroups
|
||||
|
||||
from core.models.history import History
|
||||
from core.models.manufacturer import Manufacturer
|
||||
|
||||
|
||||
|
||||
class ManufacturerHistoryPermissions(TestCase):
|
||||
class ConfigGroupSoftwaresHistoryPermissions(TestCase):
|
||||
|
||||
|
||||
item_model = Manufacturer
|
||||
item_model = ConfigGroups
|
||||
|
||||
|
||||
model = History
|
||||
@ -108,6 +108,7 @@ class ManufacturerHistoryPermissions(TestCase):
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="figure out best way to test")
|
||||
def test_auth_view_history_user_anon_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
@ -122,6 +123,7 @@ class ManufacturerHistoryPermissions(TestCase):
|
||||
assert response.status_code == 302 and response.url.startswith('/account/login')
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="figure out best way to test")
|
||||
def test_auth_view_history_no_permission_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
@ -138,6 +140,7 @@ class ManufacturerHistoryPermissions(TestCase):
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="figure out best way to test")
|
||||
def test_auth_view_history_different_organizaiton_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
@ -154,6 +157,7 @@ class ManufacturerHistoryPermissions(TestCase):
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="figure out best way to test")
|
||||
def test_auth_view_history_has_permission(self):
|
||||
""" Check correct permission for view
|
||||
|
@ -0,0 +1,286 @@
|
||||
# from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import AnonymousUser, User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.shortcuts import reverse
|
||||
from django.test import TestCase, Client
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
|
||||
from app.tests.abstract.model_permissions import ModelPermissionsAdd, ModelPermissionsChange, ModelPermissionsDelete
|
||||
|
||||
from config_management.models.groups import ConfigGroups, ConfigGroupSoftware
|
||||
|
||||
from itam.models.device import DeviceSoftware
|
||||
from itam.models.software import Software
|
||||
|
||||
|
||||
|
||||
class ConfigGroupSoftwarePermissions(TestCase, ModelPermissionsAdd, ModelPermissionsChange, ModelPermissionsDelete):
|
||||
|
||||
model = ConfigGroupSoftware
|
||||
parent_model = ConfigGroups
|
||||
|
||||
model_name = 'configgroupsoftware'
|
||||
app_label = 'config_management'
|
||||
|
||||
app_namespace = 'Config Management'
|
||||
|
||||
url_name_view = '_group_view'
|
||||
|
||||
url_name_add = '_group_software_add'
|
||||
|
||||
url_name_change = '_group_software_change'
|
||||
|
||||
url_name_delete = '_group_software_delete'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization for user and item
|
||||
2. create an organization that is different to item
|
||||
3. Create the parent item
|
||||
4. create a software item
|
||||
5. create the item
|
||||
6. create teams with each permission: view, add, change, delete
|
||||
7. 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.parent_item = self.parent_model.objects.create(
|
||||
organization=organization,
|
||||
name = 'group_one'
|
||||
)
|
||||
|
||||
self.software_item = Software.objects.create(
|
||||
organization=organization,
|
||||
name = 'softwareone',
|
||||
)
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = organization,
|
||||
software = self.software_item,
|
||||
config_group = self.parent_item,
|
||||
action = DeviceSoftware.Actions.INSTALL
|
||||
)
|
||||
|
||||
|
||||
self.url_view_kwargs = {'pk': self.item.id}
|
||||
|
||||
self.url_add_kwargs = {'pk': self.parent_item.id,}
|
||||
|
||||
self.add_data = {'device': 'device', 'organization': self.organization.id}
|
||||
|
||||
self.url_change_kwargs = {'pk': self.item.id, 'group_id': self.parent_item.id}
|
||||
|
||||
self.change_data = {'device': 'device', 'organization': self.organization.id}
|
||||
|
||||
self.url_delete_kwargs = {'pk': self.item.id, 'group_id': self.parent_item.id}
|
||||
|
||||
self.delete_data = {'device': 'device', 'organization': self.organization.id}
|
||||
|
||||
self.url_delete_response = reverse('Config Management:_group_view', kwargs={'pk': self.parent_item.id})
|
||||
|
||||
view_permissions = Permission.objects.get(
|
||||
codename = 'view_' + self.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
view_team = Team.objects.create(
|
||||
team_name = 'view_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
view_team.permissions.set([view_permissions])
|
||||
|
||||
|
||||
|
||||
add_permissions = Permission.objects.get(
|
||||
codename = 'add_' + self.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.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_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.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_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.app_label,
|
||||
model = self.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
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="figure out best way to test")
|
||||
def test_config_groups_auth_view_user_anon_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_view', kwargs={'pk': self.item.id})
|
||||
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 302 and response.url.startswith('/account/login')
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="figure out best way to test")
|
||||
def test_config_groups_auth_view_no_permission_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view with user missing permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="figure out best way to test")
|
||||
def test_config_groups_auth_view_different_organizaiton_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view with user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="figure out best way to test")
|
||||
def test_config_groups_auth_view_has_permission(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view as user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse('Config Management:_group_view', kwargs={'pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="ToDO: refactor abstract test case")
|
||||
def test_model_change_has_permission(self):
|
||||
|
||||
# this test requires re-write for 302 redirection
|
||||
# actual test
|
||||
# assert response.status_code == 302 and response.url == reverse('Config Management:_group_view', kwargs={'pk': self.parent_item.id})
|
||||
|
||||
pass
|
@ -1,6 +1,7 @@
|
||||
from django.urls import path
|
||||
|
||||
from config_management.views.groups import GroupIndexView, GroupAdd, GroupDelete, GroupView, GroupHostAdd, GroupHostDelete
|
||||
from config_management.views.groups.groups import GroupIndexView, GroupAdd, GroupDelete, GroupView, GroupHostAdd, GroupHostDelete
|
||||
from config_management.views.groups.software import GroupSoftwareAdd, GroupSoftwareChange, GroupSoftwareDelete
|
||||
|
||||
app_name = "Config Management"
|
||||
|
||||
@ -8,9 +9,14 @@ urlpatterns = [
|
||||
path('group', GroupIndexView.as_view(), name='Groups'),
|
||||
path('group/add', GroupAdd.as_view(), name='_group_add'),
|
||||
path('group/<int:pk>', GroupView.as_view(), name='_group_view'),
|
||||
|
||||
path('group/<int:group_id>/child', GroupAdd.as_view(), name='_group_add_child'),
|
||||
path('group/<int:pk>/delete', GroupDelete.as_view(), name='_group_delete'),
|
||||
|
||||
path("group/<int:pk>/software/add", GroupSoftwareAdd.as_view(), name="_group_software_add"),
|
||||
path("group/<int:group_id>/software/<int:pk>", GroupSoftwareChange.as_view(), name="_group_software_change"),
|
||||
path("group/<int:group_id>/software/<int:pk>/delete", GroupSoftwareDelete.as_view(), name="_group_software_delete"),
|
||||
|
||||
path('group/<int:group_id>/host', GroupHostAdd.as_view(), name='_group_add_host'),
|
||||
path('group/<int:group_id>/host/<int:pk>/delete', GroupHostDelete.as_view(), name='_group_delete_host'),
|
||||
|
||||
|
@ -16,7 +16,8 @@ from itam.models.device import Device
|
||||
from settings.models.user_settings import UserSettings
|
||||
|
||||
from config_management.forms.group_hosts import ConfigGroupHostsForm
|
||||
from config_management.models.groups import ConfigGroups, ConfigGroupHosts
|
||||
from config_management.forms.group.group import ConfigGroupForm
|
||||
from config_management.models.groups import ConfigGroups, ConfigGroupHosts, ConfigGroupSoftware
|
||||
|
||||
|
||||
|
||||
@ -37,6 +38,8 @@ class GroupIndexView(OrganizationPermission, generic.ListView):
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['model_docs_path'] = self.model._meta.app_label + '/'
|
||||
|
||||
context['content_title'] = 'Config Groups'
|
||||
|
||||
return context
|
||||
@ -112,6 +115,8 @@ class GroupView(OrganizationPermission, generic.UpdateView):
|
||||
|
||||
context_object_name = "group"
|
||||
|
||||
form_class = ConfigGroupForm
|
||||
|
||||
model = ConfigGroups
|
||||
|
||||
permission_required = [
|
||||
@ -121,12 +126,6 @@ class GroupView(OrganizationPermission, generic.UpdateView):
|
||||
|
||||
template_name = 'config_management/group.html.j2'
|
||||
|
||||
fields = [
|
||||
'name',
|
||||
'parent',
|
||||
'config',
|
||||
]
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
@ -146,6 +145,9 @@ class GroupView(OrganizationPermission, generic.UpdateView):
|
||||
|
||||
context['model_delete_url'] = reverse('Config Management:_group_delete', args=(self.kwargs['pk'],))
|
||||
|
||||
softwares = ConfigGroupSoftware.objects.filter(config_group=self.kwargs['pk'])[:50]
|
||||
context['softwares'] = softwares
|
||||
|
||||
context['content_title'] = self.object.name
|
||||
|
||||
# if self.request.user.is_superuser:
|
||||
@ -254,9 +256,20 @@ class GroupHostAdd(OrganizationPermission, generic.CreateView):
|
||||
|
||||
exsting_group_hosts = ConfigGroupHosts.objects.filter(group=group)
|
||||
|
||||
form_class.fields["host"].queryset = Device.objects.filter(
|
||||
organization=group.organization.id,
|
||||
).exclude(id__in=exsting_group_hosts.values_list('host', flat=True))
|
||||
form_class.fields["host"].queryset = None
|
||||
|
||||
if group.is_global:
|
||||
|
||||
form_class.fields["host"].queryset = Device.objects.filter(
|
||||
).exclude(
|
||||
id__in=exsting_group_hosts.values_list('host', flat=True)
|
||||
)
|
||||
|
||||
if form_class.fields["host"].queryset is None:
|
||||
|
||||
form_class.fields["host"].queryset = Device.objects.filter(
|
||||
organization=group.organization.id,
|
||||
).exclude(id__in=exsting_group_hosts.values_list('host', flat=True))
|
||||
|
||||
return form_class
|
||||
|
137
app/config_management/views/groups/software.py
Normal file
137
app/config_management/views/groups/software.py
Normal file
@ -0,0 +1,137 @@
|
||||
from django.urls import reverse
|
||||
from django.views import generic
|
||||
|
||||
from access.mixin import OrganizationPermission
|
||||
|
||||
from itam.models.software import Software
|
||||
|
||||
from config_management.forms.group.add_software import SoftwareAdd
|
||||
from config_management.forms.group.change_software import SoftwareUpdate
|
||||
from config_management.models.groups import ConfigGroups, ConfigGroupSoftware
|
||||
|
||||
|
||||
|
||||
class GroupSoftwareAdd(OrganizationPermission, generic.CreateView):
|
||||
|
||||
form_class = SoftwareAdd
|
||||
|
||||
model = ConfigGroupSoftware
|
||||
|
||||
parent_model = ConfigGroups
|
||||
|
||||
permission_required = [
|
||||
'config_management.add_configgroupsoftware',
|
||||
]
|
||||
|
||||
template_name = 'form.html.j2'
|
||||
|
||||
|
||||
def form_valid(self, form):
|
||||
config_group = ConfigGroups.objects.get(pk=self.kwargs['pk'])
|
||||
form.instance.organization_id = config_group.organization.id
|
||||
form.instance.config_group = config_group
|
||||
|
||||
software = Software.objects.get(pk=form.instance.software.id)
|
||||
|
||||
if ConfigGroupSoftware.objects.filter(
|
||||
config_group=config_group,
|
||||
software=software
|
||||
).exists():
|
||||
|
||||
existing_object = ConfigGroupSoftware.objects.get(
|
||||
device=device,
|
||||
software=software
|
||||
)
|
||||
|
||||
existing_object.action = form.instance.action
|
||||
existing_object.save()
|
||||
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
else:
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
obj = ConfigGroups.objects.get(pk=self.kwargs['pk'])
|
||||
kwargs['organizations'] = [ obj.organization.id ]
|
||||
return kwargs
|
||||
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
return reverse('Config Management:_group_view', args=(self.kwargs['pk'],))
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['content_title'] = 'Add Software Action'
|
||||
|
||||
return context
|
||||
|
||||
|
||||
|
||||
class GroupSoftwareChange(OrganizationPermission, generic.UpdateView):
|
||||
|
||||
form_class = SoftwareUpdate
|
||||
|
||||
model = ConfigGroupSoftware
|
||||
|
||||
permission_required = [
|
||||
'config_management.change_configgroupsoftware'
|
||||
|
||||
]
|
||||
|
||||
template_name = 'form.html.j2'
|
||||
|
||||
|
||||
def form_valid(self, form):
|
||||
config_group = ConfigGroups.objects.get(pk=self.kwargs['group_id'])
|
||||
|
||||
form.instance.organization_id = config_group.organization.id
|
||||
form.instance.config_group = config_group
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
return reverse('Config Management:_group_view', args=(self.kwargs['group_id'],))
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['model_delete_url'] = reverse('Config Management:_group_software_delete', args=(self.kwargs['group_id'], self.kwargs['pk'],))
|
||||
|
||||
context['content_title'] = 'Edit Software Action'
|
||||
|
||||
return context
|
||||
|
||||
|
||||
|
||||
class GroupSoftwareDelete(OrganizationPermission, generic.DeleteView):
|
||||
|
||||
model = ConfigGroupSoftware
|
||||
|
||||
permission_required = [
|
||||
'config_management.delete_configgroupsoftware',
|
||||
]
|
||||
|
||||
template_name = 'form.html.j2'
|
||||
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
return reverse('Config Management:_group_view', args=(self.kwargs['group_id'],))
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['content_title'] = 'Delete '
|
||||
|
||||
return context
|
89
app/core/http/browser.py
Normal file
89
app/core/http/browser.py
Normal file
@ -0,0 +1,89 @@
|
||||
import requests
|
||||
|
||||
|
||||
|
||||
class Browser:
|
||||
|
||||
response: requests.Response = None
|
||||
|
||||
@property
|
||||
def status() -> int:
|
||||
""" HTTP Status Code
|
||||
|
||||
Returns:
|
||||
int: Return the HTTP status code from the last request
|
||||
"""
|
||||
return self.response.status_code
|
||||
|
||||
|
||||
def get(
|
||||
self,
|
||||
url: str,
|
||||
headers: dict = {},
|
||||
ssl_verify: bool = True
|
||||
) -> requests.Response:
|
||||
""" Perform a HTTP/GET request
|
||||
|
||||
Args:
|
||||
url (str): URL to fetch.
|
||||
headers (dict, optional): Request Headers. Defaults to {}.
|
||||
ssl_verify (bool, optional): Verify the SSL Certificate. Defaults to True.
|
||||
|
||||
Returns:
|
||||
requests.Response: The requests response object
|
||||
"""
|
||||
|
||||
|
||||
headers.update({
|
||||
"Accept": "application/json",
|
||||
"Authorization": "Bearer xx" # AWX auth
|
||||
})
|
||||
|
||||
response = requests.get(
|
||||
headers = headers,
|
||||
timeout = 3,
|
||||
url = url,
|
||||
verify = ssl_verify,
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
|
||||
self.response = response
|
||||
|
||||
return self.response
|
||||
|
||||
|
||||
def post(
|
||||
self,
|
||||
url: str,
|
||||
headers: dict = {},
|
||||
data: dict = None,
|
||||
ssl_verify: bool = True
|
||||
) -> requests.Response:
|
||||
""" Perform an HTTP/POST request
|
||||
|
||||
Args:
|
||||
url (str): _description_
|
||||
headers (dict, optional): Request Headers. Defaults to {}.
|
||||
data (dict, optional): _description_. Defaults to None.
|
||||
ssl_verify (bool, optional): Verify the SSL Certificate. Defaults to True.
|
||||
|
||||
Returns:
|
||||
requests.Response: _description_
|
||||
"""
|
||||
|
||||
response = request.post(
|
||||
headers={
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
timeout = 3,
|
||||
url = url,
|
||||
data = data,
|
||||
verify = ssl_verify,
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
|
||||
self.response = response
|
||||
|
||||
return self.response
|
@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-11 20:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0008_alter_manufacturer_organization_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='manufacturer',
|
||||
name='model_notes',
|
||||
field=models.TextField(blank=True, default=None, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='notes',
|
||||
name='model_notes',
|
||||
field=models.TextField(blank=True, default=None, null=True),
|
||||
),
|
||||
]
|
0
app/core/mixin/__init__.py
Normal file
0
app/core/mixin/__init__.py
Normal file
@ -11,18 +11,17 @@ class SaveHistory(models.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
@property
|
||||
def fields(self):
|
||||
return [ f.name for f in self._meta.fields + self._meta.many_to_many ]
|
||||
|
||||
def save_history(self, before: dict, after: dict):
|
||||
""" Save a Models Changes
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
""" OverRides save for keeping model history.
|
||||
|
||||
Not a Full-Override as this is just to add to existing.
|
||||
|
||||
Before to fetch from DB to ensure the changed value is the actual changed value and the after
|
||||
is the data that was saved to the DB.
|
||||
Args:
|
||||
before (dict): model before saving (model.objects.get().__dict__)
|
||||
after (dict): model after saving and refetched from DB (model.objects.get().__dict__)
|
||||
"""
|
||||
|
||||
remove_keys = [
|
||||
@ -30,12 +29,6 @@ class SaveHistory(models.Model):
|
||||
'created',
|
||||
'modified'
|
||||
]
|
||||
before = {}
|
||||
|
||||
try:
|
||||
before = self.__class__.objects.get(pk=self.pk).__dict__.copy()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
clean = {}
|
||||
for entry in before:
|
||||
@ -58,11 +51,6 @@ class SaveHistory(models.Model):
|
||||
|
||||
before_json = json.dumps(clean)
|
||||
|
||||
# Process the save
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
after = self.__dict__.copy()
|
||||
|
||||
clean = {}
|
||||
for entry in after:
|
||||
|
||||
@ -89,59 +77,36 @@ class SaveHistory(models.Model):
|
||||
clean[entry] = value
|
||||
|
||||
|
||||
after = json.dumps(clean)
|
||||
after_json = json.dumps(clean)
|
||||
|
||||
item_parent_pk = None
|
||||
item_parent_class = None
|
||||
|
||||
if self._meta.model_name == 'deviceoperatingsystem':
|
||||
|
||||
item_parent_pk = self.device.pk
|
||||
item_parent_class = self.device._meta.model_name
|
||||
if hasattr(self, 'parent_object'):
|
||||
|
||||
if self._meta.model_name == 'devicesoftware':
|
||||
if self.parent_object:
|
||||
|
||||
item_parent_pk = self.device.pk
|
||||
item_parent_class = self.device._meta.model_name
|
||||
|
||||
if self._meta.model_name == 'operatingsystemversion':
|
||||
|
||||
item_parent_pk = self.operating_system_id
|
||||
item_parent_class = self.operating_system._meta.model_name
|
||||
item_parent_pk = self.parent_object.pk
|
||||
item_parent_class = self.parent_object._meta.model_name
|
||||
|
||||
|
||||
if self._meta.model_name == 'softwareversion':
|
||||
|
||||
item_parent_pk = self.software.pk
|
||||
item_parent_class = self.software._meta.model_name
|
||||
|
||||
if self._meta.model_name == 'team':
|
||||
|
||||
item_parent_pk = self.organization.pk
|
||||
item_parent_class = self.organization._meta.model_name
|
||||
|
||||
if self._meta.model_name == 'teamusers':
|
||||
|
||||
item_parent_pk = self.team.pk
|
||||
item_parent_class = self.team._meta.model_name
|
||||
|
||||
if self._meta.model_name == 'configgrouphosts':
|
||||
|
||||
item_parent_pk = self.group.id
|
||||
item_parent_class = self.group._meta.model_name
|
||||
|
||||
item_pk = self.pk
|
||||
|
||||
if not before:
|
||||
|
||||
action = History.Actions.ADD
|
||||
|
||||
elif before != after:
|
||||
elif before_json != after_json and self.pk:
|
||||
|
||||
action = History.Actions.UPDATE
|
||||
|
||||
elif not after:
|
||||
elif self.pk is None:
|
||||
|
||||
action = History.Actions.DELETE
|
||||
item_pk = before['id']
|
||||
after_json = None
|
||||
|
||||
|
||||
current_user = None
|
||||
if get_request() is not None:
|
||||
@ -152,16 +117,105 @@ class SaveHistory(models.Model):
|
||||
current_user = None
|
||||
|
||||
|
||||
if before != after and after != '{}':
|
||||
# if before != after_json and after_json != '{}':
|
||||
if before_json != after_json:
|
||||
entry = History.objects.create(
|
||||
before = before_json,
|
||||
after = after,
|
||||
after = after_json,
|
||||
user = current_user,
|
||||
action = action,
|
||||
item_pk = self.pk,
|
||||
item_pk = item_pk,
|
||||
item_class = self._meta.model_name,
|
||||
item_parent_pk = item_parent_pk,
|
||||
item_parent_class = item_parent_class,
|
||||
)
|
||||
|
||||
entry.save()
|
||||
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
""" OverRides save for keeping model history.
|
||||
|
||||
Not a Full-Override as this is just to add to existing.
|
||||
|
||||
Before to fetch from DB to ensure the changed value is the actual changed value and the after
|
||||
is the data that was saved to the DB.
|
||||
"""
|
||||
|
||||
before = {}
|
||||
|
||||
try:
|
||||
before = self.__class__.objects.get(pk=self.pk).__dict__.copy()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Process the save
|
||||
super().save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
|
||||
|
||||
after = self.__dict__.copy()
|
||||
|
||||
self.save_history(before, after)
|
||||
|
||||
|
||||
def delete_history(self, item_pk, item_class):
|
||||
""" Delete the objects history
|
||||
|
||||
When an object is no longer in the database, delete the objects history and
|
||||
that of the child objects. Only caveat is that if the history has a parent_pk
|
||||
the object history is not to be deleted.
|
||||
|
||||
Args:
|
||||
item_pk (int): Primary key of the object to be deleted
|
||||
item_class (str): Object class of the object to be deleted
|
||||
"""
|
||||
|
||||
object_history = History.objects.filter(
|
||||
item_pk = item_pk,
|
||||
item_class = item_class,
|
||||
item_parent_pk = None,
|
||||
)
|
||||
|
||||
if object_history.exists():
|
||||
|
||||
object_history.delete()
|
||||
|
||||
child_object_history = History.objects.filter(
|
||||
item_parent_pk = item_pk,
|
||||
item_parent_class = item_class,
|
||||
)
|
||||
|
||||
if child_object_history.exists():
|
||||
|
||||
child_object_history.delete()
|
||||
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
""" OverRides delete for keeping model history and on parent object ONLY!.
|
||||
|
||||
Not a Full-Override as this is just to add to existing.
|
||||
"""
|
||||
|
||||
before = {}
|
||||
item_pk = self.pk
|
||||
item_class = self._meta.model_name
|
||||
|
||||
try:
|
||||
|
||||
before = self.__class__.objects.get(pk=self.pk).__dict__.copy()
|
||||
|
||||
except Exception:
|
||||
|
||||
pass
|
||||
|
||||
# Process the delete
|
||||
super().delete(using=using, keep_parents=keep_parents)
|
||||
|
||||
after = self.__dict__.copy()
|
||||
|
||||
if hasattr(self, 'parent_object'):
|
||||
|
||||
self.save_history(before, after)
|
||||
|
||||
else:
|
||||
|
||||
self.delete_history(item_pk, item_class)
|
||||
|
@ -1,7 +1,7 @@
|
||||
{% extends 'base.html.j2' %}
|
||||
{% load json %}
|
||||
|
||||
{% block body %}
|
||||
{% block content %}
|
||||
|
||||
<script>
|
||||
|
||||
|
@ -10,4 +10,8 @@ register = template.Library()
|
||||
@stringfilter
|
||||
def json_pretty(value):
|
||||
|
||||
if value == 'None':
|
||||
|
||||
return str('{}')
|
||||
|
||||
return json.dumps(json.loads(value), indent=4, sort_keys=True)
|
||||
|
0
app/core/tests/__init__.py
Normal file
0
app/core/tests/__init__.py
Normal file
0
app/core/tests/abstract/__init__.py
Normal file
0
app/core/tests/abstract/__init__.py
Normal file
110
app/core/tests/abstract/history_entry.py
Normal file
110
app/core/tests/abstract/history_entry.py
Normal file
@ -0,0 +1,110 @@
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from core.models.history import History
|
||||
|
||||
|
||||
|
||||
class HistoryEntry:
|
||||
|
||||
|
||||
# field type testing to be done as part of model testing
|
||||
def test_history_entry_item_add_field_action(self):
|
||||
""" Ensure action is "add" for item creation """
|
||||
|
||||
history = self.history_create.__dict__
|
||||
|
||||
assert history['action'] == int(History.Actions.ADD[0])
|
||||
# assert type(history['action']) is int
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_entry_item_add_field_after(self):
|
||||
""" Ensure after field contains correct value """
|
||||
|
||||
history = self.history_create.__dict__
|
||||
|
||||
assert history['after'] == str('{}')
|
||||
# assert type(history['after']) is str
|
||||
|
||||
|
||||
def test_history_entry_item_add_field_before(self):
|
||||
""" Ensure before field is an empty JSON string for create """
|
||||
|
||||
history = self.history_create.__dict__
|
||||
|
||||
assert history['before'] == str('{}')
|
||||
# assert type(history['before']) is str
|
||||
|
||||
|
||||
def test_history_entry_item_add_field_item_pk(self):
|
||||
""" Ensure history entry field item_pk is the created items pk """
|
||||
|
||||
history = self.history_create.__dict__
|
||||
|
||||
assert history['item_pk'] == self.item_create.pk
|
||||
# assert type(history['item_pk']) is int
|
||||
|
||||
|
||||
def test_history_entry_item_add_field_item_class(self):
|
||||
""" Ensure history entry field item_class is the model name """
|
||||
|
||||
history = self.history_create.__dict__
|
||||
|
||||
assert history['item_class'] == self.model._meta.model_name
|
||||
# assert type(history['item_class']) is str
|
||||
|
||||
|
||||
|
||||
|
||||
################################## Change ##################################
|
||||
|
||||
|
||||
|
||||
|
||||
# field type testing to be done as part of model testing
|
||||
def test_history_entry_item_change_field_action(self):
|
||||
""" Ensure action is "add" for item creation """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['action'] == int(History.Actions.UPDATE[0])
|
||||
# assert type(history['action']) is int
|
||||
|
||||
|
||||
def test_history_entry_item_change_field_after(self):
|
||||
""" Ensure after field contains correct value """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['after'] == str(self.field_after_expected_value)
|
||||
# assert type(history['after']) is str
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_entry_item_change_field_before(self):
|
||||
""" Ensure before field is an empty JSON string for create """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['before'] == str('{}')
|
||||
# assert type(history['before']) is str
|
||||
|
||||
|
||||
def test_history_entry_item_change_field_item_pk(self):
|
||||
""" Ensure history entry field item_pk is the created items pk """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['item_pk'] == self.item_create.pk
|
||||
# assert type(history['item_pk']) is int
|
||||
|
||||
|
||||
def test_history_entry_item_change_field_item_class(self):
|
||||
""" Ensure history entry field item_class is the model name """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['item_class'] == self.model._meta.model_name
|
||||
# assert type(history['item_class']) is str
|
82
app/core/tests/abstract/history_entry_child_model.py
Normal file
82
app/core/tests/abstract/history_entry_child_model.py
Normal file
@ -0,0 +1,82 @@
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from core.models.history import History
|
||||
|
||||
|
||||
|
||||
class HistoryEntryChildItem:
|
||||
|
||||
|
||||
|
||||
def test_history_entry_item_delete_children_entries_not_exist(self):
|
||||
""" When an item is deleted, it's children history entries must be removed """
|
||||
|
||||
assert self.history_delete_children.exists() is False
|
||||
|
||||
|
||||
def test_history_entry_item_delete_field_action(self):
|
||||
""" Ensure action is "add" for item creation """
|
||||
|
||||
history = self.history_delete.__dict__
|
||||
|
||||
assert history['action'] == int(History.Actions.DELETE[0])
|
||||
# assert type(history['action']) is int
|
||||
|
||||
|
||||
def test_history_entry_item_delete_field_after(self):
|
||||
""" Ensure after field contains correct value """
|
||||
|
||||
history = self.history_delete.__dict__
|
||||
|
||||
assert history['after'] == None
|
||||
# assert type(history['after']) is str
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="figure out best way to test")
|
||||
def test_history_entry_item_delete_field_before(self):
|
||||
""" Ensure before field is an empty JSON string for create """
|
||||
|
||||
history = self.history_delete.__dict__
|
||||
|
||||
assert history['before'] == str('{}')
|
||||
# assert type(history['before']) is str
|
||||
|
||||
|
||||
def test_history_entry_item_delete_field_item_pk(self):
|
||||
""" Ensure history entry field item_pk is the created items pk """
|
||||
|
||||
history = self.history_delete.__dict__
|
||||
|
||||
assert history['item_pk'] == self.deleted_pk
|
||||
# assert type(history['item_pk']) is int
|
||||
|
||||
|
||||
def test_history_entry_item_delete_item_class(self):
|
||||
""" Ensure history entry field item_class is the model name """
|
||||
|
||||
history = self.history_delete.__dict__
|
||||
|
||||
assert history['item_class'] == self.model._meta.model_name
|
||||
# assert type(history['item_class']) is str
|
||||
|
||||
|
||||
|
||||
def test_history_entry_item_delete_field_parent_pk(self):
|
||||
""" Ensure history entry field item_pk is the created parents pk """
|
||||
|
||||
history = self.history_delete.__dict__
|
||||
|
||||
assert history['item_parent_pk'] == self.item_parent.pk
|
||||
# assert type(history['item_pk']) is int
|
||||
|
||||
|
||||
def test_history_entry_item_delete_field_parent_class(self):
|
||||
""" Ensure history entry field parent_class is the model name """
|
||||
|
||||
history = self.history_delete.__dict__
|
||||
|
||||
assert history['item_parent_class'] == self.item_parent._meta.model_name
|
||||
# assert type(history['item_class']) is str
|
||||
|
21
app/core/tests/abstract/history_entry_parent_model.py
Normal file
21
app/core/tests/abstract/history_entry_parent_model.py
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from core.models.history import History
|
||||
|
||||
|
||||
|
||||
class HistoryEntryParentItem:
|
||||
|
||||
|
||||
def test_history_entry_delete(self):
|
||||
""" When an item is deleted, it's history entries must be removed """
|
||||
|
||||
assert self.history_delete.exists() is False
|
||||
|
||||
|
||||
def test_history_entry_children_delete(self):
|
||||
""" When an item is deleted, it's history entries must be removed """
|
||||
|
||||
assert self.history_delete_children.exists() is False
|
115
app/core/tests/abstract/history_permissions.py
Normal file
115
app/core/tests/abstract/history_permissions.py
Normal file
@ -0,0 +1,115 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.shortcuts import reverse
|
||||
from django.test import TestCase, Client
|
||||
|
||||
from core.models.history import History
|
||||
|
||||
from itam.models.device import Device
|
||||
|
||||
|
||||
|
||||
class HistoryPermissions:
|
||||
"""Test cases for accessing History """
|
||||
|
||||
|
||||
item: object
|
||||
"""Created Model
|
||||
|
||||
Create a new item.
|
||||
"""
|
||||
|
||||
model = History
|
||||
""" The history Model """
|
||||
|
||||
namespace: str = ''
|
||||
""" URL namespace for the history view"""
|
||||
|
||||
name_view: str = '_history'
|
||||
""" URL view name for history """
|
||||
|
||||
no_permissions_user: User
|
||||
"""A User with no permissions to access the item
|
||||
|
||||
Create in `setUpTestData`
|
||||
"""
|
||||
|
||||
different_organization_user: User
|
||||
"""A User with the correct permissions to access the item
|
||||
|
||||
This user must be in a different organization than the item
|
||||
|
||||
Create in `setUpTestData`
|
||||
"""
|
||||
|
||||
view_user: User
|
||||
"""A User with the correct permissions to access the item
|
||||
|
||||
This user must be in the same organization as the item
|
||||
|
||||
Create in `setUpTestData`
|
||||
"""
|
||||
|
||||
|
||||
def test_view_history_user_anon_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.namespace + self.name_view, kwargs={'model_name': self.item._meta.model_name, 'model_pk': self.item.id})
|
||||
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 302 and response.url.startswith('/account/login')
|
||||
|
||||
|
||||
def test_view_history_no_permission_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view with user missing permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.namespace + self.name_view, kwargs={'model_name': self.item._meta.model_name, 'model_pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.no_permissions_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_view_history_different_organizaiton_denied(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view with user from different organization
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.namespace + self.name_view, kwargs={'model_name': self.item._meta.model_name, 'model_pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_view_history_has_permission(self):
|
||||
""" Check correct permission for view
|
||||
|
||||
Attempt to view as user with view permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.namespace + self.name_view, kwargs={'model_name': self.item._meta.model_name, 'model_pk': self.item.id})
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.get(url)
|
||||
|
||||
assert response.status_code == 200
|
@ -0,0 +1,75 @@
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
from django.test import TestCase, Client
|
||||
|
||||
|
||||
from access.models import Organization
|
||||
|
||||
from core.models.history import History
|
||||
from core.models.manufacturer import Manufacturer
|
||||
from core.tests.abstract.history_entry import HistoryEntry
|
||||
from core.tests.abstract.history_entry_parent_model import HistoryEntryParentItem
|
||||
|
||||
from itam.models.device import Device
|
||||
|
||||
|
||||
|
||||
class ManufacturerHistory(TestCase, HistoryEntry, HistoryEntryParentItem):
|
||||
|
||||
|
||||
model = Manufacturer
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
""" Setup Test """
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.item_create = self.model.objects.create(
|
||||
name = 'test_item_' + self.model._meta.model_name,
|
||||
organization = self.organization
|
||||
)
|
||||
|
||||
|
||||
self.history_create = History.objects.get(
|
||||
action = History.Actions.ADD[0],
|
||||
item_pk = self.item_create.pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
||||
|
||||
self.item_change = self.item_create
|
||||
self.item_change.name = 'test_item_' + self.model._meta.model_name + '_changed'
|
||||
self.item_change.save()
|
||||
|
||||
self.field_after_expected_value = '{"name": "test_item_' + self.model._meta.model_name + '_changed"}'
|
||||
|
||||
self.history_change = History.objects.get(
|
||||
action = History.Actions.UPDATE[0],
|
||||
item_pk = self.item_change.pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
||||
self.item_delete = self.model.objects.create(
|
||||
name = 'test_item_delete_' + self.model._meta.model_name,
|
||||
organization = self.organization
|
||||
)
|
||||
|
||||
self.deleted_pk = self.item_delete.pk
|
||||
|
||||
self.item_delete.delete()
|
||||
|
||||
self.history_delete = History.objects.filter(
|
||||
item_pk = self.deleted_pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
||||
self.history_delete_children = History.objects.filter(
|
||||
item_parent_pk = self.deleted_pk,
|
||||
item_parent_class = self.model._meta.model_name,
|
||||
)
|
@ -0,0 +1,94 @@
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
from django.contrib.auth.models import AnonymousUser, User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase, Client
|
||||
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
|
||||
from core.models.manufacturer import Manufacturer
|
||||
from core.tests.abstract.history_permissions import HistoryPermissions
|
||||
|
||||
|
||||
|
||||
class ManufacturerHistoryPermissions(TestCase, HistoryPermissions):
|
||||
|
||||
|
||||
item_model = Manufacturer
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization for user and item
|
||||
2. create an organization that is different to item
|
||||
3. Create a device
|
||||
4. Add history device history entry as item
|
||||
5. create a user
|
||||
6. create user in different organization (with the required permission)
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
different_organization = Organization.objects.create(name='test_different_organization')
|
||||
|
||||
self.item = self.item_model.objects.create(
|
||||
organization=organization,
|
||||
name = 'deviceone'
|
||||
)
|
||||
|
||||
self.history_model_name = self.item._meta.model_name
|
||||
|
||||
self.history = self.model.objects.get(
|
||||
item_pk = self.item.id,
|
||||
item_class = self.item._meta.model_name,
|
||||
action = self.model.Actions.ADD,
|
||||
)
|
||||
|
||||
view_permissions = Permission.objects.get(
|
||||
codename = 'view_' + self.model._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.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password")
|
||||
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_user_view", password="password")
|
||||
teamuser = TeamUsers.objects.create(
|
||||
team = view_team,
|
||||
user = self.view_user
|
||||
)
|
||||
|
||||
self.different_organization_user = User.objects.create_user(username="test_different_organization_user", password="password")
|
||||
|
||||
|
||||
different_organization_team = Team.objects.create(
|
||||
team_name = 'different_organization_team',
|
||||
organization = different_organization,
|
||||
)
|
||||
|
||||
different_organization_team.permissions.set([
|
||||
view_permissions,
|
||||
])
|
||||
|
||||
TeamUsers.objects.create(
|
||||
team = different_organization_team,
|
||||
user = self.different_organization_user
|
||||
)
|
187
app/core/tests/manufacturer/test_manufacturer_permission.py
Normal file
187
app/core/tests/manufacturer/test_manufacturer_permission.py
Normal file
@ -0,0 +1,187 @@
|
||||
# from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import AnonymousUser, User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.shortcuts import reverse
|
||||
from django.test import TestCase, Client
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
|
||||
from app.tests.abstract.model_permissions import ModelPermissions
|
||||
|
||||
from core.models.manufacturer import Manufacturer
|
||||
|
||||
|
||||
class ManufacturerPermissions(TestCase, ModelPermissions):
|
||||
|
||||
model = Manufacturer
|
||||
|
||||
app_namespace = 'Settings'
|
||||
|
||||
url_name_view = '_manufacturer_view'
|
||||
|
||||
url_name_add = '_manufacturer_add'
|
||||
|
||||
url_name_change = '_manufacturer_view'
|
||||
|
||||
url_name_delete = '_manufacturer_delete'
|
||||
|
||||
url_delete_response = reverse('Settings:_manufacturers')
|
||||
|
||||
@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 manufacturer
|
||||
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.item = self.model.objects.create(
|
||||
organization=organization,
|
||||
name = 'manufacturerone'
|
||||
)
|
||||
|
||||
|
||||
self.url_view_kwargs = {'pk': self.item.id}
|
||||
|
||||
# self.url_add_kwargs = {'pk': self.item.id}
|
||||
|
||||
self.add_data = {'manufacturer': 'manufacturer', 'organization': self.organization.id}
|
||||
|
||||
self.url_change_kwargs = {'pk': self.item.id}
|
||||
|
||||
self.change_data = {'manufacturer': 'manufacturer', 'organization': self.organization.id}
|
||||
|
||||
self.url_delete_kwargs = {'pk': self.item.id}
|
||||
|
||||
self.delete_data = {'manufacturer': 'manufacturer', 'organization': self.organization.id}
|
||||
|
||||
|
||||
view_permissions = Permission.objects.get(
|
||||
codename = 'view_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
view_team = Team.objects.create(
|
||||
team_name = 'view_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
view_team.permissions.set([view_permissions])
|
||||
|
||||
|
||||
|
||||
add_permissions = Permission.objects.get(
|
||||
codename = 'add_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
add_team = Team.objects.create(
|
||||
team_name = 'add_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
add_team.permissions.set([add_permissions])
|
||||
|
||||
|
||||
|
||||
change_permissions = Permission.objects.get(
|
||||
codename = 'change_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
change_team = Team.objects.create(
|
||||
team_name = 'change_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
change_team.permissions.set([change_permissions])
|
||||
|
||||
|
||||
|
||||
delete_permissions = Permission.objects.get(
|
||||
codename = 'delete_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
delete_team = Team.objects.create(
|
||||
team_name = 'delete_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
delete_team.permissions.set([delete_permissions])
|
||||
|
||||
|
||||
self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password")
|
||||
|
||||
|
||||
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
|
||||
)
|
@ -35,3 +35,48 @@ class History(TestCase):
|
||||
fields required `parent_item_pk` and `parent_item_class
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_save_calls_save_history():
|
||||
""" During model save, self.save_history is called
|
||||
|
||||
This method saves the history to the database
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_delete_calls_save_history():
|
||||
""" During model delete, self.save_history is called
|
||||
|
||||
This method saves the delete history to the database for parent objects
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_delete_calls_delete_history():
|
||||
""" During model delete, self.delete_history is called
|
||||
|
||||
This method deletes the item and child-item history from the database
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_function_save_attributes():
|
||||
""" Ensure save Attributes function match django default
|
||||
|
||||
the save method is overridden. the function attributes must match default django method
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_function_delete_attributes():
|
||||
""" Ensure delete Attributes function match django default
|
||||
|
||||
the delete method is overridden. the function attributes must match default django method
|
||||
"""
|
||||
pass
|
||||
|
@ -13,6 +13,7 @@ from access.models import Organization, Team, TeamUsers, Permission
|
||||
from itam.models.device import Device
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="this test needs to move to models tests that recieve notes")
|
||||
class NotesPermissions(TestCase):
|
||||
|
||||
model = Device
|
||||
|
@ -18,17 +18,21 @@ class DeviceForm(forms.ModelForm):
|
||||
'serial_number',
|
||||
'uuid',
|
||||
'device_type',
|
||||
'organization'
|
||||
'organization',
|
||||
'model_notes',
|
||||
]
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['_lastinventory'] = forms.DateTimeField(
|
||||
self.fields['lastinventory'] = forms.DateTimeField(
|
||||
label="Last Inventory Date",
|
||||
input_formats=settings.DATETIME_FORMAT,
|
||||
initial=kwargs['instance'].inventorydate,
|
||||
disabled=True,
|
||||
required=False,
|
||||
)
|
||||
|
||||
# for key in self.fields.keys():
|
||||
# self.fields[key].widget.attrs['disabled'] = True
|
||||
|
@ -16,6 +16,7 @@ class Update(forms.ModelForm):
|
||||
'id',
|
||||
'organization',
|
||||
'is_global',
|
||||
'model_notes',
|
||||
]
|
||||
|
||||
|
||||
|
@ -16,6 +16,7 @@ class Update(forms.ModelForm):
|
||||
'organization',
|
||||
'is_global',
|
||||
'category',
|
||||
'model_notes',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
@ -0,0 +1,63 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-11 20:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('itam', '0013_alter_device_organization_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='model_notes',
|
||||
field=models.TextField(blank=True, default=None, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='devicemodel',
|
||||
name='model_notes',
|
||||
field=models.TextField(blank=True, default=None, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='deviceoperatingsystem',
|
||||
name='model_notes',
|
||||
field=models.TextField(blank=True, default=None, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='devicesoftware',
|
||||
name='model_notes',
|
||||
field=models.TextField(blank=True, default=None, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='devicetype',
|
||||
name='model_notes',
|
||||
field=models.TextField(blank=True, default=None, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='operatingsystem',
|
||||
name='model_notes',
|
||||
field=models.TextField(blank=True, default=None, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='operatingsystemversion',
|
||||
name='model_notes',
|
||||
field=models.TextField(blank=True, default=None, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='software',
|
||||
name='model_notes',
|
||||
field=models.TextField(blank=True, default=None, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='softwarecategory',
|
||||
name='model_notes',
|
||||
field=models.TextField(blank=True, default=None, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='softwareversion',
|
||||
name='model_notes',
|
||||
field=models.TextField(blank=True, default=None, null=True),
|
||||
),
|
||||
]
|
@ -0,0 +1,34 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-17 08:56
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('itam', '0014_device_model_notes_devicemodel_model_notes_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='device',
|
||||
name='device_model',
|
||||
field=models.ForeignKey(blank=True, default=None, help_text='Model of the device.', null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.devicemodel'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='device',
|
||||
name='device_type',
|
||||
field=models.ForeignKey(blank=True, default=None, help_text='Type of device.', null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.devicetype'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='device',
|
||||
name='serial_number',
|
||||
field=models.CharField(blank=True, default=None, help_text='Serial number of the device.', max_length=50, null=True, unique=True, verbose_name='Serial Number'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='device',
|
||||
name='uuid',
|
||||
field=models.CharField(blank=True, default=None, help_text='System GUID/UUID.', max_length=50, null=True, unique=True, verbose_name='UUID'),
|
||||
),
|
||||
]
|
0
app/itam/models/__init__.py
Normal file
0
app/itam/models/__init__.py
Normal file
@ -1,12 +1,15 @@
|
||||
import json
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from django.db import models
|
||||
|
||||
from access.fields import *
|
||||
from access.models import TenancyObject
|
||||
|
||||
from core.mixin.history_save import SaveHistory
|
||||
from app.helpers.merge_software import merge_software
|
||||
|
||||
from core.mixin.history_save import SaveHistory
|
||||
|
||||
from itam.models.device_common import DeviceCommonFields, DeviceCommonFieldsName
|
||||
from itam.models.device_models import DeviceModel
|
||||
@ -43,6 +46,7 @@ class Device(DeviceCommonFieldsName, SaveHistory):
|
||||
null = True,
|
||||
blank = True,
|
||||
unique = True,
|
||||
help_text = 'Serial number of the device.',
|
||||
|
||||
)
|
||||
|
||||
@ -53,7 +57,7 @@ class Device(DeviceCommonFieldsName, SaveHistory):
|
||||
null = True,
|
||||
blank = True,
|
||||
unique = True,
|
||||
|
||||
help_text = 'System GUID/UUID.',
|
||||
)
|
||||
|
||||
device_model = models.ForeignKey(
|
||||
@ -61,7 +65,8 @@ class Device(DeviceCommonFieldsName, SaveHistory):
|
||||
on_delete=models.CASCADE,
|
||||
default = None,
|
||||
null = True,
|
||||
blank= True
|
||||
blank= True,
|
||||
help_text = 'Model of the device.',
|
||||
)
|
||||
|
||||
device_type = models.ForeignKey(
|
||||
@ -69,7 +74,8 @@ class Device(DeviceCommonFieldsName, SaveHistory):
|
||||
on_delete=models.CASCADE,
|
||||
default = None,
|
||||
null = True,
|
||||
blank= True
|
||||
blank= True,
|
||||
help_text = 'Type of device.',
|
||||
|
||||
)
|
||||
|
||||
@ -77,7 +83,7 @@ class Device(DeviceCommonFieldsName, SaveHistory):
|
||||
inventorydate = models.DateTimeField(
|
||||
verbose_name = 'Last Inventory Date',
|
||||
null = True,
|
||||
blank = True
|
||||
blank = True,
|
||||
)
|
||||
|
||||
|
||||
@ -85,6 +91,40 @@ class Device(DeviceCommonFieldsName, SaveHistory):
|
||||
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def status(self) -> str:
|
||||
""" Fetch Device status
|
||||
|
||||
Returns:
|
||||
str: Current status of the item
|
||||
"""
|
||||
|
||||
if self.inventorydate:
|
||||
|
||||
check_date = self.inventorydate
|
||||
|
||||
else:
|
||||
|
||||
check_date = now() + timedelta(days=99)
|
||||
|
||||
one = (now() - check_date).days
|
||||
|
||||
if (now() - check_date).days >= 0 and (now() - check_date).days <= 1:
|
||||
|
||||
return 'OK'
|
||||
|
||||
elif (now() - check_date).days >= 2 and (now() - check_date).days < 3:
|
||||
|
||||
return 'WARN'
|
||||
|
||||
elif (now() - check_date).days >= 3:
|
||||
|
||||
return 'BAD'
|
||||
|
||||
else:
|
||||
|
||||
return 'UNK'
|
||||
|
||||
|
||||
def get_configuration(self, id):
|
||||
|
||||
@ -94,6 +134,9 @@ class Device(DeviceCommonFieldsName, SaveHistory):
|
||||
"software": []
|
||||
}
|
||||
|
||||
host_software = []
|
||||
group_software = []
|
||||
|
||||
for software in softwares:
|
||||
|
||||
if software.action:
|
||||
@ -115,7 +158,7 @@ class Device(DeviceCommonFieldsName, SaveHistory):
|
||||
if software.version:
|
||||
software_action['version'] = software.version.name
|
||||
|
||||
config['software'] = config['software'] + [ software_action ]
|
||||
host_software += [ software_action ]
|
||||
|
||||
config: dict = config
|
||||
|
||||
@ -131,7 +174,15 @@ class Device(DeviceCommonFieldsName, SaveHistory):
|
||||
|
||||
if rendered_config:
|
||||
|
||||
config.update(json.loads(group.group.render_config()))
|
||||
config.update(json.loads(rendered_config))
|
||||
|
||||
rendered_config: dict = json.loads(rendered_config)
|
||||
|
||||
if 'software' in rendered_config.keys():
|
||||
|
||||
group_software = group_software + rendered_config['software']
|
||||
|
||||
config['software'] = merge_software(group_software, host_software)
|
||||
|
||||
return config
|
||||
|
||||
@ -200,6 +251,13 @@ class DeviceSoftware(DeviceCommonFields, SaveHistory):
|
||||
)
|
||||
|
||||
|
||||
@property
|
||||
def parent_object(self):
|
||||
""" Fetch the parent object """
|
||||
|
||||
return self.device
|
||||
|
||||
|
||||
|
||||
class DeviceOperatingSystem(DeviceCommonFields, SaveHistory):
|
||||
|
||||
@ -235,3 +293,10 @@ class DeviceOperatingSystem(DeviceCommonFields, SaveHistory):
|
||||
blank = True,
|
||||
default = None,
|
||||
)
|
||||
|
||||
|
||||
@property
|
||||
def parent_object(self):
|
||||
""" Fetch the parent object """
|
||||
|
||||
return self.device
|
||||
|
@ -69,6 +69,14 @@ class OperatingSystemVersion(OperatingSystemCommonFields, SaveHistory):
|
||||
unique = False,
|
||||
)
|
||||
|
||||
|
||||
@property
|
||||
def parent_object(self):
|
||||
""" Fetch the parent object """
|
||||
|
||||
return self.operating_system
|
||||
|
||||
|
||||
def __str__(self):
|
||||
|
||||
return self.operating_system.name + ' ' + self.name
|
||||
|
@ -102,6 +102,14 @@ class SoftwareVersion(SoftwareCommonFields, SaveHistory):
|
||||
unique = False,
|
||||
)
|
||||
|
||||
|
||||
@property
|
||||
def parent_object(self):
|
||||
""" Fetch the parent object """
|
||||
|
||||
return self.software
|
||||
|
||||
|
||||
def __str__(self):
|
||||
|
||||
return self.name
|
||||
|
@ -1,8 +1,10 @@
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% load markdown %}
|
||||
|
||||
{% block title %}{{ device.name }}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{% block content %}
|
||||
|
||||
<script>
|
||||
|
||||
@ -37,12 +39,43 @@
|
||||
<path d="m313-480 155 156q11 11 11.5 27.5T468-268q-11 11-28 11t-28-11L228-452q-6-6-8.5-13t-2.5-15q0-8 2.5-15t8.5-13l184-184q11-11 27.5-11.5T468-692q11 11 11 28t-11 28L313-480Zm264 0 155 156q11 11 11.5 27.5T732-268q-11 11-28 11t-28-11L492-452q-6-6-8.5-13t-2.5-15q0-8 2.5-15t8.5-13l184-184q11-11 27.5-11.5T732-692q11 11 11 28t-11 28L577-480Z" />
|
||||
</svg> Back to Devices</button>
|
||||
<button id="defaultOpen" class="tablinks" onclick="openCity(event, 'Details')">Details</button>
|
||||
<button id="defaultOpen" class="tablinks" onclick="openCity(event, 'OperatingSystem')">Operating System</button>
|
||||
<button class="tablinks" onclick="openCity(event, 'Software')">Software</button>
|
||||
<button class="tablinks" onclick="openCity(event, 'Notes')">Notes</button>
|
||||
<button class="tablinks" onclick="openCity(event, 'ConfigManagement')">Config Management</button>
|
||||
<!-- <button class="tablinks" onclick="openCity(event, 'Installations')">Installations</button> -->
|
||||
</div>
|
||||
<style>
|
||||
|
||||
.detail-view-field {
|
||||
display:unset;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 0px 20px 40px 20px;
|
||||
|
||||
}
|
||||
|
||||
.detail-view-field label {
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
width: 200px;
|
||||
margin: 10px;
|
||||
/*padding: 10px;*/
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
|
||||
}
|
||||
|
||||
.detail-view-field span {
|
||||
display: inline-block;
|
||||
width: 340px;
|
||||
margin: 10px;
|
||||
/*padding: 10px;*/
|
||||
border-bottom: 1px solid #ccc;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
|
||||
}
|
||||
</style>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
@ -52,9 +85,68 @@
|
||||
Details
|
||||
<span style="font-weight: normal; float: right;">{% include 'icons/issue_link.html.j2' with issue=6 %}</span>
|
||||
</h3>
|
||||
{{ form.as_p }}
|
||||
{% include 'icons/issue_link.html.j2' with issue=13 %}<br>
|
||||
<input name="{{form.prefix}}" type="submit" value="Submit">
|
||||
<div style="align-items:flex-start; align-content: center; display: flexbox; width: 100%">
|
||||
|
||||
<div style="display: inline; width: 40%; margin: 30px;">
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.name.label }}</label>
|
||||
<span>{{ form.name.value }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.device_model.label }}</label>
|
||||
<span>{{ form.device_model.value }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.serial_number.label }}</label>
|
||||
<span>{{ form.serial_number.value }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.uuid.label }}</label>
|
||||
<span>{{ form.uuid.value }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.device_type.label }}</label>
|
||||
<span>{{ device.device_type }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.organization.label }}</label>
|
||||
<span>{{ device.organization }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-view-field">
|
||||
<label>{{ form.lastinventory.label }}</label>
|
||||
<span>{{ form.lastinventory.value }}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div style="display: inline; width: 40%; margin: 30px; text-align: left;">
|
||||
<div>
|
||||
<label style="font-weight: bold; width: 100%; border-bottom: 1px solid #ccc; display: block; text-align: inherit;">{{ form.model_notes.label }}</label>
|
||||
|
||||
<div style="display: inline-block; text-align: left;">{{ form.model_notes.value | markdown | safe }}</div>
|
||||
{% include 'icons/issue_link.html.j2' with issue=13 %}<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<input type="button" value="Edit" onclick="window.location='{% url 'ITAM:_device_change' device.id %}';">
|
||||
|
||||
<div style="display: block; width: 100%;">
|
||||
<h3>Operating System</h3>
|
||||
<br>
|
||||
{{ operating_system.as_p }}
|
||||
<input type="submit" name="{{operating_system.prefix}}" value="Submit" />
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Get the element with id="defaultOpen" and click on it
|
||||
document.getElementById("defaultOpen").click();
|
||||
@ -62,11 +154,6 @@
|
||||
|
||||
</div>
|
||||
|
||||
<div id="OperatingSystem" class="tabcontent">
|
||||
<h3>Operating System</h3>
|
||||
{{ operating_system.as_p }}
|
||||
<input type="submit" name="{{operating_system.prefix}}" value="Submit" />
|
||||
</div>
|
||||
|
||||
<div id="Software" class="tabcontent">
|
||||
<h3>Software</h3>
|
||||
@ -126,6 +213,24 @@
|
||||
<td colspan="5">Nothing Found</td>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
<div class="pagination">
|
||||
<span class="step-links">
|
||||
{% if page_obj.has_previous %}
|
||||
<a href="?page=1">« first</a>
|
||||
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
|
||||
{% endif %}
|
||||
|
||||
<span class="current">
|
||||
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
|
||||
</span>
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<a href="?page={{ page_obj.next_page_number }}">next</a>
|
||||
<a href="?page={{ page_obj.paginator.num_pages }}">last »</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
@ -3,11 +3,78 @@
|
||||
{% block title %}Devices{% endblock %}
|
||||
{% block content_header_icon %}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{% block content %}
|
||||
|
||||
<style>
|
||||
|
||||
a.status_icon {
|
||||
display: inline-block;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
/*background-color: aqua;*/
|
||||
}
|
||||
|
||||
span.status_icon {
|
||||
/* display: inline-block;*/
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
/*background-color: aqua;*/
|
||||
width: auto;
|
||||
align-items: center;
|
||||
/*position: relative;*/
|
||||
vertical-align: middle; padding: auto; margin: 0px
|
||||
}
|
||||
|
||||
.status_icon_ok svg {
|
||||
fill: #319c3a;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
vertical-align: middle;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border: none;
|
||||
border-radius: 11px;
|
||||
/*background-color: #c7e2ca;*/
|
||||
}
|
||||
|
||||
.status_icon_warn svg {
|
||||
fill: #cf9925;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
vertical-align: middle;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border: none;
|
||||
border-radius: 11px;
|
||||
/*background-color: #ffefc5;*/
|
||||
}
|
||||
|
||||
.status_icon_bad svg {
|
||||
fill: #cf3025;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
vertical-align: middle;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border: none;
|
||||
border-radius: 11px;
|
||||
/*background-color: #ffefc5;*/
|
||||
}
|
||||
|
||||
.status_icon_ukn svg {
|
||||
fill: #999;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border: none;
|
||||
border-radius: 11px;
|
||||
/*background-color: #e9e9e9;*/
|
||||
}
|
||||
|
||||
</style>
|
||||
<input type="button" value="New Device" onclick="window.location='{% url 'ITAM:_device_add' %}';">
|
||||
<table class="data">
|
||||
<tr>
|
||||
<th style="width: 50px;"> </th>
|
||||
<th>Name</th>
|
||||
<th>Device Type</th>
|
||||
<th>Manufacturer</th>
|
||||
@ -17,7 +84,29 @@
|
||||
</tr>
|
||||
{% for device in devices %}
|
||||
<tr>
|
||||
<td><a href="{% url 'ITAM:_device_view' pk=device.id %}">{{ device.name }}</a></td>
|
||||
<td>
|
||||
<span class="status_icon status_icon_ok">
|
||||
{% if device.status == 'OK' %}
|
||||
<span class="status_icon status_icon_ok">
|
||||
{% include 'icons/status_ok.svg' %}
|
||||
</span>
|
||||
{% elif device.status == 'WARN' %}
|
||||
<span class="status_icon status_icon_warn">
|
||||
{% include 'icons/status_ok.svg' %}
|
||||
</span>
|
||||
{% elif device.status == 'BAD' %}
|
||||
<span class="status_icon status_icon_bad">
|
||||
{% include 'icons/status_bad.svg' %}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="status_icon status_icon_ukn">
|
||||
{% include 'icons/status_unknown.svg' %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'ITAM:_device_view' pk=device.id %}">{{ device.name }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if device.device_type %}
|
||||
{{ device.device_type }}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user