Merge branch 'feat-2024-06-02' into 'development'
feat: 2024 06 02 Closes #42 and #45 See merge request nofusscomputing/projects/django_template!17
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User, Group, Permission
|
||||
from django.forms import ValidationError
|
||||
|
||||
from .fields import *
|
||||
|
||||
@ -52,11 +53,19 @@ class TenancyObject(models.Model):
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
def validatate_organization_exists(self):
|
||||
|
||||
if not self:
|
||||
raise ValidationError('You must provide an organization')
|
||||
|
||||
|
||||
organization = models.ForeignKey(
|
||||
Organization,
|
||||
on_delete=models.CASCADE,
|
||||
blank = False,
|
||||
null = True,
|
||||
validators = [validatate_organization_exists],
|
||||
)
|
||||
|
||||
is_global = models.BooleanField(
|
||||
|
@ -1,46 +1,234 @@
|
||||
|
||||
from django.test import TestCase, Client
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
from django.test import TestCase, Client
|
||||
|
||||
from access.models import Organization
|
||||
|
||||
from core.models.history import History
|
||||
|
||||
from access.models import Organization
|
||||
|
||||
|
||||
# @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):
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_organization_create():
|
||||
""" History row must be added to history table on create """
|
||||
pass
|
||||
model = Organization
|
||||
|
||||
model_name = 'organization'
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_organization_update():
|
||||
""" History row must be added to history table on updatej """
|
||||
pass
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
""" Setup Test """
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.item_create = self.model.objects.create(
|
||||
name = 'test_item_' + self.model_name,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_organization_delete():
|
||||
""" History row must be added to history table on delete """
|
||||
pass
|
||||
self.history_create = History.objects.get(
|
||||
action = History.Actions.ADD[0],
|
||||
item_pk = self.item_create.pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
||||
self.item_change = self.item_create
|
||||
self.item_change.name = 'test_item_' + self.model_name + '_changed'
|
||||
self.item_change.save()
|
||||
|
||||
self.history_change = History.objects.get(
|
||||
action = History.Actions.UPDATE[0],
|
||||
item_pk = self.item_change.pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_organization_team_create():
|
||||
""" History row must be added to history table on create """
|
||||
pass
|
||||
# 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_organization_team_update():
|
||||
""" History row must be added to history table on updatej """
|
||||
pass
|
||||
@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('{"name": "test_item_' + self.model_name + '_changed"}')
|
||||
# assert type(history['after']) is str
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_entry_item_change_field_before(self):
|
||||
""" Ensure before field is an empty JSON string for create """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['before'] == str('{}')
|
||||
# assert type(history['before']) is str
|
||||
|
||||
|
||||
def test_history_entry_item_change_field_item_pk(self):
|
||||
""" Ensure history entry field item_pk is the created items pk """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['item_pk'] == self.item_create.pk
|
||||
# assert type(history['item_pk']) is int
|
||||
|
||||
|
||||
def test_history_entry_item_change_field_item_class(self):
|
||||
""" Ensure history entry field item_class is the model name """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['item_class'] == self.model._meta.model_name
|
||||
# assert type(history['item_class']) is str
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_organization_team_delete():
|
||||
""" History row must be added to history table on delete """
|
||||
pass
|
@ -1,43 +1,243 @@
|
||||
|
||||
from django.test import TestCase, Client
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
from django.test import TestCase, Client
|
||||
|
||||
from access.models import Organization
|
||||
|
||||
from core.models.history import History
|
||||
|
||||
from access.models import Team
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_team_create():
|
||||
""" History row must be added to history table on create """
|
||||
pass
|
||||
# @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_team_update():
|
||||
""" History row must be added to history table on updatej """
|
||||
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_team_delete():
|
||||
""" History row must be added to history table on delete """
|
||||
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_team_users_create():
|
||||
""" History row must be added to history table on create """
|
||||
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_team_users_update():
|
||||
""" History row must be added to history table on updatej """
|
||||
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):
|
||||
""" Setup Test """
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.item_create = self.model.objects.create(
|
||||
name = 'test_item_' + self.model_name,
|
||||
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_name + '_changed'
|
||||
self.item_change.save()
|
||||
|
||||
self.history_change = History.objects.get(
|
||||
action = History.Actions.UPDATE[0],
|
||||
item_pk = self.item_change.pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
||||
|
||||
@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 """
|
||||
|
||||
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
|
||||
|
||||
|
||||
@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
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_team_users_delete():
|
||||
""" History row must be added to history table on delete """
|
||||
pass
|
||||
|
@ -1,24 +1,243 @@
|
||||
|
||||
from django.test import TestCase, Client
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
from django.test import TestCase, Client
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_team_users_create():
|
||||
""" History row must be added to history table on create """
|
||||
pass
|
||||
from access.models import Organization
|
||||
|
||||
from core.models.history import History
|
||||
|
||||
from access.models import TeamUsers
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_team_users_update():
|
||||
""" History row must be added to history table on updatej """
|
||||
pass
|
||||
# @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
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to do")
|
||||
class TeamUsersHistory(TestCase):
|
||||
|
||||
model = TeamUsers
|
||||
|
||||
model_name = 'teamusers'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
""" Setup Test """
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.item_create = self.model.objects.create(
|
||||
name = 'test_item_' + self.model_name,
|
||||
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_name + '_changed'
|
||||
self.item_change.save()
|
||||
|
||||
self.history_change = History.objects.get(
|
||||
action = History.Actions.UPDATE[0],
|
||||
item_pk = self.item_change.pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
||||
|
||||
@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 """
|
||||
|
||||
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
|
||||
|
||||
|
||||
@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
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_team_users_delete():
|
||||
""" History row must be added to history table on delete """
|
||||
pass
|
||||
|
@ -27,7 +27,6 @@ class DeviceSerializer(serializers.ModelSerializer):
|
||||
read_only_fields = [
|
||||
'inventorydate',
|
||||
'is_global',
|
||||
'organization',
|
||||
'slug',
|
||||
]
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
from django.urls import path
|
||||
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from rest_framework.urlpatterns import format_suffix_patterns
|
||||
|
||||
@ -21,6 +22,9 @@ router.register('software', software.SoftwareViewSet, basename='software')
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path("config/<slug:slug>/", itam_config.View.as_view(), name="_api_device_config"),
|
||||
|
||||
path("device/inventory/<slug:slug>", inventory.Collect.as_view(), name="_api_device_inventory"),
|
||||
|
||||
path("organization/", access.OrganizationList.as_view(), name='_api_orgs'),
|
||||
path("organization/<int:pk>/", access.OrganizationDetail.as_view(), name='_api_organization'),
|
||||
@ -29,11 +33,6 @@ urlpatterns = [
|
||||
path("organization/<int:organization_id>/team/<int:group_ptr_id>/permissions", access.TeamPermissionDetail.as_view(), name='_api_team_permission'),
|
||||
path("organization/team/", access.TeamList.as_view(), name='_api_teams'),
|
||||
|
||||
|
||||
path("config/<slug:slug>/", itam_config.View.as_view(), name="_api_device_config"),
|
||||
|
||||
path("device/inventory/<slug:slug>", inventory.Collect.as_view(), name="_api_device_inventory"),
|
||||
|
||||
]
|
||||
|
||||
urlpatterns = format_suffix_patterns(urlpatterns)
|
||||
|
@ -1,15 +1,20 @@
|
||||
# from django.contrib.auth.mixins import PermissionRequiredMixin, LoginRequiredMixin
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from drf_spectacular.utils import extend_schema
|
||||
|
||||
from rest_framework import generics, viewsets
|
||||
|
||||
from itam.models.device import Device
|
||||
from access.mixin import OrganizationMixin
|
||||
|
||||
from api.serializers.itam.device import DeviceSerializer
|
||||
from api.views.mixin import OrganizationPermissionAPI
|
||||
|
||||
from itam.models.device import Device
|
||||
|
||||
class DeviceViewSet(viewsets.ModelViewSet):
|
||||
|
||||
|
||||
class DeviceViewSet(OrganizationMixin, viewsets.ModelViewSet):
|
||||
|
||||
permission_classes = [
|
||||
OrganizationPermissionAPI
|
||||
@ -19,5 +24,32 @@ class DeviceViewSet(viewsets.ModelViewSet):
|
||||
|
||||
serializer_class = DeviceSerializer
|
||||
|
||||
|
||||
@extend_schema( description='Fetch devices that are from the users assigned organization(s)', methods=["GET"])
|
||||
def list(self, request):
|
||||
|
||||
return super().list(request)
|
||||
|
||||
|
||||
@extend_schema( description='Fetch the selected device', methods=["GET"])
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
|
||||
return super().retrieve(request, *args, **kwargs)
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
if self.request.user.is_superuser:
|
||||
|
||||
return self.queryset.filter().order_by('name')
|
||||
|
||||
else:
|
||||
|
||||
return self.queryset.filter(Q(organization__in=self.user_organizations()) | Q(is_global = True)).order_by('name')
|
||||
|
||||
|
||||
def get_view_name(self):
|
||||
return "Device"
|
||||
if self.detail:
|
||||
return "Device"
|
||||
|
||||
return 'Devices'
|
||||
|
@ -1,12 +1,13 @@
|
||||
# from django.contrib.auth.mixins import PermissionRequiredMixin, LoginRequiredMixin
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework import generics, viewsets
|
||||
|
||||
from itam.models.software import Software
|
||||
from rest_framework import generics, viewsets
|
||||
|
||||
from api.serializers.itam.software import SoftwareSerializer
|
||||
from api.views.mixin import OrganizationPermissionAPI
|
||||
|
||||
from itam.models.software import Software
|
||||
|
||||
|
||||
|
||||
class SoftwareViewSet(viewsets.ModelViewSet):
|
||||
@ -26,7 +27,14 @@ class SoftwareViewSet(viewsets.ModelViewSet):
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
return Software.objects.all()
|
||||
|
||||
if self.request.user.is_superuser:
|
||||
|
||||
return self.queryset.filter().order_by('name')
|
||||
|
||||
else:
|
||||
|
||||
return self.queryset.filter(Q(organization__in=self.user_organizations()) | Q(is_global = True)).order_by('name')
|
||||
|
||||
|
||||
def get_view_name(self):
|
||||
|
@ -1,4 +1,6 @@
|
||||
|
||||
from django.forms import ValidationError
|
||||
|
||||
from rest_framework.permissions import DjangoObjectPermissions
|
||||
|
||||
from access.mixin import OrganizationMixin
|
||||
@ -44,6 +46,9 @@ class OrganizationPermissionAPI(DjangoObjectPermissions, OrganizationMixin):
|
||||
|
||||
if 'organization' in request.data:
|
||||
|
||||
if not request.data['organization']:
|
||||
raise ValidationError('you must provide an organization')
|
||||
|
||||
object_organization = int(request.data['organization'])
|
||||
|
||||
elif method == 'patch':
|
||||
|
@ -73,6 +73,7 @@ def nav_items(context) -> list(dict()):
|
||||
ignored_apps = [
|
||||
'admin',
|
||||
'djdt', # Debug application
|
||||
'api',
|
||||
]
|
||||
|
||||
nav_items = []
|
||||
@ -87,7 +88,7 @@ def nav_items(context) -> list(dict()):
|
||||
isinstance(nav_group, URLResolver)
|
||||
):
|
||||
|
||||
if nav_group.app_name is not None and nav_group.app_name not in ignored_apps:
|
||||
if nav_group.app_name is not None and str(nav_group.app_name).lower() not in ignored_apps:
|
||||
|
||||
group_name = str(nav_group.app_name)
|
||||
|
||||
|
@ -53,6 +53,9 @@ INSTALLED_APPS = [
|
||||
'access.apps.AccessConfig',
|
||||
'itam.apps.ItamConfig',
|
||||
'settings.apps.SettingsConfig',
|
||||
'drf_spectacular',
|
||||
'drf_spectacular_sidecar',
|
||||
'config_management.apps.ConfigManagementConfig',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
@ -201,7 +204,19 @@ if API_ENABLED:
|
||||
# 'rest_framework_json_api.renderers.JSONRenderer',
|
||||
# ),
|
||||
# 'TEST_REQUEST_DEFAULT_FORMAT': 'vnd.api+json'
|
||||
'TEST_REQUEST_DEFAULT_FORMAT': 'json'
|
||||
'TEST_REQUEST_DEFAULT_FORMAT': 'json',
|
||||
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
|
||||
}
|
||||
|
||||
SPECTACULAR_SETTINGS = {
|
||||
'TITLE': 'Your Project API',
|
||||
'DESCRIPTION': 'Your project description',
|
||||
'VERSION': '1.0.0',
|
||||
'SERVE_INCLUDE_SCHEMA': False,
|
||||
|
||||
'SWAGGER_UI_DIST': 'SIDECAR',
|
||||
'SWAGGER_UI_FAVICON_HREF': 'SIDECAR',
|
||||
'REDOC_DIST': 'SIDECAR',
|
||||
}
|
||||
|
||||
DATETIME_FORMAT = 'j N Y H:i:s'
|
||||
@ -228,6 +243,5 @@ if DEBUG:
|
||||
# Apps Under Development
|
||||
INSTALLED_APPS += [
|
||||
'information.apps.InformationConfig',
|
||||
'config_management.apps.ConfigManagementConfig',
|
||||
'project_management.apps.ProjectManagementConfig',
|
||||
]
|
||||
|
@ -20,6 +20,8 @@ from django.contrib.auth import views as auth_views
|
||||
from django.views.static import serve
|
||||
from django.urls import include, path, re_path
|
||||
|
||||
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
|
||||
|
||||
from .views import home
|
||||
|
||||
from core.views import history
|
||||
@ -38,6 +40,8 @@ urlpatterns = [
|
||||
|
||||
path("organization/", include("access.urls")),
|
||||
path("itam/", include("itam.urls")),
|
||||
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})
|
||||
]
|
||||
@ -46,6 +50,8 @@ if settings.API_ENABLED:
|
||||
urlpatterns += [
|
||||
|
||||
path("api/", include("api.urls")),
|
||||
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
|
||||
path('api/swagger/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
@ -55,7 +61,6 @@ if settings.DEBUG:
|
||||
path("__debug__/", include("debug_toolbar.urls"), name='_debug'),
|
||||
# Apps Under Development
|
||||
path("information/", include("information.urls")),
|
||||
path("config_management/", include("config_management.urls")),
|
||||
path("project_management/", include("project_management.urls")),
|
||||
]
|
||||
|
||||
|
20
app/config_management/forms/group_hosts.py
Normal file
20
app/config_management/forms/group_hosts.py
Normal file
@ -0,0 +1,20 @@
|
||||
from django import forms
|
||||
|
||||
from itam.models.device import Device
|
||||
|
||||
from config_management.models.groups import ConfigGroups, ConfigGroupHosts
|
||||
|
||||
|
||||
class ConfigGroupHostsForm(forms.ModelForm):
|
||||
|
||||
__name__ = 'asdsa'
|
||||
|
||||
class Meta:
|
||||
|
||||
fields = [
|
||||
'host'
|
||||
]
|
||||
|
||||
model = ConfigGroupHosts
|
||||
|
||||
prefix = 'config_group_hosts'
|
34
app/config_management/migrations/0001_initial.py
Normal file
34
app/config_management/migrations/0001_initial.py
Normal file
@ -0,0 +1,34 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-02 14:48
|
||||
|
||||
import access.fields
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('access', '0002_alter_team_organization'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ConfigGroups',
|
||||
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)),
|
||||
('name', models.CharField(max_length=50)),
|
||||
('config', models.JSONField(blank=True, default=None, null=True)),
|
||||
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization')),
|
||||
('parent', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='config_management.configgroups')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Config Groups',
|
||||
},
|
||||
),
|
||||
]
|
@ -0,0 +1,43 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-02 20:51
|
||||
|
||||
import access.fields
|
||||
import config_management.models.groups
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0002_alter_team_organization'),
|
||||
('config_management', '0001_initial'),
|
||||
('itam', '0012_alter_device_serial_number_alter_device_uuid'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='configgroups',
|
||||
options={},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='configgroups',
|
||||
name='config',
|
||||
field=models.JSONField(blank=True, default=None, null=True, validators=[config_management.models.groups.ConfigGroups.validate_config_keys]),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ConfigGroupHosts',
|
||||
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)),
|
||||
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='config_management.configgroups')),
|
||||
('host', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='itam.device', validators=[config_management.models.groups.ConfigGroupHosts.validate_host_no_parent_group])),
|
||||
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
@ -1 +0,0 @@
|
||||
from django.db import models
|
154
app/config_management/models/groups.py
Normal file
154
app/config_management/models/groups.py
Normal file
@ -0,0 +1,154 @@
|
||||
import json
|
||||
|
||||
from django.db import models
|
||||
from django.forms import ValidationError
|
||||
|
||||
from access.fields import *
|
||||
from access.models import TenancyObject
|
||||
|
||||
from core.mixin.history_save import SaveHistory
|
||||
|
||||
from itam.models.device import Device
|
||||
|
||||
|
||||
|
||||
class GroupsCommonFields(TenancyObject, models.Model):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
id = models.AutoField(
|
||||
primary_key=True,
|
||||
unique=True,
|
||||
blank=False
|
||||
)
|
||||
|
||||
created = AutoCreatedField()
|
||||
|
||||
modified = AutoLastModifiedField()
|
||||
|
||||
|
||||
|
||||
class ConfigGroups(GroupsCommonFields, SaveHistory):
|
||||
|
||||
reserved_config_keys: list = [
|
||||
'software'
|
||||
]
|
||||
|
||||
|
||||
def validate_config_keys(self):
|
||||
|
||||
value: dict = self
|
||||
|
||||
for invalid_key in ConfigGroups.reserved_config_keys:
|
||||
|
||||
if invalid_key in value.keys():
|
||||
raise ValidationError(f'json key "{invalid_key}" is a reserved configuration key')
|
||||
|
||||
|
||||
parent = models.ForeignKey(
|
||||
'self',
|
||||
on_delete=models.CASCADE,
|
||||
default = None,
|
||||
null = True,
|
||||
blank= True
|
||||
)
|
||||
|
||||
|
||||
name = models.CharField(
|
||||
blank = False,
|
||||
max_length = 50,
|
||||
unique = False,
|
||||
)
|
||||
|
||||
|
||||
config = models.JSONField(
|
||||
blank = True,
|
||||
default = None,
|
||||
null = True,
|
||||
validators=[validate_config_keys]
|
||||
)
|
||||
|
||||
|
||||
|
||||
def count_children(self) -> int:
|
||||
""" Count all child groups recursively
|
||||
|
||||
Returns:
|
||||
int: Total count of ALL child-groups
|
||||
"""
|
||||
|
||||
count = 0
|
||||
|
||||
children = ConfigGroups.objects.filter(parent=self.pk)
|
||||
|
||||
for child in children.all():
|
||||
|
||||
count += 1
|
||||
|
||||
count += child.count_children()
|
||||
|
||||
return count
|
||||
|
||||
|
||||
|
||||
def render_config(self) -> str:
|
||||
|
||||
config: dict = dict()
|
||||
|
||||
if self.parent:
|
||||
|
||||
config.update(json.loads(ConfigGroups.objects.get(id=self.parent.id).render_config()))
|
||||
|
||||
if self.config:
|
||||
|
||||
config.update(self.config)
|
||||
|
||||
return json.dumps(config)
|
||||
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
self.is_global = False
|
||||
|
||||
if self.parent:
|
||||
self.organization = ConfigGroups.objects.get(id=self.parent.id).organization
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
|
||||
return self.name
|
||||
|
||||
|
||||
|
||||
class ConfigGroupHosts(GroupsCommonFields, SaveHistory):
|
||||
|
||||
|
||||
def validate_host_no_parent_group(self):
|
||||
""" Ensure that the host is not within any parent group
|
||||
|
||||
Raises:
|
||||
ValidationError: host exists within group chain
|
||||
"""
|
||||
|
||||
if False:
|
||||
raise ValidationError(f'host {self} is already a member of this chain as it;s a member of group ""')
|
||||
|
||||
|
||||
host = models.ForeignKey(
|
||||
Device,
|
||||
on_delete=models.CASCADE,
|
||||
null = False,
|
||||
blank= False,
|
||||
validators = [ validate_host_no_parent_group ]
|
||||
)
|
||||
|
||||
|
||||
group = models.ForeignKey(
|
||||
ConfigGroups,
|
||||
on_delete=models.CASCADE,
|
||||
null = False,
|
||||
blank= False
|
||||
)
|
150
app/config_management/templates/config_management/group.html.j2
Normal file
150
app/config_management/templates/config_management/group.html.j2
Normal file
@ -0,0 +1,150 @@
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
<script>
|
||||
|
||||
function openCity(evt, cityName) {
|
||||
var i, tabcontent, tablinks;
|
||||
|
||||
tabcontent = document.getElementsByClassName("tabcontent");
|
||||
for (i = 0; i < tabcontent.length; i++) {
|
||||
tabcontent[i].style.display = "none";
|
||||
}
|
||||
|
||||
tablinks = document.getElementsByClassName("tablinks");
|
||||
for (i = 0; i < tablinks.length; i++) {
|
||||
tablinks[i].className = tablinks[i].className.replace(" active", "");
|
||||
}
|
||||
|
||||
document.getElementById(cityName).style.display = "block";
|
||||
evt.currentTarget.className += " active";
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="tab">
|
||||
<button
|
||||
onclick="window.location='{% if group.parent %}{% url 'Config Management:_group_view' pk=group.parent.id %}{% else %}{% url 'Config Management:Groups' %}{% endif %}';"
|
||||
style="vertical-align: middle; padding: auto; margin: 0px">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="25px" viewBox="0 -960 960 960" width="25px"
|
||||
style="vertical-align: middle; margin: 0px; padding: 0px border: none; " fill="#6a6e73">
|
||||
<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 {% if group.parent %}Parent{% else %}Groups{% endif %}</button>
|
||||
|
||||
<button id="defaultOpen" class="tablinks" onclick="openCity(event, 'Details')">Details</button>
|
||||
<button id="defaultOpen" class="tablinks" onclick="openCity(event, 'Children')">Child Groups</button>
|
||||
<button id="defaultOpen" class="tablinks" onclick="openCity(event, 'Hosts')">Hosts</button>
|
||||
<button id="defaultOpen" class="tablinks" onclick="openCity(event, 'Software')">Software</button>
|
||||
<button id="defaultOpen" class="tablinks" onclick="openCity(event, 'Configuration')">Configuration</button>
|
||||
<button class="tablinks" onclick="openCity(event, 'Notes')">Notes</button>
|
||||
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
<div id="Details" class="tabcontent">
|
||||
<h3>Details</h3>
|
||||
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
{% include 'icons/issue_link.html.j2' with issue=13 %}<br>
|
||||
<input type="submit" value="Submit">
|
||||
|
||||
<script>
|
||||
document.getElementById("defaultOpen").click();
|
||||
</script>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="Children" class="tabcontent">
|
||||
<h3>Child Groups</h3>
|
||||
|
||||
<input type="button" value="Add Child Group" onclick="window.location='{% url 'Config Management:_group_add_child' group.id %}';">
|
||||
|
||||
<table class="data">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Sub-Groups</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
{% if child_groups %}
|
||||
{% for group in child_groups %}
|
||||
<tr>
|
||||
<td><a href="{% url 'Config Management:_group_view' pk=group.id %}">{{ group.name }}</a></td>
|
||||
<td>{{ group.count_children }}</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="4">Nothing Found</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="Hosts" class="tabcontent">
|
||||
<h3>
|
||||
Hosts
|
||||
</h3>
|
||||
|
||||
<input type="button" value="Add Host" onclick="window.location='{% url 'Config Management:_group_add_host' group.id %}';">
|
||||
|
||||
<table class="data">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Organization</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
{% if config_group_hosts %}
|
||||
{% for host in config_group_hosts %}
|
||||
<tr>
|
||||
<td><a href="{% url 'ITAM:_device_view' pk=host.host.id %}">{{ host.host }}</a></td>
|
||||
<td>{{ host.host.organization }}</td>
|
||||
<td><a href="{% url 'Config Management:_group_delete_host' group_id=group.id pk=host.id %}">Delete</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="3">Nothing Found</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="Software" class="tabcontent">
|
||||
<h3>
|
||||
Software
|
||||
</h3>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="Configuration" class="tabcontent">
|
||||
<h3>Configuration</h3>
|
||||
<div>
|
||||
<textarea cols="90" rows="30" readonly>{{ config }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="Notes" class="tabcontent">
|
||||
<h3>
|
||||
Notes
|
||||
</h3>
|
||||
{{ notes_form }}
|
||||
<input type="submit" name="{{notes_form.prefix}}" value="Submit" />
|
||||
<div class="comments">
|
||||
{% if notes %}
|
||||
{% for note in notes %}
|
||||
{% include 'note.html.j2' %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,47 @@
|
||||
{% extends 'base.html.j2' %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
<input type="button" value="New Group" onclick="window.location='{% url 'Config Management:_group_add' %}';">
|
||||
<table class="data">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Organization</th>
|
||||
<th>Sub-Groups</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
{% if groups %}
|
||||
{% for group in groups %}
|
||||
<tr>
|
||||
<td><a href="{% url 'Config Management:_group_view' pk=group.id %}">{{ group.name }}</a></td>
|
||||
<td>{{ group.organization }}</td>
|
||||
<td>{{ group.count_children }}</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="4">Nothing Found</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
<br>
|
||||
<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>
|
||||
|
||||
{% endblock %}
|
@ -0,0 +1,235 @@
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
from django.test import TestCase, Client
|
||||
|
||||
from access.models import Organization
|
||||
|
||||
from core.models.history import History
|
||||
|
||||
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
|
||||
|
||||
|
||||
# @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):
|
||||
""" Setup Test """
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.item_create = self.model.objects.create(
|
||||
name = 'test_item_' + self.model_name,
|
||||
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_name + '_changed'
|
||||
self.item_change.save()
|
||||
|
||||
self.history_change = History.objects.get(
|
||||
action = History.Actions.UPDATE[0],
|
||||
item_pk = self.item_change.pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
||||
|
||||
# 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 """
|
||||
|
||||
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
|
||||
|
||||
|
@ -0,0 +1,511 @@
|
||||
# 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 config_management.models.groups import ConfigGroups
|
||||
|
||||
|
||||
class ConfigGroupPermissions(TestCase):
|
||||
|
||||
model = ConfigGroups
|
||||
|
||||
model_name = 'configgroups'
|
||||
app_label = 'config_management'
|
||||
|
||||
@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
|
||||
|
||||
different_organization = Organization.objects.create(name='test_different_organization')
|
||||
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization=organization,
|
||||
name = 'deviceone'
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
|
||||
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')
|
@ -1,9 +1,17 @@
|
||||
from django.urls import path
|
||||
|
||||
from .views import ConfigIndex
|
||||
from config_management.views.groups import GroupIndexView, GroupAdd, GroupDelete, GroupView, GroupHostAdd, GroupHostDelete
|
||||
|
||||
app_name = "Config Management"
|
||||
|
||||
urlpatterns = [
|
||||
path('', ConfigIndex.as_view(), name='Config Management'),
|
||||
|
||||
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: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'),
|
||||
|
||||
]
|
||||
|
@ -1,18 +0,0 @@
|
||||
from django.shortcuts import render
|
||||
from django.views import generic
|
||||
|
||||
|
||||
class ConfigIndex(generic.View):
|
||||
|
||||
permission_required = 'itam.view_device'
|
||||
|
||||
template_name = 'form.html.j2'
|
||||
|
||||
|
||||
def get(self, request):
|
||||
|
||||
context = {}
|
||||
|
||||
context['content_title'] = 'Config Management'
|
||||
|
||||
return render(request, self.template_name, context)
|
289
app/config_management/views/groups.py
Normal file
289
app/config_management/views/groups.py
Normal file
@ -0,0 +1,289 @@
|
||||
import json
|
||||
|
||||
from django.contrib.auth import decorators as auth_decorator
|
||||
from django.db.models import Count, Q
|
||||
from django.urls import reverse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import generic
|
||||
|
||||
from access.mixin import OrganizationPermission
|
||||
|
||||
from core.forms.comment import AddNoteForm
|
||||
from core.models.notes import Notes
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
class GroupIndexView(OrganizationPermission, generic.ListView):
|
||||
|
||||
context_object_name = "groups"
|
||||
|
||||
model = ConfigGroups
|
||||
|
||||
paginate_by = 10
|
||||
|
||||
permission_required = 'config_management.view_configgroups'
|
||||
|
||||
template_name = 'config_management/group_index.html.j2'
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['content_title'] = 'Config Groups'
|
||||
|
||||
return context
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
if self.request.user.is_superuser:
|
||||
|
||||
return self.model.objects.filter(parent=None).order_by('name')
|
||||
|
||||
else:
|
||||
|
||||
return self.model.objects.filter(Q(parent=None, organization__in=self.user_organizations()) | Q(parent=None, is_global = True)).order_by('name')
|
||||
|
||||
|
||||
|
||||
class GroupAdd(OrganizationPermission, generic.CreateView):
|
||||
|
||||
fields = [
|
||||
'name',
|
||||
'parent',
|
||||
'organization',
|
||||
]
|
||||
|
||||
model = ConfigGroups
|
||||
|
||||
permission_required = [
|
||||
'config_management.add_configgroups',
|
||||
]
|
||||
|
||||
template_name = 'form.html.j2'
|
||||
|
||||
|
||||
def get_initial(self):
|
||||
|
||||
initial: dict = {
|
||||
'organization': UserSettings.objects.get(user = self.request.user).default_organization
|
||||
}
|
||||
|
||||
if 'group_id' in self.kwargs:
|
||||
|
||||
if self.kwargs['group_id']:
|
||||
|
||||
initial.update({'parent': self.kwargs['group_id']})
|
||||
|
||||
self.model.parent.field.hidden = True
|
||||
|
||||
return initial
|
||||
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
if self.kwargs['group_id']:
|
||||
|
||||
return reverse('Config Management:_group_view', args=(self.kwargs['group_id'],))
|
||||
|
||||
return reverse('Config Management:Groups')
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['content_title'] = 'New Group'
|
||||
|
||||
return context
|
||||
|
||||
|
||||
|
||||
class GroupView(OrganizationPermission, generic.UpdateView):
|
||||
|
||||
context_object_name = "group"
|
||||
|
||||
model = ConfigGroups
|
||||
|
||||
permission_required = [
|
||||
'config_management.view_configgroups',
|
||||
'config_management.change_configgroups',
|
||||
]
|
||||
|
||||
template_name = 'config_management/group.html.j2'
|
||||
|
||||
fields = [
|
||||
'name',
|
||||
'parent',
|
||||
'config',
|
||||
]
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['child_groups'] = ConfigGroups.objects.filter(parent=self.kwargs['pk'])
|
||||
|
||||
context['config'] = json.dumps(json.loads(self.object.render_config()), indent=4, sort_keys=True)
|
||||
|
||||
context['config_group_hosts'] = ConfigGroupHosts.objects.filter(group_id = self.kwargs['pk']).order_by('-host')
|
||||
|
||||
context['notes_form'] = AddNoteForm(prefix='note')
|
||||
context['notes'] = Notes.objects.filter(config_group=self.kwargs['pk'])
|
||||
|
||||
context['model_pk'] = self.kwargs['pk']
|
||||
context['model_name'] = self.model._meta.verbose_name.replace(' ', '')
|
||||
|
||||
context['model_delete_url'] = reverse('Config Management:_group_delete', args=(self.kwargs['pk'],))
|
||||
|
||||
context['content_title'] = self.object.name
|
||||
|
||||
# if self.request.user.is_superuser:
|
||||
|
||||
# context['device_software'] = DeviceSoftware.objects.filter(
|
||||
# software=self.kwargs['pk']
|
||||
# ).order_by(
|
||||
# 'device',
|
||||
# 'organization'
|
||||
# )
|
||||
|
||||
# elif not self.request.user.is_superuser:
|
||||
# context['device_software'] = DeviceSoftware.objects.filter(
|
||||
# Q(device__in=self.user_organizations(),
|
||||
# software=self.kwargs['pk'])
|
||||
# ).order_by(
|
||||
# 'device',
|
||||
# 'organization'
|
||||
# )
|
||||
|
||||
return context
|
||||
|
||||
|
||||
@method_decorator(auth_decorator.permission_required("config_management.change_configgroups", raise_exception=True))
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
||||
item = ConfigGroups.objects.get(pk=self.kwargs['pk'])
|
||||
|
||||
notes = AddNoteForm(request.POST, prefix='note')
|
||||
|
||||
if notes.is_bound and notes.is_valid() and notes.instance.note != '':
|
||||
|
||||
notes.instance.organization = item.organization
|
||||
notes.instance.config_group = item
|
||||
notes.instance.usercreated = request.user
|
||||
|
||||
notes.save()
|
||||
|
||||
return super().post(request, *args, **kwargs)
|
||||
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
return reverse('Config Management:_group_view', args=(self.kwargs['pk'],))
|
||||
|
||||
|
||||
|
||||
class GroupDelete(OrganizationPermission, generic.DeleteView):
|
||||
|
||||
model = ConfigGroups
|
||||
|
||||
permission_required = [
|
||||
'config_management.delete_configgroups',
|
||||
]
|
||||
|
||||
template_name = 'form.html.j2'
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['content_title'] = 'Delete ' + self.object.name
|
||||
|
||||
return context
|
||||
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
return reverse('Config Management:Groups')
|
||||
|
||||
|
||||
|
||||
class GroupHostAdd(OrganizationPermission, generic.CreateView):
|
||||
|
||||
model = ConfigGroupHosts
|
||||
|
||||
permission_required = [
|
||||
'config_management.add_hosts',
|
||||
]
|
||||
|
||||
template_name = 'form.html.j2'
|
||||
|
||||
form_class = ConfigGroupHostsForm
|
||||
|
||||
|
||||
def form_valid(self, form):
|
||||
|
||||
form.instance.group_id = self.kwargs['group_id']
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['content_title'] = 'Add Host to Group'
|
||||
|
||||
return context
|
||||
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
|
||||
form_class = super().get_form(form_class=None)
|
||||
|
||||
group = ConfigGroups.objects.get(pk=self.kwargs['group_id'])
|
||||
|
||||
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))
|
||||
|
||||
return form_class
|
||||
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
return reverse('Config Management:_group_view', args=[self.kwargs['group_id'],])
|
||||
|
||||
|
||||
|
||||
class GroupHostDelete(OrganizationPermission, generic.DeleteView):
|
||||
|
||||
model = ConfigGroupHosts
|
||||
|
||||
permission_required = [
|
||||
'config_management.delete_hosts',
|
||||
]
|
||||
|
||||
template_name = 'form.html.j2'
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['content_title'] = 'Delete ' + self.object.host.name
|
||||
|
||||
return context
|
||||
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
return reverse('Config Management:_group_view', args=[self.kwargs['group_id'],])
|
20
app/core/migrations/0007_notes_config_group.py
Normal file
20
app/core/migrations/0007_notes_config_group.py
Normal file
@ -0,0 +1,20 @@
|
||||
# Generated by Django 5.0.6 on 2024-06-02 14:48
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('config_management', '0001_initial'),
|
||||
('core', '0006_alter_history_user'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='notes',
|
||||
name='config_group',
|
||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='config_management.configgroups'),
|
||||
),
|
||||
]
|
@ -125,6 +125,11 @@ class SaveHistory(models.Model):
|
||||
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
|
||||
|
||||
|
||||
if not before:
|
||||
|
||||
|
@ -4,6 +4,8 @@ from django.db import models
|
||||
from access.fields import *
|
||||
from access.models import TenancyObject
|
||||
|
||||
from config_management.models.groups import ConfigGroups
|
||||
|
||||
from itam.models.device import Device
|
||||
from itam.models.software import Software
|
||||
from itam.models.operating_system import OperatingSystem
|
||||
@ -70,6 +72,14 @@ class Notes(NotesCommonFields):
|
||||
blank= True
|
||||
)
|
||||
|
||||
config_group = models.ForeignKey(
|
||||
ConfigGroups,
|
||||
on_delete=models.CASCADE,
|
||||
default = None,
|
||||
null = True,
|
||||
blank= True
|
||||
)
|
||||
|
||||
device = models.ForeignKey(
|
||||
Device,
|
||||
on_delete=models.CASCADE,
|
||||
|
@ -1,3 +1,5 @@
|
||||
import json
|
||||
|
||||
from django.db import models
|
||||
|
||||
from access.fields import *
|
||||
@ -115,6 +117,22 @@ class Device(DeviceCommonFieldsName, SaveHistory):
|
||||
|
||||
config['software'] = config['software'] + [ software_action ]
|
||||
|
||||
config: dict = config
|
||||
|
||||
from config_management.models.groups import ConfigGroupHosts
|
||||
|
||||
if self.id:
|
||||
|
||||
config_groups = ConfigGroupHosts.objects.filter(host=self.id).order_by('group')
|
||||
|
||||
for group in config_groups:
|
||||
|
||||
rendered_config = group.group.render_config()
|
||||
|
||||
if rendered_config:
|
||||
|
||||
config.update(json.loads(group.group.render_config()))
|
||||
|
||||
return config
|
||||
|
||||
|
||||
|
@ -95,7 +95,6 @@
|
||||
{% elif software.get_action_display == 'Remove'%}
|
||||
{% include 'icons/cross_text.html.j2' with icon_text=software.get_action_display icon_link=icon_link %}
|
||||
{% else %}
|
||||
|
||||
{% include 'icons/add_link.html.j2' with icon_text='Add' icon_link=icon_link %}
|
||||
{% endif %}
|
||||
</td>
|
||||
@ -152,6 +151,29 @@
|
||||
<div>
|
||||
<textarea cols="90" rows="30" readonly>{{ config }}</textarea>
|
||||
</div>
|
||||
<br />
|
||||
<hr />
|
||||
<table class="data">
|
||||
<tr>
|
||||
<th>Group</th>
|
||||
<th>Added</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
{% if config_groups %}
|
||||
{% for group in config_groups %}
|
||||
<tr>
|
||||
<td><a href="{% url 'Config Management:_group_view' pk=group.id %}">{{ group.group }}</a></td>
|
||||
<td>{{ group.created }}</td>
|
||||
<td><a href="{% url 'Config Management:_group_delete_host' group_id=group.group.id pk=group.id %}">Delete</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="3">Nothing Found</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
@ -1,87 +1,235 @@
|
||||
|
||||
from django.test import TestCase, Client
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
from django.test import TestCase, Client
|
||||
|
||||
from access.models import Organization
|
||||
|
||||
from core.models.history import History
|
||||
|
||||
from itam.models.device import Device
|
||||
|
||||
|
||||
@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_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_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_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_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
|
||||
# @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
|
||||
# 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
|
||||
# @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
|
||||
# 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
|
||||
# @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
|
||||
# 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
|
||||
# @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
|
||||
# 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
|
||||
# @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
|
||||
# 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
|
||||
# @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
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
|
||||
|
||||
class DeviceHistory(TestCase):
|
||||
|
||||
model = Device
|
||||
|
||||
model_name = 'device'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
""" Setup Test """
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.item_create = self.model.objects.create(
|
||||
name = 'test_item_' + self.model_name,
|
||||
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_name + '_changed'
|
||||
self.item_change.save()
|
||||
|
||||
self.history_change = History.objects.get(
|
||||
action = History.Actions.UPDATE[0],
|
||||
item_pk = self.item_change.pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
||||
|
||||
# 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 """
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
@ -1,87 +1,235 @@
|
||||
|
||||
from django.test import TestCase, Client
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
from django.test import TestCase, Client
|
||||
|
||||
from access.models import Organization
|
||||
|
||||
from core.models.history import History
|
||||
|
||||
from itam.models.device_models import DeviceModel
|
||||
|
||||
|
||||
@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_auth_view():
|
||||
# """ User requires Permission view_history """
|
||||
# pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_device_model_create():
|
||||
""" History row must be added to history table on create """
|
||||
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_model_update():
|
||||
""" History row must be added to history table on updatej """
|
||||
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_model_delete():
|
||||
""" History row must be added to history table on delete """
|
||||
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_model_operating_system_create():
|
||||
""" History row must be added to history table on create
|
||||
# @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
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_device_model_operating_system_update():
|
||||
""" History row must be added to history table on update
|
||||
# @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
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_device_model_operating_system_delete():
|
||||
""" History row must be added to history table on delete
|
||||
# @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
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_device_model_software_create():
|
||||
""" History row must be added to history table on create
|
||||
# @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
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_device_model_software_update():
|
||||
""" History row must be added to history table on update
|
||||
# @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
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_device_model_software_delete():
|
||||
""" History row must be added to history table on delete
|
||||
# @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
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
|
||||
|
||||
class DeviceModelHistory(TestCase):
|
||||
|
||||
model = DeviceModel
|
||||
|
||||
model_name = 'devicemodel'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
""" Setup Test """
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.item_create = self.model.objects.create(
|
||||
name = 'test_item_' + self.model_name,
|
||||
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_name + '_changed'
|
||||
self.item_change.save()
|
||||
|
||||
self.history_change = History.objects.get(
|
||||
action = History.Actions.UPDATE[0],
|
||||
item_pk = self.item_change.pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
||||
|
||||
# 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('{"name": "test_item_' + self.model_name + '_changed"}')
|
||||
# assert type(history['after']) is str
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_entry_item_change_field_before(self):
|
||||
""" Ensure before field is an empty JSON string for create """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['before'] == str('{}')
|
||||
# assert type(history['before']) is str
|
||||
|
||||
|
||||
def test_history_entry_item_change_field_item_pk(self):
|
||||
""" Ensure history entry field item_pk is the created items pk """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['item_pk'] == self.item_create.pk
|
||||
# assert type(history['item_pk']) is int
|
||||
|
||||
|
||||
def test_history_entry_item_change_field_item_class(self):
|
||||
""" Ensure history entry field item_class is the model name """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['item_class'] == self.model._meta.model_name
|
||||
# assert type(history['item_class']) is str
|
||||
|
||||
|
||||
|
@ -1,52 +1,235 @@
|
||||
|
||||
from django.test import TestCase, Client
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
from django.test import TestCase, Client
|
||||
|
||||
from access.models import Organization
|
||||
|
||||
from core.models.history import History
|
||||
|
||||
from itam.models.operating_system import OperatingSystem
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_operating_system_create():
|
||||
""" History row must be added to history table on create """
|
||||
pass
|
||||
# @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_operating_system_update():
|
||||
""" History row must be added to history table on updatej """
|
||||
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_operating_system_delete():
|
||||
""" History row must be added to history table on delete """
|
||||
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_operating_system_version_create():
|
||||
""" History row must be added to history table on create
|
||||
# @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
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_operating_system_version_update():
|
||||
""" History row must be added to history table on update
|
||||
# @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
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_operating_system_version_delete():
|
||||
""" History row must be added to history table on delete
|
||||
# @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
|
||||
# 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 OperatingSystemHistory(TestCase):
|
||||
|
||||
model = OperatingSystem
|
||||
|
||||
model_name = 'operatingsystem'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
""" Setup Test """
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.item_create = self.model.objects.create(
|
||||
name = 'test_item_' + self.model_name,
|
||||
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_name + '_changed'
|
||||
self.item_change.save()
|
||||
|
||||
self.history_change = History.objects.get(
|
||||
action = History.Actions.UPDATE[0],
|
||||
item_pk = self.item_change.pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
||||
|
||||
# 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('{"name": "test_item_' + self.model_name + '_changed"}')
|
||||
# assert type(history['after']) is str
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_entry_item_change_field_before(self):
|
||||
""" Ensure before field is an empty JSON string for create """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['before'] == str('{}')
|
||||
# assert type(history['before']) is str
|
||||
|
||||
|
||||
def test_history_entry_item_change_field_item_pk(self):
|
||||
""" Ensure history entry field item_pk is the created items pk """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['item_pk'] == self.item_create.pk
|
||||
# assert type(history['item_pk']) is int
|
||||
|
||||
|
||||
def test_history_entry_item_change_field_item_class(self):
|
||||
""" Ensure history entry field item_class is the model name """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['item_class'] == self.model._meta.model_name
|
||||
# assert type(history['item_class']) is str
|
||||
|
||||
|
||||
|
@ -1,52 +1,235 @@
|
||||
|
||||
from django.test import TestCase, Client
|
||||
|
||||
import pytest
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
from django.test import TestCase, Client
|
||||
|
||||
from access.models import Organization
|
||||
|
||||
from core.models.history import History
|
||||
|
||||
from itam.models.software import Software
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_software_create():
|
||||
""" History row must be added to history table on create """
|
||||
pass
|
||||
# @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_software_update():
|
||||
""" History row must be added to history table on updatej """
|
||||
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_software_delete():
|
||||
""" History row must be added to history table on delete """
|
||||
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_software_version_create():
|
||||
""" History row must be added to history table on create
|
||||
# @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
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_software_version_update():
|
||||
""" History row must be added to history table on update
|
||||
# @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
|
||||
# Must also have populated parent_item_pk and parent_item_class columns
|
||||
# """
|
||||
# pass
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_software_version_delete():
|
||||
""" History row must be added to history table on delete
|
||||
# @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
|
||||
# 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 SoftwareHistory(TestCase):
|
||||
|
||||
model = Software
|
||||
|
||||
model_name = 'software'
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
""" Setup Test """
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
self.item_create = self.model.objects.create(
|
||||
name = 'test_item_' + self.model_name,
|
||||
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_name + '_changed'
|
||||
self.item_change.save()
|
||||
|
||||
self.history_change = History.objects.get(
|
||||
action = History.Actions.UPDATE[0],
|
||||
item_pk = self.item_change.pk,
|
||||
item_class = self.model._meta.model_name,
|
||||
)
|
||||
|
||||
|
||||
# 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('{"name": "test_item_' + self.model_name + '_changed"}')
|
||||
# assert type(history['after']) is str
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="to be written")
|
||||
def test_history_entry_item_change_field_before(self):
|
||||
""" Ensure before field is an empty JSON string for create """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['before'] == str('{}')
|
||||
# assert type(history['before']) is str
|
||||
|
||||
|
||||
def test_history_entry_item_change_field_item_pk(self):
|
||||
""" Ensure history entry field item_pk is the created items pk """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['item_pk'] == self.item_create.pk
|
||||
# assert type(history['item_pk']) is int
|
||||
|
||||
|
||||
def test_history_entry_item_change_field_item_class(self):
|
||||
""" Ensure history entry field item_class is the model name """
|
||||
|
||||
history = self.history_change.__dict__
|
||||
|
||||
assert history['item_class'] == self.model._meta.model_name
|
||||
# assert type(history['item_class']) is str
|
||||
|
||||
|
||||
|
@ -12,6 +12,8 @@ from django.views import generic
|
||||
from access.mixin import OrganizationPermission
|
||||
from access.models import Organization
|
||||
|
||||
from config_management.models.groups import ConfigGroupHosts
|
||||
|
||||
from ..models.device import Device, DeviceSoftware, DeviceOperatingSystem
|
||||
from ..models.software import Software
|
||||
|
||||
@ -101,6 +103,8 @@ class View(OrganizationPermission, generic.UpdateView):
|
||||
config = self.object.get_configuration(self.kwargs['pk'])
|
||||
context['config'] = json.dumps(config, indent=4, sort_keys=True)
|
||||
|
||||
context['config_groups'] = ConfigGroupHosts.objects.filter(host = self.object.id)
|
||||
|
||||
context['model_pk'] = self.kwargs['pk']
|
||||
context['model_name'] = self.model._meta.verbose_name.replace(' ', '')
|
||||
|
||||
|
@ -19,6 +19,8 @@ curl -X GET http://127.0.0.1:8000/api/ -H 'Authorization: Token <token>'
|
||||
|
||||
- Inventory Report Collection
|
||||
|
||||
- Swagger UI
|
||||
|
||||
|
||||
## Inventory Reports
|
||||
|
||||
|
33
docs/projects/django-template/config_management/index.md
Normal file
33
docs/projects/django-template/config_management/index.md
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
title: Config Management
|
||||
description: No Fuss Computings Django ITSM Config Management Module
|
||||
date: 2024-06-03
|
||||
template: project.html
|
||||
about: https://gitlab.com/nofusscomputing/infrastructure/configuration-management/django_app
|
||||
---
|
||||
|
||||
Config Management is an ITSM process that deals with the management and storing of device/host configuration. This module aims to bridge the gap between manual entry of config data via JSON/YAML to entry via a UI. For items that are yet to be integrated into the UI, if at all possible, that config is still manually entered as JSON. The rendered configuration is intended to be consumed by Ansible. For all intents and purposes, consider this module to be the equivalent of Ansible's host groups.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
This module contains the following features:
|
||||
|
||||
- Config Groups
|
||||
|
||||
- Assign host to multiple groups
|
||||
|
||||
- **Planned** Assign software action to group _See [issue #43](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/43)_
|
||||
|
||||
- History
|
||||
|
||||
|
||||
## Config Groups
|
||||
|
||||
Considerable thought was placed into as wide a scope as possible, how the host config groups would function. This includes how the end product (the config) would be rendered. To aid in conveying how the config is rendered, consider the following image, which is a basic tree from a single root at the top, with three branches.
|
||||
|
||||

|
||||
|
||||
A host can be assigned to multiple groups as long as the host is not part of the same branch. This image has had each node coloured to denote different groups of the same branch. Note: the red node is a common node for the three branches. for example a host can be placed in each of the three coloured branches. the root node however, if the host is placed in this group then the host can not be placed in any other node. this is because the red node is the root for all three coloured branches.
|
||||
|
||||
When it comes time to merge the configuration, if a parent group has the same config as it's childs config. The childs config will take precedence. For a host that is placed in all three branches (orange, green and blue), based of of the group name, sorted alphanumerically, the last group that has conflicting config will be the one that is used. A groups config will always be rendered with it's parents config included all the way up the branch to the root node.
|
@ -8,6 +8,7 @@ about: https://gitlab.com/nofusscomputing/infrastructure/configuration-managemen
|
||||
|
||||
This page contains different items related to the development of this application.
|
||||
|
||||
|
||||
## Icons
|
||||
|
||||
To locate additional icons for use see [material icons](https://fonts.google.com/icons).
|
||||
@ -116,7 +117,7 @@ Using a filter `pk__in=self.user_organizations()` for the queryset using the mix
|
||||
|
||||
### Templates
|
||||
|
||||
The base template includes blocks that are designed to assist in rendering your content. The following blocks are available:
|
||||
The base template includes blocks that are designed to assist in rendering your content. The following blocks are available:
|
||||
|
||||
- `title` - The page and title
|
||||
|
||||
|
BIN
docs/projects/django-template/images/config-groups-merging.png
Normal file
BIN
docs/projects/django-template/images/config-groups-merging.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
76
docs/projects/django-template/images/config-groups.drawio
Normal file
76
docs/projects/django-template/images/config-groups.drawio
Normal file
@ -0,0 +1,76 @@
|
||||
<mxfile host="app.diagrams.net" modified="2024-06-03T01:49:35.015Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" etag="jYMoo90cuGXn_K-Y5V_J" version="24.2.1" type="device">
|
||||
<diagram name="Page-1" id="sbUyBCaYiuDQ_FkowPAe">
|
||||
<mxGraphModel dx="1434" dy="766" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="3ROtmLI4iqX4xD-AvKj1-1" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#FF3333;" vertex="1" parent="1">
|
||||
<mxGeometry x="545" y="40" width="80" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="3ROtmLI4iqX4xD-AvKj1-2" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#FF9933;" vertex="1" parent="1">
|
||||
<mxGeometry x="345" y="214" width="80" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="3ROtmLI4iqX4xD-AvKj1-6" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="3ROtmLI4iqX4xD-AvKj1-3" target="3ROtmLI4iqX4xD-AvKj1-2">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="3ROtmLI4iqX4xD-AvKj1-3" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#FF9933;" vertex="1" parent="1">
|
||||
<mxGeometry x="345" y="374" width="80" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="3ROtmLI4iqX4xD-AvKj1-7" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="3ROtmLI4iqX4xD-AvKj1-4" target="3ROtmLI4iqX4xD-AvKj1-3">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="3ROtmLI4iqX4xD-AvKj1-4" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#FF9933;" vertex="1" parent="1">
|
||||
<mxGeometry x="345" y="534" width="80" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="3ROtmLI4iqX4xD-AvKj1-8" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#99FF33;" vertex="1" parent="1">
|
||||
<mxGeometry x="545" y="214" width="80" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="3ROtmLI4iqX4xD-AvKj1-9" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="3ROtmLI4iqX4xD-AvKj1-10" target="3ROtmLI4iqX4xD-AvKj1-8">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="3ROtmLI4iqX4xD-AvKj1-10" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#99FF33;" vertex="1" parent="1">
|
||||
<mxGeometry x="545" y="374" width="80" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="3ROtmLI4iqX4xD-AvKj1-11" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="3ROtmLI4iqX4xD-AvKj1-12" target="3ROtmLI4iqX4xD-AvKj1-10">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="3ROtmLI4iqX4xD-AvKj1-12" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#99FF33;" vertex="1" parent="1">
|
||||
<mxGeometry x="545" y="534" width="80" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="3ROtmLI4iqX4xD-AvKj1-13" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#3399FF;" vertex="1" parent="1">
|
||||
<mxGeometry x="745" y="214" width="80" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="3ROtmLI4iqX4xD-AvKj1-14" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="3ROtmLI4iqX4xD-AvKj1-15" target="3ROtmLI4iqX4xD-AvKj1-13">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="3ROtmLI4iqX4xD-AvKj1-15" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#3399FF;" vertex="1" parent="1">
|
||||
<mxGeometry x="745" y="374" width="80" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="3ROtmLI4iqX4xD-AvKj1-16" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="3ROtmLI4iqX4xD-AvKj1-17" target="3ROtmLI4iqX4xD-AvKj1-15">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="3ROtmLI4iqX4xD-AvKj1-17" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#3399FF;" vertex="1" parent="1">
|
||||
<mxGeometry x="745" y="534" width="80" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="3ROtmLI4iqX4xD-AvKj1-20" value="" style="endArrow=classic;html=1;rounded=0;" edge="1" parent="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="390" y="210" as="sourcePoint" />
|
||||
<mxPoint x="540" y="110" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="3ROtmLI4iqX4xD-AvKj1-21" value="" style="endArrow=classic;html=1;rounded=0;" edge="1" parent="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="780" y="210" as="sourcePoint" />
|
||||
<mxPoint x="630" y="110" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="3ROtmLI4iqX4xD-AvKj1-22" value="" style="endArrow=classic;html=1;rounded=0;" edge="1" parent="1">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="585" y="210" as="sourcePoint" />
|
||||
<mxPoint x="585" y="130" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
@ -17,13 +17,13 @@ This application contains the following module:
|
||||
|
||||
- [Application wide settings](settings.md)
|
||||
|
||||
- [Multi-Tenant](permissions.md)
|
||||
- [Configuration Management](config_management/index.md)
|
||||
|
||||
- History
|
||||
|
||||
- [IT Asset Management (ITAM)](itam/index.md)
|
||||
|
||||
- [Configuration ready for ansible](itam/device.md#configuration)
|
||||
|
||||
- History
|
||||
- [Multi-Tenant](permissions.md)
|
||||
|
||||
Specific features for a module can be found on the module's documentation un the features heading
|
||||
|
||||
|
@ -34,6 +34,7 @@ This tab display the details of the device.
|
||||
|
||||
To add a new model navigate to `settings -> ITAM -> Device Models`
|
||||
|
||||
|
||||
### Operating System
|
||||
|
||||
This tab shows the operating system selected as installed on the device. the version `name` is intended to be full [semver](https://semver.org/).
|
||||
@ -49,17 +50,17 @@ This tab shows the operating system selected as installed on the device. the ver
|
||||
|
||||
This tab displays both software actions and installed software. Software install details are added/updated by uploading an [inventory report](../api.md#inventory-reports).
|
||||
|
||||
You can specify a software action for any piece of software within the ITAM database. You can do this by pressing the `dd software action` button or if the software is installed clicking on the `+ Add` button on the row of the software to add the action to. An action can be set to either `Install` or `Remove` and you can also select a software version from the database if you choose to do so. Software actions are added to config management and can be pulled from the API for use within an Ansible playbook.
|
||||
You can specify a software action for any piece of software within the ITAM database. You can do this by pressing the `Add Software Action` button or if the software is installed clicking on the `+ Add` button on the row of the software to add the action to. An action can be set to either `Install` or `Remove` and you can also select a software version from the database if you choose to do so. Software actions are added to config management and can be pulled from the API for use within an Ansible playbook.
|
||||
|
||||
Display of both installed software and software actions is within a single row, if it's for the same software. Any software that you add an action to, will be displayed at the top of the list of software tab.
|
||||
|
||||
!!! info
|
||||
If you add a software action for software that is already installed using the `add software action` button, an additional row will not be added as the applications logic is smart enough to check if the software is already installed.
|
||||
If you add a software action for software that is already installed using the `Add Software Action` button, an additional row will not be added as the applications logic is smart enough to check if the software is already installed.
|
||||
|
||||
|
||||
### Configuration
|
||||
|
||||
Although, configuration is generally part of config management. This tab displays in `JSON` format configuration that is ready for use. The intended audience is Ansible users with the fields provided matching established Ansible modules, if they exist.
|
||||
This tab displays in `JSON` format configuration that is ready for use. Config from the [Config Management](../config_management/index.md) module is also included and rendered as part of this config. The intended audience is Ansible users with the fields provided matching established Ansible modules, if they exist.
|
||||
|
||||
This configuration can also be obtained from API endpoint `/api/config/<machine-slug>` where `<machine-slug>` would match the Ansible `inventory_hostname`.
|
||||
|
||||
|
@ -40,7 +40,7 @@ This tab displays the details of the software, in particular:
|
||||
If a super admin sets [application setting](../settings.md#global-software) `software is global`, when any software is created, regardless of what organization you set. The software will be created in the "global" organization.
|
||||
|
||||
|
||||
# Versions
|
||||
## Versions
|
||||
|
||||
This tab displays the different software versions and how many of each version are installed on devices within your inventory.
|
||||
|
||||
|
@ -15,10 +15,11 @@ The overall permissions system of django has not been modified with it remaining
|
||||
|
||||
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.
|
||||
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:
|
||||
|
@ -23,6 +23,8 @@ nav:
|
||||
|
||||
- projects/django-template/api.md
|
||||
|
||||
- projects/django-template/config_management/index.md
|
||||
|
||||
- projects/django-template/permissions.md
|
||||
|
||||
- Core:
|
||||
|
@ -9,6 +9,13 @@ djangorestframework-jsonapi==7.0.0
|
||||
pyyaml==6.0.1
|
||||
django-filter==24.2
|
||||
|
||||
# OpenAPI Schema
|
||||
uritemplate==4.1.1
|
||||
coreapi==2.3.3
|
||||
|
||||
drf-spectacular==0.27.2
|
||||
drf-spectacular[sidecar]==0.27.2
|
||||
|
||||
django_split_settings==1.3.1
|
||||
|
||||
markdown==3.6
|
||||
|
Reference in New Issue
Block a user